可信代码库V2版-安全智库页面原型开发、安全检测中心页面原型开发、关于我们页面原型开发

This commit is contained in:
fmk1023
2025-11-06 10:54:58 +08:00
parent 533e20b2f3
commit f5b59fadd9
29 changed files with 8077 additions and 650 deletions

View File

@@ -0,0 +1,295 @@
<template>
<div class="intelligence-detail">
<!-- 头部信息 -->
<div class="detail-header">
<h2 class="title">{{ intelligence?.title }}</h2>
<div class="header-tags">
<d-tag :color="typeColorMap[intelligence?.intelligenceType]">
{{ intelligence?.intelligenceType }}
</d-tag>
<d-tag :color="urgencyColorMap[intelligence?.urgency]">
{{ intelligence?.urgency }}
</d-tag>
</div>
</div>
<!-- 基本信息卡片 -->
<d-card class="detail-card mt-4">
<div slot="header" class="card-header">基本信息</div>
<d-descriptions column="2" bordered>
<d-description-item term="情报ID">{{ intelligence?.intelligenceId }}</d-description-item>
<d-description-item term="情报类型">{{ intelligence?.intelligenceType }}</d-description-item>
<d-description-item term="情报来源">{{ intelligence?.source }}</d-description-item>
<d-description-item term="发布时间">
{{ intelligence?.publishDate ? dayjs(intelligence.publishDate).format('YYYY-MM-DD HH:mm:ss') : '--' }}
</d-description-item>
<d-description-item term="紧急程度">{{ intelligence?.urgency }}</d-description-item>
<d-description-item term="情报版本">{{ intelligence?.version || '1.0' }}</d-description-item>
</d-descriptions>
</d-card>
<!-- 情报摘要 -->
<d-card class="detail-card mt-4">
<div slot="header" class="card-header">情报摘要</div>
<div class="summary-content">
{{ intelligence?.summary || '暂无摘要信息' }}
</div>
</d-card>
<!-- 详细内容 -->
<d-card class="detail-card mt-4">
<div slot="header" class="card-header">
详细内容
<d-button size="sm" variant="text" @click="toggleContentExpand">
{{ isContentExpanded ? '收起' : '全文展开' }}
</d-button>
</div>
<div class="detail-content" :class="{ 'expanded': isContentExpanded }">
<pre v-if="intelligence?.content">{{ formatContent(intelligence.content) }}</pre>
<p v-else>暂无详细内容</p>
</div>
</d-card>
<!-- 相关IOCs -->
<d-card class="detail-card mt-4" v-if="intelligence?.relatedIocs && intelligence.relatedIocs.length">
<div slot="header" class="card-header">
相关IOCs
<d-button size="sm" variant="text" @click="copyAllIocs">
<i class="fa fa-copy mr-1"></i>复制全部
</d-button>
</div>
<d-table :data="intelligence.relatedIocs" table-layout="auto">
<d-column field="type" header="类型" :width="100"></d-column>
<d-column field="value" header="值"></d-column>
<d-column header="操作" :width="100">
<template #default="scope">
<d-button
size="sm"
variant="text"
@click="copyIoc(scope.row.value)"
>
<i class="fa fa-copy"></i>复制
</d-button>
</template>
</d-column>
</d-table>
</d-card>
<!-- 参考信息 -->
<d-card class="detail-card mt-4" v-if="intelligence?.references && intelligence.references.length">
<div slot="header" class="card-header">参考信息</div>
<ul class="reference-list">
<li v-for="(ref, idx) in intelligence.references" :key="idx">
<a :href="ref.url" target="_blank" class="reference-link">
{{ ref.title || `参考链接 ${idx + 1}` }}
</a>
</li>
</ul>
</d-card>
<!-- 操作按钮 -->
<div class="detail-actions mt-6">
<d-button @click="$emit('close')" variant="secondary">关闭</d-button>
<d-button @click="exportReport" class="ml-2">
<i class="fa fa-download mr-1"></i>导出报告
</d-button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import dayjs from 'dayjs';
// 接收父组件传入的情报数据
const props = defineProps({
intelligence: {
type: Object,
required: true,
default: () => ({})
}
});
// 组件内部状态
const isContentExpanded = ref(false);
const toast = ref();
// 类型颜色映射(与列表页保持一致)
const typeColorMap = {
'威胁情报': '#165DFF',
'漏洞情报': '#f66f6a',
'事件情报': '#fac20a',
'APT情报': '#722ED1',
'恶意代码情报': '#f5319d'
};
// 紧急程度颜色映射(与列表页保持一致)
const urgencyColorMap = {
'紧急': '#c7000b',
'高': '#f66f6a',
'中': '#fac20a',
'低': '#5e7ce0'
};
// 切换内容展开/收起状态
const toggleContentExpand = () => {
isContentExpanded.value = !isContentExpanded.value;
};
// 格式化内容(处理换行和缩进)
const formatContent = (content: string) => {
return content.replace(/\\n/g, '\n').replace(/ /g, ' ');
};
// 复制单个IOC
const copyIoc = (value: string) => {
navigator.clipboard.writeText(value).then(() => {
toast.success({ content: '已复制到剪贴板', duration: 2000 });
}).catch(() => {
toast.error({ content: '复制失败,请手动复制', duration: 2000 });
});
};
// 复制所有IOCs
const copyAllIocs = () => {
if (!props.intelligence.relatedIocs || !props.intelligence.relatedIocs.length) return;
const allIocs = props.intelligence.relatedIocs
.map(item => `${item.type}: ${item.value}`)
.join('\n');
navigator.clipboard.writeText(allIocs).then(() => {
toast.success({ content: '全部IOC已复制到剪贴板', duration: 2000 });
}).catch(() => {
toast.error({ content: '复制失败,请手动复制', duration: 2000 });
});
};
// 导出报告
const exportReport = () => {
// 模拟导出功能
toast.info({ content: '正在准备导出报告...', duration: 2000 });
// 实际应用中应调用后端导出接口
// const reportData = { ...props.intelligence };
// exportIntelligenceReport(reportData).then(() => {
// toast.success({ content: '报告导出成功', duration: 2000 });
// });
};
</script>
<style scoped lang="scss">
@import 'devui-theme/styles-var/devui-var.scss';
.intelligence-detail {
padding: 20px;
height: 100%;
box-sizing: border-box;
overflow-y: auto;
max-height: 80vh;
}
.detail-header {
display: flex;
flex-direction: column;
gap: 12px;
}
.title {
font-size: 20px;
font-weight: 600;
color: var(--devui-text-primary);
line-height: 1.5;
margin: 0;
}
.header-tags {
display: flex;
gap: 8px;
}
.detail-card {
--devui-card-padding: 16px;
}
.card-header {
font-size: 16px;
font-weight: 600;
color: var(--devui-text-primary);
display: flex;
justify-content: space-between;
align-items: center;
}
.summary-content {
padding: 8px 0;
line-height: 1.6;
color: var(--devui-text-secondary);
text-indent: 2em;
}
.detail-content {
max-height: 300px;
overflow-y: auto;
padding: 8px 0;
&.expanded {
max-height: none;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
line-height: 1.6;
color: var(--devui-text-secondary);
font-family: inherit;
margin: 0;
}
}
.reference-list {
padding: 8px 0 8px 20px;
margin: 0;
li {
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
}
}
.reference-link {
color: var(--devui-primary);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
.detail-actions {
display: flex;
justify-content: flex-end;
//padding-top: 16px;
border-top: 1px solid var(--devui-border);
}
// 滚动条美化
.detail-content::-webkit-scrollbar,
.intelligence-detail::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.detail-content::-webkit-scrollbar-thumb,
.intelligence-detail::-webkit-scrollbar-thumb {
border-radius: 3px;
background-color: var(--devui-scrollbar-thumb-color);
}
.detail-content::-webkit-scrollbar-track,
.intelligence-detail::-webkit-scrollbar-track {
background-color: var(--devui-scrollbar-track-color);
}
</style>

View File

@@ -0,0 +1,327 @@
<template>
<div class="page-wrap">
<div class="search-form">
<d-form layout="horizontal" :label-align="'end'">
<d-row :gutter="16">
<d-col :span="6">
<d-form-item field="intelligenceId" label="情报ID">
<d-input placeholder="请输入情报ID" v-model="newFormData.intelligenceId" />
</d-form-item>
</d-col>
<d-col :span="6">
<d-form-item field="intelligenceType" label="情报类型">
<d-select
:options="typeArray"
:allow-clear="true"
v-model="newFormData.intelligenceType"
placeholder="请选择情报类型"
></d-select>
</d-form-item>
</d-col>
<d-col :span="6">
<d-form-item field="source" label="情报来源">
<d-select
:options="sourceArray"
:allow-clear="true"
v-model="newFormData.source"
placeholder="请选择情报来源"
></d-select>
</d-form-item>
</d-col>
<d-col :span="6">
<d-form-item field="urgency" label="紧急程度">
<d-select
:options="urgencyArray"
:allow-clear="true"
v-model="newFormData.urgency"
placeholder="请选择紧急程度"
></d-select>
</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="intelligenceId" header="情报ID"></d-column>
<d-column field="title" header="情报标题"></d-column>
<d-column field="intelligenceType" header="情报类型">
<template #default="scope">
<d-tag :color="typeColorMap[scope.row.intelligenceType]">{{ scope.row.intelligenceType }}</d-tag>
</template>
</d-column>
<d-column field="source" header="情报来源"></d-column>
<d-column field="publishDate" header="发布时间">
<template #default="scope">
{{ scope.row.publishDate
? dayjs(scope.row.publishDate).format('YYYY-MM-DD HH:mm:ss')
: '--'
}}
</template>
</d-column>
<d-column field="urgency" header="紧急程度">
<template #default="scope">
<d-tag :color="urgencyColorMap[scope.row.urgency]">{{ scope.row.urgency }}</d-tag>
</template>
</d-column>
<d-column header="操作" fixed-right="0px" width="130">
<template #default="scope">
<a style="margin-right: 8px" class="devui-link" @click="openIntelligenceDetail(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="detailVisible" style="width: 900px">
<IntelligenceDetail :intelligence="currentIntelligence" @close="detailVisible = false"></IntelligenceDetail>
</d-drawer>
<AIModal ref="AIModalRef" />
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import NoData from '@/components/NoData/NoData.vue';
import dayjs from 'dayjs';
import AIModal from '../AIModal.vue';
// 假设存在情报详情组件
import IntelligenceDetail from './IntelligenceDetail.vue';
const route = useRoute();
const title = computed(() => route.meta?.title || '情报列表');
// 打开弹框方法
const AIModalRef = ref(null);
const handleViewAIModal = (resultId) => {
AIModalRef.value.open(resultId); // 传递result_id参数
};
// 分页配置
const pager = ref({
total: 50,
pageIndex: 1,
pageSize: 10
});
// 搜索表单数据
const newFormData = ref({
intelligenceId: '',
intelligenceType: '',
source: '',
urgency: ''
});
// 加载状态
const loading = ref(false);
// 情报列表数据
const dataLists = ref([]);
// 详情抽屉状态
const detailVisible = ref(false);
const currentIntelligence = ref(null);
// 类型选项与颜色映射
const typeArray = [
{ value: '威胁情报', name: '威胁情报' },
{ value: '漏洞情报', name: '漏洞情报' },
{ value: '事件情报', name: '事件情报' },
{ value: 'APT情报', name: 'APT情报' },
{ value: '恶意代码情报', name: '恶意代码情报' }
];
const typeColorMap = {
'威胁情报': '#165DFF',
'漏洞情报': '#f66f6a',
'事件情报': '#fac20a',
'APT情报': '#722ED1',
'恶意代码情报': '#f5319d'
};
// 来源选项
const sourceArray = [
{ value: '内部监测', name: '内部监测' },
{ value: '合作伙伴', name: '合作伙伴' },
{ value: '公开渠道', name: '公开渠道' },
{ value: '威胁情报平台', name: '威胁情报平台' },
{ value: 'CERT/CC', name: 'CERT/CC' }
];
// 紧急程度选项与颜色映射
const urgencyArray = [
{ value: '紧急', name: '紧急' },
{ value: '高', name: '高' },
{ value: '中', name: '中' },
{ value: '低', name: '低' }
];
const urgencyColorMap = {
'紧急': '#c7000b',
'高': '#f66f6a',
'中': '#fac20a',
'低': '#5e7ce0'
};
// 清空搜索条件
const clear = () => {
newFormData.value = {
intelligenceId: '',
intelligenceType: '',
source: '',
urgency: ''
};
getList();
};
// 打开情报详情
const openIntelligenceDetail = (row) => {
currentIntelligence.value = row;
detailVisible.value = true;
};
// 获取情报列表数据
const getList = async (current?) => {
loading.value = true;
if (current) pager.value.pageIndex = current;
// 模拟API请求
setTimeout(() => {
// 模拟情报数据
const mockData = Array.from({ length: pager.value.pageSize }, (_, i) => {
const id = (pager.value.pageIndex - 1) * pager.value.pageSize + i + 1;
const types = ['威胁情报', '漏洞情报', '事件情报', 'APT情报', '恶意代码情报'];
const sources = ['内部监测', '合作伙伴', '公开渠道', '威胁情报平台', 'CERT/CC'];
const urgencies = ['紧急', '高', '中', '低'];
const randomType = types[Math.floor(Math.random() * types.length)];
return {
id: id,
intelligenceId: `INT-${2024}${String(id).padStart(5, '0')}`,
title: `${randomType}:关于${['某APT组织', '新型勒索病毒', '重大漏洞', '网络攻击事件'][Math.floor(Math.random() * 4)]}的分析`,
intelligenceType: randomType,
source: sources[Math.floor(Math.random() * sources.length)],
publishDate: dayjs().subtract(Math.floor(Math.random() * 30), 'day').toISOString(),
urgency: urgencies[Math.floor(Math.random() * urgencies.length)],
summary: '这是一份重要的安全情报,包含潜在威胁的详细分析和应对建议...',
content: '【情报详情】\n近期监测到相关威胁活动频繁发生主要影响范围包括政府、金融和能源行业...\n【影响范围】\n...\n【应对建议】\n1. 及时更新系统补丁\n2. 加强网络边界防护\n3. 部署威胁检测设备',
relatedIocs: [
{ type: 'IP', value: `192.168.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}` },
{ type: '域名', value: `malicious-${id}.com` }
]
};
});
dataLists.value = mockData;
pager.value.total = 50; // 模拟总条数
loading.value = false;
}, 600);
// 实际接口调用示例
/*
const { data } = await getIntelligenceList({
intelligenceId: newFormData.value.intelligenceId,
intelligenceType: newFormData.value.intelligenceType,
source: newFormData.value.source,
urgency: newFormData.value.urgency,
current: current || pager.value.pageIndex,
size: pager.value.pageSize
}).finally(() => {
loading.value = false;
});
dataLists.value = data?.data?.data?.records || [];
pager.value.total = data.data.data?.total || 0;
*/
};
// 页面挂载时加载数据
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;
.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;
margin-top: 16px;
}
}
// 适配情报标题较长的情况
::v-deep .devui-table-cell {
&.devui-table-cell-ellipsis {
max-width: 250px;
}
}
</style>