提交 8055f9f1 authored 作者: 朱政's avatar 朱政

Merge branch 'master' into zz-dev

差异被折叠。
<template> <template>
<div id="app"> <div id="app">
<div class="pro-wrapper"> <router-view></router-view>
<div class="home-page">
<ModuleHeader />
<div class="main-container">
<router-view />
</div>
</div>
<div class="right-btn" @click="handleClickToolBox">
<div class="item">
<div class="icon">
<img src="@/assets/icons/overview/domain.png" alt="" />
</div>
<div class="text">{{ "领域" }}</div>
</div>
<div class="item">
<div class="icon">
<img src="@/assets/icons/overview/element.png" alt="" />
</div>
<div class="text">{{ "要素" }}</div>
</div>
</div>
<div class="tool-box">
<!-- <div class="tool-item">
<img src="@/assets/icons/tool-item-icon1.png" alt="" />
</div>
<div class="tool-item">
<img src="@/assets/icons/tool-item-icon2.png" alt="" />
</div>
<div class="tool-item">
<img src="@/assets/icons/tool-item-icon3.png" alt="" />
</div>
<div class="tool-item">
<img src="@/assets/icons/tool-item-icon4.png" alt="" />
</div> -->
<el-tooltip content="智能写报" placement="left" :offset="10">
<div class="tool-item" @click="handleOpenPage('znxb')">
<img src="@/assets/icons/tool-item-icon1.png" alt="" />
</div>
</el-tooltip>
<el-tooltip content="智能翻译" placement="left" :offset="10">
<div class="tool-item" @click="handleClickToolBox">
<img src="@/assets/icons/tool-item-icon2.png" alt="" />
</div>
</el-tooltip>
<!-- <div class="tool-item">
<img src="@/assets/icons/tool-item-icon3.png" alt="" />
</div> -->
<el-tooltip content="智能问答" placement="left" :offset="10">
<div class="tool-item" @click="handleOpenPage('znwd')">
<img src="@/assets/icons/tool-item-icon4.png" alt="" />
</div>
</el-tooltip>
</div>
<!-- <div class="ai-btn" @click="openAiBox">
<div class="icon">
<img src="@/assets/icons/ai-icon.png" alt="" />
</div>
<div class="text">智能问答</div>
</div> -->
<div class="ai-dialog" v-if="isShowAiBox">
<AiBox @close="closeAiBox" />
</div>
</div>
</div> </div>
</template> </template>
...@@ -95,27 +29,6 @@ import { ElMessage } from "element-plus"; ...@@ -95,27 +29,6 @@ import { ElMessage } from "element-plus";
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
// const target = ref(null);
// const { x, y, isDragging } = useDraggable(target, {
// initialValue: { x: 1770, y: 800 },
// onStart: () => console.log("开始拖动"),
// onEnd: () => console.log("结束拖动")
// });
// const style = computed(() => ({
// position: "absolute",
// left: `${x.value}px`,
// top: `${y.value}px`,
// cursor: isDragging.value ? "grabbing" : "grab"
// }));
const handleToHome = () => {
router.push({
path: "/ZMOverView"
});
isCurrentOverview.value = true;
};
const isShowAiBox = ref(false); const isShowAiBox = ref(false);
......
...@@ -213,3 +213,18 @@ export const countryCoordMap = { ...@@ -213,3 +213,18 @@ export const countryCoordMap = {
法属波利尼西亚: [-149.5986, -17.6797], // 法属波利尼西亚帕皮提 法属波利尼西亚: [-149.5986, -17.6797], // 法属波利尼西亚帕皮提
"新喀里多尼亚(法)": [166.4572, -21.5547] // 新喀里多尼亚努美阿 "新喀里多尼亚(法)": [166.4572, -21.5547] // 新喀里多尼亚努美阿
}; };
export function convertAsiaCenterCoord(coord) {
const [lng, lat] = coord;
// 将以本初子午线为基准的坐标转换为以亚洲为中心的坐标
// world-asia-center.json 是将标准坐标的经度减去了 180 度
let newLng = lng - 180;
// 规范化到 [-180, 180] 范围
if (newLng < -180) {
newLng += 360;
}
return [newLng, lat];
}
<template>
<div class="layout-container">
<div class="layout-main">
<div class="layout-main-center">
<div class="report-header">
<div class="report-title">政令原文</div>
<el-switch v-model="isHighlight" />
<div class="switch-label switch-label-left">高亮实体</div>
<el-switch v-model="isTranslate" />
<div class="switch-label">原文显示</div>
<div
v-for="action in headerActions"
:key="action.key"
class="btn"
@click="action.onClick"
>
<div :class="['icon', action.iconGapClass]">
<img :src="action.icon" alt="" />
</div>
<div class="text">{{ action.text }}</div>
</div>
<div class="find-word-box" v-if="findWordBox">
<div class="find-word-input">
<el-input
v-model="findWordTxt"
placeholder="查找原文内容"
@input="handleUpdateWord"
/>
</div>
<div class="find-word-limit">{{ findWordNum }}/{{ findWordMax }}</div>
<div class="find-word-icon" @click="handleFindWord('last')">
<el-icon><ArrowUp /></el-icon>
</div>
<div class="find-word-icon" @click="handleFindWord('next')">
<el-icon><ArrowDown /></el-icon>
</div>
<div class="find-word-icon" @click="handleFindWord('close')">
<el-icon><Close /></el-icon>
</div>
</div>
</div>
<div class="report-main">
<div v-if="!displayReportData.length" class="no-content">暂无数据</div>
<el-scrollbar v-else height="100%">
<div
v-for="item in displayReportData"
:key="item.num"
class="content-row"
:class="{ 'high-light': isHighlight }"
>
<div class="content-cn" :class="{ 'translate-cn': !isTranslate }" v-html="item.content" />
<div v-if="isTranslate" class="content-en" v-html="item.contentEn" />
</div>
</el-scrollbar>
</div>
</div>
</div>
</div>
</template>
<script setup>
import defaultDownloadIcon from "./assets/icons/download.png";
import defaultSearchIcon from "./assets/icons/search.png";
import { nextTick, ref, watch } from "vue";
import { debounce } from "lodash";
const props = defineProps({
reportData: { type: Array, default: () => [] },
});
const emits = defineEmits(["download"]);
const isHighlight = ref(false);
const isTranslate = ref(true);
const findWordTxt = ref("");
const findWordBox = ref(false);
const findWordNum = ref(0);
const findWordMax = ref(0);
const originReportData = ref([]);
const displayReportData = ref([]);
const headerActions = [
{
key: "download",
text: "下载",
icon: defaultDownloadIcon,
iconGapClass: "icon-gap-4",
onClick: () => emits("download"),
},
{
key: "search",
text: "查找",
icon: defaultSearchIcon,
iconGapClass: "icon-gap-6",
onClick: () => handleFindWord("open"),
},
];
function escapeRegExp(text) {
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function applyHighlightToText(text, searchTerm) {
if (!text || !searchTerm) {
return { html: text || "", count: 0 };
}
const escapedTerm = escapeRegExp(searchTerm);
let count = 0;
const html = String(text).replace(new RegExp(escapedTerm, "g"), (match) => {
count += 1;
return `<span class="highlight">${match}</span>`;
});
return { html, count };
}
function setDisplayFromOrigin() {
displayReportData.value = originReportData.value.map((item) => ({
...item,
content: item.content || "",
contentEn: item.contentEn || "",
}));
}
function updateActiveHighlight() {
const spans = document.querySelectorAll("span.highlight");
spans.forEach((span, index) => {
if (index + 1 === findWordNum.value) {
span.scrollIntoView({});
span.style.backgroundColor = "#ff9632";
} else {
span.style.backgroundColor = "#ffff00";
}
});
}
const doUpdateWord = () => {
findWordNum.value = 0;
findWordMax.value = 0;
const term = findWordTxt.value?.trim();
if (!term) {
setDisplayFromOrigin();
return;
}
displayReportData.value = originReportData.value.map((item) => {
const cn = applyHighlightToText(item.content, term);
const en = isTranslate.value
? applyHighlightToText(item.contentEn, term)
: { html: item.contentEn, count: 0 };
findWordMax.value += cn.count + en.count;
return {
...item,
content: cn.html,
contentEn: en.html,
};
});
if (findWordMax.value > 0) {
nextTick(() => {
findWordNum.value = 1;
updateActiveHighlight();
});
}
};
const handleUpdateWord = debounce(() => {
doUpdateWord();
}, 300);
function handleFindWord(event) {
switch (event) {
case "open":
findWordBox.value = true;
break;
case "last":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value === 1 ? findWordMax.value : findWordNum.value - 1;
updateActiveHighlight();
}
break;
case "next":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value === findWordMax.value ? 1 : findWordNum.value + 1;
updateActiveHighlight();
}
break;
case "close":
findWordBox.value = false;
findWordTxt.value = "";
doUpdateWord();
break;
}
}
watch(
() => props.reportData,
(val) => {
originReportData.value = (val || []).map((item) => ({
content: item?.content || "",
contentEn: item?.contentEn || "",
num: item?.num,
}));
setDisplayFromOrigin();
doUpdateWord();
},
{ deep: true, immediate: true },
);
watch(isTranslate, () => {
doUpdateWord();
});
</script>
<style lang="scss" scoped>
.high-light {
:deep(span.highlight) {
background-color: #ffff00;
}
}
.layout-container {
width: 100%;
height: 100%;
background-color: #f7f8f9;
.layout-main {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
.layout-main-center {
width: 1600px;
background-color: white;
padding: 0 60px;
flex: auto;
display: flex;
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;
position: relative;
.find-word-box {
position: absolute;
top: -50px;
right: 0px;
width: 430px;
height: 60px;
border: 1px solid rgba(230, 231, 232, 1);
background-color: white;
border-radius: 6px;
display: flex;
align-items: center;
.find-word-input {
flex: auto;
}
.find-word-limit {
border-right: solid 1px rgba(230, 231, 232, 1);
color: #5f656c;
padding-right: 16px;
}
.find-word-icon {
padding: 10px 12px;
margin: 0 2px;
cursor: pointer;
}
}
.report-title {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
line-height: 20px;
font-weight: 700;
flex: 1;
}
.btn {
margin-left: 10px;
width: 88px;
height: 32px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 6px;
background: rgba(255, 255, 255, 1);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.text {
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.icon {
width: 16px;
height: 16px;
font-size: 0px;
img {
width: 100%;
height: 100%;
}
}
.icon-gap-4 {
margin-right: 4px;
}
.icon-gap-6 {
margin-right: 6px;
}
}
}
.report-main {
flex: auto;
box-sizing: border-box;
padding-top: 10px;
.no-content {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 20px;
font-weight: 400;
}
.content-row {
display: flex;
width: 100%;
padding: 0 20px;
min-height: 100px;
gap: 80px;
.content-en,
.content-cn {
width: 50%;
flex: auto;
padding-bottom: 40px;
box-sizing: border-box;
font-size: 16px;
line-height: 1.8;
color: #3b414b;
font-family: Microsoft YaHei;
text-align: justify;
white-space: pre-wrap;
}
.translate-cn {
padding-bottom: 10px;
}
}
}
}
}
}
.switch-label {
margin-left: 6px;
}
.switch-label-left {
margin-right: 10px;
}
: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>
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="16.000000" fill="none" customFrame="#000000">
<rect id="Line/Calendar" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="形状" d="M11 3.08L13.52 3.08C13.7855 3.08 14 3.2945 14 3.56L14 13.52C14 13.7855 13.7855 14 13.52 14L2.48 14C2.2145 14 2 13.7855 2 13.52L2 3.56C2 3.2945 2.2145 3.08 2.48 3.08L5 3.08L5 2.12C5 2.054 5.054 2 5.12 2L5.96 2C6.026 2 6.08 2.054 6.08 2.12L6.08 3.08L9.92 3.08L9.92 2.12C9.92 2.054 9.974 2 10.04 2L10.88 2C10.946 2 11 2.054 11 2.12L11 3.08ZM3.08 4.16L3.08 6.2L12.92 6.2L12.92 4.16L11 4.16L11 4.88C11 4.946 10.946 5 10.88 5L10.04 5C9.974 5 9.92 4.946 9.92 4.88L9.92 4.16L6.08 4.16L6.08 4.88C6.08 4.946 6.026 5 5.96 5L5.12 5C5.054 5 5 4.946 5 4.88L5 4.16L3.08 4.16ZM12.92 12.92L3.08 12.92L3.08 7.22L12.92 7.22L12.92 12.92Z" fill="rgb(59,65,75)" fill-rule="evenodd" />
</svg>
<template>
<div class="time-tab-pane-wrapper">
<div
class="time-item"
:class="{'time-item-active': item.active}"
v-for="item,index in timeList"
:key="index"
@click="handleTimeClick(item,index)"
>
<div class="icon" v-if="item.active">
<img src="./calendat-icon.svg" alt="">
</div>
<div class="text text-tip-1" :class="{'text-active': item.active}">{{ item.time }}</div>
</div>
</div>
</template>
<script setup>
import {ref} from 'vue'
const timeList = ref([
{
time: '近一周',
active: true
},
{
time: '近一月',
active: false
},
{
time: '近一年',
active: false
},
])
const handleTimeClick = (item, index) => {
timeList.value.forEach(time => {
time.active = false
})
timeList.value[index].active = true
emit('time-click', item)
}
const emit = defineEmits(['time-click'])
</script>
<style lang="scss" scoped>
.time-tab-pane-wrapper{
display: flex;
width: 248px;
height: 36px;
background: rgba(255, 255, 255, 0.5);
border-radius: 50px;
border: 2px solid rgba(255,255,255,1);
.time-item{
height: 32px;
box-sizing: border-box;
padding: 4px 12px;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
cursor: pointer;
.icon{
width: 16px;
height: 16px;
img{
width: 100%;
height: 100%;
}
}
.text{
color: var(--text-primary-50-color);
}
.text-active{
color: var(--text-primary-80-color)
}
}
.time-item-active{
background: rgba(255, 255, 255, 0.65);
border-radius: 50px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
}
}
</style>
\ No newline at end of file
<template>
<div class="tree-chart-wrapper" id="tree-chart">
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import setChart from '@/utils/setChart';
import getTreeChart from './treeChart';
import CompanyImg from "@/assets/icons/symbol.png";
const props = defineProps({
treeData: {
type: Array,
default: [
{
id: 1,
name: 'a1',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
children: [
{
id: 11,
name: 'b1',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 12,
name: 'b2',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 13,
name: 'b3',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 14,
name: 'b4',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
}
]
}
]
}
})
onMounted(() => {
const treeChart = getTreeChart(props.treeData)
setChart(treeChart, 'tree-chart')
})
</script>
<style lang="scss" scoped>
.tree-chart-wrapper {
width: 100%;
height: 100%;
}
</style>
\ No newline at end of file
const getTreeChart = (treeData) => {
const option = {
series: [{
type: 'tree',
layout: 'orthogonal', // 从上到下布局
orient: 'TB', // Top to Bottom
data: treeData,
top: '10%',
bottom: '10%',
left: '3%',
right: '3%',
symbol: 'circle',
symbolSize: 40,
label: {
position: 'top',
verticalAlign: 'middle',
align: 'center',
fontSize: 20, // 字体大小
color: 'rgb(59, 65, 75)', // 字体颜色
fontWeight: 'normal', // 字体粗细
// formatter: '{b}', // 标签内容格式器
// rotate: 0, // 旋转角度
offset: [0, -6], // 偏移量
// lineHeight: 20, // 行高
// backgroundColor: 'transparent', // 背景色
// borderColor: 'transparent', // 边框颜色
// borderWidth: 0, // 边框宽度
// borderRadius: 0, // 圆角
// padding: 0, // 内边距
// shadowBlur: 0, // 阴影模糊
// shadowColor: 'transparent' // 阴影颜色
},
leaves: {
label: {
position: 'bottom',
verticalAlign: 'middle',
align: 'center',
offset: [0, 6],
}
},
lineStyle: {
color: '#ccc',
width: 2,
type: 'dashed', // 线条类型:'solid'(实线), 'dashed'(虚线), 'dotted'(点线)
curveness: 0.5, // 曲线弧度(0-1),仅当 edgeShape 为 'curve' 时有效
cap: 'round', // 线帽:'butt', 'round', 'square'
join: 'round', // 连接点:'bevel', 'round', 'miter'
shadowBlur: 0, // 阴影模糊大小
shadowColor: 'rgba(0,0,0,0.5)', // 阴影颜色
shadowOffsetX: 0, // 阴影水平偏移
shadowOffsetY: 0 // 阴影垂直偏移
},
emphasis: {
focus: 'descendant'
},
expandAndCollapse: false,
initialTreeDepth: 3
}]
};
return option
}
export default getTreeChart
\ No newline at end of file
import 'echarts-wordcloud'; import 'echarts-wordcloud';
import { MUTICHARTCOLORS } from '@/common/constant';
const getWordCloudChart = (data) => { const getWordCloudChart = data => {
const option = { const option = {
grid: { grid: {
left: 5, left: 5,
top: 5, top: 5,
...@@ -11,52 +12,40 @@ const getWordCloudChart = (data) => { ...@@ -11,52 +12,40 @@ const getWordCloudChart = (data) => {
series: [ series: [
{ {
type: "wordCloud", type: "wordCloud",
shape: 'circle', shape: "circle",
width: '100%', width: "100%",
height: '100%', height: "100%",
// 其他形状你可以使用形状路径 // 其他形状你可以使用形状路径
// shape: 'circle', // 示例 // shape: 'circle', // 示例
// 或者自定义路径 // 或者自定义路径
gridSize: 35, // 网格大小,影响词间距。 gridSize: 15, // 网格大小,影响词间距。
sizeRange: [16, 36], // 定义词云中文字大小的范围 sizeRange: [16, 36], // 定义词云中文字大小的范围
rotationRange: [0, 0], rotationRange: [0, 0],
rotationStep: 0, // rotationRange: [-90, 90],
// rotationStep: 10,
drawOutOfBound: false, // 是否超出画布 drawOutOfBound: false, // 是否超出画布
shrinkToFit: true, // 是否自动缩小以适应容器 shrinkToFit: true, // 是否自动缩小以适应容器
// 字体 // 字体
textStyle: { textStyle: {
// normal: { color: function (params) {
// color: function () { const colors = MUTICHARTCOLORS || [];
// return 'rgb(' + [ if (!colors.length) {
// Math.round(Math.random() * 160), return "#69B1FF";
// Math.round(Math.random() * 160), }
// Math.round(Math.random() * 160) return colors[params.dataIndex % colors.length];
// ].join(',') + ')';
// }
// },
color: function () {
let colors = [
"rgba(189, 33, 33, 1)",
"rgba(232, 151, 21, 1)",
"rgba(220, 190, 68, 1)",
"rgba(96, 58, 186, 1)",
"rgba(32, 121, 69, 1)",
"rgba(22, 119, 255, 1)",
];
return colors[parseInt(Math.random() * colors.length)];
}, },
emphasis: { emphasis: {
shadowBlur: 5, shadowBlur: 5,
shadowColor: "#333", shadowColor: "#333"
}, }
}, },
// 设置词云数据 // 设置词云数据
data: data, data
}, }
], ]
}; };
return option return option;
} };
export default getWordCloudChart export default getWordCloudChart;
\ No newline at end of file \ No newline at end of file
// 法案资源库
const CountryBill = () => import('@/views/dataLibrary/components/bill/countryBill/index.vue')
const StateBill = () => import('@/views/dataLibrary/components/bill/stateBill/index.vue')
const dataBillRoutes = [
// 科技法案资源库路由
{
path: "/countryBill",
name: "CountryBill",
component: CountryBill,
meta: {
title: '国会法案', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/stateBill",
name: "StateBill",
component: StateBill,
meta: {
title: '州法案', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataBillRoutes
\ No newline at end of file
// 法案资源库
const Decree = () => import('@/views/dataLibrary/components/decree/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/dataDecree",
name: "DataDecree",
component: Decree,
meta: {
title: '科技政令', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const CommerceControlList = () => import('@/views/dataLibrary/components/exportControl/commerceControlList/index.vue')
const CommerceControlListEvent = () => import('@/views/dataLibrary/components/exportControl/commerceControlListEvent/index.vue')
const EntityList = () => import('@/views/dataLibrary/components/exportControl/entityList/index.vue')
const EntityListEvent = () => import('@/views/dataLibrary/components/exportControl/entityListEvent/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/dataCommerceControlList",
name: "CommerceControlList",
component: CommerceControlList,
meta: {
title: '商业管制清单', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataCommerceControlListEvent",
name: "CommerceControlListEvent",
component: CommerceControlListEvent,
meta: {
title: '商业管制清单事件', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataEntityList",
name: "EntityList",
component: EntityList,
meta: {
title: '实体清单', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataEntityListEvent",
name: "EntityListEvent",
component: EntityListEvent,
meta: {
title: '实体清单事件', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const MREList = () => import('@/views/dataLibrary/components/financeControl/mREList/index.vue')
const MREListEvent = () => import('@/views/dataLibrary/components/financeControl/mREListEvent/index.vue')
const SDNList = () => import('@/views/dataLibrary/components/financeControl/sDNList/index.vue')
const SDNListEvent = () => import('@/views/dataLibrary/components/financeControl/sDNListEvent/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/mREList",
name: "MREList",
component: MREList,
meta: {
title: '涉军企业清单', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/mREListEvent",
name: "MREListEvent",
component: MREListEvent,
meta: {
title: '涉军企业清单事件', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/sDNList",
name: "SDNList",
component: SDNList,
meta: {
title: 'SDN清单', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/sDNListEvent",
name: "SDNListEvent",
component: SDNListEvent,
meta: {
title: 'SDN清单事件', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const KeyLab = () => import('@/views/dataLibrary/components/innovationSubject/keyLab/index.vue')
const ResearchUniversity = () => import('@/views/dataLibrary/components/innovationSubject/researchUniversity/index.vue')
const TechnologyCompany = () => import('@/views/dataLibrary/components/innovationSubject/technologyCompany/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/keyLab",
name: "KeyLab",
component: KeyLab,
meta: {
title: '重点实验室', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/researchUniversity",
name: "ResearchUniversity",
component: ResearchUniversity,
meta: {
title: '研究型大学', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/technologyCompany",
name: "TechnologyCompany",
component: TechnologyCompany,
meta: {
title: '科技企业', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const Case337 = () => import('@/views/dataLibrary/components/marketControl/case337/index.vue')
const Case232 = () => import('@/views/dataLibrary/components/marketControl/case232/index.vue')
const Case301 = () => import('@/views/dataLibrary/components/marketControl/case301/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/case337",
name: "Case337",
component: Case337,
meta: {
title: '337调查', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/case232",
name: "Case232",
component: Case232,
meta: {
title: '232调查', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/case301",
name: "Case301",
component: Case301,
meta: {
title: '301调查', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const CongressMan = () => import('@/views/dataLibrary/components/technologyFigures/congressMan/index.vue')
const Minister = () => import('@/views/dataLibrary/components/technologyFigures/minister/index.vue')
const TechnologyLeader = () => import('@/views/dataLibrary/components/technologyFigures/technologyLeader/index.vue')
const ThinkTankResearcher = () => import('@/views/dataLibrary/components/technologyFigures/thinkTankResearcher/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/congressMan",
name: "CongressMan",
component: CongressMan,
meta: {
title: '国会议员', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/minister",
name: "Minister",
component: Minister,
meta: {
title: '机构主官', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/technologyLeader",
name: "TechnologyLeader",
component: TechnologyLeader,
meta: {
title: '科技企业领袖', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/thinkTankResearcher",
name: "ThinkTankResearcher",
component: ThinkTankResearcher,
meta: {
title: '智库研究人员', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const ThinkTank = () => import('@/views/dataLibrary/components/thinkTank/index.vue')
const dataThinkTankRoutes = [
// 科技法案资源库路由
{
path: "/dataThinkTank",
name: "DataThinkTank",
component: ThinkTank,
meta: {
title: '科技智库', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataThinkTankRoutes
\ No newline at end of file
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
const Home = () => import('@/views/home/index.vue')
const DataLibrary = () => import('@/views/dataLibrary/index.vue')
// 自动导入所有模块路由 // 自动导入所有模块路由
const modules = import.meta.glob('./modules/*.js', { eager: true }) const modules = import.meta.glob('./modules/*.js', { eager: true })
...@@ -9,8 +11,35 @@ const fileRoutes = Object.keys(modules).reduce((acc, path) => { ...@@ -9,8 +11,35 @@ const fileRoutes = Object.keys(modules).reduce((acc, path) => {
return acc return acc
}, []) }, [])
// 自动导入所有资源库模块路由
const datas = import.meta.glob('./dataLibrary/*.js', { eager: true })
const dataRoutes = Object.keys(datas).reduce((acc, path) => {
const module = datas[path].default
acc.push(...module)
return acc
}, [])
const routes = [ const routes = [
...fileRoutes, {
path: "/",
name: "Home",
component: Home,
children: [
...fileRoutes
]
},
{
path: "/dataLibrary",
name: "DataLibrary",
component: DataLibrary,
children: [
...dataRoutes
]
},
]; ];
......
import { defineStore } from 'pinia'
const useTagsViewStore = defineStore('tags-view', {
state: () => ({
visitedViews: [], // 存放打开的标签页列表 [{ path, title, name, affix }]
cachedViews: [] // 存放需要缓存的组件名称 (用于 keep-alive)
}),
actions: {
// 添加标签页
addView(view) {
this.addVisitedView(view)
this.addCachedView(view)
},
addVisitedView(view) {
this.visitedViews.forEach(item => {
item.active = false
})
// 防止重复添加
const isExists = this.visitedViews.some(v => v.path === view.path)
if (!isExists) {
// 可以给不同路由设置不同的标题,比如从 route.meta.title 获取
this.visitedViews.push({
...view,
title: view.meta?.title || '未命名'
})
} else {
this.visitedViews.forEach(v => {
if (v.path === view.path) {
v.active = true
}
})
}
},
addCachedView(view) {
// 只有配置了 keepAlive: true 的路由才加入缓存数组
if (view.meta?.keepAlive && !this.cachedViews.includes(view.name)) {
this.cachedViews.push(view.name)
}
},
// 关闭当前标签页
delView(view) {
return new Promise((resolve) => {
// 1. 先从缓存数组中移除(如果有)
this.delCachedView(view)
// 2. 再从访问数组中移除
const index = this.visitedViews.findIndex(v => v.path === view.path)
if (index !== -1) {
this.visitedViews.splice(index, 1)
}
resolve([...this.visitedViews])
})
},
// 从缓存中移除
delCachedView(view) {
if (view.meta?.keepAlive) {
const index = this.cachedViews.indexOf(view.name)
if (index !== -1) {
this.cachedViews.splice(index, 1)
}
}
},
// 关闭其他/右侧/全部
delOthersViews(view) {
// ...
}
}
})
export default useTagsViewStore
\ No newline at end of file
<template>
<el-row class="wrapper layout-grid-line">
<el-col :span="span">
<pre>
{{
`
import TimeTabPane from '@/components/base/TimeTabPane/index.vue'
<TimeTabPane @time-click="handleTimeClick" />
`
}}
</pre>
<div class="time-box">
<TimeTabPane @time-click="handleTimeClick" />
</div>
</el-col>
</el-row>
</template>
<script setup>
import { ref } from 'vue'
import '@/styles/common.scss'
import TimeTabPane from '@/components/base/TimeTabPane/index.vue'
const span = 12
const handleTimeClick = (val) => {
console.log('val',val);
}
</script>
<style lang="scss" scoped>
.time-box {
width: 700px;
height: 400px;
padding: 100px;
background: #F2F8FF;
border: 1px solid var(--bg-black-5);
}
</style>
\ No newline at end of file
<template>
<el-row class="wrapper layout-grid-line">
<el-col :span="span">
<pre>
{{
`
import TreeChart from '@/components/base/TreeChart/index.vue'
<div class="chart-box">
<TreeChart :treeData="treeData" />
</div>
`
}}
</pre>
<div class="chart-box">
<TreeChart :treeData="treeData" />
</div>
</el-col>
</el-row>
</template>
<script setup>
import { ref } from 'vue'
import '@/styles/common.scss'
import TreeChart from '@/components/base/TreeChart/index.vue'
import CompanyImg from "@/assets/icons/symbol.png";
const span = 12
const treeData = ref([
{
id: 1,
name: 'a1',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
children: [
{
id: 11,
name: 'b1',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 12,
name: 'b2',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 13,
name: 'b3',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 14,
name: 'b4',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
}
]
}
])
</script>
<style lang="scss" scoped>
.chart-box {
width: 700px;
height: 400px;
border: 1px solid var(--bg-black-5);
}
</style>
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { ElRow, ElCol } from 'element-plus'; import { ElRow, ElCol } from 'element-plus';
import '@/styles/common.scss' import '@/styles/common.scss'
import WarnningPane from '@/components/base/WarningPane/index.vue' import WarnningPane from '@/components/base/WarningPane/index.vue'
const span = 12 const span = 24
</script> </script>
<template> <template>
...@@ -11,20 +11,32 @@ const span = 12 ...@@ -11,20 +11,32 @@ const span = 12
<pre> <pre>
{{ {{
`import WarnningPane from '@/components/base/WarningPane/index.vue'; `import WarnningPane from '@/components/base/WarningPane/index.vue';
<template> <template>
<WarnningPane warnningLevel="特别重大风险" warnningContent="我是特别重大风险内容文字我是特别重大风险内容文字"> <WarnningPane warnningLevel="特别重大风险" warnningContent="我是特别重大风险内容我是特别重大风险内容我是特别重大风险内容" />
</WarnningPane> <WarnningPane warnningLevel="重大风险" warnningContent="我是重大风险内容我是重大风险内容我是重大风险内容" />
</template> <WarnningPane warnningLevel="较大风险" warnningContent="我是较大风险内容我是较大风险内容我是较大风险内容" />
`}} <WarnningPane warnningLevel="一般风险" warnningContent="我是一般风险内容我是一般风险内容我是一般风险内容" />
<WarnningPane warnningLevel="低风险" warnningContent="我是低风险内容我是低风险内容我是低风险内容" />
</template>
`
}}
</pre> </pre>
<WarnningPane warnningLevel="特别重大风险" warnningContent="我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字"> <div class="warnning-box">
</WarnningPane> <WarnningPane warnningLevel="特别重大风险" warnningContent="我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字" />
<WarnningPane warnningLevel="重大风险" warnningContent="我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容我是重大风险内容" />
<WarnningPane warnningLevel="较大风险" warnningContent="我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容我是较大风险内容" />
<WarnningPane warnningLevel="一般风险" warnningContent="我是一般风险内容我是一般风险内容我是一般风险内容我是一般风险内容我是一般风险内容我是一般风险内容我是一般风险内容我是一般风险内容我是一般风险内容我是一般风险内容我是一般风险内容我是一般风险内容我是一般风险内容" />
<WarnningPane warnningLevel="低风险" warnningContent="我是低风险内容我是低风险内容我是低风险内容我是低风险内容我是低风险内容我是低风险内容我是低风险内容我是低风险内容我是低风险内容我是低风险内容我是低风险内容" />
</div>
</el-col> </el-col>
</el-row> </el-row>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.person-avatar { .warnning-box{
width: 200px; display: flex;
flex-direction: column;
gap: 8px;
} }
</style> </style>
\ No newline at end of file
...@@ -35,6 +35,11 @@ const data = ref([ ...@@ -35,6 +35,11 @@ const data = ref([
{ name: "选举压力", value: 57 }, { name: "选举压力", value: 57 },
{ name: "主张财政紧缩", value: 72 }, { name: "主张财政紧缩", value: 72 },
{ name: "财政保守", value: 18 }, { name: "财政保守", value: 18 },
{ name: "财政保守1", value: 25 },
{ name: "财政保守2", value: 46 },
{ name: "财政保守3", value: 72 },
{ name: "财政保守4", value: 69 },
{ name: "财政保守5", value: 53 },
]) ])
</script> </script>
......
...@@ -40,12 +40,18 @@ ...@@ -40,12 +40,18 @@
<el-tab-pane label="按钮" lazy> <el-tab-pane label="按钮" lazy>
<ActionButton /> <ActionButton />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="时间选择框" lazy>
<TimeTabPane />
</el-tab-pane>
<el-tab-pane label="层级关系图" lazy> <el-tab-pane label="层级关系图" lazy>
<GraphChart /> <GraphChart />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="引力关系图" lazy> <el-tab-pane label="引力关系图" lazy>
<GraphTreeChart /> <GraphTreeChart />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="树状图" lazy>
<TreeChart />
</el-tab-pane>
<el-tab-pane label="词云图" lazy> <el-tab-pane label="词云图" lazy>
<WordCloudChart /> <WordCloudChart />
</el-tab-pane> </el-tab-pane>
...@@ -71,10 +77,12 @@ import PeoplePage from './People/index.vue'; ...@@ -71,10 +77,12 @@ import PeoplePage from './People/index.vue';
import WarnningPane from './WarnningPane/index.vue' import WarnningPane from './WarnningPane/index.vue'
import GraphChart from './GraphChart/index.vue' import GraphChart from './GraphChart/index.vue'
import GraphTreeChart from './GraphTreeChart/index.vue' import GraphTreeChart from './GraphTreeChart/index.vue'
import TreeChart from './TreeChart/index.vue'
import AreaTag from './AreaTag/index.vue' import AreaTag from './AreaTag/index.vue'
import ActionButton from './ActionButton/index.vue' import ActionButton from './ActionButton/index.vue'
import WordCloudChart from './WordCloudChart/index.vue' import WordCloudChart from './WordCloudChart/index.vue'
import NewsPage from './News/index.vue' import NewsPage from './News/index.vue'
import TimeTabPane from './TimeTabPane/index.vue'
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
...@@ -588,22 +588,18 @@ function handleUnionItemClick(item) { ...@@ -588,22 +588,18 @@ function handleUnionItemClick(item) {
createUnionChart(); createUnionChart();
}); });
} }
// ... existing code ...
// ... existing code ... // ... existing code ...
function createChart() { function createChart() {
// 如果没有数据,直接返回
if (!countryTotalList.value || countryTotalList.value.length === 0) { if (!countryTotalList.value || countryTotalList.value.length === 0) {
console.error("No country data available"); console.error("No country data available");
return; return;
} }
// 找到最大值用于颜色计算
const maxValue = Math.max(...countryTotalList.value.map(item => item.value)); const maxValue = Math.max(...countryTotalList.value.map(item => item.value));
console.log("全部国家数据 countryTotalList =>", countryTotalList.value); console.log("全部国家数据 countryTotalList =>", countryTotalList.value);
// 为每个数据项计算颜色
const processedData = countryTotalList.value.map(item => { const processedData = countryTotalList.value.map(item => {
const color = getColorByValueRandom(item.value, maxValue); const color = getColorByValueRandom(item.value, maxValue);
return { return {
...@@ -621,12 +617,12 @@ function createChart() { ...@@ -621,12 +617,12 @@ function createChart() {
}; };
}); });
// 构建基础地图配置
const option = { const option = {
geo: { geo: {
map: "world", map: "world",
roam: true, roam: true,
zoom: 1.2, zoom: 1.2,
// center: [104.1954, 35.8617], // 设置地图中心点为中国
label: { label: {
show: false show: false
}, },
...@@ -687,7 +683,6 @@ function createChart() { ...@@ -687,7 +683,6 @@ function createChart() {
] ]
}; };
// 如果有选中的国家,添加关系线
if ( if (
currentSelectedCountry.value && currentSelectedCountry.value &&
currentSelectedCountry.value.memberRelation && currentSelectedCountry.value.memberRelation &&
...@@ -698,8 +693,19 @@ function createChart() { ...@@ -698,8 +693,19 @@ function createChart() {
currentSelectedCountry.value.zhName || currentSelectedCountry.value.zhName ||
nameMap[currentSelectedCountry.value.name] || nameMap[currentSelectedCountry.value.name] ||
currentSelectedCountry.value.name; currentSelectedCountry.value.name;
const sourceCoord = countryCoordMap[sourceCountryName]; const sourceCoord = countryCoordMap[sourceCountryName];
if (!sourceCoord) {
console.warn(`无法找到源国家 ${sourceCountryName} 的坐标`);
return;
}
console.log("=== 源国家信息 ===");
console.log("源国家名称:", sourceCountryName);
console.log("源国家坐标:", sourceCoord);
console.log("==================");
const validRelations = relations.filter(relation => { const validRelations = relations.filter(relation => {
return relation.tagetMemberName && relation.tagetMemberCount; return relation.tagetMemberName && relation.tagetMemberCount;
}); });
...@@ -711,10 +717,12 @@ function createChart() { ...@@ -711,10 +717,12 @@ function createChart() {
const targetPoints = []; const targetPoints = [];
validRelations.forEach(relation => { validRelations.forEach(relation => {
const targetCountry = relation.tagetMemberName; const targetCountryZhName = relation.tagetMemberName;
const targetCoord = countryCoordMap[targetCountry]; const targetCoord = countryCoordMap[targetCountryZhName];
if (targetCoord) { if (targetCoord) {
console.log(`目标国家:${targetCountryZhName}, 坐标:`, targetCoord);
const ratio = relation.tagetMemberCount / maxRelationCount; const ratio = relation.tagetMemberCount / maxRelationCount;
const r = Math.round(5 + (255 - 5) * ratio); const r = Math.round(5 + (255 - 5) * ratio);
const g = Math.round(95 + (77 - 95) * ratio); const g = Math.round(95 + (77 - 95) * ratio);
...@@ -724,9 +732,9 @@ function createChart() { ...@@ -724,9 +732,9 @@ function createChart() {
const lineWidth = 1 + (relation.tagetMemberCount / maxRelationCount) * 7; const lineWidth = 1 + (relation.tagetMemberCount / maxRelationCount) * 7;
linesData.push({ linesData.push({
name: `${sourceCountryName} - ${targetCountry}`, name: `${sourceCountryName} - ${targetCountryZhName}`,
sourceName: sourceCountryName, sourceName: sourceCountryName,
targetName: targetCountry, targetName: targetCountryZhName,
coords: [sourceCoord, targetCoord], coords: [sourceCoord, targetCoord],
value: relation.tagetMemberCount, value: relation.tagetMemberCount,
lineStyle: { lineStyle: {
...@@ -737,13 +745,15 @@ function createChart() { ...@@ -737,13 +745,15 @@ function createChart() {
}); });
targetPoints.push({ targetPoints.push({
name: targetCountry, name: targetCountryZhName,
value: targetCoord, value: targetCoord,
symbolSize: 8 + (relation.tagetMemberCount / maxRelationCount) * 7, symbolSize: 8 + (relation.tagetMemberCount / maxRelationCount) * 7,
itemStyle: { itemStyle: {
color: lineColor color: lineColor
} }
}); });
} else {
console.warn(`无法找到目标国家 ${targetCountryZhName} 的坐标`);
} }
}); });
...@@ -795,244 +805,29 @@ function createChart() { ...@@ -795,244 +805,29 @@ function createChart() {
// ... existing code ... // ... existing code ...
// ... existing code ...
// 处理国家列表项点击事件 // 处理国家列表项点击事件
// 在 handleCountryClick 中添加调试代码
const handleCountryClick = country => { const handleCountryClick = country => {
currentSelectedCountry.value = country; currentSelectedCountry.value = country;
console.log("国家之间的关系 =>", country); console.log("=== 点击的国家信息 ===");
console.log("国家中文名:", country.zhName);
console.log("国家英文名:", country.name);
// 检查坐标映射
const coordFromZhName = countryCoordMap[country.zhName];
const coordFromEnName = countryCoordMap[country.name];
const coordFromMappedName = countryCoordMap[nameMap[country.name]];
console.log("通过中文名获取坐标:", coordFromZhName);
console.log("通过英文名获取坐标:", coordFromEnName);
console.log("通过映射名获取坐标:", coordFromMappedName);
console.log("=====================");
nextTick(() => { nextTick(() => {
createChart(); createChart();
}); });
}; };
const countryMock = {
name: "Australia",
ename: "Commonwealth of Australia",
image: "http://8.140.26.4:10010/kjb-files/images/cr_flag/AUS.jpg",
count: 7,
memberRelation: [
{
tagetMemberName: "美国",
tagetMemberCount: 7
},
{
tagetMemberName: "英国",
tagetMemberCount: 6
},
{
tagetMemberName: "日本",
tagetMemberCount: 5
},
{
tagetMemberName: "韩国",
tagetMemberCount: 4
},
{
tagetMemberName: "印度",
tagetMemberCount: 4
},
{
tagetMemberName: "新西兰",
tagetMemberCount: 3
},
{
tagetMemberName: "加拿大",
tagetMemberCount: 3
},
{
tagetMemberName: "挪威",
tagetMemberCount: 2
},
{
tagetMemberName: "德国",
tagetMemberCount: 2
},
{
tagetMemberName: "法国",
tagetMemberCount: 2
},
{
tagetMemberName: "意大利",
tagetMemberCount: 2
},
{
tagetMemberName: "荷兰",
tagetMemberCount: 2
},
{
tagetMemberName: "芬兰",
tagetMemberCount: 2
},
{
tagetMemberName: "瑞典",
tagetMemberCount: 2
},
{
tagetMemberName: "爱沙尼亚",
tagetMemberCount: 2
},
{
tagetMemberName: "新加坡",
tagetMemberCount: 2
},
{
tagetMemberName: "泰国",
tagetMemberCount: 1
},
{
tagetMemberName: "文莱",
tagetMemberCount: 1
},
{
tagetMemberName: "丹麦",
tagetMemberCount: 1
},
{
tagetMemberName: "克罗地亚",
tagetMemberCount: 1
},
{
tagetMemberName: "卢森堡",
tagetMemberCount: 1
},
{
tagetMemberName: "欧盟",
tagetMemberCount: 1
},
{
tagetMemberName: "阿根廷",
tagetMemberCount: 1
},
{
tagetMemberName: "越南",
tagetMemberCount: 1
},
{
tagetMemberName: "罗马尼亚",
tagetMemberCount: 1
},
{
tagetMemberName: "立陶宛",
tagetMemberCount: 1
},
{
tagetMemberName: "以色列",
tagetMemberCount: 1
},
{
tagetMemberName: "阿联酋",
tagetMemberCount: 1
},
{
tagetMemberName: "斐济",
tagetMemberCount: 1
},
{
tagetMemberName: "墨西哥",
tagetMemberCount: 1
},
{
tagetMemberName: "希腊",
tagetMemberCount: 1
},
{
tagetMemberName: "捷克",
tagetMemberCount: 1
},
{
tagetMemberName: "保加利亚",
tagetMemberCount: 1
},
{
tagetMemberName: "马耳他",
tagetMemberCount: 1
},
{
tagetMemberName: "匈牙利",
tagetMemberCount: 1
},
{
tagetMemberName: "波兰",
tagetMemberCount: 1
},
{
tagetMemberName: "瑞士",
tagetMemberCount: 1
},
{
tagetMemberName: "奥地利",
tagetMemberCount: 1
},
{
tagetMemberName: "俄罗斯",
tagetMemberCount: 1
},
{
tagetMemberName: "斯洛文尼亚",
tagetMemberCount: 1
},
{
tagetMemberName: "土耳其",
tagetMemberCount: 1
},
{
tagetMemberName: "西班牙",
tagetMemberCount: 1
},
{
tagetMemberName: "斯洛伐克",
tagetMemberCount: 1
},
{
tagetMemberName: "比利时",
tagetMemberCount: 1
},
{
tagetMemberName: "乌克兰",
tagetMemberCount: 1
},
{
tagetMemberName: "南非",
tagetMemberCount: 1
},
{
tagetMemberName: "拉脱维亚",
tagetMemberCount: 1
},
{
tagetMemberName: "马来西亚",
tagetMemberCount: 1
},
{
tagetMemberName: "爱尔兰",
tagetMemberCount: 1
},
{
tagetMemberName: "葡萄牙",
tagetMemberCount: 1
},
{
tagetMemberName: "菲律宾",
tagetMemberCount: 1
}
],
value: 7,
zhName: "澳大利亚"
};
// function initMap() {
// chartDom.value = document.getElementById("echartsMap");
// if (!chartDom.value) return;
// if (myChart.value) myChart.value.dispose();
// myChart.value = echarts.init(chartDom.value);
// myChart.value.showLoading();
// echarts.registerMap("world", mapJson);
// createChart();
// myChart.value.hideLoading();
// }
function initMap() { function initMap() {
chartDom.value = document.getElementById("echartsMap"); chartDom.value = document.getElementById("echartsMap");
unionChartDom.value = document.getElementById("echartsUnionMap"); unionChartDom.value = document.getElementById("echartsUnionMap");
......
...@@ -69,7 +69,13 @@ ...@@ -69,7 +69,13 @@
<div class="right-num">参与排华联盟</div> <div class="right-num">参与排华联盟</div>
</div> </div>
</div> </div>
<div class="item" v-for="(item, index) in countList" :key="index" @click="handleCountryClick(item)"> <div
class="item"
v-for="(item, index) in countList"
:key="index"
@click="handleCountryClick(item)"
:class="{ 'selected-country': currentSelectedCountry && currentSelectedCountry.name === item.name }"
>
<div class="item-left"> <div class="item-left">
<img :src="item.image" alt /> <img :src="item.image" alt />
<el-tooltip <el-tooltip
...@@ -298,7 +304,7 @@ import { link } from "d3"; ...@@ -298,7 +304,7 @@ import { link } from "d3";
import { get, union, update } from "lodash"; import { get, union, update } from "lodash";
import ButtonList from "@/components/buttonList/buttonList.vue"; import ButtonList from "@/components/buttonList/buttonList.vue";
import { fieldOptions, COLORS, countryNameMap, nameMap } from "@/views/ZMOverView/public.js"; import { fieldOptions, COLORS, countryNameMap, nameMap } from "@/views/ZMOverView/public.js";
import { countryCoordMap } from "@/assets/json/countryCoordMap.js"; import { countryCoordMap, convertAsiaCenterCoord } from "@/assets/json/countryCoordMap.js";
const buttonList = ref([ const buttonList = ref([
{ {
...@@ -589,8 +595,6 @@ function handleUnionItemClick(item) { ...@@ -589,8 +595,6 @@ function handleUnionItemClick(item) {
}); });
} }
// ... existing code ...
function createChart() { function createChart() {
if (!countryTotalList.value || countryTotalList.value.length === 0) { if (!countryTotalList.value || countryTotalList.value.length === 0) {
console.error("No country data available"); console.error("No country data available");
...@@ -622,7 +626,6 @@ function createChart() { ...@@ -622,7 +626,6 @@ function createChart() {
map: "world", map: "world",
roam: true, roam: true,
zoom: 1.2, zoom: 1.2,
// center: [104.1954, 35.8617], // 设置地图中心点为中国
label: { label: {
show: false show: false
}, },
...@@ -672,6 +675,8 @@ function createChart() { ...@@ -672,6 +675,8 @@ function createChart() {
type: "scatter", type: "scatter",
coordinateSystem: "geo", coordinateSystem: "geo",
geoIndex: 0, geoIndex: 0,
nameMap: nameMap,
data: processedData, data: processedData,
symbolSize: function (val) { symbolSize: function (val) {
return Math.max(val.value / 5, 5); return Math.max(val.value / 5, 5);
...@@ -689,20 +694,36 @@ function createChart() { ...@@ -689,20 +694,36 @@ function createChart() {
Array.isArray(currentSelectedCountry.value.memberRelation) Array.isArray(currentSelectedCountry.value.memberRelation)
) { ) {
const relations = currentSelectedCountry.value.memberRelation; const relations = currentSelectedCountry.value.memberRelation;
const sourceCountryName =
currentSelectedCountry.value.zhName ||
nameMap[currentSelectedCountry.value.name] ||
currentSelectedCountry.value.name;
const sourceCoord = countryCoordMap[sourceCountryName]; const sourceCountryZhName = currentSelectedCountry.value.zhName;
const sourceCountryEnName = currentSelectedCountry.value.name;
let sourceCoord = null;
let finalSourceName = "";
if (sourceCountryZhName && countryCoordMap[sourceCountryZhName]) {
const convertedCoord = convertAsiaCenterCoord(countryCoordMap[sourceCountryZhName]);
sourceCoord = convertedCoord;
finalSourceName = sourceCountryZhName;
} else if (sourceCountryEnName) {
const mappedName = nameMap[sourceCountryEnName] || countryNameMap[sourceCountryEnName];
const zhName = mappedName || sourceCountryEnName;
if (countryCoordMap[zhName]) {
const convertedCoord = convertAsiaCenterCoord(countryCoordMap[zhName]);
sourceCoord = convertedCoord;
finalSourceName = zhName;
}
}
if (!sourceCoord) { if (!sourceCoord) {
console.warn(`无法找到源国家 ${sourceCountryName} 的坐标`); console.warn(`无法找到源国家 ${sourceCountryZhName || sourceCountryEnName} 的坐标`);
return; return;
} }
console.log("=== 源国家信息 ==="); console.log("=== 源国家信息 ===");
console.log("源国家名称:", sourceCountryName); console.log("源国家中文名:", sourceCountryZhName);
console.log("源国家英文名:", sourceCountryEnName);
console.log("最终使用的国家名称:", finalSourceName);
console.log("源国家坐标:", sourceCoord); console.log("源国家坐标:", sourceCoord);
console.log("=================="); console.log("==================");
...@@ -718,23 +739,46 @@ function createChart() { ...@@ -718,23 +739,46 @@ function createChart() {
validRelations.forEach(relation => { validRelations.forEach(relation => {
const targetCountryZhName = relation.tagetMemberName; const targetCountryZhName = relation.tagetMemberName;
const targetCoord = countryCoordMap[targetCountryZhName];
let targetCoord = null;
let finalTargetName = "";
if (countryCoordMap[targetCountryZhName]) {
const convertedCoord = convertAsiaCenterCoord(countryCoordMap[targetCountryZhName]);
targetCoord = convertedCoord;
finalTargetName = targetCountryZhName;
} else {
const possibleNames = [
targetCountryZhName,
nameMap[targetCountryZhName],
countryNameMap[targetCountryZhName]
];
for (const name of possibleNames) {
if (name && countryCoordMap[name]) {
const convertedCoord = convertAsiaCenterCoord(countryCoordMap[name]);
targetCoord = convertedCoord;
finalTargetName = name;
break;
}
}
}
if (targetCoord) { if (targetCoord) {
console.log(`目标国家:${targetCountryZhName}, 坐标:`, targetCoord); console.log(`目标国家:${targetCountryZhName}, 最终名称:${finalTargetName}, 坐标:`, targetCoord);
const ratio = relation.tagetMemberCount / maxRelationCount; const ratio = relation.tagetMemberCount / maxRelationCount;
const r = Math.round(5 + (255 - 5) * ratio); const r = Math.round(5 + (255 - 5) * ratio);
const g = Math.round(95 + (77 - 95) * ratio); const g = Math.round(95 + (77 - 95) * ratio);
const b = Math.round(194 + (79 - 194) * ratio); const b = Math.round(194 + (194 - 194) * ratio);
const lineColor = `rgb(${r}, ${g}, ${b})`; const lineColor = `rgb(${r}, ${g}, ${b})`;
const lineWidth = 1 + (relation.tagetMemberCount / maxRelationCount) * 7; const lineWidth = 1 + (relation.tagetMemberCount / maxRelationCount) * 7;
linesData.push({ linesData.push({
name: `${sourceCountryName} - ${targetCountryZhName}`, name: `${finalSourceName} - ${finalTargetName}`,
sourceName: sourceCountryName, sourceName: finalSourceName,
targetName: targetCountryZhName, targetName: finalTargetName,
coords: [sourceCoord, targetCoord], coords: [sourceCoord, targetCoord],
value: relation.tagetMemberCount, value: relation.tagetMemberCount,
lineStyle: { lineStyle: {
...@@ -745,7 +789,7 @@ function createChart() { ...@@ -745,7 +789,7 @@ function createChart() {
}); });
targetPoints.push({ targetPoints.push({
name: targetCountryZhName, name: finalTargetName,
value: targetCoord, value: targetCoord,
symbolSize: 8 + (relation.tagetMemberCount / maxRelationCount) * 7, symbolSize: 8 + (relation.tagetMemberCount / maxRelationCount) * 7,
itemStyle: { itemStyle: {
...@@ -753,7 +797,7 @@ function createChart() { ...@@ -753,7 +797,7 @@ function createChart() {
} }
}); });
} else { } else {
console.warn(`无法找到目标国家 ${targetCountryZhName} 的坐标`); console.warn(`无法找到目标国家 ${targetCountryZhName} 的坐标,尝试的名称都不匹配`);
} }
}); });
...@@ -776,7 +820,7 @@ function createChart() { ...@@ -776,7 +820,7 @@ function createChart() {
}); });
targetPoints.unshift({ targetPoints.unshift({
name: sourceCountryName, name: finalSourceName,
value: sourceCoord, value: sourceCoord,
symbolSize: 15, symbolSize: 15,
itemStyle: { itemStyle: {
...@@ -803,44 +847,24 @@ function createChart() { ...@@ -803,44 +847,24 @@ function createChart() {
} }
} }
// ... existing code ...
// 处理国家列表项点击事件 // 处理国家列表项点击事件
// 在 handleCountryClick 中添加调试代码
const handleCountryClick = country => { const handleCountryClick = country => {
currentSelectedCountry.value = country; if (currentSelectedCountry.value?.name === country.name) {
// currentSelectedCountry.value = null;
return;
} else {
currentSelectedCountry.value = country;
}
console.log("=== 点击的国家信息 ==="); console.log("=== 点击的国家信息 ===");
console.log("国家中文名:", country.zhName); console.log("国家中文名:", country.zhName);
console.log("国家英文名:", country.name); console.log("国家英文名:", country.name);
console.log("联盟关系数据:", country.memberRelation);
// 检查坐标映射
const coordFromZhName = countryCoordMap[country.zhName];
const coordFromEnName = countryCoordMap[country.name];
const coordFromMappedName = countryCoordMap[nameMap[country.name]];
console.log("通过中文名获取坐标:", coordFromZhName);
console.log("通过英文名获取坐标:", coordFromEnName);
console.log("通过映射名获取坐标:", coordFromMappedName);
console.log("=====================");
nextTick(() => { nextTick(() => {
createChart(); createChart();
}); });
}; };
// function initMap() {
// chartDom.value = document.getElementById("echartsMap");
// if (!chartDom.value) return;
// if (myChart.value) myChart.value.dispose();
// myChart.value = echarts.init(chartDom.value);
// myChart.value.showLoading();
// echarts.registerMap("world", mapJson);
// createChart();
// myChart.value.hideLoading();
// }
function initMap() { function initMap() {
chartDom.value = document.getElementById("echartsMap"); chartDom.value = document.getElementById("echartsMap");
unionChartDom.value = document.getElementById("echartsUnionMap"); unionChartDom.value = document.getElementById("echartsUnionMap");
...@@ -1489,22 +1513,21 @@ const getPredictionList = async () => { ...@@ -1489,22 +1513,21 @@ const getPredictionList = async () => {
}; };
// 获取排华联盟数量 // 获取排华联盟数量
const getUnionCountList = async () => { const getUnionCountList = async () => {
try { try {
const res = await getUnionCount({ page: 1, pageSize: 100, domainId: selectedFieldForLatest.value }); const res = await getUnionCount({ page: 1, pageSize: 100, domainId: selectedFieldForLatest.value });
if (res && res.code === 200) { if (res && res.code === 200) {
console.log("----getUnionCountList", res.data); console.log("----getUnionCountList", res.data);
// 处理一下数据
countryTotalList.value = res.data.content countryTotalList.value = res.data.content
.sort((a, b) => b.count - a.count) .sort((a, b) => b.count - a.count)
.map(item => { .map(item => {
item.value = item.count; item.value = item.count;
item.zhName = item.name; item.zhName = item.name;
// 1. 尝试直接从映射表获取
let mappedName = countryNameMap[item.ename]; let mappedName = countryNameMap[item.ename];
// 2. 如果映射表没有,尝试简单的模糊匹配
if (!mappedName && item.ename) { if (!mappedName && item.ename) {
let tempName = item.ename let tempName = item.ename
.replace(/Republic of /i, "") .replace(/Republic of /i, "")
...@@ -1517,7 +1540,8 @@ const getUnionCountList = async () => { ...@@ -1517,7 +1540,8 @@ const getUnionCountList = async () => {
mappedName = tempName; mappedName = tempName;
} }
item.name = mappedName || item.ename; item.name = nameMap[item.ename] || mappedName || item.ename;
item.originalEnName = item.ename;
return item; return item;
}); });
...@@ -1878,11 +1902,15 @@ watch(activeDate, async () => { ...@@ -1878,11 +1902,15 @@ watch(activeDate, async () => {
padding: 4px 12px 4px 12px; padding: 4px 12px 4px 12px;
box-sizing: border-box; box-sizing: border-box;
// border: 1px solid rgba(234, 236, 238, 1); // border: 1px solid rgba(234, 236, 238, 1);
border-radius: 50px; // border-radius: 50px;
/* 业务系统/模态背景模糊 */ /* 业务系统/模态背景模糊 */
backdrop-filter: blur(30px); backdrop-filter: blur(30px);
background: rgba(255, 255, 255, 0.65); background: rgba(255, 255, 255, 0.65);
&.selected-country {
background-color: rgb(246, 250, 255) !important;
}
.item-left { .item-left {
display: flex; display: flex;
align-items: center; align-items: center;
......
<template>
<div v-if="visible" ref="dialogRef" class="dialog-wrapper" :style="position">
<div class="dialog-box1" @mousedown="handleMouseDown">
<div class="icon">
<img v-if="detailItem.orgName === '参议院'" :src="logoSenate" alt="" />
<img v-else :src="logoHouse" alt="" />
</div>
<div class="title">
<div class="date">{{ detailItem.actionDate }}</div>
<div class="text">
{{ detailItem.actionTitle }}
</div>
</div>
<div class="close" @click="handleClose">
<img :src="closeIcon" alt="" />
</div>
</div>
<div class="dialog-box2" v-if="detailItem.agreeVote !== null || detailItem.disagreeVote !== null">
<div class="vote-bar">
<div class="agree-bar" :style="{ flex: detailItem.agreeVote || 1 }"></div>
<div class="disagree-bar" :style="{ flex: detailItem.disagreeVote || 1 }"></div>
</div>
<div class="vote-text">
<div class="agree-text">{{ (detailItem.agreeVote || 0) + "赞成" }}</div>
<div class="disagree-text">{{ (detailItem.disagreeVote || 0) + "反对" }}</div>
</div>
</div>
<template v-if="detailItem.fynrList && detailItem.fynrList.length">
<div class="dialog-box4">
<div class="box4-left">
<div class="icon">
<img :src="changeIcon" alt="" />
</div>
<div class="text">{{ "变更条款" }}</div>
</div>
</div>
<div class="dialog-box5">
<div class="box5-item" v-for="(sub, subIndex) in detailItem.fynrList" :key="subIndex + '-' + sub">
<div class="icon"></div>
<div class="text">{{ sub }}</div>
</div>
</div>
</template>
</div>
</template>
<script setup>
import { ref } from "vue";
defineProps({
visible: {
type: Boolean,
default: false,
},
detailItem: {
type: Object,
default: () => ({}),
},
position: {
type: Object,
default: () => ({ left: "0px", top: "0px" }),
},
});
const emit = defineEmits(["close"]);
const logoSenate = new URL("@/views/bill/deepDig/processOverview/assets/images/logo1.png", import.meta.url).href;
const logoHouse = new URL("@/views/bill/deepDig/processOverview/assets/images/logo2.png", import.meta.url).href;
const closeIcon = new URL("@/views/bill/deepDig/processOverview/assets/images/close.png", import.meta.url).href;
const changeIcon = new URL("@/views/bill/deepDig/processOverview/assets/images/dialog-box4-icon.png", import.meta.url).href;
const dialogRef = ref(null);
const handleClose = () => {
emit("close");
};
const handleMouseDown = (e) => {
const dialog = dialogRef.value;
if (!dialog) return;
const startX = e.clientX;
const startY = e.clientY;
const initialLeft = dialog.offsetLeft;
const initialTop = dialog.offsetTop;
const move = (moveEvent) => {
const deltaX = moveEvent.clientX - startX;
const deltaY = moveEvent.clientY - startY;
dialog.style.right = "auto";
dialog.style.left = `${initialLeft + deltaX}px`;
dialog.style.top = `${initialTop + deltaY}px`;
};
const stop = () => {
document.removeEventListener("mousemove", move);
document.removeEventListener("mouseup", stop);
};
document.addEventListener("mousemove", move);
document.addEventListener("mouseup", stop);
};
</script>
<style lang="scss" scoped>
.dialog-wrapper {
position: absolute;
width: 480px;
padding-bottom: 20px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
z-index: 10000;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15);
.dialog-box1 {
display: flex;
min-height: 90px;
height: auto;
padding-bottom: 15px;
position: relative;
border-bottom: 1px solid rgba(240, 242, 244, 1);
cursor: move;
.icon {
width: 48px;
height: 48px;
margin-left: 18px;
margin-top: 20px;
img {
width: 100%;
height: 100%;
}
}
.title {
margin-top: 20px;
margin-left: 16px;
width: 350px;
.date {
height: 22px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 22px;
}
.text {
margin-top: 4px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 22px;
span {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
}
}
}
.close {
position: absolute;
top: 14px;
right: 15px;
width: 16px;
height: 16px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
.dialog-box2 {
height: 59px;
padding: 0 23px;
.vote-bar {
display: flex;
height: 4px;
width: 100%;
margin-top: 20px;
border-radius: 2px;
overflow: hidden;
.agree-bar {
background: #52c41a;
margin-right: 2px;
}
.disagree-bar {
background: #f5222d;
}
}
.vote-text {
display: flex;
justify-content: space-between;
margin-top: 8px;
.agree-text {
color: #52c41a;
font-size: 14px;
}
.disagree-text {
color: #f5222d;
font-size: 14px;
}
}
}
.dialog-box4 {
width: 438px;
margin-left: 23px;
margin-top: 20px;
display: flex;
justify-content: space-between;
height: 30px;
.box4-left {
display: flex;
.icon {
margin-top: 6px;
width: 18px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 64px;
height: 30px;
margin-left: 6px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
}
}
.dialog-box5 {
margin-top: 3px;
margin-left: 23px;
width: 438px;
.box5-item {
min-height: 30px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 1.5;
display: flex;
margin-bottom: 8px;
.icon {
flex-shrink: 0;
margin-left: 15px;
width: 6px;
height: 6px;
margin-top: 9px;
border-radius: 3px;
background: #84888e;
}
.text {
margin-left: 10px;
word-break: break-all;
}
}
}
}
</style>
...@@ -647,7 +647,7 @@ onMounted(() => { ...@@ -647,7 +647,7 @@ onMounted(() => {
.icon1 { .icon1 {
position: absolute; position: absolute;
left: 5px; left: 8px;
bottom: -8px; bottom: -8px;
width: 16px; width: 16px;
height: 16px; height: 16px;
...@@ -663,7 +663,7 @@ onMounted(() => { ...@@ -663,7 +663,7 @@ onMounted(() => {
.icon2 { .icon2 {
position: absolute; position: absolute;
right: 5px; right: 8px;
bottom: -8px; bottom: -8px;
width: 16px; width: 16px;
height: 16px; height: 16px;
......
...@@ -167,7 +167,8 @@ ...@@ -167,7 +167,8 @@
<OverviewCard class="overview-card--single box9" title="涉华法案关键条款" :icon="box7HeaderIcon"> <OverviewCard class="overview-card--single box9" title="涉华法案关键条款" :icon="box7HeaderIcon">
<div class="overview-card-body box9-main"> <div class="overview-card-body box9-main">
<div class="overview-chart-wrap"> <div class="overview-chart-wrap">
<div id="wordCloudChart" class="overview-chart"></div> <el-empty v-if="!wordCloudHasData" description="暂无数据" :image-size="100" />
<WordCloundChart v-else class="overview-chart" width="100%" height="100%" :data="wordCloudData" />
</div> </div>
<TipTab class="overview-tip" /> <TipTab class="overview-tip" />
</div> </div>
...@@ -184,8 +185,8 @@ ...@@ -184,8 +185,8 @@
</template> </template>
<script setup> <script setup>
import RiskSignal from "@/components/base/RiskSignal/index.vue"; import RiskSignal from "@/components/base/riskSignal/index.vue";
import { onMounted, ref, onUnmounted, nextTick, watch } from "vue"; import { onMounted, ref, onUnmounted, nextTick, watch, computed } from "vue";
import router from "@/router/index"; import router from "@/router/index";
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
import { import {
...@@ -207,9 +208,9 @@ import OverviewCard from "./OverviewCard.vue"; ...@@ -207,9 +208,9 @@ import OverviewCard from "./OverviewCard.vue";
import ResourceLibrarySection from "./ResourceLibrarySection.vue"; import ResourceLibrarySection from "./ResourceLibrarySection.vue";
import { useContainerScroll } from "@/hooks/useScrollShow"; import { useContainerScroll } from "@/hooks/useScrollShow";
import TipTab from "@/components/base/TipTab/index.vue"; import TipTab from "@/components/base/TipTab/index.vue";
import WordCloundChart from "@/components/base/WordCloundChart/index.vue";
import getMultiLineChart from "./utils/multiLineChart"; import getMultiLineChart from "./utils/multiLineChart";
import getWordCloudChart from "./utils/worldCloudChart";
import getPieChart from "./utils/piechart"; import getPieChart from "./utils/piechart";
import getDoublePieChart from "./utils/doublePieChart"; import getDoublePieChart from "./utils/doublePieChart";
...@@ -624,6 +625,7 @@ const handleToSocialDetail = item => { ...@@ -624,6 +625,7 @@ const handleToSocialDetail = item => {
}; };
// 关键条款 // 关键条款
const wordCloudData = ref([]); const wordCloudData = ref([]);
const wordCloudHasData = computed(() => Array.isArray(wordCloudData.value) && wordCloudData.value.length > 0);
const handleGetKeyTK = async () => { const handleGetKeyTK = async () => {
try { try {
const res = await getBillOverviewKeyTK(); const res = await getBillOverviewKeyTK();
...@@ -642,8 +644,6 @@ const handleGetKeyTK = async () => { ...@@ -642,8 +644,6 @@ const handleGetKeyTK = async () => {
}; };
const handleBox6 = async () => { const handleBox6 = async () => {
await handleGetKeyTK(); await handleGetKeyTK();
const wordCloudChart = getWordCloudChart(wordCloudData.value);
setChart(wordCloudChart, "wordCloudChart");
}; };
// 涉华领域分布 // 涉华领域分布
......
<template> <template>
<div class="bill-original-text-page"> <div class="bill-original-text-page">
<div class="page-header"> <DecreeOriginal :report-data="reportData" @download="handleDownload" />
<div class="page-title">法案原文</div>
<div class="page-actions">
<div class="action-btn" @click="handleBack">返回</div>
</div>
</div>
<div class="page-content">
<iframe v-if="billFullText" :src="billFullText" width="100%" height="100%" frameborder="0"></iframe>
<div v-else class="empty-state">暂无原文</div>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from "vue"; import { ref } from "vue";
import { useRoute, useRouter } from "vue-router"; import DecreeOriginal from "@/components/base/DecreeOriginal/index.vue";
import { getBillFullText } from "@/api/bill";
const route = useRoute();
const router = useRouter();
const billFullText = ref("");
const getBillFullTextFn = async () => { // 旧“法案原文”功能/按钮全部废弃:页面仅承载通用原文组件,后续接入新接口再赋值。
const res = await getBillFullText({ const reportData = ref([]);
id: route.query.billId
});
if (res.code === 200 && res.data) {
billFullText.value = typeof res.data === "string" ? res.data.trim() : res.data;
}
};
const handleBack = () => { const handleDownload = () => {
router.back(); // 后续接入新接口/下载逻辑
}; };
onMounted(() => {
getBillFullTextFn();
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.bill-original-text-page { .bill-original-text-page {
width: 100%; width: 100%;
box-sizing: border-box; height: 100%;
background: rgba(248, 249, 250, 1);
padding: 0 0 20px;
.page-header {
width: 100%;
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
.page-title {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
}
.page-actions {
display: flex;
justify-content: flex-end;
.action-btn {
cursor: pointer;
height: 32px;
line-height: 32px;
padding: 0 12px;
border-radius: 6px;
border: 1px solid rgba(230, 231, 232, 1);
background: rgba(255, 255, 255, 1);
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
}
}
}
.page-content {
width: 100%;
height: calc(100vh - 320px);
min-height: 600px;
background: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
overflow: hidden;
iframe {
display: block;
}
.empty-state {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
}
}
} }
</style> </style>
...@@ -111,48 +111,6 @@ ...@@ -111,48 +111,6 @@
</AnalysisBox> </AnalysisBox>
</div> </div>
<div class="box3"> <div class="box3">
<!-- <div class="box-header">
<div class="icon"></div>
<div class="title">{{ "政治献金领域分布" }}</div>
<div class="header-right">
<div class="right-icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="right-icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="right-icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box3-main">
<div class="box3-main-left" id="chart2"></div>
<div class="box3-main-right">
<el-empty v-if="!areaList.length" description="暂无数据" :image-size="100" />
<div class="box3-main-right-item" v-for="(item, index) in areaList" :key="index">
<div class="id">{{ index + 1 }}</div>
<div class="name">{{ item.name }}</div>
<div class="line">
<div class="inner-line" :style="{ width: (item.num / areaList[0].num) * 100 + '%' }"></div>
</div>
<div class="num">{{ item.numtext }}</div>
<div class="more">{{ `${item.insNum}家机构 >` }}</div>
</div>
</div>
</div>
<div class="box-footer">
<div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box-footer-center">
{{ currentPersonName }}的政治资金主要依赖于一个由亿万富翁、特定行业利益集团及通过​“超级政治行动委员会”​​
运作的大额捐款网络。
</div>
<div class="box-footer-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="" />
</div>
</div> -->
<AnalysisBox title="政治献金领域分布"> <AnalysisBox title="政治献金领域分布">
<div class="box3-main" :class="{ 'box3-main-no-footer': !showHardcodedTips }"> <div class="box3-main" :class="{ 'box3-main-no-footer': !showHardcodedTips }">
<div class="box3-main-left" id="chart2"></div> <div class="box3-main-left" id="chart2"></div>
...@@ -196,6 +154,7 @@ import setChart from "@/utils/setChart"; ...@@ -196,6 +154,7 @@ import setChart from "@/utils/setChart";
import getPieChart from "./utils/piechart"; import getPieChart from "./utils/piechart";
import getSankeyChart from "./utils/sankey"; import getSankeyChart from "./utils/sankey";
import { MUTICHARTCOLORS } from "@/common/constant";
import Img1 from "./assets/images/1.png"; import Img1 from "./assets/images/1.png";
import Img2 from "./assets/images/2.png"; import Img2 from "./assets/images/2.png";
...@@ -434,23 +393,9 @@ const topAreaList = computed(() => { ...@@ -434,23 +393,9 @@ const topAreaList = computed(() => {
return areaList.value.slice(0, 5); return areaList.value.slice(0, 5);
}); });
const chart2ColorList = ref(["#4096FF", "#FFA39E", "#ADC6FF", "#FFC069", "#B5F5EC", "#B37FEB", "#D6E4FF"]); const chart2ColorList = ref([...MUTICHARTCOLORS]);
const sankeyColors = [ const sankeyColors = [...MUTICHARTCOLORS];
"#5470c6",
"#91cc75",
"#fac858",
"#ee6666",
"#73c0de",
"#3ba272",
"#fc8452",
"#9a60b4",
"#ea7ccc",
"#a2c0f1",
"#f596aa",
"#e6b422",
"#4b2c20"
];
const partyContributionList = ref([ const partyContributionList = ref([
{ {
......
...@@ -100,11 +100,11 @@ ...@@ -100,11 +100,11 @@
<div class="term-main"> <div class="term-main">
<div class="term-row term-row-cn"> <div class="term-row term-row-cn">
<div class="term-no-cn">第{{ term.tkxh }}条.</div> <div class="term-no-cn">第{{ term.tkxh }}条.</div>
<div class="term-content-cn">{{ term.fynr }}</div> <div class="term-content-cn" v-html="getTermContentHtml(term, 'cn')"></div>
</div> </div>
<div class="term-row term-row-en" v-if="termsShowOriginal"> <div class="term-row term-row-en" v-if="termsShowOriginal">
<div class="term-no-en">Sec.{{ term.tkxh }}</div> <div class="term-no-en">Sec.{{ term.tkxh }}</div>
<div class="term-content-en">{{ term.ywnr }}</div> <div class="term-content-en" v-html="getTermContentHtml(term, 'en')"></div>
</div> </div>
</div> </div>
</div> </div>
...@@ -136,6 +136,8 @@ import * as echarts from "echarts"; ...@@ -136,6 +136,8 @@ import * as echarts from "echarts";
import { Search } from "@element-plus/icons-vue"; import { Search } from "@element-plus/icons-vue";
import getPieChart from "./utils/piechart"; import getPieChart from "./utils/piechart";
import { getBillContentId, getBillContentTk, getBillContentXzfs, getBillHyly } from "@/api/bill"; import { getBillContentId, getBillContentTk, getBillContentXzfs, getBillHyly } from "@/api/bill";
import { MUTICHARTCOLORS } from "@/common/constant";
import { extractTextEntity } from "@/api/intelligent/index";
const route = useRoute(); const route = useRoute();
...@@ -161,6 +163,134 @@ const domainLoading = ref(false); ...@@ -161,6 +163,134 @@ const domainLoading = ref(false);
const termsHighlight = ref(true); const termsHighlight = ref(true);
const termsShowOriginal = ref(true); const termsShowOriginal = ref(true);
const entityRequestToken = ref(0);
const termEntityCache = ref(new Map());
const escapeHtml = value => {
const str = String(value ?? "");
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
};
const normalizeEntities = entities => {
const list = Array.isArray(entities) ? entities : [];
return list
.map(item => {
return {
text_span: String(item?.text_span ?? "").trim(),
type: String(item?.type ?? "").trim()
};
})
.filter(item => item.text_span);
};
const getEntityRanges = (text, entities) => {
const ranges = [];
const rawText = String(text ?? "");
if (!rawText) return ranges;
const list = normalizeEntities(entities).sort((a, b) => b.text_span.length - a.text_span.length);
for (const ent of list) {
let startIndex = 0;
while (startIndex < rawText.length) {
const idx = rawText.indexOf(ent.text_span, startIndex);
if (idx === -1) break;
ranges.push({ start: idx, end: idx + ent.text_span.length, ent });
startIndex = idx + ent.text_span.length;
}
}
ranges.sort((a, b) => a.start - b.start || b.end - a.end);
const merged = [];
let lastEnd = 0;
for (const r of ranges) {
if (r.start < lastEnd) continue;
merged.push(r);
lastEnd = r.end;
}
return merged;
};
const buildHighlightedHtml = (text, entities, enableHighlight) => {
const rawText = String(text ?? "");
if (!rawText) return "";
const safeText = escapeHtml(rawText).replace(/\n/g, "<br />");
if (!enableHighlight) return safeText;
const ranges = getEntityRanges(rawText, entities);
if (!ranges.length) return safeText;
let html = "";
let cursor = 0;
for (const r of ranges) {
if (cursor < r.start) {
html += escapeHtml(rawText.slice(cursor, r.start));
}
const spanText = rawText.slice(r.start, r.end);
const type = escapeHtml(r.ent?.type ?? "");
html += `<span class="term-entity" data-entity-type="${type}">${escapeHtml(spanText)}</span>`;
cursor = r.end;
}
if (cursor < rawText.length) {
html += escapeHtml(rawText.slice(cursor));
}
return html.replace(/\n/g, "<br />");
};
const getTermEntityKey = (term, lang) => {
const baseKey = getTermKey(term, -1);
return `${baseKey}__${lang}`;
};
const ensureEntitiesForTerms = async terms => {
if (!termsHighlight.value) return;
const list = Array.isArray(terms) ? terms : [];
if (!list.length) return;
const currentToken = ++entityRequestToken.value;
const tasks = [];
for (const term of list) {
const cnKey = getTermEntityKey(term, "cn");
const enKey = getTermEntityKey(term, "en");
if (!termEntityCache.value.has(cnKey) && String(term?.fynr ?? "").trim()) {
tasks.push({ key: cnKey, text: term.fynr });
}
if (!termEntityCache.value.has(enKey) && String(term?.ywnr ?? "").trim()) {
tasks.push({ key: enKey, text: term.ywnr });
}
}
if (!tasks.length) return;
try {
const results = await Promise.all(
tasks.map(async item => {
const res = await extractTextEntity(item.text);
const entities = normalizeEntities(res?.result ?? res?.data?.result ?? res?.data ?? res);
return { key: item.key, entities };
})
);
if (currentToken !== entityRequestToken.value) return;
for (const r of results) {
termEntityCache.value.set(r.key, r.entities);
}
} catch (error) {
if (currentToken !== entityRequestToken.value) return;
}
};
const getTermContentHtml = (term, lang) => {
const raw = lang === "en" ? term?.ywnr : term?.fynr;
const key = getTermEntityKey(term, lang);
const entities = termEntityCache.value.get(key) || [];
return buildHighlightedHtml(raw, entities, termsHighlight.value);
};
const tkRequestToken = ref(0); const tkRequestToken = ref(0);
const xzfsRequestToken = ref(0); const xzfsRequestToken = ref(0);
const hylyRequestToken = ref(0); const hylyRequestToken = ref(0);
...@@ -182,9 +312,9 @@ const getTermSerial = index => { ...@@ -182,9 +312,9 @@ const getTermSerial = index => {
}; };
const chart1Data = ref([]); const chart1Data = ref([]);
const chart1ColorList = ref(["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"]); const chart1ColorList = ref([...MUTICHARTCOLORS]);
const chart2ColorList = ref(["#ff7875", "#85a5ff", "#95de64", "#ffc069", "#85e5db"]); const chart2ColorList = ref([...MUTICHARTCOLORS]);
const chart2Data = ref([]); const chart2Data = ref([]);
...@@ -218,6 +348,14 @@ watch([selectedDomain, selectedLimit], () => { ...@@ -218,6 +348,14 @@ watch([selectedDomain, selectedLimit], () => {
handleGetBillContentTk(checkedValue.value ? "Y" : "N"); handleGetBillContentTk(checkedValue.value ? "Y" : "N");
}); });
watch(
[termsHighlight, termsShowOriginal],
() => {
ensureEntitiesForTerms(displayTermsList.value);
},
{ immediate: true }
);
const handleSearchSubmit = () => { const handleSearchSubmit = () => {
searchKeyword.value = searchValue.value; searchKeyword.value = searchValue.value;
currentPage.value = 1; currentPage.value = 1;
...@@ -362,6 +500,7 @@ const handleGetBillContentTk = async cRelated => { ...@@ -362,6 +500,7 @@ const handleGetBillContentTk = async cRelated => {
return item; return item;
}); });
total.value = res.data.totalElements || 0; total.value = res.data.totalElements || 0;
ensureEntitiesForTerms(mainTermsList.value);
} else { } else {
mainTermsList.value = []; mainTermsList.value = [];
total.value = 0; total.value = 0;
...@@ -805,6 +944,14 @@ onMounted(async () => { ...@@ -805,6 +944,14 @@ onMounted(async () => {
font-weight: 700; font-weight: 700;
line-height: 24px; line-height: 24px;
color: var(--text-primary-80-color); color: var(--text-primary-80-color);
:deep(.term-entity) {
display: inline;
padding: 0 2px;
border-radius: 4px;
background: rgba(255, 213, 79, 0.35);
box-shadow: inset 0 0 0 1px rgba(255, 193, 7, 0.25);
}
} }
.term-content-en { .term-content-en {
...@@ -813,6 +960,14 @@ onMounted(async () => { ...@@ -813,6 +960,14 @@ onMounted(async () => {
font-weight: 400; font-weight: 400;
line-height: 24px; line-height: 24px;
color: var(--text-primary-65-color); color: var(--text-primary-65-color);
:deep(.term-entity) {
display: inline;
padding: 0 2px;
border-radius: 4px;
background: rgba(255, 213, 79, 0.28);
box-shadow: inset 0 0 0 1px rgba(255, 193, 7, 0.2);
}
} }
.open { .open {
......
...@@ -48,6 +48,48 @@ const getProxyUrl = (url) => { ...@@ -48,6 +48,48 @@ const getProxyUrl = (url) => {
return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`; return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`;
}; };
// ========== 创建圆形图片 ==========
const createCircularImage = (imageUrl, size, borderWidth = 2, borderColor = "rgba(174,214,255,1)") => {
return new Promise((resolve) => {
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => {
const canvas = document.createElement("canvas");
// 增加大小以容纳边框
const totalSize = size + borderWidth * 2;
canvas.width = totalSize;
canvas.height = totalSize;
const ctx = canvas.getContext("2d");
// 清空背景
ctx.clearRect(0, 0, totalSize, totalSize);
// 绘制圆形图片
ctx.beginPath();
ctx.arc(totalSize / 2, totalSize / 2, size / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.clip();
// 绘制图片到圆形区域
ctx.drawImage(img, borderWidth, borderWidth, size, size);
// 恢复上下文并绘制边框
ctx.restore();
ctx.beginPath();
ctx.arc(totalSize / 2, totalSize / 2, size / 2, 0, Math.PI * 2);
ctx.strokeStyle = borderColor;
ctx.lineWidth = borderWidth;
ctx.stroke();
resolve(canvas.toDataURL("image/png"));
};
img.onerror = () => {
resolve(imageUrl); // 失败时返回原图
};
img.src = imageUrl;
});
};
// ========== 数据 ========== // ========== 数据 ==========
const nodes = ref([]); const nodes = ref([]);
const links = ref([]); const links = ref([]);
...@@ -106,13 +148,17 @@ const getCharacterRelationFn = async () => { ...@@ -106,13 +148,17 @@ const getCharacterRelationFn = async () => {
console.error("获取人物关系失败", error); console.error("获取人物关系失败", error);
} }
// 创建圆形头像
const centerImageUrl = getProxyUrl(characterInfo.value.imageUrl);
const centerCircularImage = await createCircularImage(centerImageUrl, 76, 3, "rgba(174,214,255,1)");
// ---------- 组装节点和连线 ---------- // ---------- 组装节点和连线 ----------
const centerNode = { const centerNode = {
id: "c", id: "c",
name: characterInfo.value.name || "", name: characterInfo.value.name || "",
category: 0, category: 0,
symbolSize: 80, symbolSize: 80,
symbol: `image://${getProxyUrl(characterInfo.value.imageUrl)}`, symbol: `image://${centerCircularImage}`,
label: { label: {
show: true, show: true,
position: "bottom", position: "bottom",
...@@ -130,12 +176,19 @@ const getCharacterRelationFn = async () => { ...@@ -130,12 +176,19 @@ const getCharacterRelationFn = async () => {
}; };
if (CharacterRelation.value.length > 0) { if (CharacterRelation.value.length > 0) {
// 并行创建所有圆形头像
const circularImages = await Promise.all(
CharacterRelation.value.map((item) =>
createCircularImage(getProxyUrl(item.imageUrl), 76, 2, "rgba(174,214,255,1)")
)
);
const newNodes = CharacterRelation.value.map((item, index) => ({ const newNodes = CharacterRelation.value.map((item, index) => ({
id: index, id: index,
name: item.name, name: item.name,
category: 1, category: 1,
symbolSize: 80, symbolSize: 80,
symbol: `image://${getProxyUrl(item.imageUrl)}`, symbol: `image://${circularImages[index]}`,
})); }));
newNodes.push(centerNode); newNodes.push(centerNode);
...@@ -308,4 +361,4 @@ onMounted(async () => { ...@@ -308,4 +361,4 @@ onMounted(async () => {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
</style> </style>
\ No newline at end of file
...@@ -46,6 +46,35 @@ const getProxyUrl = (url) => { ...@@ -46,6 +46,35 @@ const getProxyUrl = (url) => {
return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}` return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`
} }
// 创建圆形图片的函数
const createCircularImage = (imageUrl, size) => {
return new Promise((resolve) => {
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => {
const canvas = document.createElement('canvas')
canvas.width = size
canvas.height = size
const ctx = canvas.getContext('2d')
// 绘制圆形裁剪区域
ctx.beginPath()
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2)
ctx.closePath()
ctx.clip()
// 绘制图片
ctx.drawImage(img, 0, 0, size, size)
resolve(canvas.toDataURL('image/png'))
}
img.onerror = () => {
resolve(imageUrl) // 失败时返回原图
}
img.src = imageUrl
})
}
const nodes = ref([]) const nodes = ref([])
const links = ref([]) const links = ref([])
const characterInfo = ref({}) const characterInfo = ref({})
...@@ -91,12 +120,16 @@ const getCharacterRelationFn = async () => { ...@@ -91,12 +120,16 @@ const getCharacterRelationFn = async () => {
} }
} catch (error) {} } catch (error) {}
// 创建圆形头像
const centerImageUrl = getProxyUrl(characterInfo.value.imageUrl)
const centerCircularImage = await createCircularImage(centerImageUrl, 168)
const centerNode = { const centerNode = {
id: 'c', id: 'c',
name: characterInfo.value.name || '', name: characterInfo.value.name || '',
category: 0, category: 0,
symbolSize: 84, symbolSize: 84,
symbol: `image://${getProxyUrl(characterInfo.value.imageUrl)}`, symbol: `image://${centerCircularImage}`,
itemStyle: { itemStyle: {
borderColor: 'rgba(174,214,255,1)', borderColor: 'rgba(174,214,255,1)',
borderWidth: 3, borderWidth: 3,
...@@ -114,12 +147,19 @@ const getCharacterRelationFn = async () => { ...@@ -114,12 +147,19 @@ const getCharacterRelationFn = async () => {
} }
if (CharacterRelation.value.length > 0) { if (CharacterRelation.value.length > 0) {
// 并行创建所有圆形头像
const circularImages = await Promise.all(
CharacterRelation.value.map((item) =>
createCircularImage(getProxyUrl(item.imageUrl), 108)
)
)
const newNodes = CharacterRelation.value.map((item, index) => ({ const newNodes = CharacterRelation.value.map((item, index) => ({
id: index, id: index,
name: item.name, name: item.name,
category: 1, category: 1,
symbolSize: 54, symbolSize: 54,
symbol: `image://${getProxyUrl(item.imageUrl)}`, symbol: `image://${circularImages[index]}`,
itemStyle: { itemStyle: {
borderColor: 'rgba(174,214,255,1)', borderColor: 'rgba(174,214,255,1)',
borderWidth: 2, borderWidth: 2,
...@@ -322,4 +362,4 @@ onMounted(async () => { ...@@ -322,4 +362,4 @@ onMounted(async () => {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
</style> </style>
\ No newline at end of file
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="16.000000" fill="none">
<rect id="icon-圆形布局 1" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="矢量 423" d="M13.81 6.6C13.67 5.97 13.39 5.41 13.04 4.85C13.18 4.64 13.25 4.36 13.25 4.15C13.25 3.38 12.62 2.75 11.85 2.75C11.57 2.75 11.36 2.82 11.15 2.96C10.59 2.61 10.03 2.4 9.4 2.19C9.33 1.49 8.7 1 8 1C7.3 1 6.74 1.56 6.6 2.19C5.97 2.4 5.41 2.61 4.85 2.96C4.64 2.82 4.36 2.75 4.15 2.75C3.38 2.75 2.75 3.38 2.75 4.15C2.75 4.43 2.82 4.64 2.96 4.85C2.61 5.41 2.4 5.97 2.19 6.6C1.56 6.74 1 7.3 1 8C1 8.7 1.56 9.26 2.19 9.4C2.33 10.03 2.61 10.59 2.96 11.15C2.82 11.36 2.75 11.64 2.75 11.85C2.75 12.62 3.38 13.25 4.15 13.25C4.43 13.25 4.64 13.18 4.85 13.04C5.41 13.39 5.97 13.6 6.6 13.81C6.67 14.51 7.3 15 8 15C8.7 15 9.26 14.44 9.4 13.81C10.03 13.67 10.59 13.39 11.15 13.04C11.36 13.18 11.64 13.25 11.85 13.25C12.62 13.25 13.25 12.62 13.25 11.85C13.25 11.57 13.18 11.36 13.04 11.15C13.39 10.59 13.6 10.03 13.81 9.4C14.51 9.33 15 8.7 15 8C15 7.3 14.44 6.74 13.81 6.6L13.81 6.6ZM12.55 10.66C12.34 10.52 12.13 10.45 11.85 10.45C11.08 10.45 10.45 11.08 10.45 11.85C10.45 12.13 10.52 12.34 10.66 12.55C10.24 12.76 9.82 12.97 9.33 13.11C9.12 12.62 8.63 12.2 8 12.2C7.44 12.2 6.88 12.55 6.67 13.11C6.18 12.97 5.76 12.83 5.34 12.55C5.48 12.34 5.55 12.13 5.55 11.85C5.55 11.08 4.92 10.45 4.15 10.45C3.87 10.45 3.66 10.52 3.45 10.66C3.24 10.24 3.03 9.82 2.89 9.33C3.45 9.12 3.8 8.56 3.8 8C3.8 7.44 3.45 6.88 2.96 6.74C3.1 6.25 3.24 5.83 3.52 5.41C3.73 5.55 3.94 5.62 4.22 5.62C4.99 5.62 5.62 4.99 5.62 4.22C5.62 3.94 5.55 3.73 5.41 3.52C5.83 3.31 6.25 3.1 6.74 2.96C6.88 3.45 7.44 3.8 8 3.8C8.56 3.8 9.12 3.45 9.33 2.89C9.82 3.03 10.24 3.17 10.66 3.45C10.52 3.66 10.45 3.87 10.45 4.15C10.45 4.92 11.08 5.55 11.85 5.55C12.13 5.55 12.34 5.48 12.55 5.34C12.76 5.76 12.97 6.18 13.11 6.67C12.55 6.88 12.2 7.44 12.2 8C12.2 8.56 12.55 9.12 13.04 9.26C12.97 9.75 12.76 10.24 12.55 10.66L12.55 10.66Z" fill="rgb(59,65,75)" fill-rule="nonzero" />
</svg>
<svg viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="28.000000" height="28.000000" fill="none">
<defs>
<clipPath id="clipPath_1">
<rect width="18.000000" height="15.000000" x="5.000000" y="5.000000" fill="rgb(255,255,255)" />
</clipPath>
</defs>
<rect id="导出数据" width="28.000000" height="28.000000" x="0.000000" y="0.000000" />
<g id="容器 742" customFrame="url(#clipPath_1)">
<rect id="容器 742" width="18.000000" height="15.000000" x="5.000000" y="5.000000" />
<rect id="矩形 347" width="2.000000" height="6.000000" x="13.000000" y="5.000000" fill="rgb(132,136,142)" />
<path id="矢量 600" d="M18 11L10 11L14 16L18 11Z" fill="rgb(132,136,142)" fill-rule="evenodd" />
<path id="矢量 601" d="M22 19.9996L22.9999 15.0012L19.9999 12.0011L18.9999 12L21.0003 15.001L17.9999 15.0015L16.9998 17.9987L14 17.9996L11.0001 17.9997L9.99998 15.002L7.00017 15.0028L8.99996 12.0008L8 12.0004L5 15.0023L6.00016 20L22 19.9996Z" fill="rgb(132,136,142)" fill-rule="evenodd" />
</g>
</svg>
<svg viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="28.000000" height="28.000000" fill="none">
<defs>
<clipPath id="clipPath_0">
<rect width="20.000000" height="20.000000" x="4.000000" y="4.000000" fill="rgb(255,255,255)" />
</clipPath>
</defs>
<rect id="数据源" width="28.000000" height="28.000000" x="0.000000" y="0.000000" />
<g id="数据库 1" clip-path="url(#clipPath_0)" customFrame="url(#clipPath_0)">
<rect id="数据库 1" width="20.000000" height="20.000000" x="4.000000" y="4.000000" />
<path id="合并" d="M10.6426 6.48828C11.6719 6.28906 12.791 6.1875 14 6.1875C15.209 6.1875 16.3281 6.28906 17.3574 6.48828C18.3867 6.6875 19.2012 6.96094 19.7988 7.30469C20.3965 7.64844 20.6973 8.01953 20.6973 8.42188L20.6973 9.53906C20.6973 9.94141 20.3984 10.3125 19.7988 10.6563C19.1992 11 18.3867 11.2715 17.3574 11.4727C16.3281 11.6699 15.209 11.7695 14 11.7695C12.791 11.7695 11.6719 11.668 10.6426 11.4688C9.61328 11.2695 8.79883 10.9961 8.20117 10.6523C7.60156 10.3086 7.30273 9.9375 7.30273 9.53516L7.30273 8.41797C7.30273 8.01563 7.60156 7.64453 8.20117 7.30078C8.80078 6.96094 9.61328 6.68945 10.6426 6.48828ZM10.1387 12.5078C11.3359 12.7578 12.623 12.8828 14 12.8828C15.377 12.8828 16.6641 12.7578 17.8613 12.5078C19.0586 12.2578 20.0039 11.8887 20.6953 11.4004L20.6953 12.8828C20.6953 13.2852 20.3965 13.6563 19.7969 14C19.1973 14.3438 18.3848 14.6152 17.3555 14.8164C16.3281 15.0156 15.209 15.1152 14 15.1152C12.791 15.1152 11.6719 15.0137 10.6426 14.8145C9.61328 14.6152 8.79883 14.3418 8.20117 13.998C7.60156 13.6543 7.30273 13.2832 7.30273 12.8809L7.30273 11.3984C7.99609 11.8906 8.94141 12.2598 10.1387 12.5078ZM10.1387 15.8574C11.3359 16.1074 12.623 16.2324 14 16.2324C14.6624 16.2324 15.3041 16.2035 15.9249 16.1456C14.2088 16.4715 12.8443 17.3161 12.2805 18.3935C11.7114 18.3432 11.1654 18.2672 10.6426 18.166C9.61328 17.9668 8.80078 17.6934 8.20117 17.3496C7.60156 17.0059 7.30273 16.6348 7.30273 16.2324L7.30273 14.75C7.9961 15.2383 8.94141 15.6074 10.1387 15.8574ZM17.5 16C17.3281 16 17.1581 16.005 16.9902 16.0148C17.2857 15.9695 17.5761 15.917 17.8613 15.8574C19.0586 15.6074 20.0039 15.2383 20.6953 14.75L20.6953 16.2324C20.6953 16.3614 20.6646 16.4872 20.6031 16.6099C19.7199 16.2251 18.6512 16 17.5 16ZM13 19.5C13 18.1193 15.0147 17 17.5 17C19.9853 17 22 18.1193 22 19.5C22 20.8807 19.9853 22 17.5 22C15.0147 22 13 20.8807 13 19.5ZM17.5 18C18.3284 18 19 18.6716 19 19.5C19 20.3284 18.3284 21 17.5 21C16.6716 21 16 20.3284 16 19.5C16 18.6716 16.6716 18 17.5 18ZM12 19.5L12 19.4861C11.3572 19.4236 10.7367 19.33 10.1387 19.2051C8.94141 18.9551 7.99609 18.5859 7.30273 18.0957L7.30273 19.5781C7.30273 19.9805 7.60156 20.3516 8.20117 20.6953C8.79883 21.0391 9.61328 21.3125 10.6426 21.5117C11.4872 21.6752 12.3923 21.7729 13.3579 21.8027C12.5123 21.1873 12 20.3817 12 19.5Z" fill="rgb(132,136,142)" fill-rule="evenodd" />
</g>
</svg>
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="16.000000" fill="none">
<rect id="拓扑 1" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="矢量 422" d="M12.7727 11.142C12.4273 11.142 12.107 11.2495 11.8433 11.4327L9.42154 9.6837C9.65586 9.26852 9.78974 8.7891 9.78974 8.27836C9.78974 8.18728 9.7853 8.09723 9.77697 8.00833L12.257 6.54325C12.6396 6.77576 13.1451 6.72689 13.4758 6.39622C13.8641 6.00791 13.8641 5.37835 13.4758 4.99004C13.0875 4.60173 12.4579 4.60173 12.0696 4.99004C11.7835 5.27612 11.7085 5.69309 11.844 6.04812L9.63655 7.35217C9.40869 6.68522 8.9416 6.12922 8.33762 5.78637L8.77881 3.58047C8.81046 3.5835 8.84253 3.58519 8.87497 3.58519C9.42413 3.58519 9.8693 3.14002 9.8693 2.59087C9.8693 2.04173 9.42413 1.59656 8.87497 1.59656C8.32582 1.59656 7.88065 2.04173 7.88065 2.59087C7.88065 2.87182 7.99736 3.12535 8.18469 3.30617L7.73951 5.53213C7.48164 5.45587 7.2087 5.41473 6.9261 5.41473C6.388 5.41473 5.88464 5.56327 5.45458 5.8214L4.08134 4.61294C4.32918 4.12086 4.24801 3.50548 3.83714 3.09461C3.32457 2.58205 2.49355 2.58205 1.98098 3.09461C1.46841 3.60717 1.46842 4.4382 1.98098 4.95078C2.43973 5.40952 3.15341 5.45735 3.66574 5.09492L4.93964 6.21596C4.39895 6.73684 4.06247 7.46826 4.06247 8.27836C4.06247 9.15881 4.45994 9.94631 5.0851 10.4716L3.95324 11.6792C3.74531 11.5408 3.49572 11.4602 3.22724 11.4602C2.50236 11.4602 1.91474 12.0478 1.91474 12.7727C1.91474 13.4975 2.50236 14.0852 3.22724 14.0852C3.95212 14.0852 4.53974 13.4975 4.53974 12.7727C4.53974 12.5477 4.48308 12.3359 4.38332 12.1508L5.62251 10.8288C6.01351 11.029 6.45662 11.142 6.9261 11.142C7.76819 11.142 8.52534 10.7785 9.04931 10.1999L11.3976 11.8958C11.2358 12.149 11.142 12.4499 11.142 12.7727C11.142 13.6733 11.8721 14.4034 12.7727 14.4034C13.6733 14.4034 14.4034 13.6733 14.4034 12.7727C14.4034 11.8721 13.6733 11.142 12.7727 11.142L12.7727 11.142Z" fill="rgb(59,65,75)" fill-rule="nonzero" />
</svg>
<svg viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="28.000000" height="28.000000" fill="none">
<rect id="收藏" width="28.000000" height="28.000000" x="0.000000" y="0.000000" />
<path id="星形 2" d="M15.9534 11.0113C15.9936 11.1349 16.1088 11.2186 16.2388 11.2186L21.6363 11.2188C21.9269 11.2188 22.0478 11.5907 21.8127 11.7615L17.446 14.9343C17.3409 15.0107 17.2969 15.1461 17.3371 15.2697L19.0048 20.4031C19.0946 20.6795 18.7783 20.9094 18.5432 20.7385L14.1763 17.5661C14.0712 17.4897 13.9288 17.4897 13.8237 17.5661L9.45683 20.7385C9.22171 20.9094 8.90539 20.6795 8.99518 20.4031L10.6629 15.2697C10.7031 15.1461 10.6591 15.0107 10.554 14.9343L6.18734 11.7615C5.95224 11.5907 6.07307 11.2188 6.36368 11.2188L11.7612 11.2186C11.8912 11.2186 12.0064 11.1349 12.0466 11.0113L13.7147 5.87799C13.8045 5.60161 14.1955 5.60161 14.2853 5.87799L15.9534 11.0113Z" fill="rgb(132,136,142)" fill-rule="evenodd" />
</svg>
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="16.000000" fill="none">
<rect id="绿洲_拓扑图 1" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="矢量 422" d="M15.0147 9.88449L14.1867 9.88449L14.1867 8.65516C14.1867 7.53912 13.312 6.63116 12.2368 6.63116L8.37334 6.63116L8.37334 5.08449L9.2317 5.08449C9.42605 5.08449 9.58222 4.94394 9.58222 4.74957L9.58222 2.34115C9.58222 2.11642 9.40148 1.92004 9.17669 1.92004L6.76829 1.92004C6.57396 1.92004 6.41778 2.09179 6.41778 2.28616L6.41778 4.69459C6.41778 4.91931 6.59853 5.08449 6.82329 5.08449L7.62667 5.08449L7.62667 6.63116L3.59268 6.63116C2.62816 6.63116 1.81334 7.45731 1.81334 8.43519L1.81334 9.88449L0.930314 9.88449C0.735986 9.88449 0.58667 10.0572 0.58667 10.2515L0.58667 12.66C0.58667 12.8847 0.760554 13.0489 0.985319 13.0489L3.39371 13.0489C3.58806 13.0489 3.75111 12.9093 3.75111 12.7149L3.75111 10.3066C3.75111 10.0818 3.56351 9.88451 3.33874 9.88451L2.52445 9.88451L2.52445 8.43521C2.52445 7.84276 3.01364 7.34227 3.59268 7.34227L7.62667 7.34227L7.62667 9.88449L6.76829 9.88449C6.57396 9.88449 6.41778 10.0572 6.41778 10.2515L6.41778 12.66C6.41778 12.8847 6.59853 13.0489 6.82329 13.0489L9.2317 13.0489C9.42605 13.0489 9.58222 12.9093 9.58222 12.7149L9.58222 10.3066C9.58222 10.0818 9.40148 9.88451 9.1767 9.88451L8.37334 9.88451L8.37334 7.34227L12.2368 7.34227C12.9199 7.34227 13.4756 7.93123 13.4756 8.65516L13.4756 9.88449L12.6063 9.88449C12.4119 9.88449 12.2489 10.0572 12.2489 10.2515L12.2489 12.66C12.2489 12.8847 12.4365 13.0489 12.6613 13.0489L15.0697 13.0489C15.264 13.0489 15.4133 12.9093 15.4133 12.7149L15.4133 10.3066C15.4133 10.0818 15.2395 9.88449 15.0147 9.88449Z" fill="rgb(59,65,75)" fill-rule="nonzero" />
</svg>
...@@ -5,12 +5,17 @@ ...@@ -5,12 +5,17 @@
v-for="(item, index) in list" v-for="(item, index) in list"
:key="index" :key="index"
class="headerItem" class="headerItem"
:class="{ active: item === activeIndex }" :class="{ active: item.value === activeIndex }"
@click="activeIndex = item" @click="handleChangeLayout(item.value)"
>{{ item }}</span
> >
<img :src="item.icon" :alt="item.label" />
</span>
</div>
<div class="headerBtnBox">
<img src="./assets/icon-expand.svg" alt="" class="btn-icon" />
<img src="./assets/icon-download.svg" alt="" class="btn-icon" />
<img src="./assets/icon-star.svg" alt="" class="btn-icon" />
</div> </div>
<div class="headerBtnBox"><img src="./assets/下载按钮.png" alt="" /><img src="./assets/收藏按钮.png" alt="" /></div>
<!-- 主要内容,人物关系图 --> <!-- 主要内容,人物关系图 -->
<div class="mainBox"> <div class="mainBox">
<div class="graph" id="relGraph"></div> <div class="graph" id="relGraph"></div>
...@@ -98,6 +103,76 @@ const getProxyUrl = (url) => { ...@@ -98,6 +103,76 @@ const getProxyUrl = (url) => {
return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`; return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`;
}; };
// 创建圆形图片的函数
const createCircularImage = (imageUrl, size) => {
return new Promise((resolve) => {
if (!imageUrl) {
console.log("[v0] No image URL provided");
resolve('');
return;
}
const img = new Image();
img.crossOrigin = 'anonymous';
const onLoadSuccess = () => {
try {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
// 绘制圆形裁剪区域
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.clip();
// 绘制图片
ctx.drawImage(img, 0, 0, size, size);
resolve(canvas.toDataURL('image/png'));
} catch (error) {
console.log("[v0] Canvas error:", error);
resolve(imageUrl);
}
};
img.onload = onLoadSuccess;
img.onerror = () => {
console.log("[v0] Image failed to load, trying without crossOrigin:", imageUrl);
// 如果加载失败,尝试不带 crossOrigin 的方式
const img2 = new Image();
img2.onload = () => {
try {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.clip();
ctx.drawImage(img2, 0, 0, size, size);
resolve(canvas.toDataURL('image/png'));
} catch (error) {
console.log("[v0] Fallback canvas error:", error);
resolve(imageUrl);
}
};
img2.onerror = () => {
console.log("[v0] Image loading failed completely:", imageUrl);
resolve(imageUrl);
};
img2.src = imageUrl;
};
img.src = imageUrl;
});
};
const nodes = ref([]); const nodes = ref([]);
const links = ref([]); const links = ref([]);
...@@ -111,20 +186,14 @@ const getCharacterGlobalInfoFn = async () => { ...@@ -111,20 +186,14 @@ const getCharacterGlobalInfoFn = async () => {
personId: personId.value personId: personId.value
}; };
try{ try{
const res = await getCharacterGlobalInfo(params); const res = await getCharacterGlobalInfo(params);
if (res.code === 200) { if (res.code === 200) {
console.log("人物全局信息4", res);
if (res.data) { if (res.data) {
characterInfo.value = res.data; characterInfo.value = res.data;
} }
} }
}catch(error){ }catch(error){
...@@ -132,28 +201,42 @@ const getCharacterGlobalInfoFn = async () => { ...@@ -132,28 +201,42 @@ const getCharacterGlobalInfoFn = async () => {
}; };
const getCharacterRelationFn = async () => { const getCharacterRelationFn = async () => {
const params = { const params = {
personId: personId.value personId: personId.value
}; };
try{ try{
const res = await getCharacterRelation(params); const res = await getCharacterRelation(params);
if (res.code === 200) { if (res.code === 200) {
console.log("人物关系", res); if (res.data) {
if (res.data) { CharacterRelation.value = res.data;
CharacterRelation.value = res.data; }
} }
}
// 创建圆形头像
console.log("[v0] characterInfo.value:", characterInfo.value);
console.log("[v0] characterInfo.value.imageUrl:", characterInfo.value.imageUrl);
const centerImageUrl = getProxyUrl(characterInfo.value.imageUrl);
console.log("[v0] centerImageUrl:", centerImageUrl);
const centerCircularImage = await createCircularImage(centerImageUrl, 160);
console.log("[v0] centerCircularImage:", centerCircularImage?.substring(0, 50));
if(CharacterRelation.value.length > 0){ if(CharacterRelation.value.length > 0){
// 并行创建所有圆形头像
const circularImages = await Promise.all(
CharacterRelation.value.map((item) =>
createCircularImage(getProxyUrl(item.imageUrl), 160)
)
);
const centerNode = { const centerNode = {
id: "c", id: "c",
name: characterInfo.value.name, name: characterInfo.value.name,
category: 0, category: 0,
symbolSize: 80, symbolSize: 80,
symbol: `image://${characterInfo.value.imageUrl}`, symbol: `image://${centerCircularImage}`,
label: { label: {
show: true, show: true,
position: "bottom", position: "bottom",
...@@ -176,7 +259,7 @@ const getCharacterRelationFn = async () => { ...@@ -176,7 +259,7 @@ const getCharacterRelationFn = async () => {
name: item.name, name: item.name,
category: 1, category: 1,
symbolSize: 80, symbolSize: 80,
symbol: `image://${getProxyUrl(item.imageUrl)}` symbol: `image://${circularImages[index]}`
} }
}); });
...@@ -199,7 +282,7 @@ const getCharacterRelationFn = async () => { ...@@ -199,7 +282,7 @@ const getCharacterRelationFn = async () => {
name: characterInfo.value.name, name: characterInfo.value.name,
category: 0, category: 0,
symbolSize: 80, symbolSize: 80,
symbol: `image://${characterInfo.value.imageUrl}`, symbol: `image://${centerCircularImage}`,
label: { label: {
show: true, show: true,
position: "bottom", position: "bottom",
...@@ -284,21 +367,36 @@ const getCharacterRelationFn = async () => { ...@@ -284,21 +367,36 @@ const getCharacterRelationFn = async () => {
}; };
const list = ref([
{
const list = ref(["圆形布局", "力导向布局", "树形布局"]); value: "圆形布局",
label: "圆形布局",
icon: new URL('./assets/icon-circle.svg', import.meta.url).href,
},
{
value: "力导向布局",
label: "力导向布局",
icon: new URL('./assets/icon-force.svg', import.meta.url).href,
},
{
value: "树形布局",
label: "树形布局",
icon: new URL('./assets/icon-tree.svg', import.meta.url).href,
},
]);
const activeIndex = ref("圆形布局"); const activeIndex = ref("圆形布局");
const handleChangeLayout = (layout) => {
activeIndex.value = layout;
};
let chart;
let chart;
onMounted(() => { onMounted(async () => {
getCharacterGlobalInfoFn(); await getCharacterGlobalInfoFn();
getCharacterRelationFn(); getCharacterRelationFn();
const el = document.getElementById("relGraph"); const el = document.getElementById("relGraph");
if (!el) return; if (!el) return;
...@@ -359,7 +457,6 @@ onMounted(() => { ...@@ -359,7 +457,6 @@ onMounted(() => {
}); });
}; };
setOption(); setOption();
console.log("node1", nodes);
const onResize = () => chart && chart.resize(); const onResize = () => chart && chart.resize();
window.addEventListener("resize", onResize); window.addEventListener("resize", onResize);
watch(activeIndex, () => setOption()); watch(activeIndex, () => setOption());
...@@ -386,34 +483,44 @@ onMounted(() => { ...@@ -386,34 +483,44 @@ onMounted(() => {
.headerBox { .headerBox {
position: absolute; position: absolute;
top: 14px; top: 14px;
right: 96px; left: 12px;
display: flex;
gap: 8px;
.headerItem { .headerItem {
padding: 1px 8px; display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 4px; border-radius: 4px;
font-size: 16px;
font-weight: 400;
line-height: 30px;
cursor: pointer; cursor: pointer;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
margin-right: 8px;
border: 1px solid rgb(230, 231, 232); border: 1px solid rgb(230, 231, 232);
background-color: #fff;
transition: all 0.3s ease;
img {
width: 20px;
height: 20px;
}
} }
.active { .active {
background-color: rgba(246, 250, 255, 1); background-color: rgba(246, 250, 255, 1);
border-color: rgb(5, 95, 194); border-color: rgb(5, 95, 194);
color: rgb(5, 95, 194);
} }
} }
.headerBtnBox { .headerBtnBox {
position: absolute; position: absolute;
top: 14px; top: 14px;
right: 12px; right: 12px;
img { display: flex;
gap: 4px;
.btn-icon {
width: 28px; width: 28px;
height: 28px; height: 28px;
margin-right: 4px;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: scale(1.1);
}
} }
} }
.mainBox { .mainBox {
......
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="16.000000" fill="none" customFrame="#000000">
<rect id="Line/Down" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="路径" d="M13.7502 4L12.5783 4C12.4986 4 12.4236 4.03906 12.3768 4.10313L7.9377 10.2219L3.49864 4.10313C3.45176 4.03906 3.37676 4 3.29707 4L2.1252 4C2.02364 4 1.96426 4.11562 2.02364 4.19844L7.53301 11.7937C7.73301 12.0688 8.14239 12.0688 8.34082 11.7937L13.8502 4.19844C13.9111 4.11562 13.8518 4 13.7502 4Z" fill="rgb(59,65,75)" fill-rule="evenodd" />
</svg>
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="16.000000" fill="none" customFrame="#000000">
<rect id="Line/Up" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="路径" d="M13.914 11.8016L8.40466 4.20625C8.20465 3.93125 7.79528 3.93125 7.59684 4.20625L2.0859 11.8016C2.02653 11.8844 2.08434 12 2.18747 12L3.35934 12C3.43903 12 3.51403 11.9609 3.5609 11.8969L7.99997 5.77812L12.439 11.8969C12.4859 11.9609 12.5609 12 12.6406 12L13.8125 12C13.914 12 13.9734 11.8844 13.914 11.8016L13.914 11.8016Z" fill="rgb(5,95,194)" fill-rule="evenodd" />
</svg>
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="16.000000" fill="none" customFrame="#000000">
<rect id="Line/Close" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="路径" d="M8.82436 8L12.9699 3.20092C13.0394 3.12117 12.9809 3 12.8735 3L11.6133 3C11.5391 3 11.468 3.03221 11.4191 3.08742L8 7.04601L4.58095 3.08742C4.53357 3.03221 4.4625 3 4.3867 3L3.12647 3C3.01908 3 2.96065 3.12117 3.03013 3.20092L7.17564 8L3.03013 12.7991C2.96065 12.8788 3.01908 13 3.12647 13L4.3867 13C4.46092 13 4.53199 12.9678 4.58095 12.9126L8 8.95399L11.4191 12.9126C11.4664 12.9678 11.5375 13 11.6133 13L12.8735 13C12.9809 13 13.0394 12.8788 12.9699 12.7991L8.82436 8Z" fill="rgb(5,95,194)" fill-rule="evenodd" />
</svg>
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="16.000000" fill="none" customFrame="#000000">
<rect id="Line/Close" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="路径" d="M8.82436 8L12.9699 3.20092C13.0394 3.12117 12.9809 3 12.8735 3L11.6133 3C11.5391 3 11.468 3.03221 11.4191 3.08742L8 7.04601L4.58095 3.08742C4.53357 3.03221 4.4625 3 4.3867 3L3.12647 3C3.01908 3 2.96065 3.12117 3.03013 3.20092L7.17564 8L3.03013 12.7991C2.96065 12.8788 3.01908 13 3.12647 13L4.3867 13C4.46092 13 4.53199 12.9678 4.58095 12.9126L8 8.95399L11.4191 12.9126C11.4664 12.9678 11.5375 13 11.6133 13L12.8735 13C12.9809 13 13.0394 12.8788 12.9699 12.7991L8.82436 8Z" fill="rgb(132,136,142)" fill-rule="evenodd" />
</svg>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24.000000" height="24.000000" fill="none" customFrame="#000000">
<defs>
<linearGradient id="paint_linear_0" x1="12" x2="12" y1="4" y2="20" gradientUnits="userSpaceOnUse">
<stop stop-color="rgb(22,119,255)" offset="0" stop-opacity="1" />
<stop stop-color="rgb(22,119,255)" offset="1" stop-opacity="0.5" />
</linearGradient>
</defs>
<rect id="容器 1594" width="24.000000" height="24.000000" x="0.000000" y="0.000000" />
<path id="矢量 1678" d="M18.1122 18.2262C18.0733 18.2132 18.0396 18.1882 18.0158 18.1549C17.992 18.1216 17.9794 18.0816 17.9797 18.0406L17.9797 17.7399C17.9797 17.6494 17.9559 17.5606 17.9106 17.4823C17.8653 17.404 17.8001 17.3389 17.7217 17.2937C17.6433 17.2485 17.5543 17.2247 17.4637 17.2247L7.23085 17.2247C7.14027 17.2247 7.05129 17.2485 6.97284 17.2937C6.8944 17.3389 6.82926 17.404 6.78397 17.4823C6.73868 17.5606 6.71483 17.6494 6.71483 17.7399L6.71483 17.9628C6.71486 17.9937 6.70754 18.0241 6.69347 18.0517C6.6794 18.0792 6.65899 18.103 6.63392 18.1211C6.60884 18.1392 6.57982 18.1511 6.54924 18.1558C6.25727 18.2014 5.98027 18.3153 5.74095 18.4884C5.50163 18.6614 5.30681 18.8887 5.17246 19.1514C5.03811 19.4142 4.96806 19.705 4.96805 20L19.4006 20C19.4004 19.6065 19.2757 19.2232 19.0443 18.9047C18.813 18.5861 18.4867 18.3487 18.1122 18.2262ZM7.02145 14.107C7.0551 14.139 7.09459 14.1643 7.13779 14.1815C7.18099 14.1987 7.22709 14.2074 7.27359 14.2072C7.34669 14.207 7.41806 14.185 7.47853 14.144C7.53899 14.103 7.58579 14.0449 7.6129 13.9771C7.64001 13.9093 7.64619 13.835 7.63066 13.7637C7.61512 13.6923 7.57858 13.6273 7.52572 13.5769L5.35053 11.5183C5.3042 11.4735 5.24676 11.4418 5.18411 11.4265C5.12146 11.4112 5.05585 11.4128 4.99403 11.4312C4.93221 11.4496 4.87641 11.4841 4.83237 11.5311C4.78779 11.5774 4.75633 11.6347 4.74121 11.6972C4.72608 11.7596 4.72784 11.8249 4.7463 11.8864C4.76476 11.948 4.79925 12.0035 4.84626 12.0474L7.02145 14.107ZM7.42637 15.3485C7.41596 15.2851 7.38903 15.2256 7.34827 15.1759C7.30752 15.1262 7.25438 15.0881 7.1942 15.0654C7.13402 15.0427 7.06891 15.0363 7.00543 15.0467L4.30673 15.4904C4.23036 15.503 4.15995 15.5394 4.10559 15.5944C4.05123 15.6495 4.01571 15.7203 4.00413 15.7967C3.99254 15.8731 4.00549 15.9512 4.0411 16.0198C4.07672 16.0884 4.13318 16.144 4.20239 16.1786C4.27161 16.2132 4.35002 16.2251 4.42639 16.2125L7.12509 15.7688C7.18844 15.7582 7.2479 15.7312 7.29752 15.6905C7.34713 15.6498 7.38515 15.5967 7.40776 15.5367C7.43038 15.4767 7.43679 15.4118 7.42637 15.3485ZM19.6976 15.3517L14.2297 10.2672L16.1955 8.3057L16.1966 8.30677C16.2541 8.36406 16.3257 8.40526 16.4042 8.42622C16.4827 8.44719 16.5654 8.44719 16.6439 8.42622C16.7224 8.40526 16.794 8.36406 16.8515 8.30677L16.8707 8.28864C16.9282 8.23108 16.9695 8.15948 16.9905 8.08099C17.0115 8.0025 17.0115 7.91988 16.9905 7.84139C16.9695 7.7629 16.9282 7.6913 16.8707 7.63374L13.3665 4.13527C13.3088 4.07793 13.2371 4.03671 13.1585 4.01573C13.0799 3.99476 12.9971 3.99476 12.9185 4.01573C12.8399 4.03671 12.7682 4.07793 12.7105 4.13527L12.6913 4.1534C12.6335 4.21077 12.5919 4.28235 12.5708 4.36091C12.5496 4.43947 12.5496 4.52222 12.5708 4.60078C12.5919 4.67934 12.6335 4.75093 12.6913 4.8083L12.6924 4.8083L7.79602 9.69656L7.79495 9.6955C7.7373 9.63816 7.66558 9.59694 7.58696 9.57596C7.50834 9.55499 7.42559 9.55499 7.34697 9.57596C7.26835 9.59694 7.19663 9.63816 7.13897 9.6955L7.12188 9.71363C7.06445 9.77119 7.02316 9.84279 7.00215 9.92128C6.98114 9.99977 6.98114 10.0824 7.00215 10.1609C7.02316 10.2394 7.06445 10.311 7.12188 10.3685L10.6261 13.8681C10.6838 13.9254 10.7555 13.9666 10.8341 13.9876C10.9127 14.0086 10.9955 14.0086 11.0741 13.9876C11.1527 13.9666 11.2245 13.9254 11.2821 13.8681L11.3003 13.8489C11.3577 13.7914 11.3989 13.7199 11.4199 13.6416C11.4409 13.5632 11.4409 13.4807 11.4199 13.4023C11.3989 13.324 11.3577 13.2525 11.3003 13.195L11.2992 13.194L13.2639 11.2314L18.3568 16.6903C18.4729 16.8149 18.6205 16.906 18.784 16.9539C18.9476 17.0018 19.1211 17.0048 19.2862 16.9625C19.4513 16.9203 19.6019 16.8343 19.7222 16.7138C19.8427 16.5936 19.9285 16.4433 19.9706 16.2786C20.0127 16.1138 20.0096 15.9408 19.9615 15.7777C19.9135 15.6146 19.8223 15.4675 19.6976 15.3517ZM9.16674 10.1051C9.14288 10.1288 9.11321 10.1458 9.08069 10.1545C9.04817 10.1632 9.01394 10.1632 8.98142 10.1545C8.9489 10.1458 8.91923 10.1288 8.89537 10.1051L8.88683 10.0965C8.86308 10.0727 8.846 10.0431 8.83731 10.0106C8.82863 9.97817 8.82863 9.94399 8.83731 9.91153C8.846 9.87906 8.86308 9.84944 8.88683 9.82562L12.8216 5.8973C12.8964 5.82264 13.0193 5.82264 13.0941 5.8973L13.1005 5.90477C13.1242 5.92858 13.1413 5.95821 13.15 5.99067C13.1587 6.02314 13.1587 6.05732 13.15 6.08978C13.1413 6.12225 13.1242 6.15187 13.1005 6.17569L9.16674 10.1051Z" fill="url(#paint_linear_0)" fill-rule="nonzero" />
</svg>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24.000000" height="24.000000" fill="none" customFrame="#000000">
<defs>
<linearGradient id="paint_linear_9" x1="12" x2="12" y1="4.02734375" y2="20.0272427" gradientUnits="userSpaceOnUse">
<stop stop-color="rgb(47,84,235)" offset="0" stop-opacity="1" />
<stop stop-color="rgb(47,84,235)" offset="1" stop-opacity="0.5" />
</linearGradient>
</defs>
<rect id="容器 1602" width="24.000000" height="24.000000" x="0.000000" y="0.000000" />
<path id="合并" d="M12.2963 5.02783C12.2963 4.37781 12.9067 3.90055 13.5375 4.05737L19.2413 5.47529C19.687 5.58611 20 5.98639 20 6.44575L20 19.0272C20 19.5795 19.5523 20.0272 19 20.0272L13.2963 20.0272C12.744 20.0272 12.2963 19.5795 12.2963 19.0272L12.2963 5.02783ZM4 9.94126C4 9.48913 4.30338 9.09324 4.73995 8.97567L9.85106 7.59918C10.4865 7.42806 11.1111 7.90672 11.1111 8.56478L11.1111 19.0272C11.1111 19.5795 10.6634 20.0272 10.1111 20.0272L5 20.0272C4.44772 20.0272 4 19.5795 4 19.0272L4 9.94126ZM13.9603 8.21739L18.336 8.21739C18.6005 8.21739 18.8148 8.43175 18.8148 8.69617C18.8148 8.96059 18.6005 9.17495 18.336 9.17495L13.9603 9.17495C13.6958 9.17495 13.4815 8.96059 13.4815 8.69617C13.4815 8.43175 13.6958 8.21739 13.9603 8.21739ZM13.9603 10.1325L18.336 10.1325C18.6005 10.1325 18.8148 10.3469 18.8148 10.6113C18.8148 10.8757 18.6005 11.0901 18.336 11.0901L13.9603 11.0901C13.6958 11.0901 13.4815 10.8757 13.4815 10.6113C13.4815 10.3469 13.6958 10.1325 13.9603 10.1325ZM5.66396 11.0901L9.44715 11.0901C9.71157 11.0901 9.92593 11.3044 9.92593 11.5688C9.92593 11.8333 9.71157 12.0476 9.44715 12.0476L5.66396 12.0476C5.39954 12.0476 5.18519 11.8333 5.18519 11.5688C5.18519 11.3044 5.39954 11.0901 5.66396 11.0901ZM13.9603 12.0476L18.336 12.0476C18.6005 12.0476 18.8148 12.262 18.8148 12.5264C18.8148 12.7908 18.6005 13.0052 18.336 13.0052L13.9603 13.0052C13.6958 13.0052 13.4815 12.7908 13.4815 12.5264C13.4815 12.262 13.6958 12.0476 13.9603 12.0476ZM5.66396 13.0052L9.44715 13.0052C9.71157 13.0052 9.92593 13.2195 9.92593 13.4839C9.92593 13.7484 9.71157 13.9627 9.44715 13.9627L5.66396 13.9627C5.39954 13.9627 5.18519 13.7484 5.18519 13.4839C5.18519 13.2195 5.39954 13.0052 5.66396 13.0052ZM13.9603 13.9627L18.336 13.9627C18.6005 13.9627 18.8148 14.1771 18.8148 14.4415C18.8148 14.7059 18.6005 14.9203 18.336 14.9203L13.9603 14.9203C13.6958 14.9203 13.4815 14.7059 13.4815 14.4415C13.4815 14.1771 13.6958 13.9627 13.9603 13.9627ZM9.44715 15.8778C9.71157 15.8778 9.92593 15.6635 9.92593 15.3991C9.92593 15.1346 9.71157 14.9203 9.44715 14.9203L5.66396 14.9203C5.39954 14.9203 5.18519 15.1346 5.18519 15.3991C5.18519 15.6635 5.39954 15.8778 5.66396 15.8778L9.44715 15.8778Z" fill="url(#paint_linear_9)" fill-rule="evenodd" />
</svg>
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论