提交 8e14f2ea authored 作者: 闫鹏's avatar 闫鹏

合并分支 'yp-dev' 到 'pre'

Yp dev 查看合并请求 !291
流水线 #261 已通过 于阶段
in 6 分 10 秒
...@@ -292,10 +292,10 @@ export function getSingleSanctionOverviewList(data) { ...@@ -292,10 +292,10 @@ export function getSingleSanctionOverviewList(data) {
* @param {string} params.sanRecordId - 制裁记录ID * @param {string} params.sanRecordId - 制裁记录ID
* @header token * @header token
*/ */
export function getSingleSanctionTotalCount(id) { export function getSingleSanctionTotalCount(sanTypeId, recordId) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/total?sanTypeId=${id}`, url: `/api/sanctionList/statistics/total?sanTypeId=${sanTypeId}&sanRecordId=${recordId}`,
}); });
} }
......
import request from "@/api/request.js";
\ No newline at end of file
<template> <template>
<div class="graph-chart-wrapper" id="graph"> <div class="graph-chart-wrapper" id="graph"></div>
</div>
</template> </template>
<script setup> <script setup>
import { onMounted, onBeforeUnmount } from 'vue'; import { onMounted, onBeforeUnmount, watch } from "vue";
import setChart from '@/utils/setChart'; import setChart from "@/utils/setChart";
import getGraphChart from './graphChart'; import getGraphChart from "./graphChart";
const emits = defineEmits(["handleClickNode"]) const emits = defineEmits(["handleClickNode"]);
const props = defineProps({ const props = defineProps({
nodes: { nodes: {
type: Array, type: Array,
...@@ -21,33 +19,44 @@ const props = defineProps({ ...@@ -21,33 +19,44 @@ const props = defineProps({
}, },
layoutType: { layoutType: {
type: String, type: String,
default: 'force' default: "force"
}, },
width: { width: {
type: String, type: String,
default: 'force' default: "force"
}, },
height: { height: {
type: String, type: String,
default: 'force' default: "force"
} }
}) });
let chart = null let chart = null;
onMounted(() => { onMounted(() => {
const graph = getGraphChart(props.nodes, props.links, props.layoutType) const graph = getGraphChart(props.nodes, props.links, props.layoutType);
chart = setChart(graph, 'graph') chart = setChart(graph, "graph");
chart.on("click", (event) => { emits("handleClickNode", event) }) chart.on("click", event => {
}) emits("handleClickNode", event);
});
});
onBeforeUnmount(() => { onBeforeUnmount(() => {
chart.off("click") chart.off("click");
chart.dispose() chart.dispose();
}) });
watch(
() => [props.nodes, props.links],
() => {
if (chart) {
const graph = getGraphChart(props.nodes, props.links, props.layoutType);
chart.setOption(graph, true);
}
},
{ deep: true }
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.graph-chart-wrapper { .graph-chart-wrapper {
width: 100%; width: 100%;
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
<el-col :span="16"> <el-col :span="16">
<custom-container titleType="primary" title="最新出口管制政策" :titleIcon="houseIcon" height="450px"> <custom-container titleType="primary" title="最新出口管制政策" :titleIcon="houseIcon" height="450px">
<template #header-right> <template #header-right>
<el-button type="primary" @click="handleToEntityList()" link> <el-button type="primary" @click="handleToEntityList" link>
{{ "查看详情 >" }} {{ "查看详情 >" }}
</el-button> </el-button>
</template> </template>
...@@ -390,7 +390,7 @@ ...@@ -390,7 +390,7 @@
</custom-container> </custom-container>
</el-col> </el-col>
<el-col :span="16"> <el-col :span="16">
<custom-container title="实体清单数量增长趋势" :titleIcon="qushiIcon" height="540px"> <custom-container title="制裁清单数量增长趋势" :titleIcon="qushiIcon" height="540px">
<template #header-right> <template #header-right>
<div style="display: flex; align-items: center; gap: 16px"> <div style="display: flex; align-items: center; gap: 16px">
<el-checkbox v-model="trendChecked" label="50%规则" size="large" /> <el-checkbox v-model="trendChecked" label="50%规则" size="large" />
...@@ -995,8 +995,6 @@ onMounted(async () => { ...@@ -995,8 +995,6 @@ onMounted(async () => {
const maxCountItem1 = _.maxBy(cclList1, "count"); const maxCountItem1 = _.maxBy(cclList1, "count");
const maxCountForList1 = maxCountItem1 ? maxCountItem1.count : 0; const maxCountForList1 = maxCountItem1 ? maxCountItem1.count : 0;
console.log("shuju list", list);
console.log("shuju total", total);
tableData1.value = _.map(list, item => { tableData1.value = _.map(list, item => {
return { return {
year: item.year, year: item.year,
...@@ -1053,7 +1051,7 @@ const fetchTrendData = async () => { ...@@ -1053,7 +1051,7 @@ const fetchTrendData = async () => {
}); });
if (res && res[0] && res[0].yearDomainCount) { if (res && res[0] && res[0].yearDomainCount) {
trendOption.value = processYearDomainCountData(res[0].yearDomainCount); trendOption.value = processYearDomainCountData(res[0].yearDomainCount);
trendChart.interpret({ type: "柱状图", name: "实体清单数量增长趋势", data: res[0].yearDomainCount }); trendChart.interpret({ type: "柱状图", name: "制裁清单数量增长趋势", data: res[0].yearDomainCount });
} }
} catch (error) { } catch (error) {
console.error("获取趋势图数据失败:", error); console.error("获取趋势图数据失败:", error);
...@@ -1147,10 +1145,13 @@ const handleCarouselChange = index => { ...@@ -1147,10 +1145,13 @@ const handleCarouselChange = index => {
// 跳转到V2.0单次制裁 // 跳转到V2.0单次制裁
const handleToEntityList = item => { const handleToEntityList = item => {
console.log("这是什么数据1 =>", item);
let id = item?.id; let id = item?.id;
let sanTypeId = item?.sanTypeId || 1;
if (!id) { if (!id) {
const currentItem = entitiesDataInfoList.value[currentCarouselIndex.value]; const currentItem = entitiesDataInfoList.value[currentCarouselIndex.value];
id = currentItem?.id; id = currentItem?.id;
sanTypeId = currentItem?.sanTypeId || 1;
} }
window.sessionStorage.setItem( window.sessionStorage.setItem(
"curTabName", "curTabName",
...@@ -1159,7 +1160,8 @@ const handleToEntityList = item => { ...@@ -1159,7 +1160,8 @@ const handleToEntityList = item => {
const routeData = router.resolve({ const routeData = router.resolve({
path: "/exportControl/singleSanction", path: "/exportControl/singleSanction",
query: { query: {
id: id id,
sanTypeId
} }
}); });
// 打开一个新页面 // 打开一个新页面
...@@ -2363,7 +2365,8 @@ const handleMediaClick = item => { ...@@ -2363,7 +2365,8 @@ const handleMediaClick = item => {
overflow-y: auto; overflow-y: auto;
.home-top-bg { .home-top-bg {
background: url("./assets/images/background.png"), background:
url("./assets/images/background.png"),
linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%); linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%);
background-size: 100% 100%; background-size: 100% 100%;
position: absolute; position: absolute;
...@@ -3631,7 +3634,7 @@ const handleMediaClick = item => { ...@@ -3631,7 +3634,7 @@ const handleMediaClick = item => {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
padding: 22px 0; padding: 22px 0;
.data-origin-icon { .data-origin-icon {
width: 16px; width: 16px;
......
import * as echarts from "echarts"; import * as echarts from "echarts";
import chinaJson from "./China.json"; import chinaJson from "./China.json";
import _ from "lodash"; import _ from "lodash";
import { name } from "dayjs/locale/zh-cn";
//饼图 //饼图
export function getPieOption(data, title) { export function getPieOption(data, title) {
let option = { let option = {
...@@ -1006,7 +1007,7 @@ export const getMultipleBarChart_m = object => { ...@@ -1006,7 +1007,7 @@ export const getMultipleBarChart_m = object => {
margin: 20 margin: 20
}, },
axisLabel: { axisLabel: {
formatter: "{value}", formatter: "{value}",
color: "rgba(95, 101, 108, 1)", color: "rgba(95, 101, 108, 1)",
margin: 20 margin: 20
}, },
...@@ -1022,6 +1023,21 @@ export const getMultipleBarChart_m = object => { ...@@ -1022,6 +1023,21 @@ export const getMultipleBarChart_m = object => {
}, },
yAxis: { yAxis: {
type: "value", type: "value",
name: "数量",
nameLocation: "end",
nameGap: 20,
nameRotate: 0,
nameTextStyle: {
color: "#666",
fontFamily: "Microsoft YaHei",
fontWeight: 400,
fontSize: 14,
lineHeight: 14,
letterSpacing: 0,
align: "right",
verticalAlign: "bottom",
padding: [0, 8, 0, 0]
},
splitNumber: 5, splitNumber: 5,
alignTicks: false, alignTicks: false,
axisLabel: { axisLabel: {
......
...@@ -105,7 +105,7 @@ ...@@ -105,7 +105,7 @@
</div> </div>
</template> </template>
<!-- <div class="echarts" ref="sanctionCountChartRef"></div> --> <!-- <div class="echarts" ref="sanctionCountChartRef"></div> -->
<EChart :option="sanctionCountChartOption" autoresize :style="{ height: '300px' }" /> <EChart :option="sanctionCountChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" />
<!-- <div class="bottom"> <!-- <div class="bottom">
<div class="ai"> <div class="ai">
<div class="left"> <div class="left">
...@@ -186,7 +186,7 @@ ...@@ -186,7 +186,7 @@
</el-select> </el-select>
</template> </template>
<!-- <div class="echarts" ref="domainChartRef"></div> --> <!-- <div class="echarts" ref="domainChartRef"></div> -->
<EChart :option="domainChartOption" autoresize :style="{ height: '300px' }" /> <EChart :option="domainChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" />
<!-- <div class="bottom"> <!-- <div class="bottom">
<div class="ai"> <div class="ai">
<div class="left"> <div class="left">
...@@ -218,7 +218,7 @@ ...@@ -218,7 +218,7 @@
</el-select> </el-select>
</template> </template>
<!-- <div class="echarts" ref="typeChartRef"></div> --> <!-- <div class="echarts" ref="typeChartRef"></div> -->
<EChart :option="typeChartOption" autoresize :style="{ height: '300px' }" /> <EChart :option="typeChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" />
<!-- <div class="bottom"> <!-- <div class="bottom">
<div class="ai"> <div class="ai">
<div class="left"> <div class="left">
...@@ -717,8 +717,21 @@ const domainChartOption = ref({ ...@@ -717,8 +717,21 @@ const domainChartOption = ref({
width: 1.1 width: 1.1
} }
}, },
labelLayout: { labelLayout: function (params) {
hideOverlap: true // hideOverlap: true
const points = params.labelLinePoints;
const isLeft = params.labelRect.x < params.rect.x + params.rect.width / 2;
// 调整指示线终点到 label 垂直中心
const labelCenterY = params.labelRect.y + params.labelRect.height / 2;
points[2][1] = labelCenterY;
// 调整指示线终点到 label 水平边缘
points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
};
}, },
itemStyle: { itemStyle: {
borderWidth: 0 borderWidth: 0
...@@ -926,10 +939,23 @@ const typeChartOption = ref({ ...@@ -926,10 +939,23 @@ const typeChartOption = ref({
} }
}, },
labelLayout: function (params) { labelLayout: function (params) {
const isLeft = params.labelRect.x < chart.getWidth() / 2; // const isLeft = params.labelRect.x < chart.getWidth() / 2;
// const points = params.labelLinePoints;
// points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width;
// return {
// labelLinePoints: points
// };
const points = params.labelLinePoints; const points = params.labelLinePoints;
// Update the end point. const isLeft = params.labelRect.x < params.rect.x + params.rect.width / 2;
// 调整指示线终点到 label 垂直中心
const labelCenterY = params.labelRect.y + params.labelRect.height / 2;
points[2][1] = labelCenterY;
// 调整指示线终点到 label 水平边缘
points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width; points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width;
return { return {
labelLinePoints: points labelLinePoints: points
}; };
...@@ -1508,8 +1534,8 @@ onMounted(() => { ...@@ -1508,8 +1534,8 @@ onMounted(() => {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
padding: 22px 0; padding: 22px;
.data-origin-icon { .data-origin-icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
......
...@@ -241,11 +241,11 @@ const handleClick = item => { ...@@ -241,11 +241,11 @@ const handleClick = item => {
const route = router.resolve({ const route = router.resolve({
path: "/exportControl/singleSanction", path: "/exportControl/singleSanction",
query: { query: {
id: item.id id: item.id,
sanTypeId: item.sanTypeId || 1
} }
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
}; };
const selectedDomain = ref(0); const selectedDomain = ref(0);
......
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
</div> </div>
</div> </div>
</div> --> </div> -->
<EChart :option="domainChartOption" autoresize :style="{ height: '300px' }" /> <EChart :option="domainChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" />
<div class="data-origin-box"> <div class="data-origin-box">
<div class="data-origin-icon"> <div class="data-origin-icon">
<img :src="tipsIcon" alt="" /> <img :src="tipsIcon" alt="" />
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
</div> </div>
</div> </div>
</div> --> </div> -->
<EChart :option="typeChartOption" autoresize :style="{ height: '300px' }" /> <EChart :option="typeChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" />
<div class="data-origin-box"> <div class="data-origin-box">
<div class="data-origin-icon"> <div class="data-origin-icon">
<img :src="tipsIcon" alt="" /> <img :src="tipsIcon" alt="" />
...@@ -348,7 +348,7 @@ const totalCount = ref({}); ...@@ -348,7 +348,7 @@ const totalCount = ref({});
const getTotalCount = async () => { const getTotalCount = async () => {
if (!sanRecordId.value) return; if (!sanRecordId.value) return;
try { try {
const res = await getSingleSanctionTotalCount(route.query.sanTypeId); const res = await getSingleSanctionTotalCount(route.query.sanTypeId, sanRecordId.value);
if (res.code === 200) { if (res.code === 200) {
totalCount.value = res.data || {}; totalCount.value = res.data || {};
} }
...@@ -1264,8 +1264,8 @@ onMounted(() => { ...@@ -1264,8 +1264,8 @@ onMounted(() => {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
padding: 22px 0; padding: 22px;
.data-origin-icon { .data-origin-icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
......
...@@ -107,22 +107,185 @@ const initGraph = (layoutType = 1) => { ...@@ -107,22 +107,185 @@ const initGraph = (layoutType = 1) => {
}); });
}; };
// const initNormalGraph = (layoutType, width, height) => {
// const data = processGraphData(props.graphData);
// console.log("初始数据", props.graphData);
// if (!data.nodes || data.nodes.length === 0) return;
// const layout = {
// type: "none",
// 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
// }
// }
// }
// // 注意:节点边框样式在 processGraphData 中单独设置,不在这里设置
// },
// defaultEdge: {
// type: "quadratic",
// style: {
// stroke: "red",
// lineWidth: 3,
// opacity: 0.9,
// shadowColor: "rgba(231, 243, 255, 1)",
// shadowBlur: 4,
// endArrow: {
// path: "M 0,0 L 12,6 L 12,-6 Z",
// fill: "#5B8FF9"
// }
// },
// labelCfg: {
// autoRotate: true,
// style: {
// fill: "rgba(137, 193, 255, 1)",
// fontSize: 10,
// fontFamily: "Microsoft YaHei",
// background: {
// fill: "rgba(231, 243, 255, 1)",
// padding: [2, 4, 2, 4],
// radius: 5
// }
// }
// }
// },
// 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 initNormalGraph = (layoutType, width, height) => { const initNormalGraph = (layoutType, width, height) => {
const data = processGraphData(props.graphData); const data = processGraphData(props.graphData);
console.log("初始数据", props.graphData); console.log("初始数据", props.graphData);
if (!data.nodes || data.nodes.length === 0) return; if (!data.nodes || data.nodes.length === 0) return;
// 中心节点坐标
const centerX = width / 2;
const centerY = height / 2;
const upperY = centerY - 240; // 上方节点 Y 坐标
const lowerY = centerY + 240; // 下方节点 Y 坐标
const nodeSpacing = 100; // 节点水平间距
// 分离中心节点和其他节点
const centerNode = data.nodes.find(n => n.isCenter);
const otherNodes = data.nodes.filter(n => !n.isCenter);
const totalNodes = otherNodes.length;
const upperCount = Math.ceil(totalNodes / 2); // 向上取整,上方多一个
const lowerCount = totalNodes - upperCount;
// 为上方节点分配坐标
const upperNodes = otherNodes.slice(0, upperCount);
upperNodes.forEach((node, index) => {
const totalWidth = (upperCount - 1) * nodeSpacing;
const startX = centerX - totalWidth / 2;
node.x = startX + index * nodeSpacing;
node.y = upperY;
// 固定位置,防止力导向布局移动
node.fx = node.x;
node.fy = node.y;
});
// 为下方节点分配坐标
const lowerNodes = otherNodes.slice(upperCount);
lowerNodes.forEach((node, index) => {
const totalWidth = (lowerCount - 1) * nodeSpacing;
const startX = centerX - totalWidth / 2;
node.x = startX + index * nodeSpacing;
node.y = lowerY;
// 固定位置,防止力导向布局移动
node.fx = node.x;
node.fy = node.y;
});
// 设置中心节点坐标
if (centerNode) {
centerNode.x = centerX;
centerNode.y = centerY;
// 固定中心节点位置
centerNode.fx = centerX;
centerNode.fy = centerY;
}
const layout = { const layout = {
type: "force", type: "none", // 使用预设坐标,不进行力导向布局
center: [width / 2, height / 2], center: [centerX, centerY]
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({ graphInstance.value = new G6.Graph({
...@@ -175,10 +338,9 @@ const initNormalGraph = (layoutType, width, height) => { ...@@ -175,10 +338,9 @@ const initNormalGraph = (layoutType, width, height) => {
} }
} }
} }
// 注意:节点边框样式在 processGraphData 中单独设置,不在这里设置
}, },
defaultEdge: { defaultEdge: {
type: "quadratic", type: "line",
style: { style: {
stroke: "red", stroke: "red",
lineWidth: 3, lineWidth: 3,
...@@ -230,7 +392,6 @@ const initNormalGraph = (layoutType, width, height) => { ...@@ -230,7 +392,6 @@ const initNormalGraph = (layoutType, width, height) => {
graphInstance.value.render(); graphInstance.value.render();
bindGraphEvents(); bindGraphEvents();
}; };
const initCircularGraph = (width, height) => { const initCircularGraph = (width, height) => {
const data = processGraphData(props.graphData); const data = processGraphData(props.graphData);
...@@ -307,7 +468,7 @@ const initCircularGraph = (width, height) => { ...@@ -307,7 +468,7 @@ const initCircularGraph = (width, height) => {
} }
}, },
defaultEdge: { defaultEdge: {
type: "quadratic", type: "line",
style: { style: {
stroke: "#5B8FF9", stroke: "#5B8FF9",
lineWidth: 3, lineWidth: 3,
......
...@@ -110,6 +110,7 @@ ...@@ -110,6 +110,7 @@
@node-click="handleNodeClick" @node-click="handleNodeClick"
@layout-change="handleLayoutChange" @layout-change="handleLayoutChange"
/> />
<!-- <GraphChart :nodes="nodes" :links="links" layoutType="none" /> -->
</div> </div>
</div> </div>
</div> </div>
...@@ -134,6 +135,7 @@ import { ...@@ -134,6 +135,7 @@ import {
} from "@/api/exportControlV2.0"; } from "@/api/exportControlV2.0";
import RelationGraph from "./components/RelationGraph.vue"; import RelationGraph from "./components/RelationGraph.vue";
import AnalysisBox from "@/components/base/boxBackground/analysisBox.vue"; import AnalysisBox from "@/components/base/boxBackground/analysisBox.vue";
import GraphChart from "@/components/base/GraphChart/index.vue";
const sanRecordId = ref(""); const sanRecordId = ref("");
const activeTab = ref(["实体穿透分析"]); const activeTab = ref(["实体穿透分析"]);
...@@ -157,6 +159,9 @@ const graphData = ref({ nodes: [], links: [] }); ...@@ -157,6 +159,9 @@ const graphData = ref({ nodes: [], links: [] });
const treeData = ref(null); const treeData = ref(null);
const selectedNode = ref(null); const selectedNode = ref(null);
const nodes = ref([]);
const links = ref([]);
const singleSanctionEntityEquityData = ref(null); const singleSanctionEntityEquityData = ref(null);
const singleSanctionEntitySupplyChainData = ref(null); const singleSanctionEntitySupplyChainData = ref(null);
const singleSanctionEntityList = ref([]); const singleSanctionEntityList = ref([]);
...@@ -204,6 +209,7 @@ const updateGraphData = () => { ...@@ -204,6 +209,7 @@ const updateGraphData = () => {
const data = const data =
rightActiveTab.value === "supplyChain" ? singleSanctionEntitySupplyChainData.value : singleSanctionEntityEquityData.value; rightActiveTab.value === "supplyChain" ? singleSanctionEntitySupplyChainData.value : singleSanctionEntityEquityData.value;
console.log("图谱数据 =>", data);
if (!data) return; if (!data) return;
const nodes = []; const nodes = [];
...@@ -252,8 +258,172 @@ const updateGraphData = () => { ...@@ -252,8 +258,172 @@ const updateGraphData = () => {
}); });
graphData.value = { nodes, links }; graphData.value = { nodes, links };
// nodes.value = nodes;
// links.value = links;
}; };
const links1 = [
{ source: 1, target: 7, label: { show: true, formatter: "合作" } },
{ source: 2, target: 7, label: { show: true, formatter: "持股" } },
{ source: 3, target: 7, label: { show: true, formatter: "合作" } },
{ source: 4, target: 7, lineStyle: { type: "dashed", color: "#d32f2f" }, label: { show: true, formatter: "从属" } },
{ source: 5, target: 7, label: { show: true, formatter: "合作" } },
{ source: 6, target: 7, label: { show: true, formatter: "持股" } },
{ source: 0, target: 7, label: { show: true, formatter: "持股" } },
{ source: 8, target: 7, label: { show: true, formatter: "合作" } },
{ source: 9, target: 7, lineStyle: { type: "dashed", color: "#d32f2f" }, label: { show: true, formatter: "从属" } },
{ source: 10, target: 7, lineStyle: { type: "dashed", color: "#d32f2f" }, label: { show: true, formatter: "合作" } },
{ source: 11, target: 7, label: { show: true, formatter: "合作" } },
{ source: 12, target: 7, label: { show: true, formatter: "合作" } },
{ source: 13, target: 7, label: { show: true, formatter: "合作" } },
{ source: 14, target: 7, label: { show: true, formatter: "合作" } },
{ source: 15, target: 7, label: { show: true, formatter: "合作", color: "red", borderColor: "red" } }
];
// const updateGraphData = () => {
// const data =
// rightActiveTab.value === "supplyChain" ? singleSanctionEntitySupplyChainData.value : singleSanctionEntityEquityData.value;
// console.log("图谱数据 =>", data);
// if (!data) return;
// const newNodes = [];
// const newLinks = [];
// // 容器尺寸(根据 .right-echarts 的高度 calc(100% - 56px) ≈ 772px)
// const containerWidth = 1000;
// const containerHeight = 700;
// // 中心节点坐标(居中)
// const centerX = containerWidth / 2;
// const centerY = containerHeight / 2;
// // 上下节点分布参数
// const upperY = centerY - 200; // 上方节点 Y 坐标
// const lowerY = centerY + 200; // 下方节点 Y 坐标
// const nodeSpacing = 100; // 节点水平间距
// // 合并所有节点列表(上游 + 下游)
// const allItems = [];
// const parentList = data.parentOrgList || [];
// const childList = data.childrenOrgList || [];
// // 添加上游节点
// parentList.forEach((item, index) => {
// allItems.push({
// ...item,
// linkType: rightActiveTab.value === "supplyChain" ? "供应商" : item.type || "持股",
// direction: "source" // 链接方向:指向中心节点
// });
// });
// // 添加下游节点
// childList.forEach((item, index) => {
// allItems.push({
// ...item,
// linkType: rightActiveTab.value === "supplyChain" ? "客户" : item.description || "投资",
// direction: "target" // 链接方向:从中心节点指出
// });
// });
// // 中心节点
// newNodes.push({
// id: 0,
// name: data.orgName || "中心节点",
// symbol: `image://${companyActive}`,
// symbolSize: 60,
// value: 10,
// isSanctioned: true,
// x: centerX,
// y: centerY
// });
// // 计算上下分配
// const totalNodes = allItems.length;
// const upperCount = Math.ceil(totalNodes / 2); // 向上取整,上方多一个
// const lowerCount = totalNodes - upperCount;
// // 上方节点(前一半)
// const upperItems = allItems.slice(0, upperCount);
// upperItems.forEach((item, index) => {
// const totalWidth = (upperCount - 1) * nodeSpacing;
// const startX = centerX - totalWidth / 2;
// const x = startX + index * nodeSpacing;
// const y = upperY;
// const nodeId = `n-${index}`;
// newNodes.push({
// id: nodeId,
// name: item.name || `节点${index}`,
// symbol: `image://${item.isSanctioned ? companyActive : company}`,
// symbolSize: 40,
// value: 5,
// isSanctioned: item.isSanctioned,
// x,
// y
// });
// // 根据 direction 决定链接方向
// if (item.direction === "source") {
// newLinks.push({
// source: nodeId,
// target: 0,
// name: item.linkType,
// label: { show: true, formatter: item.description }
// });
// } else {
// newLinks.push({
// source: 0,
// target: nodeId,
// name: item.linkType,
// label: { show: true, formatter: item.description }
// });
// }
// });
// // 下方节点(后一半)
// const lowerItems = allItems.slice(upperCount);
// lowerItems.forEach((item, index) => {
// const totalWidth = (lowerCount - 1) * nodeSpacing;
// const startX = centerX - totalWidth / 2;
// const x = startX + index * nodeSpacing;
// const y = lowerY;
// const nodeId = `n-${upperCount + index}`;
// newNodes.push({
// id: nodeId,
// name: item.name || `节点${upperCount + index}`,
// symbol: `image://${item.isSanctioned ? companyActive : company}`,
// symbolSize: 40,
// value: 5,
// isSanctioned: item.isSanctioned,
// x,
// y
// });
// // 根据 direction 决定链接方向
// if (item.direction === "source") {
// newLinks.push({
// source: nodeId,
// target: 0,
// name: item.linkType,
// label: { show: true, formatter: "{c}" }
// });
// } else {
// newLinks.push({
// source: 0,
// target: nodeId,
// name: item.linkType,
// label: { show: true, formatter: "{c}" }
// });
// }
// });
// console.log("最终节点数:", newNodes, "最终链接数:", newLinks, "上方节点:", upperCount, "下方节点:", lowerCount);
// // 更新响应式数据
// nodes.value = newNodes;
// links.value = newLinks;
// };
const updateTreeData = data => { const updateTreeData = data => {
if (!data) return; if (!data) return;
......
...@@ -1317,7 +1317,7 @@ onMounted(async () => { ...@@ -1317,7 +1317,7 @@ onMounted(async () => {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
padding: 30px 0; padding: 30px 0;
.data-origin-icon { .data-origin-icon {
width: 16px; width: 16px;
......
...@@ -1543,7 +1543,7 @@ onBeforeUnmount(() => { ...@@ -1543,7 +1543,7 @@ onBeforeUnmount(() => {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
padding: 35px 0; padding: 35px 0;
.data-origin-icon { .data-origin-icon {
width: 16px; width: 16px;
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
:close-on-click-modal="false" :close-on-click-modal="false"
@close="resetModal" @close="resetModal"
> >
<div class="sanction-list"> <div class="sanction-list" :loading="loading">
<div <div
v-for="item in sanctionList" v-for="item in sanctionList"
:key="item.id" :key="item.id"
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
@click="selectSanction(item)" @click="selectSanction(item)"
> >
{{ item.name }} {{ item.name }}
<div class="sanction-type">{{ item.postDate }}</div>
</div> </div>
</div> </div>
...@@ -193,6 +194,7 @@ const handleAnalysisClick = () => { ...@@ -193,6 +194,7 @@ const handleAnalysisClick = () => {
}; };
// ========== 新增响应式状态 ========== // ========== 新增响应式状态 ==========
const loading = ref(false);
const sanctionModalVisible = ref(false); const sanctionModalVisible = ref(false);
const sanctionList = ref([]); const sanctionList = ref([]);
const selectedSanctionId = ref(null); const selectedSanctionId = ref(null);
...@@ -204,6 +206,7 @@ const totalElements = ref(0); ...@@ -204,6 +206,7 @@ const totalElements = ref(0);
const openSanctionModal = async () => { const openSanctionModal = async () => {
sanctionModalVisible.value = true; sanctionModalVisible.value = true;
console.log("制裁事件列表11:", sanctionList.value); console.log("制裁事件列表11:", sanctionList.value);
loading.value = true;
await fetchSanctionData(); await fetchSanctionData();
}; };
...@@ -211,6 +214,7 @@ const openSanctionModal = async () => { ...@@ -211,6 +214,7 @@ const openSanctionModal = async () => {
const fetchSanctionData = async () => { const fetchSanctionData = async () => {
try { try {
const res = await getSanctionProcess([1], currentPage.value, 10); const res = await getSanctionProcess([1], currentPage.value, 10);
loading.value = false;
if (res && !!res.content) { if (res && !!res.content) {
sanctionList.value = res.content || []; sanctionList.value = res.content || [];
totalElements.value = res.totalElements || 0; totalElements.value = res.totalElements || 0;
...@@ -225,6 +229,7 @@ const fetchSanctionData = async () => { ...@@ -225,6 +229,7 @@ const fetchSanctionData = async () => {
} }
} catch (error) { } catch (error) {
console.error("获取制裁事件失败:", error); console.error("获取制裁事件失败:", error);
loading.value = false;
sanctionList.value = []; sanctionList.value = [];
totalElements.value = 0; totalElements.value = 0;
} }
...@@ -237,7 +242,7 @@ const handlePageChange = async newPage => { ...@@ -237,7 +242,7 @@ const handlePageChange = async newPage => {
}; };
// ========== 选择某项 ========== // ========== 选择某项 ==========
const selectSanction = item => { const selectSanction = async item => {
selectedSanctionId.value = item.id; selectedSanctionId.value = item.id;
router.replace({ router.replace({
path: window.location.pathname, path: window.location.pathname,
...@@ -247,7 +252,10 @@ const selectSanction = item => { ...@@ -247,7 +252,10 @@ const selectSanction = item => {
} }
}); });
sanctionModalVisible.value = false; sanctionModalVisible.value = false;
window.location.reload(); console.log("跳转URL:", window.location.href);
// 根据最新URL参数刷新当前页面
window.open(`${window.location.pathname}?id=${item.id}&sanTypeId=${item.sanTypeId}`, "_self");
}; };
// ========== 关闭弹窗时重置 ========== // ========== 关闭弹窗时重置 ==========
...@@ -467,6 +475,8 @@ onMounted(() => { ...@@ -467,6 +475,8 @@ onMounted(() => {
font-size: 14px; font-size: 14px;
color: #333; color: #333;
transition: background-color 0.2s; transition: background-color 0.2s;
display: flex;
justify-content: space-between;
} }
.sanction-item:hover { .sanction-item:hover {
......
<template>
<div class="entity-list">
<div class="header">
<div class="header-title">
<img :src="headerTitle.img" alt="" />
<div>
<div class="title">
{{ headerTitle.title }}
<!-- <span>{{ headerTitle.titleEn }}</span> -->
</div>
<div class="department">
{{ headerTitle.department }}
</div>
</div>
<!-- <div class="btn">
<img :src="icon01" alt="">切换
</div> -->
</div>
</div>
<div class="main">
<div class="pdf-container">
<iframe v-if="headerTitle.srcUrl" :src="headerTitle.srcUrl" width="100%" height="100%" frameborder="0"></iframe>
<div v-else class="no-pdf">暂无原文</div>
</div>
<div class="pdf-container">
<iframe
v-if="headerTitle.transUrl"
:src="headerTitle.transUrl"
width="100%"
height="100%"
frameborder="0"
></iframe>
<div v-else class="no-pdf">暂无译文</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { getSingleSanctionOverview } from "@/api/exportControlV2.0.js";
import title from "../assets/title.png";
import icon01 from "../assets/icon01.png";
// 单次制裁-制裁概况-基本信息
const singleSanctionOverview = ref({});
const getSingleSanctionOverviewData = async () => {
if (!sanRecordId.value) return;
try {
const res = await getSingleSanctionOverview({
sanRecordId: sanRecordId.value
});
if (res.code === 200) {
singleSanctionOverview.value = res.data || {};
// 格式化日期
let dateStr = "";
if (singleSanctionOverview.value.postDate) {
const date = new Date(singleSanctionOverview.value.postDate);
if (!isNaN(date.getTime())) {
dateStr = `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日`;
} else {
dateStr = singleSanctionOverview.value.postDate;
}
}
// 更新头部信息
headerTitle.value = {
...headerTitle.value,
title: `${dateStr}${singleSanctionOverview.value.sanTitleZh || singleSanctionOverview.value.sanTitle}》`,
titleEn: singleSanctionOverview.value.sanTitle || "",
department: singleSanctionOverview.value.fileCode || "",
srcUrl: singleSanctionOverview.value.srcUrl || "",
transUrl: singleSanctionOverview.value.transUrl || ""
};
}
} catch (error) {
console.error("获取制裁概况失败:", error);
}
};
const headerTitle = ref({
img: title
});
// 获取URL参数
const sanRecordId = ref("");
const getUrlParams = () => {
const urlParams = new URLSearchParams(window.location.search);
sanRecordId.value = urlParams.get("id") || "";
};
onMounted(() => {
getUrlParams();
getSingleSanctionOverviewData();
});
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.entity-list {
width: 100%;
height: 100%;
.header {
width: 100%;
height: 148px;
background-color: #fff;
padding-top: 16px;
.header-title {
width: 1601px;
height: 72px;
background-color: rgba(246, 250, 255, 1);
margin: 0 auto;
border-radius: 10px;
border: 2px solid rgba(174, 214, 255, 1);
display: flex;
align-items: center;
margin-bottom: 12px;
position: relative;
img {
width: 54px;
height: 54px;
margin-left: 15px;
margin-right: 11px;
}
.title {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(59, 65, 75);
span {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
margin-left: 11px;
}
}
.department {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
}
.btn {
cursor: pointer;
display: flex;
align-items: center;
position: absolute;
right: 16px;
top: 25px;
font-size: 18px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(5, 95, 194);
img {
width: 20px;
height: 20px;
margin-right: 7px;
}
}
}
.header-nav {
width: 1601px;
margin: 0 auto;
height: 48px;
display: flex;
align-items: center;
.nav-item {
display: flex;
align-items: center;
height: 100%;
margin-right: 32px;
cursor: pointer;
position: relative;
font-size: 18px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
&:last-child {
margin-right: 0;
}
img {
width: 16px;
height: 16px;
margin-right: 4px;
}
&.active {
color: rgb(5, 95, 194);
font-weight: 700;
}
.active-line {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background-color: #055fc2;
border-radius: 1.5px;
}
}
.original-text-btn {
margin-left: auto;
width: 152px;
height: 36px;
background: #ffffff;
border-radius: 4px;
border: 1px solid rgba(230, 231, 232, 1);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
img {
width: 16px;
height: 16px;
margin-right: 8px;
}
span {
font-size: 16px;
font-weight: 400;
color: rgb(95, 101, 108);
font-family: "Microsoft YaHei";
line-height: 24px;
}
}
}
}
.main {
width: 1601px;
height: calc(100% - 148px);
background-color: #f7f8f9;
margin: 0 auto;
display: flex;
justify-content: space-between;
padding-top: 20px;
box-sizing: border-box;
.pdf-container {
width: 790px;
height: calc(100% - 20px);
background-color: #fff;
// border: 1px solid rgba(174, 214, 255, 1);
border-radius: 4px;
overflow: hidden;
.no-pdf {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #909399;
font-size: 16px;
background-color: #fff;
}
}
}
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论