提交 fe67cdaa authored 作者: 张烨's avatar 张烨

feat:科技政令增政令关系挖掘页面

上级 a9b8232b
......@@ -4,13 +4,13 @@
<AnalysisBox title="相关政令" :showAllBtn="false">
<div class="box1-main">
<el-scrollbar height="100%" always>
<el-empty v-if="siderList.length === 0" style="padding-top: 240px" description="暂无数据" :image-size="100" />
<div class="left-item" :class="{ 'item-active': siderActiveIndex===index }" v-for="(item, index) in siderList" :key="index" @click="handleClickSider(index)">
<el-empty v-if="siderList.length===0" style="padding-top: 240px" description="暂无数据" :image-size="100" />
<div class="left-item" :class="{ 'item-active': siderActive===index }" v-for="(item, index) in siderList" :key="index" @click="handleClickSider(index)">
<div class="item-head">
<div class="itme-name one-line-ellipsis">{{ item.title }}</div>
<div class="itme-name one-line-ellipsis">{{ item.name }}</div>
<div class="item-tag">政令</div>
</div>
<div class="itme-time one-line-ellipsis">{{ item.time }} · {{ "美国白宫" }} </div>
<div class="itme-time one-line-ellipsis">{{ item.year }} · {{ "美国白宫" }} </div>
</div>
</el-scrollbar>
</div>
......@@ -19,7 +19,7 @@
<div class="box2">
<AnalysisBox title="政令关系挖掘" :showAllBtn="false">
<div class="box2-main">
<div ref="containerRef" class="graph-container"></div>
</div>
</AnalysisBox>
</div>
......@@ -27,173 +27,193 @@
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import { ref, onMounted, onBeforeUnmount } from "vue";
import { useRoute } from "vue-router";
import router from "@/router";
import { getDecreeMainContent } from "@/api/decree/introduction";
import * as G6 from '@antv/g6';
import { getDecreeRelateOrder } from "@/api/decree/deepdig";
import box2InfoImg from "./assets/icons/box1-info.png";
const allData = ref([]);
const relateId = ref(0);
import icon1628 from "./assets/icons/icon1628.png";
import icon1629 from "./assets/icons/icon1629.png";
const route = useRoute();
const pageSize = ref(10);
const currentPage = ref(1);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
// 相关政令
const siderList = ref([]);
const siderActive = ref(0);
const handleClickSider = async index => {
siderActive.value = index;
};
const handleGetRelateOrder = async () => {
try {
const res = await getDecreeRelateOrder({id: route.query.id});
console.log("相关政令", res);
if (res.code===200 && res.data?.length) {
siderList.value = res.data
initChart(true)
} else {
siderList.value = []
}
} catch (error) {
siderList.value = [];
}
};
const showList = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return decreeInfo.value.list.slice(startIndex, endIndex);
});
const siderList = ref([
// 政令关系挖掘
const containerRef = ref();
let graphInstance = null;
const onFormatNode = node => {
// 判断文字数量大于8的在第八位后插入换行符
if (node.label.length > 8) {
let str = node.label
node.label = str.substring(0, 8) + "\n" + str.substring(8);
}
}
const onFormatEdge = edge => {
edge.type = "line";
edge.label = ["", "相似", "继承", "冲突"][edge.status];
edge.style = {
stroke: ["", "#B9DCFF", "#87E8DE", "#FFCCC7"][edge.status],
lineWidth: 1,
endArrow: true,
};
edge.labelCfg = {
autoRotate: true,
style: {
fill: ["", "#055FC2", "#13A8A8", "#CE4F51"][edge.status],
fontSize: 10,
fontFamily: 'Microsoft YaHei',
background: {
fill: ["", "#E7F3FF", "#E6FFFB", "#FFE0E0"][edge.status],
padding: [2, 4, 2, 4],
radius: 2
}
}
};
}
let data = {
nodes: [
{
time: "2023年7月25日",
title: "拜登人工智能政令拜登人工智能政令拜登人工智能政令拜登人工智能政令",
"id": "0", "label": "济南鑫银博电子设备有限公司", "img": icon1628, "size": 60, "isCenter": true,
"clipCfg": { "show": true, "type": "circle", "r": 30 },
"style": { "cursor": "pointer" },
"labelCfg": {
"position": "bottom", "offset": 12,
"style": { "fill": "#1459BB", "fontSize": 13, "fontWeight": "bold", "fontFamily": "Microsoft YaHei", "textAlign": "center" }
},
{
time: "2025年7月25日",
title: "特朗普撤销拜登AI规则"
"name": "济南鑫银博电子设备有限公司", "image": "/src/views/decree/decreeLayout/influence/assets/images/company-active.png", "symbolSize": 60, "isSanctioned": true
},
{
time: "2023年7月25日",
title: "美国AI行动计划"
"id": "p-91320508MA1T9M2D39", "label": "苏州市创新产业发展引导基金(有限合伙)", "img": icon1629, "size": 40, "isCenter": false,
"clipCfg": { "show": true, "type": "circle", "r": 20 },
"style": { "cursor": "pointer" },
"labelCfg": {
"position": "bottom", "offset": 12,
"style": { "fill": "#333", "fontSize": 11, "fontWeight": "normal", "fontFamily": "Microsoft YaHei", "textAlign": "center" }
},
{
time: "2024年7月25日",
title: "对中国AI芯片限制"
}
]);
const siderActiveIndex = ref(0);
const handleClickSider = async index => {
siderActiveIndex.value = index;
decreeInfo.value.id = allData.value[index].id;
decreeInfo.value.img = allData.value[index].imageUrl;
decreeInfo.value.totalTitle = allData.value[index].name;
decreeInfo.value.eTotalTitle = allData.value[index].ename;
decreeInfo.value.signTime = allData.value[index].postDate;
decreeInfo.value.signOrg = allData.value[index].proposeOrgName;
relateId.value = allData.value[index].id;
"name": "苏州市创新产业发展引导基金(有限合伙)", "image": icon1629, "symbolSize": 40, "isSanctioned": false },
{ "id": "p-913706826806548758", "label": "烟台广源食品检测服务有限公司", "img": icon1629, "size": 40, "isCenter": false, "clipCfg": { "show": true, "type": "circle", "r": 20 }, "style": { "cursor": "pointer" }, "labelCfg": { "position": "bottom", "offset": 12, "style": { "fill": "#333", "fontSize": 11, "fontWeight": "normal", "fontFamily": "Microsoft YaHei", "textAlign": "center" } }, "name": "烟台广源食品检测服务有限公司", "image": icon1629, "symbolSize": 40, "isSanctioned": false },
{ "id": "p-12450000498506980D", "label": "南宁师范大学", "img": icon1629, "size": 40, "isCenter": false, "clipCfg": { "show": true, "type": "circle", "r": 20 }, "style": { "cursor": "pointer" }, "labelCfg": { "position": "bottom", "offset": 12, "style": { "fill": "#333", "fontSize": 11, "fontWeight": "normal", "fontFamily": "Microsoft YaHei", "textAlign": "center" } }, "name": "南宁师范大学", "image": icon1629, "symbolSize": 40, "isSanctioned": false },
{ "id": "p-914403007755864498", "label": "深圳市华为培训学院有限公司", "img": icon1629, "size": 40, "isCenter": false, "clipCfg": { "show": true, "type": "circle", "r": 20 }, "style": { "cursor": "pointer" }, "labelCfg": { "position": "bottom", "offset": 12, "style": { "fill": "#333", "fontSize": 11, "fontWeight": "normal", "fontFamily": "Microsoft YaHei", "textAlign": "center" } }, "name": "深圳市华为培训学院有限公司", "image": icon1629, "symbolSize": 40, "isSanctioned": false },
{ "id": "p-91230183MAD46C3N7A", "label": "尚志市聚源新能源有限公司", "img": icon1629, "size": 40, "isCenter": false, "clipCfg": { "show": true, "type": "circle", "r": 20 }, "style": { "cursor": "pointer" }, "labelCfg": { "position": "bottom", "offset": 12, "style": { "fill": "#333", "fontSize": 11, "fontWeight": "normal", "fontFamily": "Microsoft YaHei", "textAlign": "center" } }, "name": "尚志市聚源新能源有限公司", "image": icon1629, "symbolSize": 40, "isSanctioned": false },
{ "id": "p-12100000400012211J", "label": "中国科学院高能物理研究所", "img": icon1629, "size": 40, "isCenter": false, "clipCfg": { "show": true, "type": "circle", "r": 20 }, "style": { "cursor": "pointer" }, "labelCfg": { "position": "bottom", "offset": 12, "style": { "fill": "#333", "fontSize": 11, "fontWeight": "normal", "fontFamily": "Microsoft YaHei", "textAlign": "center" } }, "name": "中国科学院高能物理研究所", "image": icon1629, "symbolSize": 40, "isSanctioned": false }
],
edges: [
{ "id": "edge-0", "target": "p-91320508MA1T9M2D39", "source": "0", "status": 1 },
{ "id": "edge-1", "target": "p-913706826806548758", "source": "0", "status": 1 },
{ "id": "edge-2", "target": "p-12450000498506980D", "source": "0", "status": 2 },
{ "id": "edge-3", "target": "p-914403007755864498", "source": "0", "status": 3 },
{ "id": "edge-4", "target": "p-91230183MAD46C3N7A", "source": "0", "status": 2 },
{ "id": "edge-5", "target": "p-12100000400012211J", "source": "0", "status": 3 }
]
}
const initChart = (first=false) => {
const width = containerRef.value.offsetWidth || 800
const height = containerRef.value.offsetHeight || 600
const centerX = width / 2
const centerY = height / 2
const radius = Math.min(width, height) / 2 - 120
const params1 = {
currentPage: 0,
pageSize: 999999,
id: relateId.value
};
try {
const res = await getDecreeMainContent(params1);
console.log("政令主要内容", res);
if (res.code === 200 && res.data) {
decreeInfo.value.list = res.data.content;
} else {
decreeInfo.value.list = [];
data.edges.forEach(onFormatEdge)
data.nodes.forEach(onFormatNode)
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
}
} catch (error) { }
};
const decreeInfo = ref({
id: 0,
img: "",
totalTitle: "",
eTotalTitle: "",
signTime: "",
signOrg: "",
list: [
// {
// content:
// "要求强大AI系统开发者与政府分享安全测试结果(“红队测试”);制定生物合成筛查标准防范风险;建立AI生成内容鉴别标准"
// },
// {
// content: "优先支持隐私保护技术(PET)研发;评估各机构如何收集和使用商业信息;制定评估隐私保护技术有效性的指南。"
// },
// {
// content: "为解决算法歧视提供明确指导;确保刑事司法系统中AI使用的公平性;协调调查和起诉AI相关的民权侵犯行为。"
// }
if (first) {
graphInstance = 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',
]
});
const handleGetRelateOrder = async () => {
const params = {
id: route.query.id
};
try {
const res = await getDecreeRelateOrder(params);
console.log("相关政令关联分析", res);
if (res.code === 200 && res.data) {
allData.value = res.data;
siderList.value = res.data.map(item => {
return {
time: item.year,
title: item.name
};
});
decreeInfo.value.id = allData.value[0].id;
decreeInfo.value.img = allData.value[0].imageUrl;
decreeInfo.value.totalTitle = allData.value[0].name;
decreeInfo.value.eTotalTitle = allData.value[0].ename;
decreeInfo.value.signTime = allData.value[0].postDate;
decreeInfo.value.signOrg = allData.value[0].proposeOrgName;
relateId.value = allData.value[0].id;
const params1 = {
currentPage: 0,
pageSize: 999999,
id: relateId.value
};
try {
const res = await getDecreeMainContent(params1);
console.log("政令主要内容", res);
if (res.code === 200 && res.data) {
decreeInfo.value.list = res.data.content;
} else {
decreeInfo.value.list = [];
}
} catch (error) { }
} else {
allData.value = [];
// siderList.value = [];
decreeInfo.value.id = 0;
decreeInfo.value.img = "";
decreeInfo.value.totalTitle = "";
decreeInfo.value.eTotalTitle = "";
decreeInfo.value.signTime = "";
decreeInfo.value.signOrg = "";
decreeInfo.value.list = [];
},
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
},
}
} catch (error) {
allData.value = [];
siderList.value = [];
decreeInfo.value.id = 0;
decreeInfo.value.img = "";
decreeInfo.value.totalTitle = "";
decreeInfo.value.eTotalTitle = "";
decreeInfo.value.signTime = "";
decreeInfo.value.signOrg = "";
decreeInfo.value.list = [];
}
};
},
})
const handleToDecreeDetail = item => {
console.log("item", item.id);
window.sessionStorage.setItem("curTabName", item.totalTitle);
const route = router.resolve({
path: "/decreeLayout/overview/introduction",
query: {
id: item.id
}
// 节点点击处理
graphInstance.on('node:click', (evt) => {
console.log('节点详情:', evt.item);
});
window.open(route.href, "_blank");
};
}
graphInstance.data(data)
graphInstance.render()
}
onMounted(() => {
handleGetRelateOrder();
});
onBeforeUnmount(() => {
graphInstance?.destroy()
})
</script>
<style lang="scss" scoped>
......@@ -279,6 +299,14 @@ onMounted(() => {
.box2 {
width: 20px;
flex: auto;
.box2-main {
height: 100%;
padding: 10px;
.graph-container {
width: 100%;
height: 600px;
}
}
}
}
......
......@@ -5,7 +5,7 @@
<div class="hintWrap">
<div class="icon1"></div>
<div class="title">
2025年实体清单制裁范围扩大至芯片制造环节,为中国的芯片制造能力划定“技术天花板”,阻止其向更先进水平发展。制裁范围向上游设备和材料、下游先进封装以及关键工具(如EDA软件)延伸,意图瓦解中国构建自主可控产业链的努力
这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战
</div>
<div class="icon2Wrap">
<div class="icon2"></div>
......@@ -14,20 +14,20 @@
<div class="right-main-content-main">
<div class="fishbone-wrapper">
<div class="fishbone-scroll-container" ref="scrollContainerRef">
<div class="fishbone" ref="fishboneRef" v-if="fishboneDataList.length > 0">
<div class="main-line" :style="{ width: fishboneDataList.length * 200 + 300 + 'px' }">
<div class="fishbone" v-if="dataList.length > 0">
<div class="main-line" :style="{ width: dataList.length * 200 + 300 + 'px' }">
<!-- 主轴上的标签 -->
<div class="main-line-text" v-for="(item, index) in mainLineLabels" :key="'label-' + index"
<div class="main-line-text" v-for="(item, index) in dataList" :key="'label-' + index"
:class="{
'blue-theme': index < 2,
'green-theme': index >= 2 && index < 4,
'purple-theme': index >= 4
}" :style="{ left: index * 200 + 220 + 'px' }">
{{ item }}
{{ item.text }}
</div>
</div>
<!-- 奇数索引的数据组放在上方 -->
<div v-for="(causeGroup, groupIndex) in getOddGroups(fishboneDataList)" :key="'top-' + groupIndex"
<div v-for="(causeGroup, groupIndex) in onFilterData(1)" :key="'top-' + groupIndex"
:class="getTopBoneClass(groupIndex)" :style="{ left: groupIndex * 400 + 420 + 'px' }">
<div class="left-bone">
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="'left-' + index">
......@@ -46,7 +46,7 @@
</div>
<!-- 偶数索引的数据组放在下方 -->
<div v-for="(causeGroup, groupIndex) in getEvenGroups(fishboneDataList)" :key="'bottom-' + groupIndex"
<div v-for="(causeGroup, groupIndex) in onFilterData(0)" :key="'bottom-' + groupIndex"
:class="getBottomBoneClass(groupIndex)" :style="{ left: groupIndex * 400 + 220 + 'px' }">
<div class="left-bone">
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="'left-bottom-' + index">
......@@ -157,11 +157,32 @@ const getCnEntityOnChainData = async () => {
}
}
// 实体清单-深度挖掘-产业链鱼骨图信息
const fishboneDataList = ref([]);
const mainLineLabels = ref([]);
// 产业链鱼骨数据
const dataList = ref([]);
// 奇数索引的数据组放在上方, 偶数索引的数据组放在下方
const onFilterData = (num) => {
return dataList.value.filter((_, index) => index % 2 === num);
};
// 获取左侧显示的项目(前半部分)
const getLeftItems = items => {
const midpoint = Math.ceil(items.length / 2);
return items.slice(0, midpoint);
};
// 获取右侧显示的项目(后半部分)
const getRightItems = items => {
const midpoint = Math.ceil(items.length / 2);
return items.slice(midpoint);
};
// 获取上方鱼骨图位置类名
const getTopBoneClass = index => {
const positions = ["top-bone", "top-bone1", "top-bone2"];
return positions[index % 3] || "top-bone";
};
// 获取下方鱼骨图位置类名
const getBottomBoneClass = index => {
const positions = ["bottom-bone", "bottom-bone1", "bottom-bone2"];
return positions[index % 3] || "bottom-bone";
};
const getFishboneData = async () => {
const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value);
const date = currentSanction ? currentSanction.date : '';
......@@ -177,51 +198,34 @@ const getFishboneData = async () => {
try {
const res = await getDeepMiningIndustryFishbone(params);
console.log("获取产业链数据:", res);
if (res.code === 200 && res.data && res.data.causes && res.data.causes.length > 0) {
const rootCauses = res.data.causes;
if (rootCauses.length > 0 && rootCauses[0].causes) {
fishboneDataList.value = rootCauses.map(group => {
return {
causes: group.causes || []
};
});
mainLineLabels.value = rootCauses.map(group => group.text || '');
} else {
fishboneDataList.value = [];
mainLineLabels.value = [];
}
if (res.code === 200 && res.data?.causes?.length) {
dataList.value = res.data.causes;
} else {
fishboneDataList.value = [];
mainLineLabels.value = [];
dataList.value = [];
}
} catch (error) {
console.error("获取产业链鱼骨图数据失败:", error);
fishboneDataList.value = [];
dataList.value = [];
}
}
// 实体清单-深度挖掘-产业链列表信息
const industryList = ref([]);
const selectedIndustryId = ref(null);
const getIndustryList = async () => {
try {
const res = await getDeepMiningIndustry();
if (res.code === 200 && res.data && res.data.length > 0) {
industryList.value = res.data;
selectedIndustryId.value = res.data[0].id;
getFishboneData();
getCnEntityOnChainData();
} else {
industryList.value = [];
selectedIndustryId.value = null;
}
} catch (error) {
console.error("获取产业链列表数据失败:", error);
industryList.value = [];
selectedIndustryId.value = null;
}
}
......@@ -283,40 +287,6 @@ const sanctionList = ref([
]);
const currentSanctionId = ref(5);
// 获取奇数索引的数据组(放在上方)
const getOddGroups = data => {
return data.filter((_, index) => index % 2 !== 0);
};
// 获取偶数索引的数据组(放在下方)
const getEvenGroups = data => {
return data.filter((_, index) => index % 2 === 0);
};
// 获取上方鱼骨图位置类名
const getTopBoneClass = index => {
const positions = ["top-bone", "top-bone1", "top-bone2"];
return positions[index % 3] || "top-bone";
};
// 获取下方鱼骨图位置类名
const getBottomBoneClass = index => {
const positions = ["bottom-bone", "bottom-bone1", "bottom-bone2"];
return positions[index % 3] || "bottom-bone";
};
// 获取左侧显示的项目(前半部分)
const getLeftItems = items => {
const midpoint = Math.ceil(items.length / 2);
return items.slice(0, midpoint);
};
// 获取右侧显示的项目(后半部分)
const getRightItems = items => {
const midpoint = Math.ceil(items.length / 2);
return items.slice(midpoint);
};
// 格式化比率
const formatRate = (rate, ratio=false) => {
if (!rate) return '0.00';
......@@ -454,7 +424,6 @@ onMounted(() => {
left: 0;
width: 100%;
height: 100%;
// background: repeating-linear-gradient(to right, rgba(174, 208, 255, 1) 0, rgba(174, 208, 255, 1) 10px, transparent 10px, transparent 20px);
}
// 添加中间的文字块
......
<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)">
<!-- 这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。 -->
<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 || echartsIcon03" 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>
<p class="desc">{{ selectedNode.description || '暂无描述' }}</p>
</div>
</div>
</div>
</template>
......@@ -56,7 +38,6 @@ 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: '力导向布局' },
......@@ -594,12 +575,11 @@ const bindGraphEvents = () => {
graphInstance.value.on('node:click', (evt) => {
const node = evt.item
const model = node.getModel()
selectedNode.value = model
emit('nodeClick', model)
})
graphInstance.value.on('canvas:click', () => {
selectedNode.value = null
})
}
......@@ -713,84 +693,4 @@ defineExpose({
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>
\ No newline at end of file
......@@ -93,12 +93,12 @@
</template>
<script setup>
import { ref, computed, watch, onMounted } from "vue";
import { ref, onMounted } from "vue";
import setChart from "@/utils/setChart";
import { Search } from '@element-plus/icons-vue'
import getBarChart from "./utils/barChart";
import { getDecreeIndustry, getDecreehylyList, getDecreeCompany, getDecreeAction } from "@/api/decree/influence";
import { getCnEntityOnChain, getChainFishbone, getChainInfoByDomainId, getChainStructure } from "@/api/exportControl";
import { getDecreeIndustry, getDecreehylyList, getDecreeCompany } from "@/api/decree/influence";
import { getCnEntityOnChain, getChainInfoByDomainId } from "@/api/exportControl";
import { getSingleSanctionEntitySupplyChain } from "@/api/exportControlV2.0";
import ChartChain from "./ChartChain.vue";
import ChartRelation from "./ChartRelation.vue";
......
......@@ -77,83 +77,7 @@
</div>
</div>
</div>
</AnalysisBox>
<!-- <div class="box-header">
<div class="header-left">
</div>
<div class="title">基本信息</div>
<div class="header-right">
<div class="icon">
<img src="../assets/icons/header-right-icon1.png" alt="" />
</div>
<div class="icon">
<img src="../assets/icons/header-right-icon2.png" alt="" />
</div>
</div>
</div>
<div class="box1-main">
<div class="box1-main-left" v-if="basicInfo.img">
<img :src="basicInfo.img" alt="" />
</div>
<div v-else class="box1-main-left-img-mock">
<img class="img-mock-badge-img" src="./assets/images/badge.png">
<p class="img-mock-badge-title">{{basicInfo.eName }}</p>
<p class="img-mock-badge-org">The White House</p>
</div>
<div class="box1-main-right">
<div class="item">
<div class="item-left">{{ "政令全称:" }}</div>
<div class="item-right">{{ basicInfo.name }}</div>
</div>
<div class="item">
<div class="item-left">{{ "英文全称:" }}</div>
<div class="item-right text" v-if="basicInfo.eName?.length < 60">
{{ basicInfo.eName }}
</div>
<el-popover v-else effect="dark" :width="500" :content="basicInfo.eName"
placement="top-start">
<template #reference>
<div class="item-right text">
{{ basicInfo.eName }}
</div>
</template>
</el-popover>
</div>
<div class="item">
<div class="item-left">{{ "相关领域:" }}</div>
<div class="item-right tag-box">
<div class="tag" v-for="(area, index) in basicInfo.areaList" :key="index">
{{ area.industryName }}
</div>
</div>
</div>
<div class="item">
<div class="item-left">{{ "签署时间:" }}</div>
<div class="item-right text">{{ basicInfo.signTime }}</div>
</div>
<div class="item">
<div class="item-left">{{ "发布机构:" }}</div>
<div class="item-right text">
{{ basicInfo.proposeOrgName }}
</div>
</div>
<div class="item">
<div class="item-left">{{ "政令编号:" }}</div>
<div class="item-right text">
{{ basicInfo.bh }}
</div>
</div>
<div class="item">
<div class="item-left">{{ "执行期限:" }}</div>
<div class="item-right text">
{{ basicInfo.deadline + " 天" }}
</div>
</div>
</div>
</div> -->
</div>
<div class="box2">
<!-- <AnalysisBox title="主要指令" :showAllBtn="false">
......@@ -245,66 +169,6 @@
</div>
</div>
</AnalysisBox>
<!-- <div class="box-header">
<div class="header-left"></div>
<div class="title">发布机构</div>
<div class="header-right">
<div class="icon">
<img src="../assets/icons/header-right-icon1.png" alt="" />
</div>
<div class="icon">
<img src="../assets/icons/header-right-icon2.png" alt="" />
</div>
</div>
</div>
<div class="box3-top">
<div class="box3-top-top" @click="handleToInstitution(box3TopTopData)">
<div class="left">
<img :src="box3TopTopData.logo ? box3TopTopData.logo : DefaultIcon2" alt="" />
</div>
<div class="right">
<div class="name">{{ box3TopTopData.name + " >" }}</div>
<div class="ename">{{ box3TopTopData.eName }}</div>
</div>
</div>
<div class="box3-top-bottom">
<div class="box3-top-bottom-header">
<div class="icon">
<img src="./assets/images/box3-icon1.png" alt="" />
</div>
<div class="text">{{ "关键人物" }}</div>
</div>
<div class="box3-top-bottom-main">
<div class="box3-top-bottom-item" v-for="(item, index) in box3TopBottomData" :key="index">
<div class="box3-top-bottom-item-left">
<img :src="item.avatar ? item.avatar : DefaultIcon1" alt="" />
</div>
<div class="box3-top-bottom-item-right">
<div class="name">{{ item.name }}</div>
<div class="position">{{ item.job }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="box3-bottom">
<div class="box3-bottom-header">
<div class="header-icon">
<img src="./assets/images/box3-bottom-header-icon.png" alt="" />
</div>
<div class="header-title">{{ "机构动态" }}</div>
</div>
<div class="box3-bottom-main">
<el-timeline style="max-width: 500px">
<el-timeline-item :timestamp="item.newsDate" placement="top"
v-for="(item, index) in eventList?.slice(0, 3)" :key="index">
<div class="timeline-content">
{{ item.newsContent }}
</div>
</el-timeline-item>
</el-timeline>
</div>
</div> -->
</div>
</div>
</div>
......@@ -584,97 +448,17 @@ onMounted(() => {
}
.introduction-wrap {
display: flex;
.box-header {
height: 56px;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
line-height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
}
.header-btn-box {
position: absolute;
z-index: 9999;
width: 325px;
height: 64px;
top: 14px;
right: 82px;
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
gap: 5px 8px;
white-space: nowrap;
overflow: hidden;
overflow-y: auto;
padding-right: 5px;
.btn {
min-width: min-content;
height: 28px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 28px;
cursor: pointer;
}
.btnActive {
border: 1px solid var(--color-main-active);
color: var(--color-main-active);
background: rgba(231, 243, 255, 1);
}
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
height: 28px;
display: flex;
gap: 4px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
width: 1600px;
height: 100%;
}
}
}
}
display: flex;
gap: 16px;
.left {
width: 1064px;
width: 20px;
flex: auto;
.box1 {
margin-top: 16px;
width: 1064px;
height: 414px;
.box1-main {
......@@ -775,72 +559,12 @@ onMounted(() => {
.box2 {
margin-top: 16px;
width: 1064px;
height: 330px;
// .box2-main {
// margin-left: 22px;
// height: 280px;
// overflow: hidden;
// overflow-y: auto;
// .box2-item {
// width: 1015px;
// // height: 48px;
// margin-bottom: 8px;
// box-sizing: border-box;
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 2px;
// background: rgba(255, 255, 255, 1);
// display: flex;
// align-items: center;
// padding: 12px 0;
// &:nth-child(2n-1) {
// background: rgba(247, 248, 249, 1);
// }
// .id {
// margin-left: 15px;
// width: 24px;
// height: 24px;
// text-align: center;
// line-height: 24px;
// border-radius: 12px;
// background: #e7f3ff;
// color: #0a57a6;
// }
// .title {
// width: 1020px;
// line-height: 24px;
// margin-left: 10px;
// color: rgba(59, 65, 75, 1);
// font-family: Microsoft YaHei;
// font-size: 16px;
// font-weight: 700;
// // overflow: hidden;
// // text-overflow: ellipsis;
// // white-space: nowrap;
// }
// .open {
// width: 16px;
// height: 16px;
// margin-top: 16px;
// img {
// width: 100%;
// height: 100%;
// }
// }
// }
// }
.box2-main {
margin-top: 3px;
margin-left: 31px;
height: 330px;
height: 100%;
width: 1004px;
overflow: hidden;
overflow-y: auto;
......@@ -934,7 +658,6 @@ onMounted(() => {
.right {
width: 520px;
margin-left: 16px;
.box3 {
margin-top: 16px;
......
......@@ -2,22 +2,19 @@
<div class="layout-container">
<!-- 导航菜单 -->
<div class="layout-main">
<div class="layout-main-box">
<div class="layout-main-header">
<div class="layout-main-header-left-box">
<div class="left-box-top">
<div class="icon">
<img :src="summaryInfo.imageUrl" alt="" />
</div>
<div class="info">
<div class="info-box1">{{ summaryInfo.name }}</div>
<div class="info-box1 one-line-ellipsis">{{ summaryInfo.name }}</div>
<div class="info-box2">
<div class="info-box2-item item1">{{ summaryInfo.order }}</div>
<div class="info-box2-item">{{ summaryInfo.order }}</div>
|
<div class="info-box2-item item2">{{ summaryInfo.type }}</div>
<div class="info-box2-item">{{ summaryInfo.type }}</div>
|
<div class="info-box2-item item3">{{ summaryInfo.ename }}</div>
</div>
</div>
<div class="info-box2-item one-line-ellipsis">{{ summaryInfo.ename }}</div>
</div>
</div>
<div class="layout-main-header-right-box">
......@@ -27,22 +24,33 @@
</div>
</div>
</div>
</div>
<div class="layout-main-center">
<div class="report-box">
<div class="report-header">
{{ "政令原文" }}
<div class="report-title">政令原文</div>
<el-switch v-model="isHighlight" />
<div style="margin-left: 6px; margin-right: 10px;">高亮实体</div>
<el-switch v-model="isTranslate" />
<div style="margin-left: 6px;">原文显示</div>
<div class="btn" @click="handleDownload">
<el-icon><Document /></el-icon>
<div class="text">下载</div>
</div>
<div class="btn" @click="handleFindWord">
<el-icon><Search /></el-icon>
<div class="text">查找</div>
</div>
</div>
<div class="report-main">
<div v-if="!reportData.length" class="noContent">{{ "暂无数据" }}</div>
<template v-else>
<div v-for="(item, index) in reportData" :key="index" class="content-row">
<!-- 左侧:英文 -->
<div class="content-en">{{ item.contentEn }}</div>
<el-scrollbar height="100%" v-else>
<div v-for="(item, index) in reportData" :key="index" :class="['content-row', {'high-light':isHighlight}]">
<!-- 右侧:中文 -->
<div class="content-cn">{{ item.content }}</div>
</div>
</template>
<div class="content-cn" v-html="item.content"></div>
<!-- 左侧:英文 -->
<div class="content-en" v-html="item.contentEn" v-if="isTranslate"></div>
</div>
</el-scrollbar>
</div>
</div>
</div>
......@@ -61,6 +69,16 @@ const reportData = ref([]);
const summaryInfo = ref({});
// 政令原文操作
const isHighlight = ref(true);
const isTranslate = ref(true);
const handleDownload = () => {
}
const handleFindWord = () => {
}
// 获取全局信息
const handleGetSummary = async () => {
const params = {
......@@ -87,6 +105,12 @@ const handleGetReport = async () => {
if (res.code === 200 && res.data) {
// 假设后端返回的是数组格式,如果返回的是对象包含数组,请改为 res.data.list
reportData.value = res.data || [];
let test = {
content: "设立第二紧急委员会(以下简称“委员会”)。自2026年1月16日美国东部<span>标准时间</span>凌晨12:01起,成立一个由一名主席和两名成员组成的<span>委员会</span>,所有成员均由总统任命,负责调查并报告这些争议。任何成员均不得在任何铁路员工组织或任何铁路承运人中拥有经济或其他利益关系。委员会的运作取决于资金的可用性。",
contentEn: "Establishment of a Second <span>Emergency</span> Board (Board). There is established, effective 12:01 a.m. eastern standard time on <span>January 16</span>, 2026, a Board composed of a chair and two other members, all of whom shall be appointed by the President to investigate and report on these disputes. No member shall be pecuniarily or otherwise interested in any organization of railroad employees or any carrier. The Board shall perform its functions subject to the availability of funds.",
num: 0,
}
reportData.value.unshift(test);
}
} catch (error) { }
};
......@@ -98,37 +122,34 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
.high-light {
:deep(span) {
color: #055FC2;
}
}
.layout-container {
width: 100%;
height: 100%;
overflow: hidden;
overflow-y: auto;
background-color: #F7F8F9;
.layout-main {
width: 100%;
.layout-main-header {
height: 120px;
background: rgba(255, 255, 255, 1);
height: 100%;
display: flex;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 99999999;
box-sizing: border-box;
flex-direction: column;
align-items: center;
.layout-main-box {
padding: 16px 0;
width: 100%;
border-bottom: 1px solid rgba(234, 236, 238, 1);
border-top: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.layout-main-header-left-box {
width: 1100px;
margin-left: 160px;
margin-top: 13px;
.left-box-top {
height: 64px;
}
.layout-main-header {
width: 1600px;
display: flex;
align-items: center;
margin: 0 auto;
.icon {
width: 122px;
......@@ -143,11 +164,12 @@ onMounted(() => {
}
.info {
width: 700px;
margin-left: 9px;
margin-left: 16px;
margin-right: 40px;
width: 20px;
flex: auto;
.info-box1 {
width: 700px;
width: 100%;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
......@@ -156,9 +178,6 @@ onMounted(() => {
letter-spacing: 0px;
text-align: left;
margin-top: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.info-box2 {
......@@ -173,89 +192,20 @@ onMounted(() => {
letter-spacing: 0px;
text-align: left;
display: flex;
margin-left: -10px;
.info-box2-item {
padding: 0 10px;
}
.item1 {
width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item2 {
width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item3 {
width: 420px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 10px;
}
}
}
}
.left-box-bottom {
display: flex;
height: 40px;
margin-top: 21px;
.left-box-bottom-item {
display: flex;
margin-right: 32px;
margin-top: 3px;
height: 35px;
cursor: pointer;
.icon {
margin-top: 4px;
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.name {
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
margin-left: 3px;
}
.nameActive {
color: var(--color-main-active);
font-weight: 700;
}
}
.leftBoxBottomItemActive {
border-bottom: 3px solid var(--color-main-active);
.info-box2-item:first-child {
padding-left: 0px;
}
}
}
.layout-main-header-right-box {
width: 300px;
margin-right: 150px;
margin-top: 19px;
.right-box-top {
white-space: nowrap;
.time {
height: 24px;
line-height: 24px;
......@@ -280,17 +230,36 @@ onMounted(() => {
text-align: right;
}
}
}
}
.right-box-bottom {
margin-top: 24px;
text-align: right;
.layout-main-center {
width: 1600px;
background-color: white;
padding: 0 60px;
height: 20px;
flex: auto;
display: flex;
justify-content: flex-end;
gap: 8px;
flex-direction: column;
.report-header {
height: 80px;
display: flex;
align-items: center;
border-bottom: solid 1px rgba(234, 236, 238, 1);
margin: 0 20px 10px;
.report-title {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
line-height: 20px;
font-weight: 700;
width: 20px;
flex: auto;
}
.btn {
width: 120px;
height: 36px;
margin-left: 10px;
width: 88px;
height: 32px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 6px;
......@@ -300,97 +269,25 @@ onMounted(() => {
gap: 8px;
align-items: center;
cursor: pointer;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 64px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-size: 14px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
.btn-active {
width: 120px;
height: 36px;
border-radius: 6px;
background: var(--color-main-active);
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
cursor: pointer;
.icon-active {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text-active {
width: 64px;
height: 24px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: center;
}
}
}
}
}
.layout-main-center {
.report-box {
margin: 0 auto;
width: 1600px;
height: 926px;
background: rgba(248, 249, 250, 1);
.report-header {
height: 80px;
line-height: 80px;
padding-left: 69px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 20px;
font-weight: 700;
letter-spacing: 0px;
text-align: left;
}
.report-main {
width: 1600px;
margin-top: 24px;
background: #fff;
height: 20px;
flex: auto;
box-sizing: border-box;
padding: 24px 69px;
height: calc(100% - 100px);
overflow-y: auto; // 改为单一滚动容器,天然同步
overflow-y: auto;
// 滚动条样式
&::-webkit-scrollbar {
......@@ -421,9 +318,9 @@ onMounted(() => {
.content-row {
display: flex;
width: 100%;
padding: 0 20px;
min-height: 100px;
// border-bottom: 1px solid rgba(234, 236, 238, 1);
align-items: stretch; // 默认stretch,确保左右等高,头部对齐
gap: 80px;
&:last-child {
border-bottom: none;
......@@ -431,24 +328,35 @@ onMounted(() => {
.content-en,
.content-cn {
width: 800px;
padding: 24px 30px;
width: 50%;
flex: auto;
padding: 24px 0;
box-sizing: border-box;
font-size: 16px;
line-height: 1.8;
color: rgba(59, 65, 75, 1);
color: #3B414B;
font-family: Microsoft YaHei;
text-align: justify;
white-space: pre-wrap; // 保留换行格式
}
.content-en {
// border-right: 1px solid rgba(234, 236, 238, 1); // 中间分隔线
}
}
}
}
}
}
// 修改element-plus滚动条样式
:deep(.el-scrollbar__bar.is-vertical) {
right: 0px;
width: 4px;
background: transparent;
border-radius: 2px;
&>div {
background: #c5c7c9;
opacity: 1;
}
&>div:hover {
background: #505357;
}
}
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论