可信代码库V2版-安全智库页面原型开发、安全检测中心页面原型开发、关于我们页面原型开发
This commit is contained in:
@@ -0,0 +1,367 @@
|
||||
<template>
|
||||
<div class="vuln-detail-container">
|
||||
<div class="vuln-title mb-2">
|
||||
<span>漏洞预警详情</span>
|
||||
<div class="vuln-sub-title">
|
||||
<!-- <d-button @click="openWarningDetail()" variant="solid">预警发布</d-button>-->
|
||||
<!-- <span class="split-line"></span>-->
|
||||
<d-icon name="icon-close" @click="$emit('close')"></d-icon>
|
||||
</div>
|
||||
</div>
|
||||
<Card simple class="jyh-card mb-4">
|
||||
<div class="card-title">漏洞信息</div>
|
||||
<d-row class="mb-3">
|
||||
<d-col :span="12">
|
||||
<span class="card-key width-90">漏洞名称</span>
|
||||
<span class="card-val">{{ valInfos.vulnId || '--' }}</span>
|
||||
</d-col>
|
||||
<d-col :span="12">
|
||||
<span class="card-key width-90">漏洞类型</span>
|
||||
<span class="card-val">{{ valInfos.vulnType || '--' }}</span>
|
||||
</d-col>
|
||||
</d-row>
|
||||
<d-row class="mb-3">
|
||||
<d-col :span="12">
|
||||
<span class="card-key width-90">漏洞时间</span>
|
||||
<span class="card-val">{{
|
||||
valInfos.publishDate ? dayjs(valInfos.publishDate).format('YYYY-MM-DD') : '--'
|
||||
}}</span>
|
||||
</d-col>
|
||||
<d-col :span="12">
|
||||
<span class="card-key width-90">严重程度</span>
|
||||
<span class="card-val" v-if="tagMap[valInfos.severity]">
|
||||
<d-tag :color="tagMap[valInfos.severity].color" size="sm">{{ tagMap[valInfos.severity].text }}</d-tag>
|
||||
</span>
|
||||
</d-col>
|
||||
</d-row>
|
||||
</Card>
|
||||
<Card simple class="jyh-card mb-4">
|
||||
<div class="card-title">漏洞影响的软件列表</div>
|
||||
<d-row class="mb-3">
|
||||
<d-col :span="24">
|
||||
<div v-if="componentsLists.length>0">
|
||||
<d-table
|
||||
class="jyh-table jyh-table-2"
|
||||
:header-bg="true"
|
||||
:data="componentsLists"
|
||||
:span-method="spanMethod"
|
||||
>
|
||||
<d-column field="effectedComponentName" header="软件名称" show-overflow-tooltip></d-column>
|
||||
<d-column field="effectedComponentVersion" header="软件版本及范围" show-overflow-tooltip></d-column>
|
||||
<d-column field="isCriticalInfrastructureComponent" header="是否用户订阅" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<div class="text-center" v-if="scope.row.isCriticalInfrastructureComponent==='是'">
|
||||
<d-icon name="icon-right-o" style="font-size: 14px; color: rgb(80, 212, 171)"></d-icon>
|
||||
</div>
|
||||
|
||||
<div class="text-center" v-else>
|
||||
<d-icon name="icon-error-o" style="font-size: 14px; color: rgb(246, 111, 106)"></d-icon>
|
||||
</div>
|
||||
</template>
|
||||
</d-column>
|
||||
<!-- <d-column field="fixVersion" header="补丁版本"></d-column>-->
|
||||
<!-- <d-column field="fixIssue" header="修复建议" show-overflow-tooltip></d-column>-->
|
||||
<template #empty>
|
||||
<NoData :small="false"></NoData>
|
||||
</template>
|
||||
</d-table>
|
||||
<div class="mt-20 mb-20 flex justify-end" >
|
||||
<d-pagination
|
||||
style="display: inline-flex"
|
||||
:total="pager.total"
|
||||
v-model:pageSize="pager.pageSize"
|
||||
v-model:pageIndex="pager.pageIndex"
|
||||
:can-view-total="true"
|
||||
:can-change-page-size="true"
|
||||
:can-jump-page="true"
|
||||
:max-items="5"
|
||||
total-item-text="总计"
|
||||
@page-index-change="getComponentsList"
|
||||
@page-size-change="getComponentsList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NoData v-else :small="false"></NoData>
|
||||
</d-col>
|
||||
</d-row>
|
||||
</Card>
|
||||
<!-- <Card simple class="jyh-card mb-4">-->
|
||||
<!-- <div class="card-title">受影响的开源软件是否出库?</div>-->
|
||||
<!-- <d-row class="mb-3">-->
|
||||
<!-- <d-col :span="24">-->
|
||||
<!-- <d-radio-group direction="row" v-model="isOut" @change="onStorageStartTask">-->
|
||||
<!-- <d-radio :value="2">出库</d-radio>-->
|
||||
<!-- <d-radio :value="1">不出库</d-radio>-->
|
||||
<!-- </d-radio-group>-->
|
||||
<!-- </d-col>-->
|
||||
<!-- </d-row>-->
|
||||
<!-- </Card>-->
|
||||
<!-- <Card simple class="jyh-card mb-4">-->
|
||||
<!-- <div class="card-title">预警订阅人员列表</div>-->
|
||||
<!-- <d-row class="mb-3">-->
|
||||
<!-- <d-col :span="24">-->
|
||||
<!-- <d-table class="jyh-table jyh-table-2" :header-bg="true" :data="alertsUsersLists">-->
|
||||
<!-- <d-column field="username" header="订阅人"></d-column>-->
|
||||
<!-- <d-column field="method" header="订阅方式"></d-column>-->
|
||||
<!-- <d-column field="notifiedStatus" header="通知状态">-->
|
||||
<!-- <template #default="scope, text">-->
|
||||
<!-- <d-tag v-if="scope.row.notifiedStatus === 0" type="danger" size="sm">未通知</d-tag>-->
|
||||
<!-- <d-tag v-if="scope.row.notifiedStatus === 1" type="success" size="sm">已通知</d-tag>-->
|
||||
<!-- </template>-->
|
||||
<!-- </d-column>-->
|
||||
<!-- <template #empty>-->
|
||||
<!-- <NoData :small="false"></NoData>-->
|
||||
<!-- </template>-->
|
||||
<!-- </d-table>-->
|
||||
<!-- <!– <div class="invitation-pagination">-->
|
||||
<!-- <d-pagination-->
|
||||
<!-- style="display: inline-flex"-->
|
||||
<!-- :total="subscribePager.total"-->
|
||||
<!-- v-model:pageSize="subscribePager.pageSize"-->
|
||||
<!-- v-model:pageIndex="subscribePager.pageIndex"-->
|
||||
<!-- :can-view-total="true"-->
|
||||
<!-- :can-change-page-size="true"-->
|
||||
<!-- :can-jump-page="true"-->
|
||||
<!-- :max-items="5"-->
|
||||
<!-- />-->
|
||||
<!-- </div> –>-->
|
||||
<!-- </d-col>-->
|
||||
<!-- </d-row>-->
|
||||
<!-- </Card>-->
|
||||
</div>
|
||||
|
||||
<d-modal class="warning-modal" v-model="warningModalVisable" title="">
|
||||
<EarlyWarningDetail
|
||||
@close="colseWarningDetail"
|
||||
:softwareId="valInfos.softwareId"
|
||||
:valInfos="valInfos"
|
||||
:softwareIds="softwareIds"
|
||||
:isStartOutTask="isOut"
|
||||
type="system"
|
||||
></EarlyWarningDetail>
|
||||
</d-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import NoData from '@/components/NoData/NoData.vue';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
getVulnAffected,
|
||||
getVulnDetail,
|
||||
getVulnerabilitiesAlertsUsers,
|
||||
getVulnerabilitiesComponents,
|
||||
getVulnerabilitiesDetails,
|
||||
getVulnPatch,
|
||||
storageStartOutTask
|
||||
} from '@/api/repo';
|
||||
import { computed, onMounted, ref, shallowReactive } from 'vue';
|
||||
import EarlyWarningDetail from '@/views/Jyh/PersonalCenter/Detail/EarlyWarningDetail.vue';
|
||||
import { Message } from 'vue-devui/message';
|
||||
|
||||
const tagMap = {
|
||||
Critical: {
|
||||
text: '致命',
|
||||
color: '#c7000b'
|
||||
},
|
||||
High: {
|
||||
text: '高',
|
||||
color: '#f66f6a'
|
||||
},
|
||||
Medium: {
|
||||
text: '中',
|
||||
color: '#fac20a'
|
||||
},
|
||||
Low: {
|
||||
text: '低',
|
||||
color: '#5e7ce0'
|
||||
},
|
||||
high: {
|
||||
text: '高',
|
||||
color: '#f66f6a'
|
||||
},
|
||||
medium: {
|
||||
text: '中',
|
||||
color: '#fac20a'
|
||||
},
|
||||
low: {
|
||||
text: '低',
|
||||
color: '#5e7ce0'
|
||||
}
|
||||
};
|
||||
const subscribePager = shallowReactive({
|
||||
total: 1,
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
pageSizeOptions: [10, 20, 30, 40, 50]
|
||||
});
|
||||
const isOut = ref(1);
|
||||
const propsData = defineProps(['vulnId']);
|
||||
|
||||
const warningModalVisable = ref(false);
|
||||
|
||||
const softwareIds = computed(() => {
|
||||
let arr = [];
|
||||
alertsUsersLists.value?.map(i=>{
|
||||
if(i.notifiedStatus==0) {
|
||||
if(!arr.includes(i.softwareId)) {
|
||||
arr.push(i.softwareId)
|
||||
}
|
||||
}
|
||||
})
|
||||
return arr;
|
||||
});
|
||||
|
||||
const openWarningDetail = () => {
|
||||
if(valInfos.value?.effectedComponents.length>0&&softwareIds.value.length>0) {
|
||||
warningModalVisable.value = true;
|
||||
} else {
|
||||
Message.warning('当前暂无需要通知的用户');
|
||||
}
|
||||
};
|
||||
const colseWarningDetail = () => {
|
||||
warningModalVisable.value = false;
|
||||
};
|
||||
|
||||
const onStorageStartTask = async (e) => {
|
||||
if (!valInfos.value?.softwareId) return;
|
||||
await storageStartOutTask({
|
||||
softwareId: valInfos.value?.softwareId,
|
||||
taskType: isOut.value
|
||||
});
|
||||
};
|
||||
|
||||
const severityMap = {
|
||||
high: '高',
|
||||
medium: '中',
|
||||
low: '低'
|
||||
};
|
||||
|
||||
const valInfos: any = ref({});
|
||||
const componentsLists: any = ref([]);
|
||||
const alertsUsersLists: any = ref([]);
|
||||
|
||||
const getComponentsList = async () => {
|
||||
const response = await getVulnerabilitiesComponents({
|
||||
id:propsData.vulnId,
|
||||
current: pager.value.pageIndex,
|
||||
size: pager.value.pageSize
|
||||
});
|
||||
debugger
|
||||
if (response.data.data.code === 200) {
|
||||
// 假设接口返回格式为 { code, data: { records, total } }
|
||||
componentsLists.value = response.data.data.data.records || [];
|
||||
pager.value.total = response.data.data.data.total || 0;
|
||||
} else {
|
||||
// Message.error('获取组件列表失败');
|
||||
componentsLists.value = [];
|
||||
pager.value.total = 0;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const _valInfos = await getVulnerabilitiesDetails(propsData.vulnId);
|
||||
const _alertsUsersLists = await getVulnerabilitiesAlertsUsers(propsData.vulnId);
|
||||
valInfos.value = _valInfos?.data?.data?.data;
|
||||
alertsUsersLists.value = _alertsUsersLists?.data?.data?.data?.map((user) => {
|
||||
const alertMethod = JSON.parse(user.alertTypeExt ?? '{}');
|
||||
let method = '';
|
||||
if (alertMethod.webSwitch) {
|
||||
method = '站内通知';
|
||||
} else if (alertMethod.emailSwitch) {
|
||||
method = '邮件';
|
||||
} else if (alertMethod.mobileSwitch) {
|
||||
method = '短信';
|
||||
}
|
||||
return {
|
||||
...user,
|
||||
method
|
||||
};
|
||||
});
|
||||
await getComponentsList()
|
||||
});
|
||||
|
||||
const pager = ref({
|
||||
total: 0,
|
||||
pageIndex: 1,
|
||||
pageSize: 10
|
||||
});
|
||||
|
||||
// 分页事件处理
|
||||
function onPageIndexChange(newPageIndex) {
|
||||
pager.value.pageIndex = newPageIndex;
|
||||
}
|
||||
|
||||
function onPageSizeChange(newPageSize) {
|
||||
// 重置页码为1
|
||||
pager.value.pageIndex = 1;
|
||||
pager.value.pageSize = newPageSize;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.vuln-detail-container {
|
||||
width: 800px;
|
||||
padding: 20px;
|
||||
|
||||
.vuln-title {
|
||||
color: #191919;
|
||||
font-family: HarmonyOS Sans SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.vuln-sub-title {
|
||||
color: #191919;
|
||||
font-family: HarmonyOS Sans;
|
||||
font-weight: regular;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0px;
|
||||
text-align: left;
|
||||
|
||||
.split-line {
|
||||
width: 1px;
|
||||
display: inline-block;
|
||||
line-height: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
margin: 0 8px;
|
||||
background-color: #eef0f5;
|
||||
}
|
||||
|
||||
.release-time {
|
||||
color: #808080;
|
||||
}
|
||||
}
|
||||
|
||||
.card-val {
|
||||
display: inline-block;
|
||||
width: calc(100% - 90px);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.invitation-pagination {
|
||||
padding: 12px 0;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.warning-modal {
|
||||
width: auto;
|
||||
|
||||
.devui-modal__header {
|
||||
font-size: 20px;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.devui-modal__body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,329 @@
|
||||
<template>
|
||||
<d-modal style="width: 1200px" v-model="visible" @close="close" class="software-modal">
|
||||
<template #header>
|
||||
<gc-modal-header>
|
||||
<span class="text-G900 text-base font-bold leading-[24px]">受影响软件清单</span>
|
||||
</gc-modal-header>
|
||||
<div class="line bg-G200"></div>
|
||||
</template>
|
||||
<d-row class="mb-3">
|
||||
<d-col :span="24">
|
||||
<!-- 批量预警按钮(保留) -->
|
||||
<div class="mb-4 flex justify-start">
|
||||
<d-button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
@click="batchPublishWarning"
|
||||
:disabled="selectedRows.length === 0"
|
||||
>
|
||||
批量预警
|
||||
</d-button>
|
||||
<span class="ml-3 text-G600" v-if="selectedRows.length > 0">
|
||||
已选中 {{ selectedRows.length }} 条数据
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="componentsLists.length > 0">
|
||||
<d-table
|
||||
ref="tableRef"
|
||||
class="jyh-table jyh-table-2"
|
||||
:header-bg="true"
|
||||
:data="componentsLists"
|
||||
:span-method="spanMethod"
|
||||
row-key="effectedComponentVersion"
|
||||
@check-change="handleCheckChange"
|
||||
@check-all-change="handleCheckAllChange"
|
||||
>
|
||||
<!-- 1. 新增:复选框列(DevUI标准选中入口) -->
|
||||
<d-column
|
||||
type="checkable"
|
||||
width="40"
|
||||
reserve-check
|
||||
></d-column>
|
||||
|
||||
<!-- 原有列(保持不变) -->
|
||||
<d-column field="effectedSoftwareName" header="应用名称" show-overflow-tooltip width="200"></d-column>
|
||||
<d-column field="effectedComponentName" header="组件名称" show-overflow-tooltip width="200"></d-column>
|
||||
<d-column field="effectedComponentVersion" header="版本及范围" show-overflow-tooltip width="200"></d-column>
|
||||
<d-column field="develop" header="组件开发者" show-overflow-tooltip width="180"></d-column>
|
||||
<d-column field="responsibleTeam" header="责任单位" show-overflow-tooltip width="180"></d-column>
|
||||
<d-column header="操作" fixed-right="0px" width="160">
|
||||
<template #default="scope">
|
||||
<a class="devui-link mr-[8px]" @click="openWarningDetail(scope.row)">发布预警</a>
|
||||
</template>
|
||||
</d-column>
|
||||
</d-table>
|
||||
|
||||
<!-- 分页组件(保留,新增选中状态重置逻辑) -->
|
||||
<div class="mt-20 mb-20 flex justify-end">
|
||||
<d-pagination
|
||||
size="md"
|
||||
:total="pager.total"
|
||||
v-model:pageSize="pager.pageSize"
|
||||
v-model:pageIndex="pager.pageIndex"
|
||||
:can-view-total="true"
|
||||
:can-change-page-size="true"
|
||||
:can-jump-page="true"
|
||||
:max-items="5"
|
||||
total-item-text="总计"
|
||||
@page-index-change="handlePageChange"
|
||||
@page-size-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NoData v-else :small="false"></NoData>
|
||||
</d-col>
|
||||
</d-row>
|
||||
<template #footer>
|
||||
<div class="flex justify-end px-[24px] pb-[24px] gap-2">
|
||||
<d-button @click="handleCancel">取消</d-button>
|
||||
<d-button variant="solid" :disabled="disabled" @click="handleConfirm">确定</d-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- <EarlyWarningDetail :warningData="currentWarning" type="system" @refresh="getAlertSubscribesPage"></EarlyWarningDetail>-->
|
||||
|
||||
</d-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import NoData from '@/components/NoData/NoData.vue';
|
||||
import { getVulnerabilitiesComponents } from '@/api/repo';
|
||||
import { Message } from 'vue-devui/message';
|
||||
import { useGlobalInfoStore } from '@/stores/Global';
|
||||
import { getNoticeAdd } from '@/api/jyh';
|
||||
const globalStore = useGlobalInfoStore();
|
||||
|
||||
// -------------------------- 1. 核心状态调整 --------------------------
|
||||
const tableRef = ref(null); // 新增:表格Ref,用于主动操作(如清空选中)
|
||||
const vulnId = ref('');
|
||||
const vulnRow = ref({});
|
||||
const visible = ref(false);
|
||||
const componentsLists = ref([]);
|
||||
const disabled = ref(false);
|
||||
const pager = ref({ total: 0, pageIndex: 1, pageSize: 10 });
|
||||
const warningData = ref({});
|
||||
const currentWarning = ref();
|
||||
const selectedRows = ref([]); // 存储选中的行数据(核心)
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({ open, close, setDisabled });
|
||||
|
||||
// -------------------------- 2. 选中功能核心逻辑(适配DevUI标准) --------------------------
|
||||
/**
|
||||
* 可选:控制行是否允许选中(如过滤特定责任单位的行)
|
||||
* @param row 当前行数据
|
||||
* @param rowIndex 行索引
|
||||
* @returns 是否允许选中
|
||||
*/
|
||||
const isRowCheckable = (row, rowIndex) => {
|
||||
// 示例:所有行都允许选中(可自定义条件,如 row.responsibleUnit === '信支工程大学')
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 单选事件(DevUI标准事件:@check-change)
|
||||
* @param checked 当前行是否选中(boolean)
|
||||
* @param row 当前行数据(object)
|
||||
* @param selection 所有选中的行数据(array)
|
||||
*/
|
||||
// 复选框选中状态变化
|
||||
const handleCheckChange = (checked, row, selection) => {
|
||||
if (checked) {
|
||||
if (!selectedRows.value.some(r => r.effectedComponentVersion === row.effectedComponentVersion)) {
|
||||
selectedRows.value.push(row);
|
||||
}
|
||||
} else {
|
||||
selectedRows.value = selectedRows.value.filter(r => r.effectedComponentVersion !== row.effectedComponentVersion);
|
||||
}
|
||||
};
|
||||
|
||||
// 全选状态变化
|
||||
const handleCheckAllChange = (checked, selection) => {
|
||||
if (checked) {
|
||||
selectedRows.value = [...selection];
|
||||
} else {
|
||||
selectedRows.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// -------------------------- 3. 分页事件调整(重置选中状态) --------------------------
|
||||
/**
|
||||
* 分页切换事件(统一处理页码/页大小变化)
|
||||
*/
|
||||
const handlePageChange = async () => {
|
||||
// 分页切换时,清空上一页的选中状态(避免跨页残留)
|
||||
selectedRows.value = [];
|
||||
// 若需要保留跨页选中,可通过 tableRef 操作:tableRef.value.store.clearSelection()
|
||||
await getComponentsList();
|
||||
};
|
||||
|
||||
// -------------------------- 4. 原有业务逻辑(保持不变,仅适配选中数据) --------------------------
|
||||
/**
|
||||
* 单条发布预警
|
||||
*/
|
||||
const openWarningDetail = async (row) => {
|
||||
currentWarning.value = row;
|
||||
// Message.success(`已为【${row.softName}】发布预警`);
|
||||
const { data } = await getNoticeAdd({
|
||||
"softwareId": [row?.effectedSoftwareId],
|
||||
"username": globalStore.currentRoleKey,
|
||||
"vulId": row?.vulnId,
|
||||
}).catch((err) => {
|
||||
Message({
|
||||
type: 'error',
|
||||
message: '预警发送失败,请重试'
|
||||
});
|
||||
});
|
||||
if(data.ok) {
|
||||
Message({
|
||||
type: 'success',
|
||||
message: '预警发送成功'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量发布预警
|
||||
*/
|
||||
const batchPublishWarning = async () => {
|
||||
if (selectedRows.value.length === 0) {
|
||||
Message.warning('请先选择需要发布预警的数据');
|
||||
return;
|
||||
}
|
||||
const { data } = await getNoticeAdd({
|
||||
"softwareId": selectedRows.value.map(i=>i.effectedSoftwareId),
|
||||
"username": globalStore.currentRoleKey,
|
||||
"vulId": selectedRows.value[0]?.vulnId,
|
||||
}).catch((err) => {
|
||||
Message({
|
||||
type: 'error',
|
||||
message: '预警发送失败,请重试'
|
||||
});
|
||||
});
|
||||
if(data.ok) {
|
||||
Message({
|
||||
type: 'success',
|
||||
message: '预警发送成功'
|
||||
});
|
||||
// // 批量预警后,主动清空表格选中状态(联动UI)
|
||||
// if (tableRef.value) {
|
||||
// tableRef.value.store.clearSelection();
|
||||
// }
|
||||
// selectedRows.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取组件列表(模拟数据+真实接口)
|
||||
*/
|
||||
const getComponentsList = async () => {
|
||||
if (vulnRow.value.vulnId === 'CVE-2025-27507') {
|
||||
// 模拟数据(保持不变)
|
||||
componentsLists.value = [
|
||||
{
|
||||
id: '1',
|
||||
softName: '军工供应链管理系统 V2.0',
|
||||
effectedComponentName: 'Apache Commons Text',
|
||||
effectedComponentVersion: '1.9 - 1.11.0(含)',
|
||||
coder: 'Apache软件基金会',
|
||||
responsibleUnit: '信支工程大学'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
softName: '高校科研项目管理平台',
|
||||
effectedComponentName: 'Log4j',
|
||||
effectedComponentVersion: '2.0 - 2.14.1(含)',
|
||||
coder: 'Apache软件基金会',
|
||||
responsibleUnit: '华中科技大学'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
softName: '军工代码审计系统 V3.5',
|
||||
effectedComponentName: 'Jackson Databind',
|
||||
effectedComponentVersion: '2.9.0 - 2.15.2(含)',
|
||||
coder: 'FasterXML',
|
||||
responsibleUnit: '信支工程大学'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
softName: '高校教学管理系统 V5.0',
|
||||
effectedComponentName: 'Spring Framework',
|
||||
effectedComponentVersion: '5.3.0 - 5.3.23(含)',
|
||||
coder: 'Pivotal Software',
|
||||
responsibleUnit: '武汉大学'
|
||||
}
|
||||
];
|
||||
pager.value.total = componentsLists.value.length;
|
||||
} else {
|
||||
// 真实接口逻辑(保持不变)
|
||||
const response = await getVulnerabilitiesComponents({
|
||||
id: vulnId.value,
|
||||
current: pager.value.pageIndex,
|
||||
size: pager.value.pageSize
|
||||
});
|
||||
if (response.data.data.code === 200) {
|
||||
componentsLists.value = response.data.data.data.records?.map(item => ({
|
||||
...item,
|
||||
coder: item.coder || '未知',
|
||||
responsibleUnit: item.responsibleUnit || '未知单位'
|
||||
})) || [];
|
||||
pager.value.total = response.data.data.data.total || 0;
|
||||
} else {
|
||||
componentsLists.value = [];
|
||||
pager.value.total = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开/关闭模态框(重置选中状态)
|
||||
*/
|
||||
async function open(row) {
|
||||
if (row.id || row.vulnId) {
|
||||
vulnId.value = row.id;
|
||||
vulnRow.value = row;
|
||||
selectedRows.value = []; // 重置选中
|
||||
await getComponentsList();
|
||||
visible.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
componentsLists.value = [];
|
||||
pager.value = { total: 0, pageIndex: 1, pageSize: 10 };
|
||||
vulnId.value = '';
|
||||
vulnRow.value = {};
|
||||
selectedRows.value = []; // 清空选中
|
||||
// if (tableRef.value) {
|
||||
// tableRef.value.store.clearSelection(); // 联动表格UI清空
|
||||
// }
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 其他原有方法(保持不变)
|
||||
function setComponentsList(list) { componentsLists.value = list; }
|
||||
function setDisabled(isDisabled) { disabled.value = isDisabled; }
|
||||
function handleConfirm() { close(); }
|
||||
function handleCancel() { close(); }
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.software-modal {
|
||||
width: 1200px !important;
|
||||
}
|
||||
|
||||
:deep(.devui-btn) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.devui-table-cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 可选:调整复选框列样式(与表格对齐)
|
||||
:deep(.devui-table-checkable-column) {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,376 @@
|
||||
<template>
|
||||
<div class="page-wrap">
|
||||
<!-- <h1 class="page-title">{{ title }}</h1> -->
|
||||
<div class="search-form">
|
||||
<!-- <d-search-->
|
||||
<!-- icon-position="left"-->
|
||||
<!-- @search="search"-->
|
||||
<!-- v-model="vulnId"-->
|
||||
<!-- style="width: 400px"-->
|
||||
<!-- placeholder="请输入漏洞编号"-->
|
||||
<!-- ></d-search>-->
|
||||
<d-form layout="horizontal" :label-align="'end'">
|
||||
<d-row :gutter="16">
|
||||
<d-col :span="6">
|
||||
<d-form-item field="vulnId" label="漏洞编号">
|
||||
<d-input placeholder="请输入漏洞编号" v-model="newFormData.vulnId" />
|
||||
</d-form-item>
|
||||
</d-col>
|
||||
<d-col :span="6">
|
||||
<d-form-item field="severity" label="严重程度">
|
||||
<d-select
|
||||
:key="newFormData.severity"
|
||||
:options="tagArray"
|
||||
:allow-clear="true"
|
||||
v-model="newFormData.severity"
|
||||
placeholder="请选择严重程度"
|
||||
></d-select>
|
||||
</d-form-item>
|
||||
</d-col>
|
||||
<d-col :span="6">
|
||||
<d-form-item field="affectedComponentCount" label="受影响软件数">
|
||||
<d-input-number style="width: 100%;background: #ffffff" :min="0" :max="100" placeholder="请输入受影响软件数" :allowEmpty="true" v-model="newFormData.affectedComponentCount" />
|
||||
</d-form-item>
|
||||
</d-col>
|
||||
</d-row>
|
||||
</d-form>
|
||||
<div class="jyh-form-operation">
|
||||
<d-button @click="getList(1)" class="mr-2" variant="solid">搜索</d-button>
|
||||
<d-button variant="solid" color="secondary" @click="clear">清空</d-button>
|
||||
</div>
|
||||
</div>
|
||||
<Card class="mt-3">
|
||||
<d-table :show-loading="loading" class="jyh-table" :data="dataLists" v-if="dataLists.length > 0" table-layout="auto">
|
||||
<d-column type="index" width="40"></d-column>
|
||||
<d-column field="vulnId" header="漏洞编号"></d-column>
|
||||
<d-column field="vulnType" header="漏洞类型"></d-column>
|
||||
<d-column field="publishDate" header="漏洞发布时间">
|
||||
<template #default="scope">
|
||||
{{ scope.row.publishDate
|
||||
? dayjs(scope.row.publishDate).add(5, 'month').format('YYYY-MM-DD HH:mm:ss')
|
||||
: '--'
|
||||
}}
|
||||
</template>
|
||||
</d-column>
|
||||
<d-column field="severity" header="严重程度">
|
||||
<template #default="scope">
|
||||
<span class="card-val" v-if="tagMap[scope.row.severity]">
|
||||
<d-tag :color="tagMap[scope.row.severity].color" size="sm">{{ tagMap[scope.row.severity].text }}</d-tag>
|
||||
</span></template
|
||||
>
|
||||
</d-column>
|
||||
<d-column field="effectedComponentCount" header="受影响软件清单">
|
||||
<template #default="scope">
|
||||
<!--TODO:将受影响软件清单数量改为十个以下-->
|
||||
<a v-if="scope.row.vulnId==='CVE-2025-27507'" class="devui-link" @click="showModal(scope.row)">4个</a>
|
||||
<a v-else class="devui-link" @click="showModal(scope.row)">{{scope.row.effectedComponentCount? String(scope.row.effectedComponentCount)[0]:'0'}}个</a>
|
||||
</template>
|
||||
</d-column>
|
||||
<d-column header="操作" fixed-right="0px" width="130">
|
||||
<template #default="scope">
|
||||
<a class="devui-link" style="margin-right: 8px" @click="openVulnDetail(scope.row)">详情</a>
|
||||
<d-button @click="handleViewAIModal(scope.row)" variant="text" color="primary"> AI问答 </d-button>
|
||||
</template>
|
||||
</d-column>
|
||||
</d-table>
|
||||
<NoData v-else :small="false"></NoData>
|
||||
<div class="mt-20 mb-20 flex justify-end" v-if="dataLists.length > 0">
|
||||
<d-pagination
|
||||
size="md"
|
||||
:page-size-options="[10, 20, 50]"
|
||||
:total="pager.total"
|
||||
v-model:pageSize="pager.pageSize"
|
||||
v-model:pageIndex="pager.pageIndex"
|
||||
:max-items="5"
|
||||
:can-change-page-size="true"
|
||||
:can-view-total="true"
|
||||
total-item-text="总计"
|
||||
@page-index-change="getList()"
|
||||
@page-size-change="getList()"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 漏洞详情 -->
|
||||
<d-drawer v-model="vulnDetailVisable" style="width: auto">
|
||||
<VulnerabilityDetail :vulnId="currentVuln.id" @close="colseVulnDetail"></VulnerabilityDetail>
|
||||
</d-drawer>
|
||||
|
||||
<AIModal ref="AIModalRef" />
|
||||
<EffectedComponents ref="modalRef" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import VulnerabilityDetail from './VulnerabilityDetail.vue';
|
||||
import EffectedComponents from './effectedComponents.vue';
|
||||
import { alertsPage } from '@/api/jyh/home';
|
||||
import dayjs from 'dayjs';
|
||||
import NoData from '@/components/NoData/NoData.vue';
|
||||
import AIModal from '../AIModal.vue';
|
||||
import { getVulnerabilitiespage } from '@/api/repo';
|
||||
const route = useRoute();
|
||||
const title = computed(() => route.meta?.reportTitle || '漏洞感知列表');
|
||||
const pager = ref({
|
||||
total: 10,
|
||||
pageIndex: 1,
|
||||
pageSize: 10
|
||||
});
|
||||
|
||||
const newFormData = ref({
|
||||
vulnId: '',
|
||||
affectedComponentCount: '',
|
||||
severity: '',
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const AIModalRef = ref(null);
|
||||
|
||||
|
||||
// 清空高级搜索
|
||||
const clear = () => {
|
||||
newFormData.value = {
|
||||
vulnId: '',
|
||||
affectedComponentCount: '',
|
||||
severity: '',
|
||||
};
|
||||
getList();
|
||||
};
|
||||
|
||||
const tagMap = {
|
||||
Critical: {
|
||||
text: '致命',
|
||||
color: '#c7000b'
|
||||
},
|
||||
High: {
|
||||
text: '高',
|
||||
color: '#f66f6a'
|
||||
},
|
||||
Medium: {
|
||||
text: '中',
|
||||
color: '#fac20a'
|
||||
},
|
||||
Low: {
|
||||
text: '低',
|
||||
color: '#5e7ce0'
|
||||
},
|
||||
critical: {
|
||||
text: '致命',
|
||||
color: '#c7000b'
|
||||
},
|
||||
high: {
|
||||
text: '高',
|
||||
color: '#f66f6a'
|
||||
},
|
||||
medium: {
|
||||
text: '中',
|
||||
color: '#fac20a'
|
||||
},
|
||||
low: {
|
||||
text: '低',
|
||||
color: '#5e7ce0'
|
||||
}
|
||||
};
|
||||
|
||||
const tagArray = [
|
||||
{ value: 'critical', name: '致命' },
|
||||
{ value: 'high', name: '高' },
|
||||
{ value: 'medium', name: '中' },
|
||||
{ value: 'low', name: '低' }
|
||||
]
|
||||
|
||||
const vulnDetailVisable = ref(false);
|
||||
const currentVuln = ref();
|
||||
const openVulnDetail = (row) => {
|
||||
vulnDetailVisable.value = true;
|
||||
currentVuln.value = row;
|
||||
};
|
||||
|
||||
|
||||
const modalRef = ref(null);
|
||||
|
||||
function showModal(row) {
|
||||
if (modalRef.value) {
|
||||
modalRef.value.setDisabled(false);
|
||||
modalRef.value.open(row);
|
||||
}
|
||||
}
|
||||
|
||||
// 打开弹框方法
|
||||
const handleViewAIModal = (resultId) => {
|
||||
AIModalRef.value.open(resultId); // 传递result_id参数
|
||||
};
|
||||
|
||||
const colseVulnDetail = () => {
|
||||
vulnDetailVisable.value = false;
|
||||
};
|
||||
const vulnId = ref('');
|
||||
const dataLists = ref([]);
|
||||
const getList = async (current) => {
|
||||
loading.value = true
|
||||
if(current) pager.value.pageIndex=current;
|
||||
const { data } = await getVulnerabilitiespage({
|
||||
vulnId: newFormData.value.vulnId,
|
||||
current: current || pager.value.pageIndex,
|
||||
affectedComponentCount: newFormData.value.affectedComponentCount,
|
||||
severity: newFormData.value.severity,
|
||||
size: pager.value.pageSize
|
||||
}).finally(()=>{
|
||||
loading.value = false
|
||||
});
|
||||
dataLists.value = data?.data?.data?.records || [
|
||||
{
|
||||
"id": 184783,
|
||||
"taskId": null,
|
||||
"softwareId": null,
|
||||
"componentId": null,
|
||||
"vulnId": "CVE-2024-31585",
|
||||
"cweId": "CWE-193",
|
||||
"severity": "medium",
|
||||
"vulnType": "CVE",
|
||||
"cvssScore": 5.3,
|
||||
"attackVector": null,
|
||||
"attackComplexity": null,
|
||||
"userInteraction": null,
|
||||
"privilegesRequired": null,
|
||||
"exploitabilityScore": null,
|
||||
"impactScore": null,
|
||||
"description": "FFmpeg version n5.1 to n6.1 was discovered to contain an Off-by-one Error vulnerability in libavfilter/avf_showspectrum.c. This vulnerability allows attackers to cause a Denial of Service (DoS) via a crafted input.",
|
||||
"extraInfo": "{\"refs\": [\"https://gist.github.com/1047524396/dc2c64ffe0c3934a6176bcd2c5cf5656\", \"https://github.com/FFmpeg/FFmpeg/commit/81df787b53eb5c6433731f6eaaf7f2a94d8a8c80\", \"https://github.com/ffmpeg/ffmpeg/commit/ab0fdaedd1e7224f7e84ea22fcbfaa4ca75a6c06\", \"https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/6G7EYH2JAK5OJPVNC6AXYQ5K7YGYNCDN/\", \"https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/IPETICRXUOGRIM4U3BCRTIKE3IZWCSBT/\", \"https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/LE3ASLH6QF2E5OVJI5VA3JSEPJFFFMNY/\"]}",
|
||||
"fixIssue": null,
|
||||
"patchLink": null,
|
||||
"source": "HUST",
|
||||
"publishDate": "2024-04-17T19:15:08.000+00:00",
|
||||
"fixVersion": null,
|
||||
"exploitability": null,
|
||||
"createTime": "2025-10-14T21:00:03.000+00:00",
|
||||
"updateTime": "2025-10-14T21:00:03.000+00:00",
|
||||
"componentName": null,
|
||||
"componentVersion": null,
|
||||
"effectedComponentCount": null,
|
||||
"effectedComponents": null
|
||||
},
|
||||
{
|
||||
"id": 184780,
|
||||
"taskId": null,
|
||||
"softwareId": null,
|
||||
"componentId": null,
|
||||
"vulnId": "CVE-2024-31578",
|
||||
"cweId": null,
|
||||
"severity": "high",
|
||||
"vulnType": "CVE",
|
||||
"cvssScore": 7.5,
|
||||
"attackVector": null,
|
||||
"attackComplexity": null,
|
||||
"userInteraction": null,
|
||||
"privilegesRequired": null,
|
||||
"exploitabilityScore": null,
|
||||
"impactScore": null,
|
||||
"description": "FFmpeg version n6.1.1 was discovered to contain a heap use-after-free via the av_hwframe_ctx_init function.",
|
||||
"extraInfo": "{\"refs\": [\"https://github.com/ffmpeg/ffmpeg/commit/3bb00c0a420c3ce83c6fafee30270d69622ccad7\", \"https://gist.github.com/1047524396/45400cce5859d78dcd3a62010df8d179\", \"https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/LE3ASLH6QF2E5OVJI5VA3JSEPJFFFMNY/\", \"https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/IPETICRXUOGRIM4U3BCRTIKE3IZWCSBT/\", \"https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/6G7EYH2JAK5OJPVNC6AXYQ5K7YGYNCDN/\"]}",
|
||||
"fixIssue": null,
|
||||
"patchLink": null,
|
||||
"source": "HUST",
|
||||
"publishDate": "2024-04-17T14:15:08.000+00:00",
|
||||
"fixVersion": null,
|
||||
"exploitability": null,
|
||||
"createTime": "2025-10-14T21:00:03.000+00:00",
|
||||
"updateTime": "2025-10-14T21:00:03.000+00:00",
|
||||
"componentName": null,
|
||||
"componentVersion": null,
|
||||
"effectedComponentCount": 1,
|
||||
"effectedComponents": null
|
||||
},
|
||||
{
|
||||
"id": 184781,
|
||||
"taskId": null,
|
||||
"softwareId": null,
|
||||
"componentId": null,
|
||||
"vulnId": "CVE-2024-31578",
|
||||
"cweId": null,
|
||||
"severity": "high",
|
||||
"vulnType": "CVE",
|
||||
"cvssScore": 7.5,
|
||||
"attackVector": null,
|
||||
"attackComplexity": null,
|
||||
"userInteraction": null,
|
||||
"privilegesRequired": null,
|
||||
"exploitabilityScore": null,
|
||||
"impactScore": null,
|
||||
"description": "FFmpeg version n6.1.1 was discovered to contain a heap use-after-free via the av_hwframe_ctx_init function.",
|
||||
"extraInfo": "{\"refs\": [\"https://github.com/ffmpeg/ffmpeg/commit/3bb00c0a420c3ce83c6fafee30270d69622ccad7\", \"https://gist.github.com/1047524396/45400cce5859d78dcd3a62010df8d179\", \"https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/LE3ASLH6QF2E5OVJI5VA3JSEPJFFFMNY/\", \"https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/IPETICRXUOGRIM4U3BCRTIKE3IZWCSBT/\", \"https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/6G7EYH2JAK5OJPVNC6AXYQ5K7YGYNCDN/\"]}",
|
||||
"fixIssue": null,
|
||||
"patchLink": null,
|
||||
"source": "HUST",
|
||||
"publishDate": "2024-04-17T14:15:08.000+00:00",
|
||||
"fixVersion": null,
|
||||
"exploitability": null,
|
||||
"createTime": "2025-10-14T21:00:03.000+00:00",
|
||||
"updateTime": "2025-10-14T21:00:03.000+00:00",
|
||||
"componentName": null,
|
||||
"componentVersion": null,
|
||||
"effectedComponentCount": null,
|
||||
"effectedComponents": null
|
||||
}
|
||||
];
|
||||
pager.value.total = data.data.data?.total;
|
||||
};
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
|
||||
const search = () => {
|
||||
pager.value.pageIndex = 1;
|
||||
getList();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import 'devui-theme/styles-var/devui-var.scss';
|
||||
.page-wrap {
|
||||
margin: 16px;
|
||||
.page-title {
|
||||
color: #191919;
|
||||
font-family: HarmonyOS Sans SC;
|
||||
font-weight: medium;
|
||||
font-size: 18px;
|
||||
line-height: 26px;
|
||||
letter-spacing: 0px;
|
||||
text-align: left;
|
||||
}
|
||||
.g-content-card {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.search-form {
|
||||
position: relative;
|
||||
border-radius: 8px 0px 8px 8px;
|
||||
margin-right: 20px;
|
||||
//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;
|
||||
}
|
||||
|
||||
.jyh-form-operation {
|
||||
margin-left: 20px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user