提交 84b2e01f authored 作者: 张伊明's avatar 张伊明

Merge branch 'master' of http://8.140.26.4:10003/caijian/risk-monitor into zym-dev

...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"axios": "^1.12.2", "axios": "^1.12.2",
"d3": "^7.9.0", "d3": "^7.9.0",
"d3-cloud": "^1.2.7", "d3-cloud": "^1.2.7",
"dayjs": "^1.11.20",
"default-passive-events": "^4.0.0", "default-passive-events": "^4.0.0",
"echarts": "^5.4.3", "echarts": "^5.4.3",
"echarts-liquidfill": "^3.1.0", "echarts-liquidfill": "^3.1.0",
...@@ -2558,10 +2559,9 @@ ...@@ -2558,10 +2559,9 @@
} }
}, },
"node_modules/dayjs": { "node_modules/dayjs": {
"version": "1.11.18", "version": "1.11.20",
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.18.tgz", "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz",
"integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="
"license": "MIT"
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
"axios": "^1.12.2", "axios": "^1.12.2",
"d3": "^7.9.0", "d3": "^7.9.0",
"d3-cloud": "^1.2.7", "d3-cloud": "^1.2.7",
"dayjs": "^1.11.20",
"default-passive-events": "^4.0.0", "default-passive-events": "^4.0.0",
"echarts": "^5.4.3", "echarts": "^5.4.3",
"echarts-liquidfill": "^3.1.0", "echarts-liquidfill": "^3.1.0",
......
...@@ -393,13 +393,13 @@ export function getDomainDistribution(sanctionDate = "2025-11-11") { ...@@ -393,13 +393,13 @@ export function getDomainDistribution(sanctionDate = "2025-11-11") {
* startTime: string * startTime: string
* }[]>} * }[]>}
*/ */
export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize = 10, sanctionDate = "", isCn = false) { export function getEntitiesList(sanTypeId=1, pageNum = 1, pageSize = 10, sanctionDate = "", isCn = false) {
return request200( return request200(
request({ request({
method: "POST", method: "POST",
url: "/api/sanctionList/pageQuery", url: "/api/sanctionList/pageQuery",
data: { data: {
typeName, sanTypeId,
pageNum, pageNum,
pageSize, pageSize,
sanctionDate, sanctionDate,
......
...@@ -392,6 +392,14 @@ export function getSingleSanctionEntitySupplyChain(params) { ...@@ -392,6 +392,14 @@ export function getSingleSanctionEntitySupplyChain(params) {
}); });
} }
// 单次制裁-深度挖掘-制裁实体信息
export function getSingleSanctionEntityInfo(id) {
return request({
method: "GET",
url: `/api/organization/sanInfo?orgId=${id}`,
});
}
// 单次制裁-深度挖掘-制裁实体股权信息 // 单次制裁-深度挖掘-制裁实体股权信息
/** /**
* @param {Object} params * @param {Object} params
...@@ -550,10 +558,10 @@ export function getSingleSanctionEntityInternationalPaper(params) { ...@@ -550,10 +558,10 @@ export function getSingleSanctionEntityInternationalPaper(params) {
} }
// 商业管制清单-CCL清单简介-基本信息 // 商业管制清单-CCL清单简介-基本信息
export function getCCLInfo() { export function getCCLInfo(sanTypeId = 13) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/baseInfo/ccl` url: `/api/sanctionList/baseInfoById/${sanTypeId}`
}); });
} }
...@@ -620,3 +628,11 @@ export function getCclQuery(data) { ...@@ -620,3 +628,11 @@ export function getCclQuery(data) {
data data
}); });
} }
// 商业管制清单-CCL清单列表-清单版本
export function getCCLVersionList() {
return request({
method: "GET",
url: `/api/ccl/version/dateList`
});
}
...@@ -41,6 +41,12 @@ const getGraphChart = (nodes, links, layoutType) => { ...@@ -41,6 +41,12 @@ const getGraphChart = (nodes, links, layoutType) => {
itemStyle: { itemStyle: {
color: '#73C0DE' color: '#73C0DE'
}, },
// 方法1:通过 left/right/top/bottom 控制绘图区域
left: '5%',
right: '5%',
top: '5%',
bottom: '5%',
layout: layoutType, layout: layoutType,
data: nodes, data: nodes,
links: links, links: links,
......
...@@ -52,5 +52,7 @@ onBeforeUnmount(() => { ...@@ -52,5 +52,7 @@ onBeforeUnmount(() => {
.graph-chart-wrapper { .graph-chart-wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
// width: 800px;
// height: 500px;
} }
</style> </style>
\ No newline at end of file
...@@ -120,7 +120,7 @@ const exportControlRoutes = [ ...@@ -120,7 +120,7 @@ const exportControlRoutes = [
name: "commercialControlList", name: "commercialControlList",
component: () => import("@/views/exportControl/v2.0CommercialControlList/index.vue"), component: () => import("@/views/exportControl/v2.0CommercialControlList/index.vue"),
meta: { meta: {
title: "全部实体清单" title: "商业管制清单"
} }
} }
] ]
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
`}} `}}
</pre> </pre>
<div class="chart-box"> <div class="chart-box">
<GraphChart :nodes="nodes" :links="links" layoutType="none"> <GraphChart :nodes="nodes" :links="links" layoutType="force">
</GraphChart> </GraphChart>
</div> </div>
</el-col> </el-col>
...@@ -207,6 +207,7 @@ const links = ref([ ...@@ -207,6 +207,7 @@ const links = ref([
{ source: 15, target: 7, label: { show: true, formatter: '合作', color: 'red', borderColor: 'red' } }, { source: 15, target: 7, label: { show: true, formatter: '合作', color: 'red', borderColor: 'red' } },
]); ]);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -218,5 +219,4 @@ const links = ref([ ...@@ -218,5 +219,4 @@ const links = ref([
width: 800px; width: 800px;
height: 500px; height: 500px;
} }
</style> </style>
\ No newline at end of file
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
</template> </template>
`}} `}}
</pre> </pre>
<div class="chart-box"> <div class="graphtree-chart-box">
<GraphChart :nodes="nodes" :links="links"> <GraphChart :nodes="nodes" :links="links" chartType="force">
</GraphChart> </GraphChart>
</div> </div>
</el-col> </el-col>
...@@ -176,6 +176,129 @@ const links = ref([ ...@@ -176,6 +176,129 @@ const links = ref([
{ source: 16, target: 7, label: { show: true, formatter: '合作', color: 'red', borderColor: 'red' } }, { source: 16, target: 7, label: { show: true, formatter: '合作', color: 'red', borderColor: 'red' } },
]); ]);
// const nodes = ref([
// {
// "id": 0,
// "name": "布拉德·米勒 (Brad Miller)",
// // "category": 1,
// "symbolSize": 80,
// "symbol": "image://data:image/png;base64,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"
// },
// {
// "id": 1,
// "name": "以利亚·E·卡明斯(Elijah E. Cummings)",
// // "category": 1,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// },
// {
// "id": 2,
// "name": "托马斯·L·阿什利 (Thomas L. Ashley)",
// // "category": 1,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// },
// {
// "id": 3,
// "name": "沃伦·拉德曼 (Warren Rudman)",
// // "category": 1,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// },
// {
// "id": 4,
// "name": "比尔·D·伯利森 (Bill D. Burlison)",
// // "category": 1,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// },
// {
// "id": 5,
// "name": "柯特·韦尔登 (Curt Weldon)",
// // "category": 1,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// },
// {
// "id": 6,
// "name": "詹姆斯·R·曼 (James R. Mann)",
// // "category": 1,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// },
// {
// "id": 7,
// "name": "迈克尔·N·卡斯尔 (Michael N. Castle)",
// // "category": 1,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// },
// {
// "id": 8,
// "name": "弗兰克·哈里森 (Frank Harrison)",
// // "category": 1,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// },
// {
// "id": 9,
// "name": "艾伦·努内利(Alan Nunnelee)",
// // "category": 1,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// },
// {
// "id": 10,
// "name": "阿尔丰索·贝尔 (Alphonzo Bell)",
// // "category": 1,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// },
// {
// "id": 11,
// "name": "劳拉·吉伦(Laura Gillen)",
// // "category": 1,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// },
// {
// "id": "c",
// "name": "托德·扬 (Todd Young)",
// // "category": 0,
// "symbolSize": 80,
// symbol: `image://${CompanyImg}`,
// "label": {
// "show": true,
// "position": "bottom",
// "formatter": "{n|{b}}",
// "rich": {
// "n": {
// "color": "rgba(5,95,194,1)",
// "fontSize": 24,
// "fontWeight": 700,
// "fontFamily": "Microsoft YaHei",
// "lineHeight": 36
// }
// }
// }
// }
// ])
// const links = ref(
// [
// { "source": 0, "target": "c", "label": { "show": true, "formatter": "儿子" }, "value": 2 },
// { "source": 1, "target": "c", "label": { "show": true, "formatter": "弟弟" }, "value": 2 },
// { "source": 2, "target": "c", "label": { "show": true, "formatter": "哥哥" }, "value": 2 },
// { "source": 3, "target": "c", "label": { "show": true, "formatter": "经理" }, "value": 2 },
// { "source": 4, "target": "c", "label": { "show": true, "formatter": "属下" }, "value": 2 },
// { "source": 5, "target": "c", "label": { "show": true, "formatter": "领导" }, "value": 2 },
// { "source": 6, "target": "c", "label": { "show": true, "formatter": "同事" }, "value": 2 },
// { "source": 7, "target": "c", "label": { "show": true, "formatter": "投资人" }, "value": 2 },
// { "source": 8, "target": "c", "label": { "show": true, "formatter": "爷爷" }, "value": 2 },
// { "source": 9, "target": "c", "label": { "show": true, "formatter": "妈妈" }, "value": 2 },
// { "source": 10, "target": "c", "label": { "show": true, "formatter": "奶奶" }, "value": 2 },
// { "source": 11, "target": "c", "label": { "show": true, "formatter": "孙子" }, "value": 2 }]
// )
</script> </script>
...@@ -185,7 +308,7 @@ const links = ref([ ...@@ -185,7 +308,7 @@ const links = ref([
width: 100%; width: 100%;
} }
.chart-box { .graphtree-chart-box {
width: 1600px; width: 1600px;
height: 800px; height: 800px;
} }
......
// 绘制echarts图表 // 绘制echarts图表
import * as echarts from 'echarts' import * as echarts from 'echarts'
import 'echarts-wordcloud'; import 'echarts-wordcloud';
const setChart = (option, chartId) => { import router from '@/router/index'
const setChart = (option, chartId, allowClick, selectParam) => {
let chartDom = document.getElementById(chartId); let chartDom = document.getElementById(chartId);
if (!chartDom) { if (!chartDom) {
return null; return null;
...@@ -9,6 +10,26 @@ const setChart = (option, chartId) => { ...@@ -9,6 +10,26 @@ const setChart = (option, chartId) => {
chartDom.removeAttribute("_echarts_instance_"); chartDom.removeAttribute("_echarts_instance_");
let chart = echarts.init(chartDom); let chart = echarts.init(chartDom);
chart.setOption(option); chart.setOption(option);
if (allowClick) {
// 3. 添加点击事件监听
chart.on('click', function (params) {
switch (selectParam.moduleType) {
case '国会法案':
// 判断点击的是否为饼图的数据项
if (params.componentType === 'series' && params.seriesType === 'pie') {
console.log('点击的扇形名称:', params.name);
selectParam.domains = JSON.stringify([params.name])
const route = router.resolve({
path: "/dataLibrary/countryBill",
query: selectParam
});
window.open(route.href, "_blank");
}
}
});
}
// 容器可能受布局/异步渲染影响,强制一次 resize 保证 canvas 与容器一致 // 容器可能受布局/异步渲染影响,强制一次 resize 保证 canvas 与容器一致
setTimeout(() => { setTimeout(() => {
chart.resize(); chart.resize();
......
...@@ -40,8 +40,7 @@ ...@@ -40,8 +40,7 @@
<div class="center-top"> <div class="center-top">
<overviewMainBox class="box1" title="热门法案" @toDetail="handleClickToDetail"> <overviewMainBox class="box1" title="热门法案" @toDetail="handleClickToDetail">
<template #headerIcon> <template #headerIcon>
<img style="width: 100%; height: 100%" src="./assets/images/box1-header-icon.png" <img style="width: 100%; height: 100%" src="./assets/images/box1-header-icon.png" alt="" />
alt="" />
</template> </template>
<div class="box1-left" @click="handleSwithCurBill('left')"> <div class="box1-left" @click="handleSwithCurBill('left')">
<div class="icon"> <div class="icon">
...@@ -122,27 +121,23 @@ ...@@ -122,27 +121,23 @@
<DivideHeader id="position2" class="divide2" :titleText="'资讯要闻'"></DivideHeader> <DivideHeader id="position2" class="divide2" :titleText="'资讯要闻'"></DivideHeader>
<div class="center-center"> <div class="center-center">
<NewsList :newsList="newsList" img="newsImage" title="newsTitle" from="from" <NewsList :newsList="newsList" img="newsImage" title="newsTitle" from="from" content="newsContent" />
content="newsContent" /> <MessageBubble :messageList="messageList" imageUrl="personImage" @more-click="handleToSocialDetail"
<MessageBubble :messageList="messageList" imageUrl="personImage" @person-click="handleClickToCharacter" name="personName" content="remarks" source="orgName" />
@more-click="handleToSocialDetail" @person-click="handleClickToCharacter" name="personName"
content="remarks" source="orgName" />
</div> </div>
<DivideHeader id="position3" class="divide3" :titleText="'数据总览'"></DivideHeader> <DivideHeader id="position3" class="divide3" :titleText="'数据总览'"></DivideHeader>
<div class="center-footer"> <div class="center-footer">
<OverviewCard class="overview-card--double box5" title="数量变化趋势" :icon="box5HeaderIcon"> <OverviewCard class="overview-card--double box5" title="数量变化趋势" :icon="box5HeaderIcon">
<template #right> <template #right>
<el-select v-model="box5Select" placeholder="选择领域" @change="handleBox5Change" <el-select v-model="box5Select" placeholder="选择领域" @change="handleBox5Change" style="width: 150px">
style="width: 150px">
<el-option label="全部领域" value="全部领域" /> <el-option label="全部领域" value="全部领域" />
<el-option v-for="item in categoryList" :key="item.id" :label="item.name" <el-option v-for="item in categoryList" :key="item.id" :label="item.name" :value="item.id" />
:value="item.id" />
</el-select> </el-select>
<el-select v-model="box5ProposalTime" placeholder="提案时间" @change="handleBox5Change" <el-select v-model="box5ProposalTime" placeholder="提案时间" @change="handleBox5Change"
style="width: 120px; margin-left: 8px"> style="width: 120px; margin-left: 8px">
<el-option v-for="item in box5ProposalTimeList" :key="item.value" <el-option v-for="item in box5ProposalTimeList" :key="item.value" :label="item.label"
:label="item.label" :value="item.value" /> :value="item.value" />
</el-select> </el-select>
</template> </template>
<div class="overview-card-body box5-main"> <div class="overview-card-body box5-main">
...@@ -154,8 +149,7 @@ ...@@ -154,8 +149,7 @@
<TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" /> <TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box5')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box5')" />
</div> </div>
<div v-if="aiPaneVisible.box5" class="overview-ai-pane" <div v-if="aiPaneVisible.box5" class="overview-ai-pane" @mouseleave="handleHideAiPane('box5')">
@mouseleave="handleHideAiPane('box5')">
<AiPane :aiContent="overviewAiContent.box5" /> <AiPane :aiContent="overviewAiContent.box5" />
</div> </div>
</div> </div>
...@@ -163,13 +157,12 @@ ...@@ -163,13 +157,12 @@
<OverviewCard class="overview-card--single box6" title="领域分布情况" :icon="box6HeaderIcon"> <OverviewCard class="overview-card--single box6" title="领域分布情况" :icon="box6HeaderIcon">
<template #right> <template #right>
<el-select v-model="box9selectetedTime" placeholder="选择时间" style="width: 90px"> <el-select v-model="box9selectetedTime" placeholder="选择时间" style="width: 90px">
<el-option v-for="item in box9YearList" :key="item.value" :label="item.label" <el-option v-for="item in box9YearList" :key="item.value" :label="item.label" :value="item.value" />
:value="item.value" />
</el-select> </el-select>
<el-select v-model="box9LegislativeStatus" placeholder="立法状态" @change="handleBox9StatusChange" <el-select v-model="box9LegislativeStatus" placeholder="立法状态" @change="handleBox9StatusChange"
style="width: 110px; margin-left: 8px"> style="width: 110px; margin-left: 8px">
<el-option v-for="item in box9LegislativeStatusList" :key="item.value" <el-option v-for="item in box9LegislativeStatusList" :key="item.value" :label="item.label"
:label="item.label" :value="item.value" /> :value="item.value" />
</el-select> </el-select>
</template> </template>
<div class="overview-card-body box6-main"> <div class="overview-card-body box6-main">
...@@ -181,8 +174,7 @@ ...@@ -181,8 +174,7 @@
<TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" /> <TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box6')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box6')" />
</div> </div>
<div v-if="aiPaneVisible.box6" class="overview-ai-pane" <div v-if="aiPaneVisible.box6" class="overview-ai-pane" @mouseleave="handleHideAiPane('box6')">
@mouseleave="handleHideAiPane('box6')">
<AiPane :aiContent="overviewAiContent.box6" /> <AiPane :aiContent="overviewAiContent.box6" />
</div> </div>
</div> </div>
...@@ -192,8 +184,7 @@ ...@@ -192,8 +184,7 @@
<OverviewCard class="overview-card--single box7" title="提案委员会分布情况" :icon="box7HeaderIcon"> <OverviewCard class="overview-card--single box7" title="提案委员会分布情况" :icon="box7HeaderIcon">
<template #right> <template #right>
<el-select v-model="box7selectetedTime" placeholder="选择时间" style="width: 90px"> <el-select v-model="box7selectetedTime" placeholder="选择时间" style="width: 90px">
<el-option v-for="item in box7YearList" :key="item.value" :label="item.label" <el-option v-for="item in box7YearList" :key="item.value" :label="item.label" :value="item.value" />
:value="item.value" />
</el-select> </el-select>
</template> </template>
<div class="overview-card-body box7-main"> <div class="overview-card-body box7-main">
...@@ -205,8 +196,7 @@ ...@@ -205,8 +196,7 @@
<TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" /> <TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box7')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box7')" />
</div> </div>
<div v-if="aiPaneVisible.box7" class="overview-ai-pane" <div v-if="aiPaneVisible.box7" class="overview-ai-pane" @mouseleave="handleHideAiPane('box7')">
@mouseleave="handleHideAiPane('box7')">
<AiPane :aiContent="overviewAiContent.box7" /> <AiPane :aiContent="overviewAiContent.box7" />
</div> </div>
</div> </div>
...@@ -214,8 +204,7 @@ ...@@ -214,8 +204,7 @@
<OverviewCard class="overview-card--single box8" title="进展分布情况" :icon="box7HeaderIcon"> <OverviewCard class="overview-card--single box8" title="进展分布情况" :icon="box7HeaderIcon">
<template #right> <template #right>
<el-select v-model="box8selectetedTime" placeholder="选择时间" style="width: 90px"> <el-select v-model="box8selectetedTime" placeholder="选择时间" style="width: 90px">
<el-option v-for="item in box8YearList" :key="item.value" :label="item.label" <el-option v-for="item in box8YearList" :key="item.value" :label="item.label" :value="item.value" />
:value="item.value" />
</el-select> </el-select>
</template> </template>
<div class="overview-card-body box8-main"> <div class="overview-card-body box8-main">
...@@ -230,8 +219,7 @@ ...@@ -230,8 +219,7 @@
<TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" /> <TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box8')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box8')" />
</div> </div>
<div v-if="aiPaneVisible.box8" class="overview-ai-pane" <div v-if="aiPaneVisible.box8" class="overview-ai-pane" @mouseleave="handleHideAiPane('box8')">
@mouseleave="handleHideAiPane('box8')">
<AiPane :aiContent="overviewAiContent.box8" /> <AiPane :aiContent="overviewAiContent.box8" />
</div> </div>
</div> </div>
...@@ -240,15 +228,13 @@ ...@@ -240,15 +228,13 @@
<div class="overview-card-body box9-main"> <div class="overview-card-body box9-main">
<div class="overview-chart-wrap"> <div class="overview-chart-wrap">
<el-empty v-if="!wordCloudHasData" description="暂无数据" :image-size="100" /> <el-empty v-if="!wordCloudHasData" description="暂无数据" :image-size="100" />
<WordCloundChart v-else class="overview-chart" width="100%" height="100%" <WordCloundChart v-else class="overview-chart" width="100%" height="100%" :data="wordCloudData" />
:data="wordCloudData" />
</div> </div>
<div class="overview-tip-row"> <div class="overview-tip-row">
<TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" /> <TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box9')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box9')" />
</div> </div>
<div v-if="aiPaneVisible.box9" class="overview-ai-pane" <div v-if="aiPaneVisible.box9" class="overview-ai-pane" @mouseleave="handleHideAiPane('box9')">
@mouseleave="handleHideAiPane('box9')">
<AiPane :aiContent="overviewAiContent.box9" /> <AiPane :aiContent="overviewAiContent.box9" />
</div> </div>
</div> </div>
...@@ -258,8 +244,7 @@ ...@@ -258,8 +244,7 @@
</div> </div>
<div class="home-content-footer"> <div class="home-content-footer">
<DivideHeader id="position4" class="divide4" :titleText="'资源库'"></DivideHeader> <DivideHeader id="position4" class="divide4" :titleText="'资源库'"></DivideHeader>
<ResourceLibrarySection :on-click-to-detail="handleClickToDetailO" <ResourceLibrarySection :on-click-to-detail="handleClickToDetailO" :on-after-page-change="handlePageChange" />
:on-after-page-change="handlePageChange" />
</div> </div>
</div> </div>
</div> </div>
...@@ -286,7 +271,7 @@ import { ...@@ -286,7 +271,7 @@ import {
import { getPersonSummaryInfo } from "@/api/common/index"; import { getPersonSummaryInfo } from "@/api/common/index";
import { getChartAnalysis } from "@/api/aiAnalysis/index"; import { getChartAnalysis } from "@/api/aiAnalysis/index";
import DivideHeader from "@/components/DivideHeader.vue"; import DivideHeader from "@/components/DivideHeader.vue";
import overviewMainBox from "@/components/base/boxBackground/overviewMainBox.vue"; import overviewMainBox from "@/components/base/BoxBackground/OverviewMainBox.vue";
import OverviewCard from "./OverviewCard.vue"; import OverviewCard from "./OverviewCard.vue";
import ResourceLibrarySection from "./ResourceLibrarySection.vue"; import ResourceLibrarySection from "./ResourceLibrarySection.vue";
import { useContainerScroll } from "@/hooks/useScrollShow"; import { useContainerScroll } from "@/hooks/useScrollShow";
...@@ -1000,7 +985,13 @@ const handleBox9Data = async () => { ...@@ -1000,7 +985,13 @@ const handleBox9Data = async () => {
null, null,
{ showCount: false } { showCount: false }
); );
box9ChartInstance = setChart(box9Chart, "box9Chart"); const selectParam = {
moduleType: '国会法案',
proposedDateStart: box9selectetedTime.value,
status: box9LegislativeStatus.value === '提出法案' ? 0 : 1
}
box9ChartInstance = setChart(box9Chart, "box9Chart", true, selectParam);
} }
}; };
......
...@@ -7,16 +7,16 @@ ...@@ -7,16 +7,16 @@
<SelectBox :placeholder-name="DatePlaceHolder" select-title="提出时间" :select-list="dateList" <SelectBox :placeholder-name="DatePlaceHolder" select-title="提出时间" :select-list="dateList"
:select-name="selectedDate" :custom-time="customTime" @update:select-text="handleSelectDate" :select-name="selectedDate" :custom-time="customTime" @update:select-text="handleSelectDate"
@update:custom-time="handleCustomDate" /> @update:custom-time="handleCustomDate" />
<SelectBox v-if="isFolderAll" :placeholder-name="areaPlaceHolder" select-title="科技领域" :select-list="areaList" <SelectBox v-if="isFolderAll" :placeholder-name="partyPlaceHolder" select-title="所属党派" :select-list="partyList"
:select-name="selectedParty" @update:select-text="handleSelectArea" />
<SelectBox v-if="isFolderAll" :placeholder-name="congressPlaceHolder" select-title="提出议院" :select-list="congressList"
:select-name="selectedCongress" @update:select-text="handleSelectArea" />
<SelectBox v-if="isFolderAll" :placeholder-name="areaPlaceHolder" select-title="委员会" :select-list="areaList"
:select-name="selectedArea" @update:select-text="handleSelectArea" /> :select-name="selectedArea" @update:select-text="handleSelectArea" />
<SelectBox v-if="isFolderAll" :placeholder-name="areaPlaceHolder" select-title="科技领域" :select-list="areaList" <SelectBox v-if="isFolderAll" :placeholder-name="areaPlaceHolder" select-title="提出议员" :select-list="areaList"
:select-name="selectedArea" @update:select-text="handleSelectArea" />
<SelectBox v-if="isFolderAll" :placeholder-name="areaPlaceHolder" select-title="科技领域" :select-list="areaList"
:select-name="selectedArea" @update:select-text="handleSelectArea" />
<SelectBox v-if="isFolderAll" :placeholder-name="areaPlaceHolder" select-title="科技领域" :select-list="areaList"
:select-name="selectedArea" @update:select-text="handleSelectArea" />
<SelectBox v-if="isFolderAll" :placeholder-name="areaPlaceHolder" select-title="科技领域" :select-list="areaList"
:select-name="selectedArea" @update:select-text="handleSelectArea" /> :select-name="selectedArea" @update:select-text="handleSelectArea" />
<SelectBox v-if="isFolderAll" :placeholder-name="statusPlaceHolder" select-title="所处阶段" :select-list="statusList"
:select-name="selectedStauts" @update:select-text="handleSelectArea" />
</div> </div>
<div class="header-footer"> <div class="header-footer">
<div class="header-footer-left"> <div class="header-footer-left">
...@@ -50,10 +50,8 @@ ...@@ -50,10 +50,8 @@
</ChartHeader> </ChartHeader>
</div> </div>
<div class="content-main"> <div class="content-main">
<ChartContainer <ChartContainer chartTitle="美国会法案提出数量随时间变化趋势" :chartTypeList="curChartTypeList"
chartTitle="美国会法案提出数量随时间变化趋势" @clickChartItem="handleSwitchActiveChart">
:chartTypeList="curChartTypeList"
@clickChartItem="handleSwitchActiveChart">
<template #chart-box> <template #chart-box>
<LineChart v-if="activeChart === '折线图'" :lineChartData="lineChartData" /> <LineChart v-if="activeChart === '折线图'" :lineChartData="lineChartData" />
<PieChart v-if="activeChart === '饼状图'" :pieChartData="pieChartData" /> <PieChart v-if="activeChart === '饼状图'" :pieChartData="pieChartData" />
...@@ -78,10 +76,11 @@ ...@@ -78,10 +76,11 @@
<div class="data-main-box-main-header"> <div class="data-main-box-main-header">
<div class="header-left"> <div class="header-left">
<div class="header-left-item1"> <div class="header-left-item1">
<el-checkbox v-model="isSelectedAll" label="全选" size="large" /> <el-checkbox v-model="isSelectedAll" label="全选" @change="handleSelectAllChange" size="large" />
</div>
<div class="header-left-item2 text-tip-1">{{ `已选择${selectedCount}项` }}</div>
<div class="header-left-item2 text-tip-1 cancel" @click="handleClearAll" v-show="selectedCount">{{ '取消' }}
</div> </div>
<div class="header-left-item2 text-tip-1">{{ `已选择${selectedNum}项` }}</div>
<div class="header-left-item2 text-tip-1 cancel">{{ '取消' }}</div>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="header-right-item item1"> <div class="header-right-item item1">
...@@ -108,7 +107,8 @@ ...@@ -108,7 +107,8 @@
</div> </div>
</div> </div>
<div class="data-main-box-main-content"> <div class="data-main-box-main-content">
<el-table :data="tableData" style="width: 100%" :row-style="{ height: '52px' }"> <el-table ref="tableRef" :data="tableData" row-key="id" @selection-change="handleSelectionChange"
@select="handleSelect" @select-all="handleSelectAll" style="width: 100%" :row-style="{ height: '52px' }">
<el-table-column type="selection" width="55" /> <el-table-column type="selection" width="55" />
<el-table-column label="Date" width="180" class-name="date-column"> <el-table-column label="Date" width="180" class-name="date-column">
<template #default="scope">{{ scope.row.date }}</template> <template #default="scope">{{ scope.row.date }}</template>
...@@ -121,14 +121,14 @@ ...@@ -121,14 +121,14 @@
</div> </div>
<div class="data-main-box-footer"> <div class="data-main-box-footer">
<el-pagination background layout="prev, pager, next" :total="totalNum" v-model:current-page="currentPage" <el-pagination background layout="prev, pager, next" :total="totalNum" v-model:current-page="currentPage"
:page-size="pageSize" @current-change="handleGetData" /> :page-size="pageSize" @current-change="handleCurrentChange" />
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue' import { ref, computed, watch, onMounted, nextTick } from 'vue'
import ChartContainer from '../../components/ChartContainer/index.vue' import ChartContainer from '../../components/ChartContainer/index.vue'
import ChartHeader from '../../components/ChartHeader/index.vue' import ChartHeader from '../../components/ChartHeader/index.vue'
import ActiveTag from '../../components/ActiveTag/index.vue' import ActiveTag from '../../components/ActiveTag/index.vue'
...@@ -138,7 +138,8 @@ import PieChart from '../../components/PieChart/index.vue' ...@@ -138,7 +138,8 @@ import PieChart from '../../components/PieChart/index.vue'
import BarChart from '../../components/BarChart/index.vue' import BarChart from '../../components/BarChart/index.vue'
import RaderChart from '../../components/RadarChart/idnex.vue' import RaderChart from '../../components/RadarChart/idnex.vue'
import SelectBox from '../../components/SelectBox/index.vue' import SelectBox from '../../components/SelectBox/index.vue'
import { useRoute } from "vue-router";
const route = useRoute();
// 图表/数据 // 图表/数据
const isShowChart = ref(true) const isShowChart = ref(true)
...@@ -164,12 +165,12 @@ const staticsDemensionList = ref([ ...@@ -164,12 +165,12 @@ const staticsDemensionList = ref([
{ {
name: '提出议院', name: '提出议院',
active: false, active: false,
chartTypeList: ['饼状图'] chartTypeList: ['饼状图', '雷达图']
}, },
{ {
name: '提出委员会', name: '提出委员会',
active: false, active: false,
chartTypeList: ['饼状图'] chartTypeList: ['折线图', '柱状图', '饼状图']
}, },
{ {
name: '提出议员党派', name: '提出议员党派',
...@@ -184,7 +185,7 @@ const staticsDemensionList = ref([ ...@@ -184,7 +185,7 @@ const staticsDemensionList = ref([
]) ])
const curChartTypeList = computed(() => { const curChartTypeList = computed(() => {
let arr = staticsDemensionList.value.filter(item =>item.active) let arr = staticsDemensionList.value.filter(item => item.active)
return arr[0].chartTypeList return arr[0].chartTypeList
}) })
...@@ -351,7 +352,6 @@ const radarChartData = ref({ ...@@ -351,7 +352,6 @@ const radarChartData = ref({
// 数据- 是否全选 // 数据- 是否全选
const isSelectedAll = ref(false) const isSelectedAll = ref(false)
const selectedNum = ref(8) // 选择项数
const curOperation = ref('') const curOperation = ref('')
const operationList = ref([ const operationList = ref([
...@@ -369,35 +369,9 @@ const operationList = ref([ ...@@ -369,35 +369,9 @@ const operationList = ref([
}, },
]) ])
const releaseTimeList = ref([
{
label: "按发布时间倒序",
value: true
},
{
label: "按发布时间升序",
value: false
}
]);
const isSort = ref(true); // true 倒序 false 升序
// const handleSwithSort = () => {
// isSort.value = !isSort.value;
// };
const handlePxChange = val => {
};
const currentPage = ref(1);
const pageSize = ref(10)
const handleGetData = () => {
}
// 领域 // 领域
const areaPlaceHolder = ref('请选择领域') const areaPlaceHolder = ref('请选择领域')
const selectedArea = ref('人工智能') const selectedArea = ref('')
const areaList = ref([ const areaList = ref([
{ {
name: '人工智能', name: '人工智能',
...@@ -419,19 +393,31 @@ const handleSelectArea = (value) => { ...@@ -419,19 +393,31 @@ const handleSelectArea = (value) => {
// 提出时间 // 提出时间
const DatePlaceHolder = ref('请选择时间') const DatePlaceHolder = ref('请选择时间')
const selectedDate = ref('时间1') const selectedDate = ref('')
const dateList = ref([ const dateList = ref([
{ {
name: '时间1', name: '自定义',
id: '时间1' id: '自定义'
},
{
name: '2026',
id: '2026'
}, },
{ {
name: '时间2', name: '2025',
id: '时间2' id: '2025'
}, },
{ {
name: '自定义', name: '2024',
id: '自定义' id: '2024'
},
{
name: '2023',
id: '2023'
},
{
name: '2022',
id: '2022'
}, },
]) ])
const customTime = ref('') // 自定义时间 const customTime = ref('') // 自定义时间
...@@ -445,65 +431,321 @@ const handleSelectDate = (value) => { ...@@ -445,65 +431,321 @@ const handleSelectDate = (value) => {
selectedDate.value = value selectedDate.value = value
} }
// 党派列表
const partyList = ref([
{
name: '共和党',
id: '共和党'
},
{
name: '民主党',
id: '民主党'
},
{
name: '其他',
id: '其他'
},
])
const selectedParty = ref('')
const partyPlaceHolder = ref('请选择党派')
// 议院列表
const congressList = ref([
{
name: '众议院',
id: '众议院'
},
{
name: '参议院',
id: '参议院'
}
])
const selectedCongress = ref('')
const congressPlaceHolder = ref('请选择议院')
// 议院列表
const statusList = ref([
{
name: '提出',
id: '0'
},
{
name: '通过',
id: '1'
}
])
const selectedStauts = ref('')
const statusPlaceHolder = ref('请选择立法阶段')
// 展开全部 / 收起 // 展开全部 / 收起
const isFolderAll = ref(false) const isFolderAll = ref(false)
const handleSwitchFolderAll = () => { const handleSwitchFolderAll = () => {
isFolderAll.value = !isFolderAll.value isFolderAll.value = !isFolderAll.value
} }
const tableRef = ref(null)
// 表格数据 // 表格数据
const tableData = [ const tableData = ref([
{ {
id: 1,
date: '2016-05-04', date: '2016-05-04',
name: 'Aleyna Kutzner', name: 'Aleyna Kutzner',
address: 'Lohrbergstr. 86c, Süd Lilli, Saarland', address: 'Lohrbergstr. 86c, Süd Lilli, Saarland',
}, },
{ {
id: 2,
date: '2016-05-03', date: '2016-05-03',
name: 'Helen Jacobi', name: 'Helen Jacobi',
address: '760 A Street, South Frankfield, Illinois', address: '760 A Street, South Frankfield, Illinois',
}, },
{ {
id: 3,
date: '2016-05-02', date: '2016-05-02',
name: 'Brandon Deckert', name: 'Brandon Deckert',
address: 'Arnold-Ohletz-Str. 41a, Alt Malinascheid, Thüringen', address: 'Arnold-Ohletz-Str. 41a, Alt Malinascheid, Thüringen',
}, },
{ {
id: 4,
date: '2016-05-01', date: '2016-05-01',
name: 'Margie Smith', name: 'Margie Smith',
address: '23618 Windsor Drive, West Ricardoview, Idaho', address: '23618 Windsor Drive, West Ricardoview, Idaho',
}, },
{ {
id: 5,
date: '2016-05-01', date: '2016-05-01',
name: 'Margie Smith', name: 'Margie Smith',
address: '23618 Windsor Drive, West Ricardoview, Idaho', address: '23618 Windsor Drive, West Ricardoview, Idaho',
}, },
{ {
id: 6,
date: '2016-05-01', date: '2016-05-01',
name: 'Margie Smith', name: 'Margie Smith',
address: '23618 Windsor Drive, West Ricardoview, Idaho', address: '23618 Windsor Drive, West Ricardoview, Idaho',
}, },
{ {
id: 7,
date: '2016-05-01', date: '2016-05-01',
name: 'Margie Smith', name: 'Margie Smith',
address: '23618 Windsor Drive, West Ricardoview, Idaho', address: '23618 Windsor Drive, West Ricardoview, Idaho',
}, },
{ {
id: 8,
date: '2016-05-01', date: '2016-05-01',
name: 'Margie Smith', name: 'Margie Smith',
address: '23618 Windsor Drive, West Ricardoview, Idaho', address: '23618 Windsor Drive, West Ricardoview, Idaho',
}, },
{ {
id: 9,
date: '2016-05-01', date: '2016-05-01',
name: 'Margie Smith', name: 'Margie Smith',
address: '23618 Windsor Drive, West Ricardoview, Idaho', address: '23618 Windsor Drive, West Ricardoview, Idaho',
}, },
{ {
id: 10,
date: '2016-05-01', date: '2016-05-01',
name: 'Margie Smith', name: 'Margie Smith',
address: '23618 Windsor Drive, West Ricardoview, Idaho', address: '23618 Windsor Drive, West Ricardoview, Idaho',
},
// {
// id: 11,
// date: '2016-05-01',
// name: 'Margie Smith',
// address: '23618 Windsor Drive, West Ricardoview, Idaho',
// },
// {
// id: 12,
// date: '2016-05-01',
// name: 'Margie Smith',
// address: '23618 Windsor Drive, West Ricardoview, Idaho',
// }
])
const releaseTimeList = ref([
{
label: "按发布时间倒序",
value: true
},
{
label: "按发布时间升序",
value: false
}
]);
const isSort = ref(true); // true 倒序 false 升序
// const handleSwithSort = () => {
// isSort.value = !isSort.value;
// };
const handlePxChange = val => {
};
const currentPage = ref(1);
const pageSize = ref(10)
// 存储选中的数据(跨页)[citation:3][citation:8]
const selectedMap = ref(new Map()) // 使用 Map 存储,key 为唯一 id
// 计算已选中的条数
const selectedCount = computed(() => selectedMap.value.size)
// 获取表格数据(示例)
const fetchTableData = async () => {
// 调用接口获取数据...
// const res = await getList({ page: currentPage.value, size: pageSize.value })
// tableData.value = res.data
// total.value = res.total
// 数据加载后,回显已选中的行
nextTick(() => {
tableData.value.forEach(row => {
if (selectedMap.value.has(row.id)) {
tableRef.value?.toggleRowSelection(row, true)
}
})
})
}
// 单选事件
const handleSelect = (selection, row) => {
if (selection.some(item => item.id === row.id)) {
// 选中:添加到 Map
selectedMap.value.set(row.id, row)
} else {
// 取消选中:从 Map 移除
selectedMap.value.delete(row.id)
} }
] }
// 全选/全不选事件
const handleSelectAll = (selection) => {
if (selection.length > 0) {
// 全选:将当前页所有数据添加到 Map
tableData.value.forEach(row => {
if (!selectedMap.value.has(row.id)) {
selectedMap.value.set(row.id, row)
}
})
} else {
// 全不选:从 Map 中移除当前页的所有数据
tableData.value.forEach(row => {
selectedMap.value.delete(row.id)
})
}
}
// 处理选择变化(用于统计)
const handleSelectionChange = (val) => {
// 这里可以做一些额外的处理,但主要统计使用 selectedMap
console.log('当前页选中数量:', val.length)
}
// 全选当前页按钮
const handleSelectAllPage = () => {
if (tableData.value.length === 0) return
// 检查当前页是否已全选
const currentPageSelected = tableData.value.every(row =>
selectedMap.value.has(row.id)
)
if (currentPageSelected) {
// 已全选,则取消当前页的全选
tableData.value.forEach(row => {
tableRef.value.toggleRowSelection(row, false)
selectedMap.value.delete(row.id)
})
} else {
// 未全选,则全选当前页
tableData.value.forEach(row => {
tableRef.value.toggleRowSelection(row, true)
if (!selectedMap.value.has(row.id)) {
selectedMap.value.set(row.id, row)
}
})
}
}
// 处理全选
const handleSelectAllChange = () => {
if (isSelectedAll.value) {
handleSelectAllPage()
} else {
handleClearAll()
}
// if (isSelectedAll.value) {
// // 全选:将当前页所有数据添加到 Map
// tableData.value.forEach(row => {
// if (!selectedMap.value.has(row.id)) {
// selectedMap.value.set(row.id, row)
// }
// })
// } else {
// // 全不选:从 Map 中移除当前页的所有数据
// tableData.value.forEach(row => {
// selectedMap.value.delete(row.id)
// })
// }
}
// 清空所有选择
const handleClearAll = () => {
selectedMap.value.clear()
tableRef.value?.clearSelection()
}
// 翻页
const handleCurrentChange = (val) => {
currentPage.value = val
fetchTableData()
}
// 监听数据变化,回显选中状态 [citation:4][citation:8]
watch(tableData, () => {
nextTick(() => {
tableData.value.forEach(row => {
if (selectedMap.value.has(row.id)) {
tableRef.value?.toggleRowSelection(row, true)
}
})
})
})
onMounted(() => {
selectedArea.value = route.query.domains?JSON.parse(route.query.domains)[0]:''
selectedDate.value = route.query.proposedDateStart
activeTagList.value = [
]
if (selectedArea.value) {
activeTagList.value.push(
{ name: selectedArea.value }
)
}
if (selectedDate.value) {
activeTagList.value.push({
name: selectedDate.value
})
}
if (route.query.status === '0' || route.query.status === '1') {
activeTagList.value.push({
name: route.query.status === '0' ? '提出' : '通过'
})
}
// 初始化
fetchTableData()
})
</script> </script>
......
...@@ -100,16 +100,6 @@ const chartItemList = computed(() => { ...@@ -100,16 +100,6 @@ const chartItemList = computed(() => {
return arr return arr
}) })
// nextTick(() => {
// let arr = chartItemList.value.filter(item => item.active)
// if(!arr.length) {
// alert(1)
// chartItemList.value[0].active = true
// }
// })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
<template> <template>
<div class="view-box"> <div class="view-box">
<el-empty v-if="!listData?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" /> <el-empty v-if="!listData?.length" style="padding: 60px 0" description="暂无数据" :image-size="100" />
<div v-if="listData.length" class="main-content-main" <div
@wheel.prevent="handleWheel" v-if="listData.length"
@mousedown="handleMouseDown" class="main-content-main"
@mouseup="handleMouseUp" @wheel.prevent="handleWheel"
@mouseleave="handleMouseUp" @mousedown="handleMouseDown"
@mousemove="handleMouseMove" @mouseup="handleMouseUp"
> @mouseleave="handleMouseUp"
<div class="fishbone-container" :style="{ transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`, transformOrigin: 'center center' }"> @mousemove="handleMouseMove"
<!-- 主轴上的标签 --> >
<div class="main-line" :style="{ width: listData.length * 200 + 300 + 'px' }"> <div
<div v-for="(item, index) in listData" :key="item.id" class="main-line-text" :class="getThemeClass(item.level)" :style="{ left: index * 200 + 220 + 'px' }"> class="fishbone-container"
{{ item.name }} :style="{
</div> transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`,
</div> transformOrigin: 'center center'
<!-- 奇数索引的数据组放在上方 --> }"
<div v-for="(causeGroup, groupIndex) in onFilterData(1)" :key="groupIndex" >
class="top-bone" :style="{ left: groupIndex * 400 + 510 + 'px', height: (causeGroup.children?.length) * 22 + 100 + 'px' }"> <!-- 主轴上的标签 -->
<div class="left-bone"> <div class="main-line" :style="{ width: listData.length * 200 + 300 + 'px' }">
<div class="bone-item-box bone-item-end bone-item-top" v-for="item in getLeftItems(causeGroup.children)" :key="item.id"> <div
<div :class="['bone-item-word', {'bone-item-back':item.back}]"> v-for="(item, index) in listData"
<div class="bone-item-icon"> :key="item.id"
<img :src="item.image || defaultIcon2" alt="" /> class="main-line-text"
</div> :class="getThemeClass(item.level)"
<div class="bone-item-text one-line-ellipsis" :style="{color: item.isEntity==1? '#ce4f51' : '#3b414b'}" :title="item.companyName">{{ item.companyName }}</div> :style="{ left: index * 200 + 220 + 'px' }"
</div> >
<div class="bone-item-line"></div> {{ item.name }}
</div> </div>
</div> </div>
<div class="right-bone"> <!-- 奇数索引的数据组放在上方 -->
<div class="bone-item-box bone-item-start bone-item-top" v-for="item in getRightItems(causeGroup.children)" :key="item.id"> <div
<div class="bone-item-line"></div> v-for="(causeGroup, groupIndex) in onFilterData(1)"
<div :class="['bone-item-word', {'bone-item-back':item.back}]"> :key="groupIndex"
<div class="bone-item-icon"> class="top-bone"
<img :src="item.image || defaultIcon2" alt="" /> :style="{ left: groupIndex * 400 + 510 + 'px', height: causeGroup.children?.length * 22 + 100 + 'px' }"
</div> >
<div class="bone-item-text one-line-ellipsis" :style="{color: item.isEntity==1? '#ce4f51' : '#3b414b'}" :title="item.companyName">{{ item.companyName }}</div> <div class="left-bone">
</div> <div
</div> class="bone-item-box bone-item-end bone-item-top"
</div> v-for="item in getLeftItems(causeGroup.children)"
</div> :key="item.id"
<!-- 偶数索引的数据组放在下方 --> >
<div v-for="(causeGroup, groupIndex) in onFilterData(0)" :key="groupIndex" <div :class="['bone-item-word', { 'bone-item-back': item.back }]">
class="bottom-bone" :style="{ left: groupIndex * 400 + 310 + 'px', height: (causeGroup.children?.length) * 22 + 100 + 'px' }"> <div class="bone-item-icon">
<div class="left-bone"> <img :src="item.image || defaultIcon2" alt="" />
<div class="bone-item-box bone-item-end bone-item-down" v-for="item in getRightItems(causeGroup.children)" :key="item.id"> </div>
<div :class="['bone-item-word', {'bone-item-back':item.back}]"> <div
<div class="bone-item-icon"> class="bone-item-text one-line-ellipsis"
<img :src="item.image || defaultIcon2" alt="" /> :style="{ color: item.isEntity == 1 ? '#ce4f51' : '#3b414b' }"
</div> :title="item.companyName"
<div class="bone-item-text one-line-ellipsis" :style="{color: item.isEntity==1? '#ce4f51' : '#3b414b'}" :title="item.companyName">{{ item.companyName }}</div> >
</div> {{ item.companyName }}
<div class="bone-item-line"></div> </div>
</div> </div>
</div> <div class="bone-item-line"></div>
<div class="right-bone"> </div>
<div class="bone-item-box bone-item-start bone-item-down" v-for="item in getLeftItems(causeGroup.children)" :key="item.id"> </div>
<div class="bone-item-line"></div> <div class="right-bone">
<div :class="['bone-item-word', {'bone-item-back':item.back}]"> <div
<div class="bone-item-icon"> class="bone-item-box bone-item-start bone-item-top"
<img :src="item.image || defaultIcon2" alt="" /> v-for="item in getRightItems(causeGroup.children)"
</div> :key="item.id"
<div class="bone-item-text one-line-ellipsis" :style="{color: item.isEntity==1? '#ce4f51' : '#3b414b'}" :title="item.companyName">{{ item.companyName }}</div> >
</div> <div class="bone-item-line"></div>
</div> <div :class="['bone-item-word', { 'bone-item-back': item.back }]">
</div> <div class="bone-item-icon">
</div> <img :src="item.image || defaultIcon2" alt="" />
</div> </div>
</div> <div
<div v-if="listData.length" class="main-content-footer"> class="bone-item-text one-line-ellipsis"
<div v-for="(item, index) in props.baseData" :key="index" class="footer-item"> :style="{ color: item.isEntity == 1 ? '#ce4f51' : '#3b414b' }"
<div class="footer-item-bottom"> :title="item.companyName"
<div class="icon"> >
<img :src="noticeIcon" alt="" /> {{ item.companyName }}
</div> </div>
<div class="text"> </div>
{{ </div>
`中国企业${item.isChinaCount}家(${formatRate(item, 'isChinaCount')}%),受制裁${item.sanCount}家(${formatRate(item, 'sanCount')}%)` </div>
}} </div>
</div> <!-- 偶数索引的数据组放在下方 -->
</div> <div
<div class="footer-item-top" :class="getThemeClass(item.name)">{{ item.name }}</div> v-for="(causeGroup, groupIndex) in onFilterData(0)"
</div> :key="groupIndex"
</div> class="bottom-bone"
</div> :style="{ left: groupIndex * 400 + 310 + 'px', height: causeGroup.children?.length * 22 + 100 + 'px' }"
>
<div class="left-bone">
<div
class="bone-item-box bone-item-end bone-item-down"
v-for="item in getRightItems(causeGroup.children)"
:key="item.id"
>
<div :class="['bone-item-word', { 'bone-item-back': item.back }]">
<div class="bone-item-icon">
<img :src="item.image || defaultIcon2" alt="" />
</div>
<div
class="bone-item-text one-line-ellipsis"
:style="{ color: item.isEntity == 1 ? '#ce4f51' : '#3b414b' }"
:title="item.companyName"
>
{{ item.companyName }}
</div>
</div>
<div class="bone-item-line"></div>
</div>
</div>
<div class="right-bone">
<div
class="bone-item-box bone-item-start bone-item-down"
v-for="item in getLeftItems(causeGroup.children)"
:key="item.id"
>
<div class="bone-item-line"></div>
<div :class="['bone-item-word', { 'bone-item-back': item.back }]">
<div class="bone-item-icon">
<img :src="item.image || defaultIcon2" alt="" />
</div>
<div
class="bone-item-text one-line-ellipsis"
:style="{ color: item.isEntity == 1 ? '#ce4f51' : '#3b414b' }"
:title="item.companyName"
>
{{ item.companyName }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="listData.length" class="main-content-footer">
<div v-for="(item, index) in props.baseData" :key="index" class="footer-item">
<div class="footer-item-bottom">
<div class="icon">
<img :src="noticeIcon" alt="" />
</div>
<div class="text">
{{
`中国企业${item.isChinaCount}家(${formatRate(item, "isChinaCount")}%),受制裁${
item.sanCount
}家(${formatRate(item, "sanCount")}%)`
}}
</div>
</div>
<div class="footer-item-top" :class="getThemeClass(item.name)">{{ item.name }}</div>
</div>
</div>
</div>
</template> </template>
<script setup name="ChartChain"> <script setup name="ChartChain">
...@@ -93,62 +157,62 @@ import defaultIcon2 from "@/assets/icons/default-icon2.png"; ...@@ -93,62 +157,62 @@ import defaultIcon2 from "@/assets/icons/default-icon2.png";
import noticeIcon from "../assets/images/notice-icon.png"; import noticeIcon from "../assets/images/notice-icon.png";
const props = defineProps({ const props = defineProps({
baseData: { baseData: {
type: Object, type: Object,
default: () => ([]) default: () => []
}, },
listData: { listData: {
type: Array, type: Array,
default: () => ([]) default: () => []
} }
}); });
const getThemeClass = (name) => { const getThemeClass = name => {
if (name=="上游") return "blue-theme"; if (name == "上游") return "blue-theme";
if (name=="中游") return "green-theme"; if (name == "中游") return "green-theme";
if (name=="下游") return "purple-theme"; if (name == "下游") return "purple-theme";
} };
// #region 缩放功能处理 // #region 缩放功能处理
const scale = ref(1) const scale = ref(1);
const minScale = 0.1 const minScale = 0.1;
const maxScale = 10 const maxScale = 10;
const handleWheel = (e) => { const handleWheel = e => {
if (e.deltaY < 0) { if (e.deltaY < 0) {
// 放大:不超过最大值 // 放大:不超过最大值
scale.value = Math.min(scale.value + 0.1, maxScale) scale.value = Math.min(scale.value + 0.1, maxScale);
} else { } else {
// 缩小:不低于最小值 // 缩小:不低于最小值
scale.value = Math.max(scale.value - 0.1, minScale) scale.value = Math.max(scale.value - 0.1, minScale);
} }
} };
// #endregion 缩放功能处理 // #endregion 缩放功能处理
// #region 移动功能处理 // #region 移动功能处理
const translateX = ref(0) // X轴位移 const translateX = ref(0); // X轴位移
const translateY = ref(0) // Y轴位移 const translateY = ref(0); // Y轴位移
let isDragging = false let isDragging = false;
let startX = 0 let startX = 0;
let startY = 0 let startY = 0;
const handleMouseMove = (e) => { const handleMouseMove = e => {
if (!isDragging) return if (!isDragging) return;
translateX.value = e.clientX - startX translateX.value = e.clientX - startX;
translateY.value = e.clientY - startY translateY.value = e.clientY - startY;
} };
const handleMouseDown = (e) => { const handleMouseDown = e => {
// 排除右键/中键,只响应左键(e.button=0为左键) // 排除右键/中键,只响应左键(e.button=0为左键)
if (e.button !== 0) return if (e.button !== 0) return;
isDragging = true isDragging = true;
startX = e.clientX - translateX.value startX = e.clientX - translateX.value;
startY = e.clientY - translateY.value startY = e.clientY - translateY.value;
} };
const handleMouseUp = () => { const handleMouseUp = () => {
isDragging = false isDragging = false;
} };
// #endregion 移动功能处理 // #endregion 移动功能处理
// 奇数索引的数据组放在上方, 偶数索引的数据组放在下方 // 奇数索引的数据组放在上方, 偶数索引的数据组放在下方
const onFilterData = (num) => { const onFilterData = num => {
return props.listData.filter((_, index) => index % 2 === num); return props.listData.filter((_, index) => index % 2 === num);
}; };
// 获取左侧显示的项目(前半部分) // 获取左侧显示的项目(前半部分)
...@@ -163,246 +227,246 @@ const getRightItems = items => { ...@@ -163,246 +227,246 @@ const getRightItems = items => {
}; };
// 格式化比率 // 格式化比率
const formatRate = (item, key) => { const formatRate = (item, key) => {
if (!item[key] || !item.total) return "0.00" if (!item[key] || !item.total) return "0.00";
return (item[key]*100/item.total).toFixed(2) return ((item[key] * 100) / item.total).toFixed(2);
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.view-box { .view-box {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.main-content-main { .main-content-main {
position: relative; position: relative;
height: 20px; height: 20px;
flex: auto; flex: auto;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden; overflow: hidden;
.fishbone-container { .fishbone-container {
position: relative; position: relative;
.main-line { .main-line {
height: 3px; height: 3px;
background: rgb(230, 231, 232); background: rgb(230, 231, 232);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
// 添加中间的文字块 // 添加中间的文字块
.main-line-text { .main-line-text {
top: -16px; top: -16px;
position: absolute; position: absolute;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
padding: 0 10px; padding: 0 10px;
z-index: 2; z-index: 2;
// 箭头背景 // 箭头背景
height: 32px; height: 32px;
line-height: 32px; line-height: 32px;
width: 160px; width: 160px;
text-align: center; text-align: center;
clip-path: polygon(0% 0%, 90% 0%, 100% 50%, 90% 100%, 0% 100%, 10% 50%); clip-path: polygon(0% 0%, 90% 0%, 100% 50%, 90% 100%, 0% 100%, 10% 50%);
} }
} }
} }
.top-bone { .top-bone {
position: absolute; position: absolute;
bottom: 0px; bottom: 0px;
width: 3px; width: 3px;
background: rgb(230, 231, 232); background: rgb(230, 231, 232);
transform-origin: bottom center; transform-origin: bottom center;
transform: skew(30deg); transform: skew(30deg);
z-index: 1; z-index: 1;
.left-bone { .left-bone {
color: #777; color: #777;
position: absolute; position: absolute;
top: -20px; top: -20px;
right: 0; right: 0;
width: 180px; width: 180px;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-end; justify-content: flex-end;
} }
.right-bone { .right-bone {
color: #777; color: #777;
position: absolute; position: absolute;
top: -44px; top: -44px;
left: 0; left: 0;
width: 180px; width: 180px;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-end; justify-content: flex-end;
} }
} }
.bottom-bone { .bottom-bone {
position: absolute; position: absolute;
top: 0px; top: 0px;
width: 3px; width: 3px;
background: rgb(230, 231, 232); background: rgb(230, 231, 232);
transform-origin: top center; transform-origin: top center;
transform: skew(-30deg); transform: skew(-30deg);
z-index: 1; z-index: 1;
.left-bone { .left-bone {
color: #777; color: #777;
position: absolute; position: absolute;
bottom: -44px; bottom: -44px;
right: 0; right: 0;
width: 180px; width: 180px;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
} }
.right-bone { .right-bone {
color: #777; color: #777;
position: absolute; position: absolute;
bottom: -20px; bottom: -20px;
left: 0; left: 0;
width: 180px; width: 180px;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
} }
} }
.bone-item-box { .bone-item-box {
transform: skew(30deg); transform: skew(30deg);
height: 40px; height: 40px;
margin: 4px 0; margin: 4px 0;
display: flex; display: flex;
align-items: center; align-items: center;
.bone-item-line { .bone-item-line {
width: 30px; width: 30px;
height: 2px; height: 2px;
background: rgb(230, 231, 232); background: rgb(230, 231, 232);
} }
.bone-item-word { .bone-item-word {
display: flex; display: flex;
align-items: center; align-items: center;
height: 30px; height: 30px;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 0 8px; padding: 0 8px;
.bone-item-icon { .bone-item-icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
font-size: 0px; font-size: 0px;
position: relative; position: relative;
&::after { &::after {
content: ""; content: "";
position: absolute; position: absolute;
top: 0px; top: 0px;
left: 0px; left: 0px;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: contain; object-fit: contain;
} }
} }
.bone-item-text { .bone-item-text {
max-width: 100px; max-width: 100px;
line-height: 14px; line-height: 14px;
font-size: 14px; font-size: 14px;
} }
} }
} }
.bone-item-back { .bone-item-back {
background-color: #FFF1F0; background-color: #fff1f0;
border: 1px solid var(--color-red-100); border: 1px solid var(--color-red-100);
border-radius: 4px; border-radius: 4px;
} }
.bone-item-start { .bone-item-start {
justify-content: flex-start; justify-content: flex-start;
} }
.bone-item-end { .bone-item-end {
justify-content: flex-end; justify-content: flex-end;
} }
.bone-item-top { .bone-item-top {
transform: skew(-30deg); transform: skew(-30deg);
} }
.bone-item-down { .bone-item-down {
transform: skew(30deg); transform: skew(30deg);
} }
} }
.main-content-footer { .main-content-footer {
margin-top: 16px; margin-top: 16px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
.footer-item { .footer-item {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-end; justify-content: flex-end;
} }
.footer-item { .footer-item {
.footer-item-top { .footer-item-top {
height: 28px; height: 28px;
text-align: center; text-align: center;
line-height: 28px; line-height: 28px;
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
margin-top: 10px; margin-top: 10px;
position: relative; position: relative;
z-index: 1; z-index: 1;
} }
.footer-item-bottom { .footer-item-bottom {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
.text { .text {
color: rgba(206, 79, 81, 1); color: rgba(206, 79, 81, 1);
font-size: 14px; font-size: 14px;
line-height: 14px; line-height: 14px;
margin-left: 6px; margin-left: 6px;
} }
.icon { .icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
font-size: 0; font-size: 0;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
} }
} }
} }
.blue-theme { .blue-theme {
background: rgba(231, 243, 255, 1); background: rgba(231, 243, 255, 1);
color: rgba(22, 119, 255, 1); color: rgba(22, 119, 255, 1);
} }
.green-theme { .green-theme {
background: rgba(225, 255, 251, 1); background: rgba(225, 255, 251, 1);
color: rgba(19, 168, 168, 1); color: rgba(19, 168, 168, 1);
} }
.purple-theme { .purple-theme {
background: rgba(246, 235, 255, 1); background: rgba(246, 235, 255, 1);
color: rgba(146, 84, 222, 1); color: rgba(146, 84, 222, 1);
} }
} }
</style> </style>
\ No newline at end of file
...@@ -1454,7 +1454,8 @@ watch( ...@@ -1454,7 +1454,8 @@ watch(
// 获取实体清单数据 // 获取实体清单数据
const fetchEntitiesList = async (page = 1, size = 10) => { const fetchEntitiesList = async (page = 1, size = 10) => {
try { try {
const res = await getEntitiesList("实体清单", page, size); console.log("activeResourceTabItem.value.id", activeResourceTabItem.value.id);
const res = await getEntitiesList(activeResourceTabItem.value.id.join(","), page, size);
if (res) { if (res) {
entitiesList.value = res.content.map(item => ({ entitiesList.value = res.content.map(item => ({
...item, ...item,
...@@ -1500,8 +1501,13 @@ const handleGetMore = async () => { ...@@ -1500,8 +1501,13 @@ const handleGetMore = async () => {
// 获取历次制裁过程数据 // 获取历次制裁过程数据
const fetchSanctionProcess = async (page = 1, size = 10) => { const fetchSanctionProcess = async (page = 1, size = 10) => {
try { try {
const res = await getSanctionProcess(allSanTypeIds.value, page, size); const res = await getSanctionProcess(
activeResourceTabItem.value.id ? activeResourceTabItem.value.id : allSanTypeIds.value,
page,
size
);
if (res) { if (res) {
// 暂无商业管制清单数据
sanctionProcessList.value = res.content.map(item => ({ sanctionProcessList.value = res.content.map(item => ({
...item, ...item,
title: item.name, title: item.name,
...@@ -1544,6 +1550,11 @@ const handleResourceTabClick = tab => { ...@@ -1544,6 +1550,11 @@ const handleResourceTabClick = tab => {
if (tab.disabled) return; if (tab.disabled) return;
activeResourceTab.value = tab.value; activeResourceTab.value = tab.value;
activeResourceTabItem.value = tab; activeResourceTabItem.value = tab;
fetchSanctionProcess();
console.log("tabMap[tab.id]", tabMap[tab.id]);
if (tabMap[tab.id] === "entity") {
fetchEntitiesList();
}
}; };
const strengthLabels = { const strengthLabels = {
......
...@@ -407,7 +407,7 @@ const CCLInfo = ref({ ...@@ -407,7 +407,7 @@ const CCLInfo = ref({
}); });
const getCCLInfoFn = async () => { const getCCLInfoFn = async () => {
try { try {
const res = await getCCLInfo(); const res = await getCCLInfo(route.query.sanTypeId || 13);
if (res && res.code === 200) { if (res && res.code === 200) {
CCLInfo.value = res.data; CCLInfo.value = res.data;
console.log("getCCLInfoFn", CCLInfo.value); console.log("getCCLInfoFn", CCLInfo.value);
......
...@@ -2,13 +2,16 @@ ...@@ -2,13 +2,16 @@
<div class="list-page"> <div class="list-page">
<div class="search-box"> <div class="search-box">
<div style="display: flex; justify-content: center"> <div style="display: flex; justify-content: center">
<el-select v-model="currentCCLType" style="width: 388px; height: 32px; margin-right: 14px"> <el-select v-model="currentCCLVersion" style="width: 388px; height: 32px; margin-right: 14px">
<el-option v-for="item in CCLTypeList" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in cclVersionList" :key="item.key" :label="item.value" :value="item.key" />
</el-select> </el-select>
<el-input v-model="searchKeyword" class="search-input" placeholder="搜索物项或ECCN编码" :suffix-icon="Search" /> <el-input v-model="searchKeyword" class="search-input" placeholder="搜索物项或ECCN编码" :suffix-icon="Search" />
</div> </div>
<div class="filters"> <div class="filters">
<el-checkbox v-model="viewNew" label="查看最近更新内容" /> <el-checkbox v-model="viewNew" label="查看最近更新内容" />
<el-select v-model="currentCCLType" style="width: 388px; height: 32px; margin-right: 14px">
<el-option v-for="item in CCLTypeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div> </div>
</div> </div>
<div class="main"> <div class="main">
...@@ -18,16 +21,26 @@ ...@@ -18,16 +21,26 @@
<div class="text">科技领域</div> <div class="text">科技领域</div>
</div> </div>
<div class="checkbox-group"> <div class="checkbox-group">
<el-checkbox v-for="(item, index) in techFields" :key="index" v-model="item.checked" :label="item.name" <el-checkbox
@change="handleFilterChange(item, techFields, 'tech')" /> v-for="(item, index) in techFields"
:key="index"
v-model="item.checked"
:label="item.name"
@change="handleFilterChange(item, techFields, 'tech')"
/>
</div> </div>
<div class="title"> <div class="title">
<div class="box"></div> <div class="box"></div>
<div class="text">管控原因</div> <div class="text">管控原因</div>
</div> </div>
<div class="checkbox-group"> <div class="checkbox-group">
<el-checkbox v-for="(item, index) in controlReason" :key="index" v-model="item.checked" :label="item.name" <el-checkbox
@change="handleFilterChange(item, controlReason, 'reason')" /> v-for="(item, index) in controlReason"
:key="index"
v-model="item.checked"
:label="item.name"
@change="handleFilterChange(item, controlReason, 'reason')"
/>
</div> </div>
</div> </div>
<div class="right"> <div class="right">
...@@ -41,7 +54,7 @@ ...@@ -41,7 +54,7 @@
</div> </div>
<div style="width: 100%" v-if="item.isExpand"> <div style="width: 100%" v-if="item.isExpand">
<div style="width: 100%" v-for="element,index in item.cclChildren" :key="index"> <div style="width: 100%" v-for="(element, index) in item.cclChildren" :key="index">
<div class="list-desc">{{ element.cclCode }}. {{ element.cclTitleZh }}</div> <div class="list-desc">{{ element.cclCode }}. {{ element.cclTitleZh }}</div>
<div class="list-content" v-for="(ele, j) in element.cclChildren" :key="j"> <div class="list-content" v-for="(ele, j) in element.cclChildren" :key="j">
...@@ -74,71 +87,81 @@ import { ref, computed, onMounted, watch } from "vue"; ...@@ -74,71 +87,81 @@ import { ref, computed, onMounted, watch } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { Search, Select } from "@element-plus/icons-vue"; import { Search, Select } from "@element-plus/icons-vue";
import defaultIcon from "../../../../../assets/icons/default-avatar.png"; import defaultIcon from "../../../../../assets/icons/default-avatar.png";
import { getECCNCategory, getAreaType, getControlReason, getCclQuery, getExportControlList, get50PercentEntityCount } from "@/api/exportControlV2.0.js" import {
getECCNCategory,
getAreaType,
getControlReason,
getCclQuery,
getCCLVersionList,
getExportControlList,
get50PercentEntityCount
} from "@/api/exportControlV2.0.js";
import dayjs from "dayjs";
const router = useRouter(); const router = useRouter();
const currentCCLType = ref('') const currentCCLType = ref("");
const CCLTypeList = ref([]) const CCLTypeList = ref([]);
const getTypeList = async () => { const getTypeList = async () => {
try { try {
const res = await getECCNCategory() const res = await getECCNCategory();
if (res && res.code === 200) { if (res && res.code === 200) {
console.log('-----getTypeList', res.data) console.log("-----getTypeList", res.data);
CCLTypeList.value = res.data CCLTypeList.value = res.data;
currentCCLType.value = CCLTypeList.value[0].id currentCCLType.value = CCLTypeList.value[0].id;
} }
} catch (error) { } catch (error) {
console.error("获取类别字典失败:", error); console.error("获取类别字典失败:", error);
} }
} };
const techFields = ref([]) const techFields = ref([]);
const getTechFields = async () => { const getTechFields = async () => {
try { try {
const res = await getAreaType() const res = await getAreaType();
if (res && res.code === 200) { if (res && res.code === 200) {
console.log('-----getTechFields', res.data) console.log("-----getTechFields", res.data);
techFields.value = res.data techFields.value = res.data;
// 默认选中第一个 // 默认选中第一个
techFields.value[0].checked = true techFields.value[0].checked = true;
} }
} catch (error) { } catch (error) {
console.error("获取科技领域字典失败:", error); console.error("获取科技领域字典失败:", error);
} }
} };
const controlReason = ref([]) const controlReason = ref([]);
const getControlReasonList = async () => { const getControlReasonList = async () => {
try { try {
const res = await getControlReason() const res = await getControlReason();
if (res && res.code === 200) { if (res && res.code === 200) {
console.log('-----getControlReasonList', res.data) console.log("-----getControlReasonList", res.data);
controlReason.value = res.data controlReason.value = res.data;
// 默认选中第一个 // 默认选中第一个
controlReason.value[0].checked = true controlReason.value[0].checked = true;
} }
} catch (error) { } catch (error) {
console.error("获取管控原因字典失败:", error); console.error("获取管控原因字典失败:", error);
} }
} };
const searchKeyword = ref(''); const searchKeyword = ref("");
const onlyChina = ref(false); const onlyChina = ref(false);
const viewNew = ref(true) const viewNew = ref(true);
// 获取ccl清单列表 // 获取ccl清单列表
const getCclList = async () => { const getCclList = async () => {
let techDomains = techFields.value.filter(item => item.checked).map(item => +item.id) let techDomains = techFields.value.filter(item => item.checked).map(item => +item.id);
let controls = controlReason.value.filter(item => item.checked).map(item => +item.id) let controls = controlReason.value.filter(item => item.checked).map(item => +item.id);
const params = { const params = {
categoryCode: currentCCLType.value, categoryCode: currentCCLType.value,
techDomainIds: techDomains, techDomainIds: techDomains,
keyword: searchKeyword.value || '', keyword: searchKeyword.value || "",
controlIds: controls, controlIds: controls,
isLatest: viewNew.value isLatest: viewNew.value,
} recordId: currentCCLVersion.value || ""
console.log(JSON.stringify(params)) };
console.log(JSON.stringify(params));
try { try {
// const res = await getCclQuery(null); // const res = await getCclQuery(null);
const res = await getCclQuery(params); const res = await getCclQuery(params);
...@@ -207,38 +230,66 @@ const getCclList = async () => { ...@@ -207,38 +230,66 @@ const getCclList = async () => {
// }) // })
// }) // })
if (res && res.code === 200) { if (res && res.code === 200) {
console.log('----getCclList', res.data) console.log("----getCclList", res.data);
cclList.value = res.data cclList.value = res.data;
// 给数据添加isExpand字段 // 给数据添加isExpand字段
cclList.value.forEach((item) => { cclList.value.forEach(item => {
item.isExpand = false item.isExpand = false;
item.cclChildren.forEach((ele) => { item.cclChildren.forEach(ele => {
ele.cclChildren.forEach((i) => { ele.cclChildren.forEach(i => {
i.isExpand = false i.isExpand = false;
}) });
}) });
}) });
} }
} catch (error) { } catch (error) {
console.error("获取ccl清单列表失败:", error); console.error("获取ccl清单列表失败:", error);
}
};
// 商业管制清单-CCL清单列表-清单版本
const cclVersionList = ref([]);
// 当前选中的清单版本
const currentCCLVersion = ref("");
// 获取清单版本列表
const getCCLVersionListApi = async () => {
try {
const res = await getCCLVersionList();
if (res && res.code === 200) {
console.log("----getCCLVersionList", res.data);
cclVersionList.value = res.data.map(item => {
return {
key: item.key,
value: `清单版本-${dayjs(item.value).format("YYYY年MM月DD日")}`
// value: `清单版本-${item.value}`
};
});
currentCCLVersion.value = res.data[0].key;
}
} catch (error) {
console.error("获取清单版本列表失败:", error);
} }
} };
// 筛选逻辑处理 // 筛选逻辑处理
const handleFilterChange = (item, list, type) => { const handleFilterChange = (item, list, type) => {
console.log(item, list, type) console.log(item, list, type);
getCclList() getCclList();
}; };
watch(viewNew, (newValue) => { watch(viewNew, newValue => {
getCclList();
});
watch(currentCCLType, newValue => {
console.log(newValue);
getCclList(); getCclList();
}); });
watch(currentCCLType, (newValue) => { watch(currentCCLVersion, newValue => {
console.log(newValue) console.log(newValue);
getCclList() getCclList();
}) });
// const searchDebounceTimer = ref(null); // const searchDebounceTimer = ref(null);
// watch(searchKeyword, () => { // watch(searchKeyword, () => {
...@@ -250,52 +301,52 @@ watch(currentCCLType, (newValue) => { ...@@ -250,52 +301,52 @@ watch(currentCCLType, (newValue) => {
// getExportControlListApi(); // getExportControlListApi();
// }, 300); // }, 300);
// }); // });
watch(searchKeyword, (newValue) => { watch(searchKeyword, newValue => {
console.log('-----searchKey', newValue) console.log("-----searchKey", newValue);
getCclList() getCclList();
}); });
// 模拟清单列表 // 模拟清单列表
const cclList = ref([ const cclList = ref([
{ {
name: '类别 0-核材料、设施和设备、枪支、弹药[以及其他物品]', name: "类别 0-核材料、设施和设备、枪支、弹药[以及其他物品]",
desc: 'A."最终产品"、"设备"、"附件"、"附加装置"、"零件"、"组件"和"系统"', desc: 'A."最终产品"、"设备"、"附件"、"附加装置"、"零件"、"组件"和"系统"',
isExpand: true, isExpand: true,
list: [ list: [
{ {
code: '0A002', code: "0A002",
name: '发电或推进设备,"专门设计"用于与太空、海洋或移动"核反应堆"一起使用。(这些项目"受 ITAR 管辖。"参见 22 CFR 第 120 至 130 部分。)', name: '发电或推进设备,"专门设计"用于与太空、海洋或移动"核反应堆"一起使用。(这些项目"受 ITAR 管辖。"参见 22 CFR 第 120 至 130 部分。)',
isExpand: false, isExpand: false,
isDot: false isDot: false
}, },
{ {
code: '0A501', code: "0A501",
name: '枪支(不包括 0A502 霰弹枪、0A506 半自动步枪、0A507 半自动手枪和 0A508 半自动霰弹枪)及相关商品(不包括在 Eccn 0A509 中列举或以其他方式描述的与半自动相关的商品,用于 Eccn 0A506、0A507 或 0A508)如下(参见受控物品清单)', name: "枪支(不包括 0A502 霰弹枪、0A506 半自动步枪、0A507 半自动手枪和 0A508 半自动霰弹枪)及相关商品(不包括在 Eccn 0A509 中列举或以其他方式描述的与半自动相关的商品,用于 Eccn 0A506、0A507 或 0A508)如下(参见受控物品清单)",
isExpand: true, isExpand: true,
isDot: true isDot: true
}, },
{ {
code: '0A501', code: "0A501",
name: '枪支(不包括 0A502 霰弹枪、0A506 半自动步枪、0A507 半自动手枪和 0A508 半自动霰弹枪)及相关商品(不包括在 Eccn 0A509 中列举或以其他方式描述的与半自动相关的商品,用于 Eccn 0A506、0A507 或 0A508)如下(参见受控物品清单)', name: "枪支(不包括 0A502 霰弹枪、0A506 半自动步枪、0A507 半自动手枪和 0A508 半自动霰弹枪)及相关商品(不包括在 Eccn 0A509 中列举或以其他方式描述的与半自动相关的商品,用于 Eccn 0A506、0A507 或 0A508)如下(参见受控物品清单)",
isExpand: false, isExpand: false,
isDot: true isDot: true
} }
] ]
} }
]) ]);
onMounted(async () => { onMounted(async () => {
// 获取类别字段 // 获取类别字段
await getTypeList() await getTypeList();
// 获取科技领域字典 // 获取科技领域字典
await getTechFields() await getTechFields();
// 获取管控原因字典 // 获取管控原因字典
await getControlReasonList() await getControlReasonList();
// 获取ccl清单列表 // 获取ccl清单列表
getCclList(); getCclList();
// 获取清单版本列表
getCCLVersionListApi();
}); });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
......
...@@ -17,8 +17,8 @@ ...@@ -17,8 +17,8 @@
class="nav-item" class="nav-item"
v-for="(item, index) in headerNavList" v-for="(item, index) in headerNavList"
:key="index" :key="index"
:class="{ active: activeIndex === index }" :class="{ active: activeIndex === index, disabled: item.disable }"
@click="activeIndex = index" @click="!item.disable && (activeIndex = index)"
> >
<img :src="activeIndex === index ? item.imgActive : item.img" alt /> <img :src="activeIndex === index ? item.imgActive : item.img" alt />
<span>{{ item.title }}</span> <span>{{ item.title }}</span>
...@@ -81,17 +81,20 @@ const headerNavList = ref([ ...@@ -81,17 +81,20 @@ const headerNavList = ref([
{ {
img: icon5, img: icon5,
imgActive: icon5Active, imgActive: icon5Active,
title: "数据统计" title: "数据统计",
disable: true
}, },
{ {
img: icon2, img: icon2,
imgActive: icon2Active, imgActive: icon2Active,
title: "深度挖掘" title: "深度挖掘",
disable: true
}, },
{ {
img: icon3, img: icon3,
imgActive: icon3Active, imgActive: icon3Active,
title: "影响分析" title: "影响分析",
disable: true
} }
]); ]);
</script> </script>
...@@ -206,6 +209,11 @@ const headerNavList = ref([ ...@@ -206,6 +209,11 @@ const headerNavList = ref([
font-weight: 700; font-weight: 700;
} }
&.disabled {
cursor: not-allowed;
color: rgb(95, 101, 108);
}
.active-line { .active-line {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
......
<template>
<div class="relation-graph-wrapper">
<div class="graph-controls">
<div
v-for="item in controlBtns"
:key="item.type"
:class="['control-btn', { 'control-btn-active': currentLayoutType === item.type }]"
@click="handleClickControlBtn(item.type)"
>
<img :src="item.icon" alt="" />
</div>
</div>
<div ref="containerRef" class="graph-container"></div>
<div v-if="selectedNode" class="node-popup">
<div class="popup-header">
<img :src="selectedNode.image || defaultIcon" alt="" class="popup-icon" />
<div class="popup-title">{{ selectedNode.name }}</div>
<el-icon class="close-icon" @click="selectedNode = null">
<Close />
</el-icon>
</div>
<div class="popup-body">
<div v-if="selectedNode.isSanctioned" class="tag-row">
<span class="red-dot"></span>
<span class="red-text">被制裁实体</span>
</div>
<div v-if="selectedNode.description && selectedNode.description.length > 0" class="desc-row">
<p class="desc" v-for="item in selectedNode.description" :key="item">{{ item }}</p>
</div>
<p v-else class="desc">暂无描述</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from "vue";
import G6 from "@antv/g6";
import { Close } from "@element-plus/icons-vue";
import echartsIcon01 from "../assets/echartsicon01.png";
import echartsIcon02 from "../assets/echartsicon02.png";
import echartsIcon03 from "../assets/echartsicon03.png";
import defaultIcon from "../assets/echartsicon03.png";
import { getSingleSanctionEntityInfo } from "@/api/exportControlV2.0";
const props = defineProps({
graphData: {
type: Object,
default: () => ({ nodes: [], links: [] })
},
treeData: {
type: Object,
default: () => null
},
controlActive: {
type: Number,
default: 1
}
});
const emit = defineEmits(["nodeClick", "layoutChange"]);
const containerRef = ref(null);
const graphInstance = ref(null);
const currentLayoutType = ref(1);
const selectedNode = ref(null);
const controlBtns = [
{ type: 1, icon: echartsIcon01, name: "力导向布局" },
{ type: 2, icon: echartsIcon02, name: "树布局" },
{ type: 3, icon: echartsIcon03, name: "环状布局" }
];
const initGraph = (layoutType = 1) => {
if (!containerRef.value) return;
destroyGraph();
nextTick(() => {
const container = containerRef.value;
const width = container.offsetWidth || 800;
const height = container.offsetHeight || 600;
if (layoutType === 2) {
initTreeGraph(width, height);
} else if (layoutType === 3) {
initCircularGraph(width, height);
} else {
initNormalGraph(layoutType, width, height);
}
});
};
const initNormalGraph = (layoutType, width, height) => {
const data = processGraphData(props.graphData);
if (!data.nodes || data.nodes.length === 0) return;
const layout = {
type: "force",
center: [width / 2, height / 2],
preventOverlap: true,
nodeSpacing: 80,
linkDistance: 250,
nodeStrength: -800,
edgeStrength: 0.1,
collideStrength: 0.8,
alphaDecay: 0.01,
alphaMin: 0.001
};
graphInstance.value = new G6.Graph({
container: containerRef.value,
width,
height,
fitView: true,
fitViewPadding: 100,
fitCenter: true,
animate: true,
animateCfg: {
duration: 300,
easing: "easeLinear"
},
minZoom: 0.1,
maxZoom: 10,
modes: {
default: [
"drag-canvas",
"zoom-canvas",
"drag-node",
{
type: "activate-relations",
trigger: "mouseenter",
resetSelected: true
}
]
},
layout,
defaultNode: {
type: "image",
size: 40,
clipCfg: {
show: true,
type: "circle",
r: 20
},
labelCfg: {
position: "bottom",
offset: 10,
style: {
fill: "#333",
fontSize: 11,
fontFamily: "Microsoft YaHei",
textAlign: "center",
background: {
fill: "rgba(255, 255, 255, 0.95)",
padding: [4, 6, 4, 6],
radius: 4
}
}
}
},
defaultEdge: {
type: "quadratic",
style: {
stroke: "#5B8FF9",
lineWidth: 3,
opacity: 0.9,
// 添加边的边框效果
strokeOpacity: 1,
endArrow: {
path: "M 0,0 L 12,6 L 12,-6 Z",
fill: "#5B8FF9"
}
},
labelCfg: {
autoRotate: true,
style: {
fill: "#333",
fontSize: 10,
fontFamily: "Microsoft YaHei",
background: {
fill: "#fff",
padding: [2, 4, 2, 4],
radius: 2
}
}
}
},
nodeStateStyles: {
active: {
shadowColor: "#1459BB",
shadowBlur: 15,
stroke: "#1459BB",
lineWidth: 3
},
inactive: {
opacity: 0.3
}
},
edgeStateStyles: {
active: {
stroke: "#1459BB",
lineWidth: 4
},
inactive: {
opacity: 0.15
}
}
});
graphInstance.value.data(data);
graphInstance.value.render();
bindGraphEvents();
};
const initCircularGraph = (width, height) => {
const data = processGraphData(props.graphData);
if (!data.nodes || data.nodes.length === 0) return;
const centerX = width / 2;
const centerY = height / 2;
const radius = Math.min(width, height) / 2 - 120;
const otherNodes = data.nodes.filter(n => !n.isCenter);
const nodeCount = otherNodes.length;
otherNodes.forEach((node, index) => {
const angle = (2 * Math.PI * index) / nodeCount - Math.PI / 2;
node.x = centerX + radius * Math.cos(angle);
node.y = centerY + radius * Math.sin(angle);
});
const centerNode = data.nodes.find(n => n.isCenter);
if (centerNode) {
centerNode.x = centerX;
centerNode.y = centerY;
centerNode.fx = centerX;
centerNode.fy = centerY;
}
graphInstance.value = new G6.Graph({
container: containerRef.value,
width,
height,
fitView: false,
fitCenter: false,
animate: true,
animateCfg: {
duration: 300,
easing: "easeLinear"
},
minZoom: 0.1,
maxZoom: 10,
modes: {
default: [
"drag-canvas",
"zoom-canvas",
"drag-node",
{
type: "activate-relations",
trigger: "mouseenter",
resetSelected: true
}
]
},
defaultNode: {
type: "image",
size: 40,
clipCfg: {
show: true,
type: "circle",
r: 20
},
labelCfg: {
position: "bottom",
offset: 10,
style: {
fill: "#333",
fontSize: 11,
fontFamily: "Microsoft YaHei",
textAlign: "center",
background: {
fill: "rgba(255, 255, 255, 0.95)",
padding: [4, 6, 4, 6],
radius: 4
}
}
}
},
defaultEdge: {
type: "quadratic",
style: {
stroke: "#5B8FF9",
lineWidth: 3,
opacity: 0.9,
endArrow: {
path: "M 0,0 L 12,6 L 12,-6 Z",
fill: "#5B8FF9"
}
},
labelCfg: {
autoRotate: true,
style: {
fill: "#333",
fontSize: 10,
fontFamily: "Microsoft YaHei",
background: {
fill: "#fff",
padding: [2, 4, 2, 4],
radius: 2
}
}
}
},
nodeStateStyles: {
active: {
shadowColor: "#1459BB",
shadowBlur: 15,
stroke: "#1459BB",
lineWidth: 3
},
inactive: {
opacity: 0.3
}
},
edgeStateStyles: {
active: {
stroke: "#1459BB",
lineWidth: 4
},
inactive: {
opacity: 0.15
}
}
});
graphInstance.value.data(data);
graphInstance.value.render();
bindGraphEvents();
};
const initTreeGraph = (width, height) => {
const treeDataSource = convertGraphToTree(props.graphData);
if (!treeDataSource) return;
graphInstance.value = new G6.TreeGraph({
container: containerRef.value,
width,
height,
fitView: true,
fitViewPadding: 80,
animate: true,
animateCfg: {
duration: 300,
easing: "easeLinear"
},
minZoom: 0.1,
maxZoom: 10,
modes: {
default: [
"drag-canvas",
"zoom-canvas",
"drag-node",
{
type: "collapse-expand",
onChange: function onChange(item, collapsed) {
const data = item.getModel();
data.collapsed = collapsed;
return true;
}
}
]
},
layout: {
type: "compactBox",
direction: "LR",
getId: function getId(d) {
return d.id;
},
getHeight: function getHeight() {
return 16;
},
getWidth: function getWidth() {
return 16;
},
getVGap: function getVGap() {
return 30;
},
getHGap: function getHGap() {
return 120;
}
},
defaultNode: {
type: "image",
size: 40,
clipCfg: {
show: true,
type: "circle",
r: 20
},
labelCfg: {
position: "right",
offset: 10,
style: {
fill: "#333",
fontSize: 11,
fontFamily: "Microsoft YaHei",
background: {
fill: "rgba(255, 255, 255, 0.95)",
padding: [4, 6, 4, 6],
radius: 4
}
}
}
},
defaultEdge: {
type: "cubic-horizontal",
style: {
stroke: "#5B8FF9",
lineWidth: 3
}
},
nodeStateStyles: {
active: {
shadowColor: "#1459BB",
shadowBlur: 15,
stroke: "#1459BB",
lineWidth: 3
}
}
});
graphInstance.value.data(treeDataSource);
graphInstance.value.render();
graphInstance.value.fitView();
bindGraphEvents();
};
const convertGraphToTree = graphData => {
if (!graphData || !graphData.nodes || graphData.nodes.length === 0) {
return null;
}
const nodes = graphData.nodes;
const links = graphData.links || graphData.edges || [];
const centerNode = nodes[0];
const centerId = String(centerNode.id || "0");
const childIdSet = new Set();
const childrenNodes = [];
links.forEach(link => {
const source = String(link.source);
const target = String(link.target);
if (source === centerId && !childIdSet.has(target)) {
const node = nodes.find(n => String(n.id) === target);
if (node) {
childIdSet.add(target);
childrenNodes.push({
id: target,
label: node.name || "",
img: node.image || defaultIcon,
size: node.symbolSize || 40,
name: node.name,
image: node.image,
isSanctioned: node.isSanctioned
});
}
} else if (target === centerId && !childIdSet.has(source)) {
const node = nodes.find(n => String(n.id) === source);
if (node) {
childIdSet.add(source);
childrenNodes.push({
id: source,
label: node.name || "",
img: node.image || defaultIcon,
size: node.symbolSize || 40,
name: node.name,
image: node.image,
isSanctioned: node.isSanctioned
});
}
}
});
if (childrenNodes.length === 0) {
nodes.slice(1).forEach(node => {
const nodeId = String(node.id);
if (!childIdSet.has(nodeId)) {
childIdSet.add(nodeId);
childrenNodes.push({
id: nodeId,
label: node.name || "",
img: node.image || defaultIcon,
size: node.symbolSize || 40,
name: node.name,
image: node.image,
isSanctioned: node.isSanctioned
});
}
});
}
return {
id: centerId,
label: centerNode.name || "",
img: centerNode.image || defaultIcon,
size: centerNode.symbolSize || 60,
name: centerNode.name,
image: centerNode.image,
isSanctioned: centerNode.isSanctioned,
children: childrenNodes
};
};
const processGraphData = rawData => {
if (!rawData || !rawData.nodes || rawData.nodes.length === 0) {
return { nodes: [], edges: [] };
}
const nodeMap = new Map();
const nodes = [];
rawData.nodes.forEach((node, index) => {
const nodeId = String(node.id || index);
if (nodeMap.has(nodeId)) {
return;
}
nodeMap.set(nodeId, true);
const isCenter = index === 0;
const size = node.symbolSize || (isCenter ? 60 : 40);
nodes.push({
id: nodeId,
label: node.name || "",
img: node.image || defaultIcon,
size,
isCenter,
clipCfg: {
show: true,
type: "circle",
r: size / 2
},
style: {
cursor: "pointer"
},
labelCfg: {
position: "bottom",
offset: 12,
style: {
fill: isCenter ? "#1459BB" : "#333",
fontSize: isCenter ? 13 : 11,
fontWeight: isCenter ? "bold" : "normal",
fontFamily: "Microsoft YaHei",
textAlign: "center"
}
},
...node,
id: nodeId
});
});
const edgeMap = new Map();
const edges = [];
const rawEdges = rawData.links || rawData.edges || [];
rawEdges.forEach((edge, index) => {
const source = String(edge.source);
const target = String(edge.target);
const edgeKey = `${source}-${target}`;
if (edgeMap.has(edgeKey)) {
return;
}
if (!nodeMap.has(source) || !nodeMap.has(target)) {
return;
}
edgeMap.set(edgeKey, true);
edges.push({
id: `edge-${index}`,
source,
target,
label: edge.name || ""
});
});
return { nodes, edges };
};
const bindGraphEvents = () => {
if (!graphInstance.value) return;
graphInstance.value.on("node:click", evt => {
const node = evt.item;
const model = node.getModel();
console.log("点击节点model", model);
const id = model.id.split("-")[1];
let desList = [];
getSingleSanctionEntityInfo(id).then(res => {
console.log("制裁实体信息", res);
if (res.data.sanInfoList && res.data.sanInfoList.length > 0) {
desList = res.data.sanInfoList.map(item => item.sanReason);
}
});
selectedNode.value = { ...model, description: desList };
emit("nodeClick", model);
});
graphInstance.value.on("canvas:click", () => {
selectedNode.value = null;
});
};
const handleClickControlBtn = btn => {
currentLayoutType.value = btn;
emit("layoutChange", btn);
initGraph(btn);
};
const destroyGraph = () => {
if (graphInstance.value) {
graphInstance.value.destroy();
graphInstance.value = null;
}
};
const handleResize = () => {
if (graphInstance.value && containerRef.value) {
const width = containerRef.value.offsetWidth;
const height = containerRef.value.offsetHeight;
graphInstance.value.changeSize(width, height);
graphInstance.value.fitView();
}
};
watch(
() => props.graphData,
() => {
initGraph(currentLayoutType.value);
},
{ deep: true }
);
watch(
() => props.treeData,
() => {
if (currentLayoutType.value === 2) {
initGraph(2);
}
},
{ deep: true }
);
watch(
() => props.controlActive,
newVal => {
if (newVal !== currentLayoutType.value) {
handleClickControlBtn(newVal);
}
}
);
onMounted(() => {
initGraph(1);
window.addEventListener("resize", handleResize);
});
onUnmounted(() => {
window.removeEventListener("resize", handleResize);
destroyGraph();
});
defineExpose({
refresh: () => initGraph(currentLayoutType.value),
changeLayout: type => handleClickControlBtn(type),
getGraph: () => graphInstance.value
});
</script>
<style lang="scss" scoped>
.relation-graph-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.graph-container {
width: 100%;
height: 100%;
}
.graph-controls {
position: absolute;
top: 16px;
right: 16px;
display: flex;
gap: 8px;
z-index: 10;
.control-btn {
width: 32px;
height: 32px;
border-radius: 4px;
border: 1px solid rgba(234, 236, 238, 1);
background: rgba(255, 255, 255, 1);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
img {
width: 16px;
height: 16px;
}
&:hover {
border-color: rgba(5, 95, 194, 0.5);
}
}
.control-btn-active {
border-color: rgba(5, 95, 194, 1);
background: rgba(231, 243, 255, 1);
}
}
.node-popup {
position: absolute;
bottom: 16px;
left: 16px;
width: 320px;
background: rgba(255, 255, 255, 1);
border-radius: 8px;
box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(234, 236, 238, 1);
z-index: 20;
.popup-header {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
.popup-icon {
width: 32px;
height: 32px;
margin-right: 8px;
border-radius: 50%;
object-fit: cover;
}
.popup-title {
flex: 1;
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
color: rgba(59, 65, 75, 1);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.close-icon {
cursor: pointer;
color: rgba(132, 136, 142, 1);
font-size: 16px;
&:hover {
color: rgba(5, 95, 194, 1);
}
}
}
.popup-body {
padding: 12px 16px;
.tag-row {
display: flex;
align-items: center;
margin-bottom: 8px;
.red-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(245, 63, 63, 1);
margin-right: 8px;
}
.red-text {
font-size: 14px;
font-family: "Microsoft YaHei";
color: rgba(245, 63, 63, 1);
}
}
.desc {
font-size: 14px;
font-family: "Microsoft YaHei";
line-height: 22px;
color: rgba(95, 101, 108, 1);
margin: 0;
}
}
}
</style>
<template> <template>
<div class="relation-graph-wrapper"> <div class="relation-graph-wrapper">
<div class="graph-controls"> <div class="graph-controls">
<div v-for="item in controlBtns" :key="item.type" <div class="graph-chart-controls">
:class="['control-btn', { 'control-btn-active': currentLayoutType === item.type }]" <div
@click="handleClickControlBtn(item.type)"> v-for="item in controlBtns"
<img :src="item.icon" alt="" /> :key="item.type"
</div> :class="['control-btn', { 'control-btn-active': currentLayoutType === item.type }]"
</div> @click="handleClickControlBtn(item.type)"
<div ref="containerRef" class="graph-container"></div> >
<div v-if="selectedNode" class="node-popup"> <img :src="item.icon" alt="" />
<div class="popup-header"> </div>
<img :src="selectedNode.image || defaultIcon" alt="" class="popup-icon" /> </div>
<div class="popup-title">{{ selectedNode.name }}</div> <div class="graph-chart-legend">
<el-icon class="close-icon" @click="selectedNode = null"> <div class="legend-item">
<Close /> <div class="legend-dot"></div>
</el-icon> <span class="legend-text">已被制裁实体</span>
</div> </div>
<div class="popup-body"> <div class="legend-item">
<div v-if="selectedNode.isSanctioned" class="tag-row"> <div class="legend-dot"></div>
<span class="red-dot"></span> <span class="legend-text">未被制裁实体</span>
<span class="red-text">被制裁实体</span> </div>
</div> </div>
<p class="desc">{{ selectedNode.description || '暂无描述' }}</p> </div>
</div> <div ref="containerRef" class="graph-container"></div>
</div>
</div> <div v-if="selectedNode" class="node-popup">
<div class="popup-header">
<img :src="selectedNode.image || defaultIcon" alt="" class="popup-icon" />
<div class="popup-title">{{ selectedNode.name }}</div>
<el-icon class="close-icon" @click="selectedNode = null">
<Close />
</el-icon>
</div>
<div class="popup-body">
<div v-if="selectedNode.isSanctioned" class="tag-row">
<span class="red-dot"></span>
<span class="red-text">被制裁实体</span>
</div>
<div v-if="selectedNode.description && selectedNode.description.length > 0" class="desc-row">
<p class="desc" v-for="item in selectedNode.description" :key="item">{{ item }}</p>
</div>
<p v-else class="desc">暂无描述</p>
</div>
</div>
</div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue' import { ref, onMounted, onUnmounted, watch, nextTick } from "vue";
import G6 from '@antv/g6' import G6 from "@antv/g6";
import { Close } from '@element-plus/icons-vue' import { Close } from "@element-plus/icons-vue";
import echartsIcon01 from '../assets/echartsicon01.png' import echartsIcon01 from "../assets/echartsicon01.png";
import echartsIcon02 from '../assets/echartsicon02.png' import echartsIcon02 from "../assets/echartsicon02.png";
import echartsIcon03 from '../assets/echartsicon03.png' import echartsIcon03 from "../assets/echartsicon03.png";
import defaultIcon from '../assets/echartsicon03.png' import defaultIcon from "../assets/echartsicon03.png";
import { getSingleSanctionEntityInfo } from "@/api/exportControlV2.0";
const props = defineProps({ const props = defineProps({
graphData: { graphData: {
type: Object, type: Object,
default: () => ({ nodes: [], links: [] }) default: () => ({ nodes: [], links: [] })
}, },
treeData: { treeData: {
type: Object, type: Object,
default: () => null default: () => null
}, },
controlActive: { controlActive: {
type: Number, type: Number,
default: 1 default: 1
} }
}) });
const emit = defineEmits(['nodeClick', 'layoutChange']) const emit = defineEmits(["nodeClick", "layoutChange"]);
const containerRef = ref(null) const containerRef = ref(null);
const graphInstance = ref(null) const graphInstance = ref(null);
const currentLayoutType = ref(1) const currentLayoutType = ref(1);
const selectedNode = ref(null) const selectedNode = ref(null);
const controlBtns = [ const controlBtns = [
{ type: 1, icon: echartsIcon01, name: '力导向布局' }, { type: 1, icon: echartsIcon01, name: "力导向布局" },
{ type: 2, icon: echartsIcon02, name: '树布局' }, { type: 2, icon: echartsIcon02, name: "树布局" },
{ type: 3, icon: echartsIcon03, name: '环状布局' } { type: 3, icon: echartsIcon03, name: "环状布局" }
] ];
const initGraph = (layoutType = 1) => { const initGraph = (layoutType = 1) => {
if (!containerRef.value) return if (!containerRef.value) return;
destroyGraph() destroyGraph();
nextTick(() => { nextTick(() => {
const container = containerRef.value const container = containerRef.value;
const width = container.offsetWidth || 800 const width = container.offsetWidth || 800;
const height = container.offsetHeight || 600 const height = container.offsetHeight || 600;
if (layoutType === 2) { if (layoutType === 2) {
initTreeGraph(width, height) initTreeGraph(width, height);
} else if (layoutType === 3) { } else if (layoutType === 3) {
initCircularGraph(width, height) initCircularGraph(width, height);
} else { } else {
initNormalGraph(layoutType, width, height) initNormalGraph(layoutType, width, height);
} }
}) });
} };
const initNormalGraph = (layoutType, width, height) => { const initNormalGraph = (layoutType, width, height) => {
const data = processGraphData(props.graphData) const data = processGraphData(props.graphData);
if (!data.nodes || data.nodes.length === 0) return if (!data.nodes || data.nodes.length === 0) return;
const layout = { const layout = {
type: 'force', type: "force",
center: [width / 2, height / 2], center: [width / 2, height / 2],
preventOverlap: true, preventOverlap: true,
nodeSpacing: 80, nodeSpacing: 80,
linkDistance: 250, linkDistance: 250,
nodeStrength: -800, nodeStrength: -800,
edgeStrength: 0.1, edgeStrength: 0.1,
collideStrength: 0.8, collideStrength: 0.8,
alphaDecay: 0.01, alphaDecay: 0.01,
alphaMin: 0.001 alphaMin: 0.001
} };
graphInstance.value = new G6.Graph({ graphInstance.value = new G6.Graph({
container: containerRef.value, container: containerRef.value,
width, width,
height, height,
fitView: true, fitView: true,
fitViewPadding: 100, fitViewPadding: 100,
fitCenter: true, fitCenter: true,
animate: true, animate: true,
animateCfg: { animateCfg: {
duration: 300, duration: 300,
easing: 'easeLinear' easing: "easeLinear"
}, },
minZoom: 0.1, minZoom: 0.1,
maxZoom: 10, maxZoom: 10,
modes: { modes: {
default: [ default: [
'drag-canvas', "drag-canvas",
'zoom-canvas', "zoom-canvas",
'drag-node', "drag-node",
{ {
type: 'activate-relations', type: "activate-relations",
trigger: 'mouseenter', trigger: "mouseenter",
resetSelected: true resetSelected: true
} }
] ]
}, },
layout, layout,
defaultNode: { defaultNode: {
type: 'image', type: "image",
size: 40, size: 40,
clipCfg: { clipCfg: {
show: true, show: true,
type: 'circle', type: "circle",
r: 20 r: 20
}, },
labelCfg: { labelCfg: {
position: 'bottom', position: "bottom",
offset: 10, offset: 10,
style: { style: {
fill: '#333', fill: "#333",
fontSize: 11, fontSize: 11,
fontFamily: 'Microsoft YaHei', fontFamily: "Microsoft YaHei",
textAlign: 'center', textAlign: "center",
background: { background: {
fill: 'rgba(255, 255, 255, 0.95)', fill: "rgba(255, 255, 255, 0.95)",
padding: [4, 6, 4, 6], padding: [4, 6, 4, 6],
radius: 4 radius: 4
} }
} }
} }
}, // 注意:节点边框样式在 processGraphData 中单独设置,不在这里设置
defaultEdge: { },
type: 'quadratic', defaultEdge: {
style: { type: "quadratic",
stroke: '#5B8FF9', style: {
lineWidth: 3, stroke: "red",
opacity: 0.9, lineWidth: 3,
endArrow: { opacity: 0.9,
path: 'M 0,0 L 12,6 L 12,-6 Z', shadowColor: "rgba(231, 243, 255, 1)",
fill: '#5B8FF9' shadowBlur: 4,
} endArrow: {
}, path: "M 0,0 L 12,6 L 12,-6 Z",
labelCfg: { fill: "#5B8FF9"
autoRotate: true, }
style: { },
fill: '#333', labelCfg: {
fontSize: 10, autoRotate: true,
fontFamily: 'Microsoft YaHei', style: {
background: { fill: "rgba(137, 193, 255, 1)",
fill: '#fff', fontSize: 10,
padding: [2, 4, 2, 4], fontFamily: "Microsoft YaHei",
radius: 2 background: {
} fill: "rgba(231, 243, 255, 1)",
} padding: [2, 4, 2, 4],
} radius: 5
}, }
nodeStateStyles: { }
active: { }
shadowColor: '#1459BB', },
shadowBlur: 15, nodeStateStyles: {
stroke: '#1459BB', active: {
lineWidth: 3 shadowColor: "#1459BB",
}, shadowBlur: 15,
inactive: { stroke: "#1459BB",
opacity: 0.3 lineWidth: 3
} },
}, inactive: {
edgeStateStyles: { opacity: 0.3
active: { }
stroke: '#1459BB', },
lineWidth: 4 edgeStateStyles: {
}, active: {
inactive: { stroke: "#1459BB",
opacity: 0.15 lineWidth: 4
} },
} inactive: {
}) opacity: 0.15
}
graphInstance.value.data(data) }
graphInstance.value.render() });
bindGraphEvents()
} graphInstance.value.data(data);
graphInstance.value.render();
bindGraphEvents();
};
const initCircularGraph = (width, height) => { const initCircularGraph = (width, height) => {
const data = processGraphData(props.graphData) const data = processGraphData(props.graphData);
if (!data.nodes || data.nodes.length === 0) return if (!data.nodes || data.nodes.length === 0) return;
const centerX = width / 2 const centerX = width / 2;
const centerY = height / 2 const centerY = height / 2;
const radius = Math.min(width, height) / 2 - 120 const radius = Math.min(width, height) / 2 - 120;
const otherNodes = data.nodes.filter(n => !n.isCenter) const otherNodes = data.nodes.filter(n => !n.isCenter);
const nodeCount = otherNodes.length const nodeCount = otherNodes.length;
otherNodes.forEach((node, index) => { otherNodes.forEach((node, index) => {
const angle = (2 * Math.PI * index) / nodeCount - Math.PI / 2 const angle = (2 * Math.PI * index) / nodeCount - Math.PI / 2;
node.x = centerX + radius * Math.cos(angle) node.x = centerX + radius * Math.cos(angle);
node.y = centerY + radius * Math.sin(angle) node.y = centerY + radius * Math.sin(angle);
}) });
const centerNode = data.nodes.find(n => n.isCenter) const centerNode = data.nodes.find(n => n.isCenter);
if (centerNode) { if (centerNode) {
centerNode.x = centerX centerNode.x = centerX;
centerNode.y = centerY centerNode.y = centerY;
centerNode.fx = centerX centerNode.fx = centerX;
centerNode.fy = centerY centerNode.fy = centerY;
} }
graphInstance.value = new G6.Graph({ graphInstance.value = new G6.Graph({
container: containerRef.value, container: containerRef.value,
width, width,
height, height,
fitView: false, fitView: false,
fitCenter: false, fitCenter: false,
animate: true, animate: true,
animateCfg: { animateCfg: {
duration: 300, duration: 300,
easing: 'easeLinear' easing: "easeLinear"
}, },
minZoom: 0.1, minZoom: 0.1,
maxZoom: 10, maxZoom: 10,
modes: { modes: {
default: [ default: [
'drag-canvas', "drag-canvas",
'zoom-canvas', "zoom-canvas",
'drag-node', "drag-node",
{ {
type: 'activate-relations', type: "activate-relations",
trigger: 'mouseenter', trigger: "mouseenter",
resetSelected: true resetSelected: true
} }
] ]
}, },
defaultNode: { defaultNode: {
type: 'image', type: "image",
size: 40, size: 40,
clipCfg: { clipCfg: {
show: true, show: true,
type: 'circle', type: "circle",
r: 20 r: 20
}, },
labelCfg: { labelCfg: {
position: 'bottom', position: "bottom",
offset: 10, offset: 10,
style: { style: {
fill: '#333', fill: "#333",
fontSize: 11, fontSize: 11,
fontFamily: 'Microsoft YaHei', fontFamily: "Microsoft YaHei",
textAlign: 'center', textAlign: "center",
background: { background: {
fill: 'rgba(255, 255, 255, 0.95)', fill: "rgba(255, 255, 255, 0.95)",
padding: [4, 6, 4, 6], padding: [4, 6, 4, 6],
radius: 4 radius: 4
} }
} }
} }
}, },
defaultEdge: { defaultEdge: {
type: 'quadratic', type: "quadratic",
style: { style: {
stroke: '#5B8FF9', stroke: "#5B8FF9",
lineWidth: 3, lineWidth: 3,
opacity: 0.9, opacity: 0.9,
endArrow: { endArrow: {
path: 'M 0,0 L 12,6 L 12,-6 Z', path: "M 0,0 L 12,6 L 12,-6 Z",
fill: '#5B8FF9' fill: "#5B8FF9"
} }
}, },
labelCfg: { labelCfg: {
autoRotate: true, autoRotate: true,
style: { style: {
fill: '#333', fill: "#333",
fontSize: 10, fontSize: 10,
fontFamily: 'Microsoft YaHei', fontFamily: "Microsoft YaHei",
background: { background: {
fill: '#fff', fill: "#fff",
padding: [2, 4, 2, 4], padding: [2, 4, 2, 4],
radius: 2 radius: 2
} }
} }
} }
}, },
nodeStateStyles: { nodeStateStyles: {
active: { active: {
shadowColor: '#1459BB', shadowColor: "#1459BB",
shadowBlur: 15, shadowBlur: 15,
stroke: '#1459BB', stroke: "#1459BB",
lineWidth: 3 lineWidth: 3
}, },
inactive: { inactive: {
opacity: 0.3 opacity: 0.3
} }
}, },
edgeStateStyles: { edgeStateStyles: {
active: { active: {
stroke: '#1459BB', stroke: "#1459BB",
lineWidth: 4 lineWidth: 4
}, },
inactive: { inactive: {
opacity: 0.15 opacity: 0.15
} }
} }
}) });
graphInstance.value.data(data) graphInstance.value.data(data);
graphInstance.value.render() graphInstance.value.render();
bindGraphEvents() bindGraphEvents();
} };
const initTreeGraph = (width, height) => { const initTreeGraph = (width, height) => {
const treeDataSource = convertGraphToTree(props.graphData) const treeDataSource = convertGraphToTree(props.graphData);
if (!treeDataSource) return if (!treeDataSource) return;
graphInstance.value = new G6.TreeGraph({ graphInstance.value = new G6.TreeGraph({
container: containerRef.value, container: containerRef.value,
width, width,
height, height,
fitView: true, fitView: true,
fitViewPadding: 80, fitViewPadding: 80,
animate: true, animate: true,
animateCfg: { animateCfg: {
duration: 300, duration: 300,
easing: 'easeLinear' easing: "easeLinear"
}, },
minZoom: 0.1, minZoom: 0.1,
maxZoom: 10, maxZoom: 10,
modes: { modes: {
default: [ default: [
'drag-canvas', "drag-canvas",
'zoom-canvas', "zoom-canvas",
'drag-node', "drag-node",
{ {
type: 'collapse-expand', type: "collapse-expand",
onChange: function onChange(item, collapsed) { onChange: function onChange(item, collapsed) {
const data = item.getModel() const data = item.getModel();
data.collapsed = collapsed data.collapsed = collapsed;
return true return true;
} }
} }
] ]
}, },
layout: { layout: {
type: 'compactBox', type: "compactBox",
direction: 'LR', direction: "LR",
getId: function getId(d) { getId: function getId(d) {
return d.id return d.id;
}, },
getHeight: function getHeight() { getHeight: function getHeight() {
return 16 return 16;
}, },
getWidth: function getWidth() { getWidth: function getWidth() {
return 16 return 16;
}, },
getVGap: function getVGap() { getVGap: function getVGap() {
return 30 return 30;
}, },
getHGap: function getHGap() { getHGap: function getHGap() {
return 120 return 120;
} }
}, },
defaultNode: { defaultNode: {
type: 'image', type: "image",
size: 40, size: 40,
clipCfg: { clipCfg: {
show: true, show: true,
type: 'circle', type: "circle",
r: 20 r: 20
}, },
labelCfg: { labelCfg: {
position: 'right', position: "right",
offset: 10, offset: 10,
style: { style: {
fill: '#333', fill: "#333",
fontSize: 11, fontSize: 11,
fontFamily: 'Microsoft YaHei', fontFamily: "Microsoft YaHei",
background: { background: {
fill: 'rgba(255, 255, 255, 0.95)', fill: "rgba(255, 255, 255, 0.95)",
padding: [4, 6, 4, 6], padding: [4, 6, 4, 6],
radius: 4 radius: 4
} }
} }
} }
}, },
defaultEdge: { defaultEdge: {
type: 'cubic-horizontal', type: "cubic-horizontal",
style: { style: {
stroke: '#5B8FF9', stroke: "#5B8FF9",
lineWidth: 3 lineWidth: 3
} }
}, },
nodeStateStyles: { nodeStateStyles: {
active: { active: {
shadowColor: '#1459BB', shadowColor: "#1459BB",
shadowBlur: 15, shadowBlur: 15,
stroke: '#1459BB', stroke: "#1459BB",
lineWidth: 3 lineWidth: 3
} }
} }
}) });
graphInstance.value.data(treeDataSource) graphInstance.value.data(treeDataSource);
graphInstance.value.render() graphInstance.value.render();
graphInstance.value.fitView() graphInstance.value.fitView();
bindGraphEvents() bindGraphEvents();
} };
const convertGraphToTree = (graphData) => { const convertGraphToTree = graphData => {
if (!graphData || !graphData.nodes || graphData.nodes.length === 0) { if (!graphData || !graphData.nodes || graphData.nodes.length === 0) {
return null return null;
} }
const nodes = graphData.nodes const nodes = graphData.nodes;
const links = graphData.links || graphData.edges || [] const links = graphData.links || graphData.edges || [];
const centerNode = nodes[0] const centerNode = nodes[0];
const centerId = String(centerNode.id || '0') const centerId = String(centerNode.id || "0");
const childIdSet = new Set() const childIdSet = new Set();
const childrenNodes = [] const childrenNodes = [];
links.forEach((link) => { links.forEach(link => {
const source = String(link.source) const source = String(link.source);
const target = String(link.target) const target = String(link.target);
if (source === centerId && !childIdSet.has(target)) { if (source === centerId && !childIdSet.has(target)) {
const node = nodes.find(n => String(n.id) === target) const node = nodes.find(n => String(n.id) === target);
if (node) { if (node) {
childIdSet.add(target) childIdSet.add(target);
childrenNodes.push({ childrenNodes.push({
id: target, id: target,
label: node.name || '', label: node.name || "",
img: node.image || defaultIcon, img: node.image || defaultIcon,
size: node.symbolSize || 40, size: node.symbolSize || 40,
name: node.name, name: node.name,
image: node.image, image: node.image,
isSanctioned: node.isSanctioned isSanctioned: node.isSanctioned
}) });
} }
} else if (target === centerId && !childIdSet.has(source)) { } else if (target === centerId && !childIdSet.has(source)) {
const node = nodes.find(n => String(n.id) === source) const node = nodes.find(n => String(n.id) === source);
if (node) { if (node) {
childIdSet.add(source) childIdSet.add(source);
childrenNodes.push({ childrenNodes.push({
id: source, id: source,
label: node.name || '', label: node.name || "",
img: node.image || defaultIcon, img: node.image || defaultIcon,
size: node.symbolSize || 40, size: node.symbolSize || 40,
name: node.name, name: node.name,
image: node.image, image: node.image,
isSanctioned: node.isSanctioned isSanctioned: node.isSanctioned
}) });
} }
} }
}) });
if (childrenNodes.length === 0) { if (childrenNodes.length === 0) {
nodes.slice(1).forEach((node) => { nodes.slice(1).forEach(node => {
const nodeId = String(node.id) const nodeId = String(node.id);
if (!childIdSet.has(nodeId)) { if (!childIdSet.has(nodeId)) {
childIdSet.add(nodeId) childIdSet.add(nodeId);
childrenNodes.push({ childrenNodes.push({
id: nodeId, id: nodeId,
label: node.name || '', label: node.name || "",
img: node.image || defaultIcon, img: node.image || defaultIcon,
size: node.symbolSize || 40, size: node.symbolSize || 40,
name: node.name, name: node.name,
image: node.image, image: node.image,
isSanctioned: node.isSanctioned isSanctioned: node.isSanctioned
}) });
} }
}) });
} }
return { return {
id: centerId, id: centerId,
label: centerNode.name || '', label: centerNode.name || "",
img: centerNode.image || defaultIcon, img: centerNode.image || defaultIcon,
size: centerNode.symbolSize || 60, size: centerNode.symbolSize || 60,
name: centerNode.name, name: centerNode.name,
image: centerNode.image, image: centerNode.image,
isSanctioned: centerNode.isSanctioned, isSanctioned: centerNode.isSanctioned,
children: childrenNodes children: childrenNodes
} };
} };
const processGraphData = (rawData) => { const processGraphData = rawData => {
if (!rawData || !rawData.nodes || rawData.nodes.length === 0) { if (!rawData || !rawData.nodes || rawData.nodes.length === 0) {
return { nodes: [], edges: [] } return { nodes: [], edges: [] };
} }
console.log("数据处理 =>", rawData);
const nodeMap = new Map() const nodeMap = new Map();
const nodes = [] const nodes = [];
rawData.nodes.forEach((node, index) => { rawData.nodes.forEach((node, index) => {
const nodeId = String(node.id || index) const nodeId = String(node.id || index);
if (nodeMap.has(nodeId)) {
return if (nodeMap.has(nodeId)) {
} return;
nodeMap.set(nodeId, true) }
nodeMap.set(nodeId, true);
const isCenter = index === 0
const size = node.symbolSize || (isCenter ? 60 : 40) const isCenter = index === 0;
const size = node.symbolSize || (isCenter ? 60 : 40);
nodes.push({ // 根据 isSanctioned 设置节点边框
id: nodeId, const strokeColor = !node.isSanctioned ? "rgba(231, 243, 255, 1)" : "transparent";
label: node.name || '', const lineWidth = !node.isSanctioned ? 10 : 0;
img: node.image || defaultIcon, const fontColor = node.isSanctioned ? "rgba(5, 95, 194, 1)" : "#333";
size, nodes.push({
isCenter, id: nodeId,
clipCfg: { label: node.name || "",
show: true, img: node.image || defaultIcon,
type: 'circle', size,
r: size / 2 isCenter,
}, clipCfg: {
style: { show: true,
cursor: 'pointer' type: "circle",
}, r: size / 2
labelCfg: { },
position: 'bottom', style: {
offset: 12, cursor: "pointer",
style: { stroke: strokeColor,
fill: isCenter ? '#1459BB' : '#333', lineWidth: lineWidth
fontSize: isCenter ? 13 : 11, },
fontWeight: isCenter ? 'bold' : 'normal', labelCfg: {
fontFamily: 'Microsoft YaHei', position: "bottom",
textAlign: 'center' offset: 12,
} style: {
}, fill: fontColor,
...node, fontSize: isCenter ? 13 : 11,
id: nodeId fontWeight: isCenter ? "bold" : "normal",
}) fontFamily: "Microsoft YaHei",
}) textAlign: "center"
}
const edgeMap = new Map() },
const edges = [] ...node,
id: nodeId
const rawEdges = rawData.links || rawData.edges || [] });
rawEdges.forEach((edge, index) => { });
const source = String(edge.source)
const target = String(edge.target) const edgeMap = new Map();
const edgeKey = `${source}-${target}` const edges = [];
if (edgeMap.has(edgeKey)) { const rawEdges = rawData.links || rawData.edges || [];
return rawEdges.forEach((edge, index) => {
} const source = String(edge.source);
if (!nodeMap.has(source) || !nodeMap.has(target)) { const target = String(edge.target);
return const edgeKey = `${source}-${target}`;
} const isSanctioned = rawData.nodes[index].isSanctioned;
edgeMap.set(edgeKey, true) const strokeColor = isSanctioned ? "rgba(5, 95, 194, 1)" : "#5B8FF9";
const lineColor = isSanctioned ? "rgba(5, 95, 194, 1)" : "rgba(59, 65, 75, .3)";
edges.push({
id: `edge-${index}`, if (edgeMap.has(edgeKey)) {
source, return;
target, }
label: edge.name || '' if (!nodeMap.has(source) || !nodeMap.has(target)) {
}) return;
}) }
edgeMap.set(edgeKey, true);
return { nodes, edges }
} edges.push({
id: `edge-${index}`,
source,
target,
label: edge.name || "",
// 添加边的边框效果(使用 shadow 模拟)
style: {
stroke: lineColor,
lineWidth: 2,
opacity: 0.9,
shadowColor: "rgba(231, 243, 255, 1)",
shadowBlur: 4,
endArrow: {
path: "M 0,0 L 12,6 L 12,-6 Z",
fill: lineColor
}
}
});
});
return { nodes, edges };
};
const bindGraphEvents = () => { const bindGraphEvents = () => {
if (!graphInstance.value) return if (!graphInstance.value) return;
graphInstance.value.on('node:click', (evt) => { graphInstance.value.on("node:click", evt => {
const node = evt.item const node = evt.item;
const model = node.getModel() const model = node.getModel();
selectedNode.value = model console.log("点击节点model", model);
emit('nodeClick', model) const id = model.id.split("-")[1];
}) let desList = [];
getSingleSanctionEntityInfo(id).then(res => {
graphInstance.value.on('canvas:click', () => { console.log("制裁实体信息", res);
selectedNode.value = null if (res.data.sanInfoList && res.data.sanInfoList.length > 0) {
}) desList = res.data.sanInfoList.map(item => item.sanReason);
} }
});
const handleClickControlBtn = (btn) => { selectedNode.value = { ...model, description: desList };
currentLayoutType.value = btn emit("nodeClick", model);
emit('layoutChange', btn) });
initGraph(btn)
} graphInstance.value.on("canvas:click", () => {
selectedNode.value = null;
});
};
const handleClickControlBtn = btn => {
currentLayoutType.value = btn;
emit("layoutChange", btn);
initGraph(btn);
};
const destroyGraph = () => { const destroyGraph = () => {
if (graphInstance.value) { if (graphInstance.value) {
graphInstance.value.destroy() graphInstance.value.destroy();
graphInstance.value = null graphInstance.value = null;
} }
} };
const handleResize = () => { const handleResize = () => {
if (graphInstance.value && containerRef.value) { if (graphInstance.value && containerRef.value) {
const width = containerRef.value.offsetWidth const width = containerRef.value.offsetWidth;
const height = containerRef.value.offsetHeight const height = containerRef.value.offsetHeight;
graphInstance.value.changeSize(width, height) graphInstance.value.changeSize(width, height);
graphInstance.value.fitView() graphInstance.value.fitView();
} }
} };
watch( watch(
() => props.graphData, () => props.graphData,
() => { () => {
initGraph(currentLayoutType.value) initGraph(currentLayoutType.value);
}, },
{ deep: true } { deep: true }
) );
watch( watch(
() => props.treeData, () => props.treeData,
() => { () => {
if (currentLayoutType.value === 2) { if (currentLayoutType.value === 2) {
initGraph(2) initGraph(2);
} }
}, },
{ deep: true } { deep: true }
) );
watch( watch(
() => props.controlActive, () => props.controlActive,
(newVal) => { newVal => {
if (newVal !== currentLayoutType.value) { if (newVal !== currentLayoutType.value) {
handleClickControlBtn(newVal) handleClickControlBtn(newVal);
} }
} }
) );
onMounted(() => { onMounted(() => {
initGraph(1) initGraph(1);
window.addEventListener('resize', handleResize) window.addEventListener("resize", handleResize);
}) });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', handleResize) window.removeEventListener("resize", handleResize);
destroyGraph() destroyGraph();
}) });
defineExpose({ defineExpose({
refresh: () => initGraph(currentLayoutType.value), refresh: () => initGraph(currentLayoutType.value),
changeLayout: (type) => handleClickControlBtn(type), changeLayout: type => handleClickControlBtn(type),
getGraph: () => graphInstance.value getGraph: () => graphInstance.value
}) });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.relation-graph-wrapper { .relation-graph-wrapper {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.graph-container { .graph-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.graph-controls { .graph-controls {
position: absolute; position: absolute;
top: 16px; top: 16px;
right: 16px; // right: 16px;
display: flex; display: flex;
gap: 8px; gap: 8px;
z-index: 10; z-index: 10;
width: 100%;
.control-btn { display: flex;
width: 32px; justify-content: space-between;
height: 32px; padding: 0 20px;
border-radius: 4px; .graph-chart-controls {
border: 1px solid rgba(234, 236, 238, 1); display: flex;
background: rgba(255, 255, 255, 1); gap: 0px;
display: flex; box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
align-items: center; }
justify-content: center;
cursor: pointer; .control-btn {
width: 32px;
img { height: 32px;
width: 16px; border-radius: 4px;
height: 16px; // border: 1px solid rgba(234, 236, 238, 1);
} background: rgba(255, 255, 255, 1);
display: flex;
&:hover { align-items: center;
border-color: rgba(5, 95, 194, 0.5); justify-content: center;
} cursor: pointer;
}
img {
.control-btn-active { width: 16px;
border-color: rgba(5, 95, 194, 1); height: 16px;
background: rgba(231, 243, 255, 1); }
}
&:hover {
border-color: rgba(5, 95, 194, 0.5);
}
}
.control-btn-active {
border-color: rgba(5, 95, 194, 1);
background: rgba(231, 243, 255, 1);
}
.graph-chart-legend {
display: flex;
gap: 8px;
.legend-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
font-family: "Microsoft YaHei";
color: rgba(59, 65, 75, 1);
.legend-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: rgba(5, 95, 194, 1);
}
}
.legend-item:last-child {
.legend-dot {
background: #333;
}
}
}
} }
.node-popup { .node-popup {
position: absolute; position: absolute;
bottom: 16px; bottom: 16px;
left: 16px; left: 16px;
width: 320px; width: 320px;
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
border-radius: 8px; border-radius: 8px;
box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.1); box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(234, 236, 238, 1); border: 1px solid rgba(234, 236, 238, 1);
z-index: 20; z-index: 20;
.popup-header { .popup-header {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 12px 16px; padding: 12px 16px;
border-bottom: 1px solid rgba(234, 236, 238, 1); border-bottom: 1px solid rgba(234, 236, 238, 1);
.popup-icon { .popup-icon {
width: 32px; width: 32px;
height: 32px; height: 32px;
margin-right: 8px; margin-right: 8px;
border-radius: 50%; border-radius: 50%;
object-fit: cover; object-fit: cover;
} }
.popup-title { .popup-title {
flex: 1; flex: 1;
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.close-icon { .close-icon {
cursor: pointer; cursor: pointer;
color: rgba(132, 136, 142, 1); color: rgba(132, 136, 142, 1);
font-size: 16px; font-size: 16px;
&:hover { &:hover {
color: rgba(5, 95, 194, 1); color: rgba(5, 95, 194, 1);
} }
} }
} }
.popup-body { .popup-body {
padding: 12px 16px; padding: 12px 16px;
.tag-row { .tag-row {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 8px; margin-bottom: 8px;
.red-dot { .red-dot {
width: 6px; width: 6px;
height: 6px; height: 6px;
border-radius: 50%; border-radius: 50%;
background: rgba(245, 63, 63, 1); background: rgba(245, 63, 63, 1);
margin-right: 8px; margin-right: 8px;
} }
.red-text { .red-text {
font-size: 14px; font-size: 14px;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
color: rgba(245, 63, 63, 1); color: rgba(245, 63, 63, 1);
} }
} }
.desc { .desc {
font-size: 14px; font-size: 14px;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
line-height: 22px; line-height: 22px;
color: rgba(95, 101, 108, 1); color: rgba(95, 101, 108, 1);
margin: 0; margin: 0;
} }
} }
} }
</style> </style>
\ No newline at end of file
...@@ -187,19 +187,11 @@ const handleMouseLeave = () => { ...@@ -187,19 +187,11 @@ const handleMouseLeave = () => {
isInChart.value = false; isInChart.value = false;
}; };
<<<<<<< HEAD
const handleNodeClick = (node) => { const handleNodeClick = (node) => {
selectedNode.value = node; selectedNode.value = node;
}; };
const handleLayoutChange = (type) => { const handleLayoutChange = (type) => {
=======
const handleNodeClick = node => {
selectedNode.value = node;
};
const handleLayoutChange = type => {
>>>>>>> master
controlActive.value = type; controlActive.value = type;
if (type !== 2) { if (type !== 2) {
isInChart.value = true; isInChart.value = true;
...@@ -209,14 +201,9 @@ const handleLayoutChange = type => { ...@@ -209,14 +201,9 @@ const handleLayoutChange = type => {
}; };
const updateGraphData = () => { const updateGraphData = () => {
<<<<<<< HEAD
const data = rightActiveTab.value === 'supplyChain' const data = rightActiveTab.value === 'supplyChain'
? singleSanctionEntitySupplyChainData.value ? singleSanctionEntitySupplyChainData.value
: singleSanctionEntityEquityData.value; : singleSanctionEntityEquityData.value;
=======
const data =
rightActiveTab.value === "supplyChain" ? singleSanctionEntitySupplyChainData.value : singleSanctionEntityEquityData.value;
>>>>>>> master
if (!data) return; if (!data) return;
...@@ -244,11 +231,7 @@ const updateGraphData = () => { ...@@ -244,11 +231,7 @@ const updateGraphData = () => {
links.push({ links.push({
source: `p-${item.id || index}`, source: `p-${item.id || index}`,
target: "0", target: "0",
<<<<<<< HEAD
name: rightActiveTab.value === 'supplyChain' ? "供应商" : (item.type || "持股") name: rightActiveTab.value === 'supplyChain' ? "供应商" : (item.type || "持股")
=======
name: rightActiveTab.value === "supplyChain" ? "供应商" : item.type || "持股"
>>>>>>> master
}); });
}); });
...@@ -265,22 +248,14 @@ const updateGraphData = () => { ...@@ -265,22 +248,14 @@ const updateGraphData = () => {
links.push({ links.push({
source: "0", source: "0",
target: `c-${item.id || index}`, target: `c-${item.id || index}`,
<<<<<<< HEAD
name: rightActiveTab.value === 'supplyChain' ? "客户" : (item.type || "投资") name: rightActiveTab.value === 'supplyChain' ? "客户" : (item.type || "投资")
=======
name: rightActiveTab.value === "supplyChain" ? "客户" : item.description || "投资"
>>>>>>> master
}); });
}); });
graphData.value = { nodes, links }; graphData.value = { nodes, links };
}; };
<<<<<<< HEAD
const updateTreeData = (data) => { const updateTreeData = (data) => {
=======
const updateTreeData = data => {
>>>>>>> master
if (!data) return; if (!data) return;
treeData.value = { treeData.value = {
...@@ -315,7 +290,8 @@ const getSingleSanctionEntityEquityRequest = async () => { ...@@ -315,7 +290,8 @@ const getSingleSanctionEntityEquityRequest = async () => {
const getSingleSanctionEntitySupplyChainRequest = async () => { const getSingleSanctionEntitySupplyChainRequest = async () => {
try { try {
const res = await getSingleSanctionEntitySupplyChain({ const res = await getSingleSanctionEntitySupplyChain({
orgId: activeEntityId.value orgId: activeEntityId.value,
domainId: searchDomain.value || undefined
}); });
if (res.code === 200) { if (res.code === 200) {
singleSanctionEntitySupplyChainData.value = res.data || null; singleSanctionEntitySupplyChainData.value = res.data || null;
...@@ -360,28 +336,17 @@ const getSingleSanctionEntityListRequest = async () => { ...@@ -360,28 +336,17 @@ const getSingleSanctionEntityListRequest = async () => {
} }
}; };
<<<<<<< HEAD
watch(rightActiveTab, async (newTab) => { watch(rightActiveTab, async (newTab) => {
if (newTab === 'supplyChain') { if (newTab === 'supplyChain') {
=======
watch(rightActiveTab, async newTab => {
if (newTab === "supplyChain") {
>>>>>>> master
await getSingleSanctionEntitySupplyChainRequest(); await getSingleSanctionEntitySupplyChainRequest();
} else { } else {
await getSingleSanctionEntityEquityRequest(); await getSingleSanctionEntityEquityRequest();
} }
}); });
<<<<<<< HEAD
watch(activeEntityId, async (newId) => { watch(activeEntityId, async (newId) => {
if (newId) { if (newId) {
if (rightActiveTab.value === 'supplyChain') { if (rightActiveTab.value === 'supplyChain') {
=======
watch(activeEntityId, async newId => {
if (newId) {
if (rightActiveTab.value === "supplyChain") {
>>>>>>> master
await getSingleSanctionEntitySupplyChainRequest(); await getSingleSanctionEntitySupplyChainRequest();
} else { } else {
await getSingleSanctionEntityEquityRequest(); await getSingleSanctionEntityEquityRequest();
...@@ -390,15 +355,15 @@ watch(activeEntityId, async newId => { ...@@ -390,15 +355,15 @@ watch(activeEntityId, async newId => {
}); });
watch(is50PercentRule, async () => { watch(is50PercentRule, async () => {
<<<<<<< HEAD
if (rightActiveTab.value === 'equity') { if (rightActiveTab.value === 'equity') {
=======
if (rightActiveTab.value === "equity") {
>>>>>>> master
await getSingleSanctionEntityEquityRequest(); await getSingleSanctionEntityEquityRequest();
} }
}); });
watch(searchDomain, async () => {
getSingleSanctionEntityListRequest();
});
onMounted(async () => { onMounted(async () => {
getUrlParams(); getUrlParams();
await getSingleSanctionEntityListRequest(); await getSingleSanctionEntityListRequest();
...@@ -929,9 +894,11 @@ onMounted(async () => { ...@@ -929,9 +894,11 @@ onMounted(async () => {
margin-left: 16px; margin-left: 16px;
.toggle-btn { .toggle-btn {
width: 108px;
height: 100%; height: 100%;
padding: 4px 16px; padding: 4px 16px;
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
transition: all 0.3s; transition: all 0.3s;
......
...@@ -90,6 +90,37 @@ ...@@ -90,6 +90,37 @@
<div class="right" v-loading="isLoading"> <div class="right" v-loading="isLoading">
<AnalysisBox title="制裁清单" :showAllBtn="false"> <AnalysisBox title="制裁清单" :showAllBtn="false">
<div class="right-title"> <div class="right-title">
<div class="filter-row">
<div class="filter-left">
<el-select v-model="filterEntity" placeholder="受制裁实体" style="width: 184px">
<el-option label="受制裁实体" value="2" />
<el-option label="受制裁地址" value="7" />
<el-option label="受制裁个人" value="1" />
</el-select>
</div>
<div class="filter-right">
<el-checkbox v-model="onlyChina" label="只看中国实体" />
<el-select
v-model="filterField"
placeholder="全部领域"
style="width: 150px; margin: 0 12px 0 16px"
>
<el-option label="全部领域" value="" />
<el-option
v-for="item in domainOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-model="searchKeyword"
placeholder="搜索实体"
style="width: 150px"
:suffix-icon="Search"
/>
</div>
</div>
<div class="stats-row"> <div class="stats-row">
<div class="tabs"> <div class="tabs">
<div class="tab-btn" :class="{ active: activeTab === 'add' }" @click="activeTab = 'add'"> <div class="tab-btn" :class="{ active: activeTab === 'add' }" @click="activeTab = 'add'">
...@@ -120,35 +151,6 @@ ...@@ -120,35 +151,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="filter-row">
<div class="filter-left">
<!-- <el-select v-model="filterEntity" placeholder="受制裁实体" style="width: 184px">
<el-option label="受制裁实体" value="1" />
</el-select> -->
</div>
<div class="filter-right">
<el-checkbox v-model="onlyChina" label="只看中国实体" />
<el-select
v-model="filterField"
placeholder="全部领域"
style="width: 150px; margin: 0 12px 0 16px"
>
<el-option label="全部领域" value="" />
<el-option
v-for="item in domainOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-model="searchKeyword"
placeholder="搜索实体"
style="width: 150px"
:suffix-icon="Search"
/>
</div>
</div>
</div> </div>
<div class="right-content"> <div class="right-content">
<div class="sanction-group-list"> <div class="sanction-group-list">
...@@ -286,7 +288,8 @@ const getSanctionOverviewList = async () => { ...@@ -286,7 +288,8 @@ const getSanctionOverviewList = async () => {
isOnlyCn: onlyChina.value, isOnlyCn: onlyChina.value,
domainId: filterField.value || undefined, domainId: filterField.value || undefined,
searchText: searchKeyword.value || undefined, searchText: searchKeyword.value || undefined,
searchType: searchType.value searchType: searchType.value,
entityTypeId: filterEntity.value || undefined
}); });
isLoading.value = false; isLoading.value = false;
if (res.code === 200) { if (res.code === 200) {
...@@ -446,7 +449,7 @@ const formattedData = computed(() => { ...@@ -446,7 +449,7 @@ const formattedData = computed(() => {
}; };
}); });
const filterEntity = ref(""); const filterEntity = ref("2");
const onlyChina = ref(false); const onlyChina = ref(false);
const filterField = ref(""); const filterField = ref("");
const searchKeyword = ref(""); const searchKeyword = ref("");
...@@ -454,7 +457,7 @@ const activeTab = ref("add"); ...@@ -454,7 +457,7 @@ const activeTab = ref("add");
const searchType = computed(() => activeTab.value); const searchType = computed(() => activeTab.value);
// 监听筛选条件变化 // 监听筛选条件变化
watch([onlyChina, filterField, activeTab], () => { watch([onlyChina, filterField, activeTab, filterEntity], () => {
getSanctionOverviewList(); getSanctionOverviewList();
}); });
...@@ -865,9 +868,9 @@ onMounted(() => { ...@@ -865,9 +868,9 @@ onMounted(() => {
.filter-row { .filter-row {
display: flex; display: flex;
justify-content: right; justify-content: space-between;
align-items: center; align-items: center;
// margin-bottom: 20px; margin-bottom: 20px;
:deep(.el-input__inner) { :deep(.el-input__inner) {
font-size: 16px; font-size: 16px;
...@@ -897,7 +900,7 @@ onMounted(() => { ...@@ -897,7 +900,7 @@ onMounted(() => {
.stats-row { .stats-row {
display: flex; display: flex;
margin-bottom: 20px; // margin-bottom: 20px;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论