提交 7bc0a1ee authored 作者: 张烨's avatar 张烨

fix:政令模块细节优化

上级 3042de15
......@@ -5,10 +5,11 @@
</template>
<script setup>
import { onMounted, nextTick } from 'vue';
import { onMounted, onBeforeUnmount } from 'vue';
import setChart from '@/utils/setChart';
import getGraphChart from './graphChart';
const emits = defineEmits(["handleClickNode"])
const props = defineProps({
nodes: {
type: Array,
......@@ -32,11 +33,17 @@ const props = defineProps({
}
})
let chart = null
onMounted(() => {
const graph = getGraphChart(props.nodes, props.links, props.layoutType)
setChart(graph, 'graph')
chart = setChart(graph, 'graph')
chart.on("click", (event) => { emits("handleClickNode", event) })
})
onBeforeUnmount(() => {
chart.off("click")
chart.dispose()
})
</script>
......
......@@ -6,14 +6,6 @@
<div class="home-main-header-center">
<SearchContainer style="margin-bottom: 0; margin-top: 48px; height: fit-content" v-if="containerRef"
placeholder="搜索政令" :containerRef="containerRef" areaName="政令" />
<!-- <el-input v-model="searchDecreeText" @keyup.enter="handleSearch" style="width: 838px; height: 100%"
placeholder="搜索政令" />
<div class="search">
<div class="search-icon">
<img src="./assets/images/search-icon.png" alt="" />
</div>
<div class="search-text" @click="handleSearch">搜索</div>
</div> -->
</div>
<!-- <div class="home-main-header-footer" v-show="!isShow">
<div class="home-main-header-footer-item">
......@@ -37,32 +29,13 @@
<div class="item-footer">分析报告</div>
</div>
</div> -->
<!-- <div class="home-main-header-btn-box" v-show="!isShow">
<div class="btn" @click="handleToPosi('position1')">
<div class="btn-text">{{ "最新动态" }}</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt="" />
</div>
</div>
<div class="btn" @click="handleToPosi('position2')">
<div class="btn-text">{{ "资讯要闻" }}</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt="" />
</div>
</div>
<div class="btn" @click="handleToPosi('position3')">
<div class="btn-text">{{ "数据总览" }}</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt="" />
</div>
<div class="date-box" v-if="govInsList.length">
<div class="date-icon">
<img :src="tipsTcon" alt="">
</div>
<div class="btn" @click="handleToPosi('position4')">
<div class="btn-text">{{ "资源库" }}</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt="" />
<div class="date-text">近期美国各联邦政府机构发布涉华政令数量汇总</div>
<TimeTabPane @time-click="handleTimeClick" />
</div>
</div>
</div> -->
<div class="home-main-header-item-box" v-if="govInsList.length">
<div class="item" v-for="(item, index) in govInsList.slice(0, 7)" :key="index" @click="handleToInstitution(item)">
<div class="item-left">
......@@ -396,6 +369,7 @@ import { onMounted, ref, watch, nextTick, reactive } from "vue";
import router from "@/router";
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
import SimplePagination from "@/components/SimplePagination.vue";
import TimeTabPane from '@/components/base/TimeTabPane/index.vue'
import {
getDepartmentList,
getLatestDecree,
......@@ -418,21 +392,9 @@ import getPieChart from "./utils/piechart";
import setChart from "@/utils/setChart";
import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import tipsTcon from "./assets/images/tips-icon.png";
import { ElMessage } from "element-plus";
// 跳转行政机构主页
const handleToInstitution = item => {
window.sessionStorage.setItem("curTabName", item.orgName);
const curRoute = router.resolve({
path: "/institution",
query: {
id: item.orgId
}
});
window.open(curRoute.href, "_blank");
};
const containerRef = ref(null);
const { isShow } = useContainerScroll(containerRef);
const currentPage = ref(1);
......@@ -444,10 +406,9 @@ const handleCurrentChange = page => {
handleGetDecreeOrderList();
};
// 页面 header
// 机构列表
const govInsList = ref([]);
const checkedGovIns = ref([]);
const handleGetDepartmentList = async () => {
try {
const res = await getDepartmentList();
......@@ -459,7 +420,20 @@ const handleGetDepartmentList = async () => {
console.error("获取机构列表error", error);
}
};
handleGetDepartmentList();
const handleTimeClick = (time) => {
console.log("time", time);
}
// 跳转行政机构主页
const handleToInstitution = item => {
window.sessionStorage.setItem("curTabName", item.orgName);
const curRoute = router.resolve({
path: "/institution",
query: {
id: item.orgId
}
});
window.open(curRoute.href, "_blank");
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
......@@ -486,16 +460,6 @@ const box1DataList = ref([
}
]);
// const curBox1Data = ref({
// id: 89,
// name: "",
// postDate: "",
// describe: null,
// imageUrl: null,
// officialUrl: null,
// industryList: null
// });
const handleGetLatestDecree = async () => {
try {
const res = await getLatestDecree();
......@@ -1131,6 +1095,7 @@ const handleSearch = () => {
};
onMounted(async () => {
handleGetDepartmentList();
handleGetNews();
handleGetDecreeTypeList();
handleGetAreaList();
......@@ -1389,8 +1354,34 @@ onMounted(async () => {
}
}
.date-box {
display: flex;
align-items: center;
width: 1600px;
margin-top: 48px;
.date-icon {
width: 16px;
height: 16px;
font-size: 0px;
margin-right: 6px;
img {
width: 100%;
height: 100%;
}
}
.date-text {
width: 20px;
flex: auto;
font-size: 18px;
line-height: 18px;
font-family: Source Han Sans CN;
color: var(--text-primary-80-color);
}
}
.home-main-header-item-box {
margin: 48px 0 64px;
margin: 20px 0 64px;
width: 1600px;
display: flex;
flex-wrap: wrap;
......
......@@ -3,7 +3,7 @@
<div class="box1">
<AnalysisBox title="相关政令" :showAllBtn="false">
<div class="box1-main">
<el-empty v-if="!siderList?.length" style="padding-top: 40%;" description="暂无数据" :image-size="100" />
<el-empty v-if="!siderList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<el-scrollbar height="100%" always>
<div class="left-item" :class="{ 'item-active': false }" v-for="(item, index) in siderList" :key="index" @click="handleClickDecree(item)">
<div class="item-head">
......@@ -18,9 +18,9 @@
</div>
<div class="box2">
<AnalysisBox title="政令关系挖掘" :showAllBtn="false">
<el-empty v-if="!siderList?.length" style="padding-top: 20%;" description="暂无数据" :image-size="100" />
<div class="box2-main">
<div ref="containerRef" class="graph-container"></div>
<el-empty v-if="!siderList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<div class="box2-main" v-if="graphData.nodes?.length">
<GraphChart :nodes="graphData.nodes" :links="graphData.links" layoutType="force" @handleClickNode="handleClickNode" />
</div>
</AnalysisBox>
</div>
......@@ -48,12 +48,13 @@
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
import { ref, onMounted, onBeforeUnmount, reactive } from "vue";
import { useRoute } from "vue-router";
import router from "@/router";
import * as G6 from '@antv/g6';
import { getDecreeRelatedOrder } from "@/api/decree/deepdig";
import { getDecreeSummary } from "@/api/decree/introduction";
import GraphChart from "@/components/base/GraphChart/index.vue";
import icon1628 from "./assets/icons/icon1628.png";
import icon1629 from "./assets/icons/icon1629.png";
......@@ -64,7 +65,7 @@ const route = useRoute();
const dialogVisible = ref(false);
// 基本信息
const mainInfo = ref({});
const mainInfo = ref({ label: "", time: "", id: "" });
const nodeInfo = ref({});
const onDecreeSummaryData = async () => {
try {
......@@ -74,10 +75,9 @@ const onDecreeSummaryData = async () => {
mainInfo.value.label = res.data.name;
mainInfo.value.time = res.data.postDate;
mainInfo.value.id = route.query.id;
mainInfo.value.isCenter = true
}
} catch (error) {
mainInfo.value = {};
mainInfo.value = { label: "", time: "", id: "" };
console.log("获取基本信息数据失败:", error);
}
};
......@@ -109,145 +109,64 @@ const handleGetRelateOrder = async () => {
};
// 政令关系挖掘
const containerRef = ref();
let graphInstance = null;
// 文本插入换行符
const onWordWrap = (word, num) => {
const list = word.split('');
let label = "";
for (let i = 0; i < list.length; i++) {
if (i % num === 0 && i !== 0) {
label += "\n";
const graphData = reactive({
nodes: [],
links: [],
})
// 节点点击处理
const handleClickNode = ({data}) => {
if (data.target) {
let node = siderList.value.find(item => item.id==data.target)
if (node) handleClickSider(node)
} else {
let node = siderList.value.find(item => item.id==data.id)
if (node) handleClickDecree(node)
}
label += list[i];
}
const initGraphChart = () => {
Promise.all([onDecreeSummaryData(), handleGetRelateOrder()]).then(() => {
if (mainInfo.value.id && siderList.value.length) {
graphData.links = siderList.value.map(onFormatLink)
graphData.nodes = siderList.value.map(onFormatNode)
graphData.nodes.unshift(onFormatNode(mainInfo.value))
}
return label;
})
}
const onFormatNode = (item) => {
let isCenter = item.isCenter || false
const onFormatLink = (item, index) => {
return {
id: item.id+'', label:onWordWrap(item.label, 15), isCenter,
img: isCenter ? icon1628 : icon1629,
clipCfg: { r: isCenter ? 40 : 30 },
labelCfg: {
style: {
fill: isCenter ? "#1459BB" : "#333333",
fontSize: isCenter ? 13 : 13,
fontWeight: isCenter ? "bold" : "normal"
}
}
id: `link-${index+1}`,
source: route.query.id, target: item.id+'',
label: { show: true, color: "#055fc2", backgroundColor: "#eef7ff", borderWidth: 0, offset: [0, 15], formatter: item.relation },
lineStyle: { color: '#B9DCFF', type: "solid", opacity: 1 }
}
}
const onFormatEdge = (item, index) => {
const onFormatNode = (item) => {
let leader = item.id == mainInfo.value.id;
return {
id: `edge-${index+1}`,
target: item.id+'',
source: route.query.id,
// label: ["", "相似", "继承", "冲突"][1],
label: item.relation,
style: {
stroke: ["", "#B9DCFF", "#87E8DE", "#FFCCC7"][1],
id: item.id+'',
name: onWordWrap(item.label, 8),
label: {
show: true,
color: leader ? "#055fc2" : "#3b414b",
fontSize: leader ? 16 : 14,
fontWeight: leader ? 700 : 400,
fontFamily: 'Source Han Sans CN',
},
labelCfg: {
style: {
fill: ["", "#055FC2", "#13A8A8", "#CE4F51"][1],
background: {
fill: ["", "#E7F3FF", "#E6FFFB", "#FFE0E0"][1],
}
}
}
symbolSize: leader ? 60 : 40,
symbol: `image://${leader ? icon1628 : icon1629}`
}
}
const initChart = () => {
let edgeList = siderList.value.map(onFormatEdge)
let nodeList = siderList.value.map(onFormatNode)
nodeList.unshift(onFormatNode(mainInfo.value))
console.log(nodeList)
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 otherNodes = nodeList.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 = nodeList.find(n => n.isCenter)
if (centerNode) {
centerNode.x = centerX
centerNode.y = centerY
centerNode.fx = centerX
centerNode.fy = centerY
}
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' ]
},
defaultNode: {
type: 'image',
size: 50,
style: { cursor: "pointer" },
clipCfg: { show: true, type: 'circle' },
labelCfg: {
position: "bottom", offset: 12,
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: "line",
style: { lineWidth: 1, endArrow: true },
labelCfg: {
autoRotate: true,
style: {
cursor: "pointer",
fontSize: 12,
fontFamily: 'Microsoft YaHei',
background: { padding: [4, 4, 4, 4] }
}
// 文本插入换行符
const onWordWrap = (word, num) => {
const list = word.split('');
let label = "";
for (let i = 0; i < list.length; i++) {
if (i % num === 0 && i !== 0) {
label += "\n";
}
label += list[i];
}
})
// 节点点击处理
graphInstance.on('node:click', (evt) => {
let node = siderList.value.find(item => item.id==evt.item._cfg.model.id)
if (node) handleClickDecree(node)
});
graphInstance.on('edge:click', (evt) => {
let node = siderList.value.find(item => item.id==evt.item._cfg.model.target)
if (node) handleClickSider(node)
});
graphInstance.data({nodes: nodeList, edges: edgeList})
graphInstance.render()
return label;
}
const handleClickDecree = decree => {
......@@ -293,9 +212,9 @@ const onRelationChart = () => {
},
labelCfg: {
style: {
fill: ["", "#055FC2", "#13A8A8", "#CE4F51"][1],
fill: ["", "#055fc2", "#13A8A8", "#CE4F51"][1],
background: {
fill: ["", "#E7F3FF", "#E6FFFB", "#FFE0E0"][1],
fill: ["", "#eef7ff", "#E6FFFB", "#FFE0E0"][1],
}
}
}
......@@ -363,13 +282,10 @@ const onRelationChart = () => {
}
onMounted(() => {
Promise.all([onDecreeSummaryData(), handleGetRelateOrder()]).then(() => {
if (mainInfo.value.id && siderList.value.length) initChart()
})
initGraphChart()
});
onBeforeUnmount(() => {
graphInstance?.destroy()
graph?.destroy()
})
</script>
......@@ -462,10 +378,6 @@ onBeforeUnmount(() => {
.box2-main {
height: 100%;
padding: 10px;
.graph-container {
width: 100%;
height: 600px;
}
}
}
}
......
<template>
<div class="view-box">
<el-empty v-if="!dataList?.length" style="padding-top: 15%;" description="暂无数据" :image-size="100" />
<el-empty v-if="!dataList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<div v-if="dataList.length" class="main-content-main">
<div class="main-mask"
@wheel.prevent="handleWheel"
......
<template>
<div class="fishbone-wrapper">
<div class="fishbone-scroll-container" ref="scrollContainerRef">
<div class="fishbone" ref="fishboneRef" v-if="fishboneData.length > 0">
<div class="main-line" :style="{ width: (fishboneData.length / 2) * 340 - 200 + 'px' }"></div>
<!-- 奇数索引的数据组放在上方 -->
<div
v-for="(causeGroup, groupIndex) in getOddGroups(fishboneData)"
:key="'top-' + groupIndex"
:class="getTopBoneClass(groupIndex)"
:style="{ left: groupIndex * 300 + 400 + 'px' }"
>
<div class="left-bone">
<div
class="left-bone-item"
v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-' + index"
>
<div class="text">{{ item.name }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div
class="right-bone-item"
v-for="(item, index) in getRightItems(causeGroup.causes)"
:key="'right-' + index"
>
<div class="line"></div>
<div class="text">{{ item.name }}</div>
</div>
</div>
</div>
<!-- 偶数索引的数据组放在下方 -->
<div
v-for="(causeGroup, groupIndex) in getEvenGroups(fishboneData)"
:key="'bottom-' + groupIndex"
:class="getBottomBoneClass(groupIndex)"
:style="{ left: groupIndex * 300 + 200 + 'px' }"
>
<div class="left-bone">
<div
class="left-bone-item"
v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-bottom-' + index"
>
<div class="text">{{ item.name }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div
class="right-bone-item"
v-for="(item, index) in getRightItems(causeGroup.causes)"
:key="'right-bottom-' + index"
>
<div class="line"></div>
<div class="text">{{ item.name }}</div>
</div>
</div>
</div>
</div>
<div v-else style="display: flex; justify-content: center; align-items: center; height: 200px; width: 100%">
<el-empty description="暂无相关数据" />
</div>
</div>
<!-- 滚动指示器 -->
<!-- <div class="scroll-indicators" v-if="showScrollIndicator">
<div class="scroll-btn left" :class="{ disabled: !canScrollLeft }" @click="scrollLeft">‹</div>
<div class="scroll-btn right" :class="{ disabled: !canScrollRight }" @click="scrollRight">›</div>
</div> -->
</div>
</template>
<script setup>
import { getChainFishbone } from "@/api/exportControl";
import { onMounted, ref, nextTick, watch } from "vue";
// 这儿需要接收父组件传递来的产业链ID
const props = defineProps({
chainId: {
type: Number,
default: 1
}
});
// const chainId = ref(1);
const fishboneData = ref([]);
const scrollContainerRef = ref(null);
const fishboneRef = ref(null);
const showScrollIndicator = ref(false);
const canScrollLeft = ref(false);
const canScrollRight = ref(true);
// 获取奇数索引的数据组(放在上方)
const getOddGroups = data => {
console.log(
"getOddGroups:",
data.filter((_, index) => index % 2 === 1)
);
return data.filter((_, index) => index % 2 === 1);
};
// 获取偶数索引的数据组(放在下方)
const getEvenGroups = data => {
console.log(
"getEvenGroups:",
data.filter((_, index) => index % 2 === 0)
);
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 updateScrollState = () => {
if (!scrollContainerRef.value) return;
const container = scrollContainerRef.value;
canScrollLeft.value = container.scrollLeft > 0;
canScrollRight.value = container.scrollLeft < container.scrollWidth - container.clientWidth;
};
// 滚动处理
const scrollLeft = () => {
if (scrollContainerRef.value) {
scrollContainerRef.value.scrollBy({ left: -200, behavior: "smooth" });
}
};
const scrollRight = () => {
if (scrollContainerRef.value) {
scrollContainerRef.value.scrollBy({ left: 200, behavior: "smooth" });
}
};
// 处理滚动事件
const handleScroll = () => {
updateScrollState();
};
onMounted(async () => {
// try {
// const chainFishboneData = await getChainFishbone(props.chainId);
// fishboneData.value = chainFishboneData?.causes ?? [];
// // 等待DOM更新后检查是否需要滚动
// nextTick(() => {
// if (scrollContainerRef.value && fishboneRef.value) {
// showScrollIndicator.value = fishboneRef.value.scrollWidth > scrollContainerRef.value.clientWidth;
// updateScrollState();
// }
// });
// console.log("鱼骨图数据:", fishboneData.value);
// } catch (error) {
// console.log(error);
// }
});
// 监听props中的chainId变化
watch(
() => props.chainId,
async () => {
try {
const chainFishboneData = await getChainFishbone(props.chainId);
fishboneData.value = chainFishboneData?.causes ?? [];
} catch (error) {
console.log(error);
}
}
);
</script>
<style lang="scss" scoped>
.fishbone-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.fishbone-scroll-container {
display: flex;
align-items: center;
width: 100%;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
scrollbar-color: rgba(144, 202, 249, 0.5) transparent;
&::-webkit-scrollbar {
height: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(144, 202, 249, 0.5);
border-radius: 3px;
}
}
/* ... 原有的样式保持不变 ... */
.fishbone {
position: relative;
width: fit-content;
height: 100%;
margin-top: 40px;
min-width: 100%;
padding-left: 275px;
.main-line {
margin-top: 280px;
width: 1888px;
height: 3px;
background: rgba(174, 208, 255, 1);
}
}
.top-bone {
position: absolute;
top: 20px;
right: 200px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
margin-left: 4px;
height: 70px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.top-bone1 {
position: absolute;
top: 20px;
right: 500px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.top-bone2 {
position: absolute;
top: 20px;
right: 800px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.bottom-bone {
position: absolute;
top: 280px;
right: 360px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(-30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.bottom-bone1 {
position: absolute;
top: 280px;
right: 660px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(-30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.bottom-bone2 {
position: absolute;
top: 280px;
right: 960px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(-30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.scroll-indicators {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
display: flex;
justify-content: space-between;
pointer-events: none;
padding: 0 10px;
z-index: 10;
}
.scroll-btn {
width: 30px;
height: 30px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: bold;
color: #90caf9;
cursor: pointer;
pointer-events: auto;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
transition: all 0.2s ease;
&:hover:not(.disabled) {
background: #90caf9;
color: white;
transform: scale(1.1);
}
&.disabled {
color: #c0c4cc;
cursor: not-allowed;
background: rgba(255, 255, 255, 0.5);
}
}
</style>
......@@ -16,7 +16,7 @@
</div>
<div class="data-title">实体名称</div>
<div style="height: 20px; flex: auto;">
<el-empty v-if="!showCompanyList?.length" style="padding-top: 35%;" description="暂无数据" :image-size="100" />
<el-empty v-if="!showCompanyList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<el-scrollbar height="100%" always>
<div class="list-data">
<div class="list-item" v-for="item in showCompanyList" :key="item.id" :class="{ 'item-active': activeEntityId === item.id }" @click="handleToCompanyDetail(item)">
......@@ -81,7 +81,7 @@
<ChartChain />
</div>
<div class="graph-box" v-if="contentType==2">
<GraphChart :nodes="testData.nodes" :links="testData.links" layoutType="force" />
<GraphChart :nodes="graphData.nodes" :links="graphData.links" layoutType="force" />
</div>
</div>
</AnalysisBox>
......@@ -90,13 +90,9 @@
</template>
<script setup>
import { ref, onMounted } from "vue";
import setChart from "@/utils/setChart";
import { ref, onMounted, reactive } from "vue";
import { Search } from '@element-plus/icons-vue'
import getBarChart from "./utils/barChart";
import { getDecreeIndustry, getDecreehylyList, getDecreeCompany } from "@/api/decree/influence";
import { getCnEntityOnChain, getChainInfoByDomainId } from "@/api/exportControl";
import { getSingleSanctionEntitySupplyChain } from "@/api/exportControlV2.0";
import { getDecreehylyList, getDecreeCompany } from "@/api/decree/influence";
import ChartChain from "./com/ChartChain.vue";
import AiTips from "./com/AiTips.vue";
import GraphChart from "@/components/base/GraphChart/index.vue";
......@@ -107,68 +103,65 @@ import icon423 from "./assets/images/icon423.png";
import icon1620 from "./assets/images/icon1620.png";
import icon1621 from "./assets/images/icon1621.png";
import company from "./assets/images/company.png";
import companyActive from "./assets/images/company-active.png";
const tips = "这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。"
// 关系图数据
const testData = {
// 节点数据
nodes: [
{ id: 0, name: "泰丰先行", symbolSize: 60, symbol: `image://${company}`, x:0, y:0 },
{ id: 1, name: "国轩高科", symbolSize: 40, symbol: `image://${company}` },
{ id: 2, name: "智方纳米", symbolSize: 40, symbol: `image://${company}` },
{ id: 3, name: "香百科技", symbolSize: 40, symbol: `image://${company}` },
{ id: 4, name: "格林滨", symbolSize: 40, symbol: `image://${company}` },
{ id: 5, name: "江西紫宸", symbolSize: 40, symbol: `image://${company}` },
{ id: 6, name: "紫江企业", symbolSize: 40, symbol: `image://${company}` },
{ id: 7, name: "大而美法案", symbolSize: 40, symbol: `image://${company}` },
{ id: 8, name: "比亚迪", symbolSize: 40, symbol: `image://${company}` },
const graphData = reactive({
leader: { id: 0, name: "泰丰先行" },
list: [
{ id: 1, name: "国轩高科", formatter: '持股' },
{ id: 2, name: "智方纳米", formatter: '持股' },
{ id: 3, name: "香百科技", formatter: '合作' },
{ id: 4, name: "格林滨", formatter: '从属' },
{ id: 5, name: "江西紫宸", formatter: '合作' },
{ id: 6, name: "紫江企业", formatter: '持股' },
{ id: 7, name: "大而美法案", formatter: '合作' },
{ id: 8, name: "比亚迪", formatter: '合作' },
],
// 关系数据
links: [
{
source: 1, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 2, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 3, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 4, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '从属' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 5, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 6, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 7, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 8, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
lineStyle: { color: '#B9DCFF', type: "solid" }
nodes: [],
links: [],
});
const initGraphChart = () => {
graphData.links = graphData.list.map(onFormatLink)
graphData.nodes = graphData.list.map(onFormatNode)
graphData.nodes.unshift(onFormatNode(graphData.leader))
}
const onFormatLink = (item, index) => {
return {
id: `link-${index+1}`,
source: item.id+'', target: '0',
label: { show: true, color: "#055fc2", backgroundColor: "#eef7ff", borderWidth: 0, offset: [0, 15], formatter: item.formatter },
lineStyle: { color: '#B9DCFF', type: "solid", opacity: 1 }
}
}
const onFormatNode = (item) => {
let leader = item.id == '0';
return {
id: item.id+'',
name: onWordWrap(item.name, 7),
label: {
show: true,
color: "#3b414b",
fontSize: leader ? 18 : 14,
fontWeight: leader ? 700 : 400,
fontFamily: 'Source Han Sans CN',
},
],
};
symbolSize: 40,
symbol: `image://${company}`
}
}
// 文本插入换行符
const onWordWrap = (word, num) => {
const list = word.split('');
let label = "";
for (let i = 0; i < list.length; i++) {
if (i % num === 0 && i !== 0) {
label += "\n";
}
label += list[i];
}
return label;
}
// 受影响实体
const companyList = ref([]);
......@@ -181,15 +174,10 @@ const handleToCompanyDetail = (item) => {
const handleCurrentChange = page => {
currentPage.value = page;
};
// const showCompanyList = computed(() => {
// const startIndex = (currentPage.value - 1) * pageSize.value;
// const endIndex = startIndex + pageSize.value;
// return companyList.value.slice(startIndex, endIndex);
// });
const showCompanyList = ref([
{ id: 1, name: "北京市", status: "上市" },
{ id: 2, name: "上海市", status: "上市" },
{ id: 3, name: "广州市广州市广州市广州市广州市广州市广州市广州市", status: "上市" },
{ id: 3, name: "广州市", status: "上市" },
{ id: 4, name: "深圳市", status: "上市" },
{ id: 5, name: "成都市", status: "上市" },
{ id: 7, name: "天津市", status: "上市" },
......@@ -238,9 +226,10 @@ const handleGetHylyList = async () => {
console.log("行业领域列表", res);
if (res.code === 200 && res.data) {
areaList.value = res.data;
handleGetChainId();
}
} catch (error) {}
} catch (error) {
areaList.value = [];
}
};
// 产业链/实体关系
......@@ -248,178 +237,14 @@ const contentType = ref(1);
const headerContentType = (type) => {
contentType.value = type;
};
const chainId = ref(0);
const handleGetChainId = async () => {
try {
const res = await getChainInfoByDomainId(curAreaId.value);
console.log("获取chainId", res);
if (res && res.length) {
chainId.value = res[0].id;
// handleGetChainInfoByChainId();
}
} catch (error) {
console.error("chainId error", error);
}
};
const handleNodeClick = (node) => {
};
const handleLayoutChange = (type) => {
};
const treeData = ref(null);
const graphData = ref({ nodes: [], links: [] });
const singleSanctionEntitySupplyChainData = ref(null);
const updateGraphData = () => {
const data = singleSanctionEntitySupplyChainData.value;
if (!data) return;
const nodes = [];
const links = [];
nodes.push({
id: "0",
name: data.orgName,
image: companyActive,
symbolSize: 60,
isSanctioned: true
});
const parentList = data.parentOrgList || [];
parentList.forEach((item, index) => {
nodes.push({
id: `p-${item.id || index}`,
name: item.name,
image: item.isSanctioned ? companyActive : company,
symbolSize: 40,
isSanctioned: item.isSanctioned
});
links.push({
source: `p-${item.id || index}`,
target: "0",
name: "供应商"
});
});
const childList = data.childrenOrgList || [];
childList.forEach((item, index) => {
nodes.push({
id: `c-${item.id || index}`,
name: item.name,
image: item.isSanctioned ? companyActive : company,
symbolSize: 40,
isSanctioned: item.isSanctioned
});
links.push({
source: "0",
target: `c-${item.id || index}`,
name: "客户"
});
});
graphData.value = { nodes, links };
};
const getSingleSanctionEntitySupplyChainRequest = async () => {
try {
const res = await getSingleSanctionEntitySupplyChain({
orgId: "91370102723265504D"
});
console.log("data1", res)
if (res.code === 200 && res.data) {
singleSanctionEntitySupplyChainData.value = res.data;
updateGraphData();
treeData.value = {
id: res.data.orgId,
name: res.data.orgName,
image: companyActive,
symbolSize: 50,
children: (res.data.parentOrgList || []).map((item, index) => ({
id: item.id || `p-${index}`,
name: item.name,
image: item.isSanctioned ? companyActive : company,
symbolSize: 30
}))
};
}
} catch (error) {
console.log(error);
}
};
// 企业影响分析
const companyTotalNum = ref(0); // 企业数量
const chart1Data = ref({
// title: ["集成电路", "新能源", "人工智能", "先进制造", "量子科技"],
// value: [109, 95, 79, 25, 11]
});
const handleGetChart1Data = async () => {
const params = {
id: 147
};
try {
const res = await getDecreeIndustry(params);
console.log("企业影响分析", res);
if (res.code === 200 && res.data) {
chart1Data.value.title = res.data.map(item => {
return item.hylyName;
});
chart1Data.value.value = res.data.map(item => {
return item.companyNum;
});
} else {
chart1Data.value.title = [];
chart1Data.value.value = [];
}
} catch (error) {
chart1Data.value.title = [];
chart1Data.value.value = [];
}
};
const handelBox1 = async () => {
await handleGetChart1Data();
let chart1 = getBarChart(chart1Data.value.title, chart1Data.value.value);
setChart(chart1, "chart1");
};
const chainInfo = ref({
upstreamInternalCount: 0,
upstreamInternalRate: 0,
upstreamEntityCount: 0,
upstreamEntityRate: 0,
midstreamInternalCount: 0,
midstreamInternalRate: 0,
midstreamEntityCount: 0,
midstreamEntityRate: 0,
downstreamInternalCount: 0,
downstreamInternalRate: 0,
downstreamEntityCount: 0,
downstreamEntityRate: 0
});
// 根据chainId获取chainInfo
const handleGetChainInfoByChainId = async () => {
try {
const res = await getCnEntityOnChain(chainId.value);
console.log("chainInfo", res);
if (res) {
chainInfo.value = res;
}
} catch (error) {
console.log("chainInfo error", error);
}
};
onMounted(() => {
// handleGetCompanyListByArea();
handleGetChart1Data();
initGraphChart()
handleGetHylyList();
handelBox1();
getSingleSanctionEntitySupplyChainRequest()
});
</script>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论