Files
Situation-Awareness-Platfor…/src/views/Jyh/Search/Components/BatchVerify.vue
2025-04-03 14:49:18 +08:00

570 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="BatchVerify">
<div class="search-form" v-if="isLogin">
<FileUploadButton buttonText="批量导入匹配" accept=".xlsx" :onFileUpload="handleFileUpload" />
<d-search
class="mt-0 mb-2"
style="width: 400px"
:delay="1000"
@search="onSearch($event)"
placeholder="请输入软件名称"
></d-search>
<div class="tjsj">
<span style="height: 14px" class="split"></span>
<span>可使用</span>
<span class="green" @click="filterVerifyResult(1)">{{ useNum }}</span>
<span style="height: 14px" class="split"></span>
<span>有风险</span>
<span class="red" @click="filterVerifyResult(2)">{{ riskNum }}</span>
<span style="height: 14px" class="split"></span>
<span>未治理</span>
<span class="yellow" @click="filterVerifyResult(0)">{{ noNum }}</span>
</div>
<div class="left-btn">
<a v-if="isLogin" class="cursor-pointer" href="/file/软件存在及可信校验模版.xlsx" download
><i class="icon icon-download-2"></i>下载excel模板</a
>
<span v-if="isLogin" style="height: 14px" class="split"></span>
<d-button v-if="isLogin" class="output-btn" icon="icon-share" variant="text" @click="exportResult"
>导出</d-button
>
<span v-if="isLogin" style="height: 14px" class="split"></span>
<TableSetting
:columns="columns"
@setDefult="setDefult"
@updateTableKey="updateTableKey"
:show-column-list="showColumnList"
@update:show-column-list="updateShowColumnList"
/>
</div>
</div>
<div class="mt-3 px-20">
<d-table
class="jyh-table"
v-if="tableData.length"
:striped="false"
:data="tableData"
table-layout="auto"
:show-loading="loading"
>
<!-- 索引列 -->
<d-column type="index" width="40"></d-column>
<!-- 动态渲染列 -->
<template v-for="column in columns" :key="column.key">
<d-column v-if="column.visible" :field="column.key" :header="column.label">
<!-- 自定义列内容 -->
<template #default="scope">
<template v-if="column.key === 'validStatus'">
<div v-if="scope.row.validStatus == 1" style="display: flex; align-items: center">
<d-icon class="mr-1" name="icon-right-o" style="font-size: 14px; color: rgb(80, 212, 171)"></d-icon>
<span>可使用</span>
</div>
<div v-else-if="scope.row.validStatus == 2" style="display: flex; align-items: center">
<d-icon class="mr-1" name="icon-warning-o" style="font-size: 14px; color: rgb(250, 194, 10)"></d-icon>
<span>有风险</span>
</div>
<div v-else style="display: flex; align-items: center">
<d-icon class="mr-1" name="icon-error-o" style="font-size: 14px; color: rgb(246, 111, 106)"></d-icon>
<span>未治理</span>
</div>
</template>
<template v-if="column.key === 'versionName'">
<a @click="gotoDetail(scope.row)">{{ scope.row.versionName }}</a>
</template>
<template v-else-if="column.key === 'softwareName'">
<a @click="gotoDetail(scope.row)">{{ scope.row.softwareName }}</a>
</template>
<template v-else-if="column.key === 'softwareVersion'">
<a @click="gotoDetail(scope.row)">{{ scope.row.softwareVersion }}</a>
</template>
<template v-else-if="column.key === 'versionCode'">
<a @click="gotoDetail(scope.row)">{{ scope.row.versionCode }}</a>
</template>
<template v-else-if="column.key === 'smStatus'">
<a>{{ scope.row.smStatus }}</a>
</template>
<template v-else-if="column.key === 'type'">
<d-tag type="success">{{ scope.row.type }}</d-tag>
</template>
<template v-else-if="column.key === 'license'">
<span class="mr-2">{{ scope.row.license }}</span>
<d-tag color="gray">+3</d-tag>
</template>
<template v-else-if="column.key === 'url'">
<a>{{ scope.row.url }}</a>
</template>
<template v-else>
{{ scope.row[column.key] }}
</template>
</template>
</d-column>
</template>
<d-column header="操作" width="200" fixed-right="0px">
<!--TODO:申请入口只有校验不通过的才显示-->
<template #default="scope">
<d-button variant="text" v-if="scope.row.validStatus == 0" :disabled="scope.row.validStatus !== 0">
申请入库
</d-button>
</template>
</d-column>
</d-table>
<!-- <DataPanel v-else skeleton :card="false" :empty="true">-->
<!-- </DataPanel>-->
<NoData v-else :small="false"></NoData>
</div>
<!-- 删除组件 -->
<GModal
v-model="deletevModels"
ref="deleteModal"
title="删除?"
@confirm="confirmDelete"
showWarnIcon
confirmColor="danger"
>
<span class="inline-block text-G900 text-sm font-normal leading-[20px] break-all"
>你确认删除此项{{ item.cla_name }} </span
>
</GModal>
</div>
</template>
<script setup lang="ts">
import SettingTitle from '@/components/Setting/SettingTitle/index.vue';
import SettingText from '@/components/Setting/SettingText/index.vue';
import { ref, reactive, onMounted, computed } from 'vue';
import { getGroupClaList, setGroupClaStatus, deleteGroupCla } from '@/api/cla';
import { useRouter, useRoute } from 'vue-router';
import { GModal } from '@/components/Setting/index';
import { reqCatch } from '@/utils/catch';
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
import { Message } from 'vue-devui';
import { useAccountStore } from '@/stores/user';
import { storeToRefs } from 'pinia';
import TableSetting from '@/components/TableSetting/index.vue';
import FileUploadButton from '@/components/FileUploadButton/index.vue';
import { releasePage, repoImport } from '@/api/jyh';
import NoData from '@/components/NoData/NoData.vue';
import * as XLSX from 'xlsx';
const downloadTemplate = async () => {
try {
const response = await fetch('/static/软件存在及可信校验模版.xlsx');
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '软件存在及可信校验模版.xlsx';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.error('下载失败:', error);
}
};
const useNum = computed(() => {
if (tableData.value.length > 0) {
return tableData.value.filter((item) => item.validStatus === 1).length;
} else {
return 0;
}
});
const riskNum = computed(() => {
if (tableData.value.length > 0) {
return tableData.value.filter((item) => item.validStatus === 2).length;
} else {
return 0;
}
});
const noNum = computed(() => {
if (tableData.value.length > 0) {
return tableData.value.filter((item) => item.validStatus === 0).length;
} else {
return 0;
}
});
const setDefult = () => {
columns.value = defultColumns.value;
};
// 处理文件上传
const handleFileUpload = async (file) => {
// 1. 校验文件类型
const allowedExtensions = ['.xlsx'];
const fileExtension = file.name.slice(file.name.lastIndexOf('.')).toLowerCase();
if (!allowedExtensions.includes(fileExtension)) {
Message.error('仅支持上传 .xlsx 格式的Excel文件');
return Promise.reject(new Error('文件格式不正确'));
}
const { data, error } = await repoImport({ file });
if (!error && data) {
currentReusltFilter.value = null;
allTableData.value = data.data.data;
tableData.value = [...allTableData.value];
}
// 模拟文件上传逻辑
return new Promise((resolve) => {
setTimeout(() => {
console.log('文件上传成功:', file.name);
resolve();
}, 2000); // 模拟 2 秒上传时间
});
};
const userInfo = useAccountStore();
const { isLogin } = storeToRefs(userInfo);
const { namespace } = getOrgInfo();
const allTableData = ref([]);
const tableData = ref([]);
const loading = ref(false);
const columns = ref([
{ key: 'validStatus', label: '校验结果', visible: true, filterable: true, sortable: false },
{ key: 'softwareName', label: '软件名称', visible: true, filterable: true, sortable: false },
{ key: 'softwareVersion', label: '软件版本', visible: true, filterable: true, sortable: false },
{ key: 'securityScore', label: '软件评分', visible: true, filterable: false, sortable: true },
{ key: 'licenseCount', label: 'License数量', visible: true, filterable: false, sortable: false },
{ key: 'industryCategory', label: '行业类型', visible: true, filterable: true, sortable: false },
{ key: 'primaryLanguage', label: '编程语言', visible: true, filterable: true, sortable: false },
{ key: 'codeLines', label: '代码量', visible: true, filterable: false, sortable: false }
]);
const defultColumns = ref([
{ key: 'validStatus', label: '校验结果', visible: true, filterable: true, sortable: false },
{ key: 'softwareName', label: '软件名称', visible: true, filterable: true, sortable: false },
{ key: 'softwareVersion', label: '软件版本', visible: true, filterable: true, sortable: false },
{ key: 'securityScore', label: '软件评分', visible: true, filterable: false, sortable: true },
{ key: 'licenseCount', label: 'License数量', visible: true, filterable: false, sortable: false },
{ key: 'industryCategory', label: '行业类型', visible: true, filterable: true, sortable: false },
{ key: 'primaryLanguage', label: '编程语言', visible: true, filterable: true, sortable: false },
{ key: 'codeLines', label: '代码量', visible: true, filterable: false, sortable: false }
]);
const validStatusMap = {
0: '未治理',
1: '可使用',
2: '有风险'
};
const formData = ref({
title: '',
type: '',
useType: '',
formData: '',
select: '',
time1: null,
name: '',
versionCode: '',
pulishTime: ''
});
const typeOptions = reactive(['主软件', '依赖软件']);
const riskOptions = reactive(['低', '中', '高']);
const useTypeOptions = reactive(['软件', '文档', '文档+软件']);
const isUseOptions = reactive(['是', '否']);
const currentReusltFilter = ref(null);
const searchStr = ref('');
// 定义 tableKey用于强制刷新表格
const tableKey = ref(0);
const pager = ref({
total: 0,
pageIndex: 1,
pageSize: 10
});
const loadingStatus = reactive({
batchMigrating: false
});
// 删除弹窗
const item = ref('');
const deleteModal = ref();
const deletevModels = ref(false);
const openDelete = (row: any) => {
item.value = row;
deleteModal.value.showFlag = true;
deletevModels.value = true;
};
const getReleaseList = async () => {
if (!isLogin.value) {
return;
}
loading.value = true;
const { data, error } = await releasePage({ current: pager.value.pageIndex, size: pager.value.pageSize });
if (!error && data) {
loading.value = false;
allTableData.value = data.data.records;
tableData.value = [...allTableData.value];
pager.value.total = data.data.total;
} else {
loading.value = false;
}
};
onMounted(async () => {
// getReleaseList();
});
const filterTable = () => {
tableData.value = allTableData.value.filter((item) => {
if (currentReusltFilter.value !== null) {
return (
item.softwareName.trim().toLowerCase().includes(searchStr.value.trim().toLowerCase()) &&
item.validStatus === currentReusltFilter.value
);
} else {
return item.softwareName.trim().toLowerCase().includes(searchStr.value.trim().toLowerCase());
}
});
};
const filterVerifyResult = (type) => {
if (currentReusltFilter.value === type) {
currentReusltFilter.value = null;
} else {
currentReusltFilter.value = type;
}
filterTable();
};
const onSearch = (str: string) => {
searchStr.value = str;
filterTable();
};
const router = useRouter();
const updateTableKey = () => {
tableKey.value++; // 更新 tableKey强制刷新表格
};
// 定义 showColumnList 状态
const showColumnList = ref(false);
// 更新 showColumnList 的方法
const updateShowColumnList = (newValue) => {
showColumnList.value = newValue;
// tableKey.value++; // 更新 tableKey强制刷新表格
};
const handleBatchImport = async () => {
// 多个项目导入
// importRepo.value = queryTable.value.filter((val) => val.checked);
loadingStatus.batchMigrating = true;
// await batchMigrate();
loadingStatus.batchMigrating = false;
};
const exportResult = () => {
if (!allTableData.value.length) {
Message.info('暂无结果');
return;
}
const exportData = allTableData.value.map((item) => {
let res = {};
columns.value.forEach((col) => {
if (col.key === 'validStatus') {
res[col.label] = validStatusMap[item[col.key]] ? validStatusMap[item[col.key]] : '';
} else {
res[col.label] = item[col.key] ? item[col.key] : '';
}
});
return res;
});
try {
const workSheet = XLSX.utils.json_to_sheet(exportData);
const workBook: any = { Sheets: { 校验结果: workSheet }, SheetNames: ['校验结果'] };
const excelBuffer = XLSX.write(workBook, { bookType: 'xlsx', type: 'array' });
const link = document.createElement('a');
const blob = new Blob([excelBuffer]);
link.setAttribute('href', window.URL.createObjectURL(blob));
link.setAttribute('download', '软件存在及可信校验结果.xlsx');
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
Message.success('导出成功');
} catch {
Message.error('导出失败');
}
};
// 跳转
const gotoDetail = (row) => {
// 后续请换成params暂时params传参没传过去
router.push({
path: 'repoDetail/' + row.id
});
};
// 分页
const changeIndex = () => {
// getReleaseList();
};
const changeSize = () => {
pager.value.pageIndex = 1;
// getReleaseList();
};
const repoFun = (arr: any) => {
return arr ? (arr.length > 3 ? arr.slice(0, 3) : arr) : [];
};
// 编辑
const editRow = (row: any) => {
// 后续请换成params暂时params传参没传过去
router.push({
path: 'cla/edit/' + row.object_id,
query: { cla: row.cla, pageType: 'edit' }
});
};
// 确认删除
const confirmDelete = async (e: any) => {
// const res = await reqCatch(deleteGroupCla, { group_id: namespace.value, cla_id: item.value.object_id });
// if (!res.error) {
// getGroupClaListData();
// }
};
// 去成员页
const goMember = (row: any) => {
// 后续请换成params暂时params传参没传过去,注意路由有个:claID
router.push({
path: 'cla/claId/member',
query: { claId: row.object_id, claName: row.cla_name, group_path: namespace.value, pageType: 'edit' }
});
};
const getGroupClaListData = async () => {
// const params = {
// group_id: namespace.value,
// page: pager.value.pageIndex,
// per_page: pager.value.pageSize
// };
// const res = await reqCatch(getGroupClaList, params);
// if (!res.error) {
// tableData.value = res?.data?.data?.content;
// pager.value.total = res.data.data.total;
// }
};
getGroupClaListData();
const setClaStatus = async (row: any) => {
// const params = {
// group_id: namespace.value,
// cla_id: row.object_id,
// status: row.status
// };
// const res = await reqCatch(setGroupClaStatus, params);
// if (!res.error) {
// Message.success('操作成功!');
// getGroupClaListData();
// }
};
const signCla = (row: any) => {
router.push('/cla/' + row.object_id);
};
</script>
<style scoped lang="scss">
@import 'devui-theme/styles-var/devui-var.scss';
.BatchVerify {
position: relative;
min-height: 400px;
.secret-op {
position: absolute;
right: 0;
top: 0;
}
}
.error {
color: $devui-contrast;
}
:deep(.devui-table__row td) {
padding-top: 16px;
padding-bottom: 16px;
}
:deep(.devui-tag .devui-tag--default) {
background-color: #fff;
border-radius: 12px;
padding: 4px 8px;
font-size: 12px;
line-height: 16px;
margin-right: 8px;
border: 1px solid #e3e3ee;
}
.one-line {
word-break: break-all;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.title {
font-size: 14px;
font-weight: 400;
color: #9a9b9c;
line-height: 20px;
}
.cur-title {
font-size: 14px;
font-weight: 500;
color: #2d2d2e;
line-height: 20px;
}
.search-form {
position: relative;
padding: 12px 20px;
display: flex;
border-radius: 8px 0px 8px 8px;
//background: #F2F5FC;
.left-btn {
position: absolute;
right: 28px;
bottom: 4px;
}
.output-btn {
color: #191919;
font-family: HarmonyOS Sans SC;
font-weight: regular;
font-size: 14px;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
}
.secret-breadcrumb {
padding: 7px 20px 12px;
background: #ffffff;
}
.tjsj {
display: flex;
align-items: center;
margin-left: 8px;
}
</style>