提交 22288f9d authored 作者: 张烨's avatar 张烨

feat:政令增加思维导图弹出框

上级 efc73a58
...@@ -91,10 +91,11 @@ export function getDecreeTypeList() { ...@@ -91,10 +91,11 @@ export function getDecreeTypeList() {
} }
// 关键机构 // 关键机构
export function getKeyOrganization() { export function getKeyOrganization(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/commonFeature/keyOrganization`, url: `/api/commonFeature/keyOrganization`,
params
}) })
} }
......
...@@ -80,10 +80,10 @@ export function getDecreeReport(params) { ...@@ -80,10 +80,10 @@ export function getDecreeReport(params) {
} }
// 政令关键词云 // 政令关键词云
export function getKeyWordUp() { export function getKeyWordUp(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/element/getKeyWordUp/2025-01-01`, url: `/api/administrativeOrderInfo/wordCloud/${params.id}`,
}) })
} }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<div class="switch-label switch-label-left">高亮实体</div> <div class="switch-label switch-label-left">高亮实体</div>
<el-switch v-model="isTranslate" /> <el-switch v-model="isTranslate" />
<div class="switch-label">文显示</div> <div class="switch-label">文显示</div>
<div <div
v-for="action in headerActions" v-for="action in headerActions"
...@@ -53,8 +53,8 @@ ...@@ -53,8 +53,8 @@
class="content-row" class="content-row"
:class="{ 'high-light': isHighlight }" :class="{ 'high-light': isHighlight }"
> >
<div class="content-cn" :class="{ 'translate-cn': !isTranslate }" v-html="item.content" /> <div class="content-en" v-html="item.contentEn" :class="{ 'translate-cn': !isTranslate }"></div>
<div v-if="isTranslate" class="content-en" v-html="item.contentEn" /> <div class="content-cn" v-html="item.content" v-if="isTranslate"></div>
</div> </div>
</el-scrollbar> </el-scrollbar>
</div> </div>
...@@ -150,11 +150,9 @@ const doUpdateWord = () => { ...@@ -150,11 +150,9 @@ const doUpdateWord = () => {
} }
displayReportData.value = originReportData.value.map((item) => { displayReportData.value = originReportData.value.map((item) => {
const cn = applyHighlightToText(item.content, term); const en = applyHighlightToText(item.contentEn, term);
const en = isTranslate.value const cn = isTranslate.value ? applyHighlightToText(item.content, term) : { html: item.content, count: 0 };
? applyHighlightToText(item.contentEn, term) findWordMax.value += en.count + cn.count;
: { html: item.contentEn, count: 0 };
findWordMax.value += cn.count + en.count;
return { return {
...item, ...item,
content: cn.html, content: cn.html,
......
<template>
<div class="view-box">
<div ref="graphContainer" style="height: 100%; width: 100%;"></div>
</div>
</template>
<script setup name="MindGraph">
import { ref, onBeforeUnmount } from "vue"
import * as G6 from '@antv/g6';
// 初始化画布
const graphContainer = ref(null);
let graph = null;
const onInitGraph = () => {
const container = graphContainer.value;
const width = container.clientWidth;
const height = container.clientHeight;
graph = new G6.Graph({
container: container,
width, height,
fitView: true,
fitViewPadding: 50,
defaultNode: {
type: "rect",
size: [250, 45],
style: {
fill: "#F6FAFF",
stroke: "#B9DCFF",
lineWidth: 1
},
labelCfg: {
style: {
fill: "#055FC2",
fontSize: 18,
lineHeight: 25,
fontWeight: "bold",
fontFamily: "Source Han Sans CN",
}
}
},
defaultEdge: {
type: "cubic-horizontal",
style: {
stroke: "#B9DCFF",
lineWidth: 2,
endArrow: true,
}
},
layout: {
type: 'dagre', // 层次布局
rankdir: 'LR', // 布局从左向右
controlPoints: true, // 节点间连线的控制点
nodesep: 10, // 同一层节点之间的距离
ranksep: 50, // 不同层节点之间的距离
},
modes: {
default: [
'drag-canvas', // 鼠标拖拽移动画布
'zoom-canvas', // 鼠标滚轮缩放
// 'drag-node' // 可选:允许拖拽节点
]
},
});
}
// 加载思维导图数据
const onMindGraphData = (nodes=[], edges=[]) => {
let data = { nodes:[], edges }
nodes.forEach(node => {
if (node.maxWidth) onFormatLineFeed(node);
data.nodes.push(node);
})
if (!graph) onInitGraph();
graph.data(data);
graph.render();
}
// 获取文本宽度
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const getLabelWidth = (label, size, family) => {
ctx.font = `${size}px ${family}`;
return ctx.measureText(label).width;
}
// 文本插入换行符
const onFormatLineFeed = (node) => {
let size = node?.labelCfg?.style?.fontSize || '16'
let family = node?.labelCfg?.style?.fontFamily || 'Source Han Sans CN'
const lines = [];
let line = '';
for (let char of node.label) {
const testLine = line + char;
const width = getLabelWidth(testLine, size, family);
if (width > node.maxWidth-40) {
lines.push(line);
line = char;
} else {
line = testLine;
}
}
if (line) lines.push(line);
node.label = lines.join("\n")
node.size = [node.maxWidth, 25*lines.length+20]
}
defineExpose({ onMindGraphData })
onBeforeUnmount(() => {
graph?.destroy()
})
</script>
<style scoped lang="scss">
.view-box {
width: 100%;
height: 100%;
}
</style>
\ No newline at end of file
...@@ -29,15 +29,15 @@ ...@@ -29,15 +29,15 @@
<div class="item-footer">分析报告</div> <div class="item-footer">分析报告</div>
</div> </div>
</div> --> </div> -->
<div class="date-box" v-if="govInsList.length"> <div class="date-box" v-if="keyOrganizationList.length">
<div class="date-icon"> <div class="date-icon">
<img :src="tipsTcon" alt=""> <img :src="tipsTcon" alt="">
</div> </div>
<div class="date-text">近期美国各联邦政府机构发布涉华政令数量汇总</div> <div class="date-text">近期美国各联邦政府机构发布涉华政令数量汇总</div>
<TimeTabPane @time-click="handleGetDepartmentList" /> <TimeTabPane @time-click="onKeyOrganization" />
</div> </div>
<div class="home-main-header-item-box" v-if="govInsList.length"> <div class="home-main-header-item-box" v-if="keyOrganizationList.length">
<div class="organization-item" v-for="(item, index) in govInsList.slice(0, 7)" :key="index" @click="handleToInstitution(item)"> <div class="organization-item" v-for="(item, index) in keyOrganizationList" :key="index" @click="handleToInstitution(item)">
<div class="item-left"> <div class="item-left">
<img :src="item.orgImage || DefaultIcon2" alt="" /> <img :src="item.orgImage || DefaultIcon2" alt="" />
</div> </div>
...@@ -499,14 +499,9 @@ const handleCurrentChange = page => { ...@@ -499,14 +499,9 @@ const handleCurrentChange = page => {
// 机构列表 // 机构列表
const govInsList = ref([]); const govInsList = ref([]);
const checkedGovIns = ref([]); const checkedGovIns = ref([]);
const handleGetDepartmentList = async (event) => { const handleGetDepartmentList = async () => {
let day = 7
if (event?.time === '近一周') day = 7
if (event?.time === '近一月') day = 30
if (event?.time === '近一年') day = 365
try { try {
const res = await getDepartmentList({day}); const res = await getDepartmentList({day:7});
console.log("机构列表", res); console.log("机构列表", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
govInsList.value = res.data; govInsList.value = res.data;
...@@ -1213,9 +1208,13 @@ const handleSearch = () => { ...@@ -1213,9 +1208,13 @@ const handleSearch = () => {
// 关键机构 // 关键机构
const keyOrganizationList = ref([]); const keyOrganizationList = ref([]);
const onKeyOrganization = async () => { const onKeyOrganization = async (event) => {
let day = 7
if (event?.time === '近一周') day = 7
if (event?.time === '近一月') day = 30
if (event?.time === '近一年') day = 365
try { try {
const res = await getKeyOrganization(); const res = await getKeyOrganization({day});
console.log("关键机构", res); console.log("关键机构", res);
if (res.code === 200) { if (res.code === 200) {
keyOrganizationList.value = res.data.map(item => ({ orgName:item.orgName, orgId:item.id })); keyOrganizationList.value = res.data.map(item => ({ orgName:item.orgName, orgId:item.id }));
......
...@@ -235,7 +235,7 @@ handleGetBasicInfo(); ...@@ -235,7 +235,7 @@ handleGetBasicInfo();
const wordCloudData = ref([]) const wordCloudData = ref([])
const onKeyWordUp = async () => { const onKeyWordUp = async () => {
try { try {
const res = await getKeyWordUp(); const res = await getKeyWordUp({id: decreeId.value});
console.log("政令关键词云", res); console.log("政令关键词云", res);
wordCloudData.value = res.data.slice(0, 10).map(item => ({name: item.name, value: item.count})); wordCloudData.value = res.data.slice(0, 10).map(item => ({name: item.name, value: item.count}));
} catch (error) { } catch (error) {
......
...@@ -3,6 +3,14 @@ ...@@ -3,6 +3,14 @@
<div class="page-left"> <div class="page-left">
<div class="box1"> <div class="box1">
<AnalysisBox title="主要指令" :showAllBtn="false"> <AnalysisBox title="主要指令" :showAllBtn="false">
<template #header-btn>
<div class="mind-bnt" @click="headerTreeDialog()">
<div class="mind-icon">
<img src="./assets/images/edit-line.png" alt="">
</div>
<div class="mind-text">思维导图</div>
</div>
</template>
<div class="analysis-box"> <div class="analysis-box">
<div class="analysis-top"> <div class="analysis-top">
<el-select v-model="areaType" :empty-values="[null, undefined]" @change="onMainContentData()" style="width: 200px;"> <el-select v-model="areaType" :empty-values="[null, undefined]" @change="onMainContentData()" style="width: 200px;">
...@@ -115,14 +123,14 @@ ...@@ -115,14 +123,14 @@
</div> </div>
</div> </div>
<el-dialog v-model="isTreeDialog" width="1400px" top="8vh" class="viewpoint-dialog"> <el-dialog v-model="isTreeDialog" width="1400px" top="8vh" class="viewpoint-dialog" destroy-on-close>
<template #header> <template #header>
<div class="viewpoint-header"> <div class="viewpoint-header">
<div class="viewpoint-title">政令举措思维导图</div> <div class="viewpoint-title">政令举措思维导图</div>
</div> </div>
</template> </template>
<div class="viewpoint-body"> <div class="viewpoint-body">
<div ref="graphContainer" style="height: 100%; width: 100%;"></div> <MindGraph ref="refMindGraph"></MindGraph>
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
...@@ -134,6 +142,7 @@ import { useRoute } from "vue-router"; ...@@ -134,6 +142,7 @@ import { useRoute } from "vue-router";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import router from "@/router"; import router from "@/router";
import { Search } from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
import MindGraph from "@/views/decree/com/MindGraph.vue"
import { getDecreeOrganization } from "@/api/decree/introduction"; import { getDecreeOrganization } from "@/api/decree/introduction";
import { getDecreeRelatedEntity, getDecreeMainContent } from "@/api/decree/background"; import { getDecreeRelatedEntity, getDecreeMainContent } from "@/api/decree/background";
import { getDecreehylyList } from "@/api/decree/home"; import { getDecreehylyList } from "@/api/decree/home";
...@@ -289,86 +298,88 @@ const simpleNumToChinese = (num) => { ...@@ -289,86 +298,88 @@ const simpleNumToChinese = (num) => {
} }
// 思维导图 // 思维导图
import * as G6 from '@antv/g6'; const isTreeDialog = ref(false);
const isTreeDialog = ref(true); const refMindGraph = ref(null);
const graphContainer = ref(null); let mindData = [
let graph = null; {
const onRelationChart = () => { id: 1,
label: "在本命令发布之日起90天内,商务部长应与国务卿和白宫科学技术政策办公室(OSTP)主任协商,建立并实施“美国人工智能出口计划”(Program),以支持美国全栈人工智能出口包的开发和部署。执行期限:在本命令发布之日起90天内完成计划的建立与实施。",
const container = graphContainer.value; children: [
const nodeWidth = 180;
const width = container.clientWidth;
const height = container.clientHeight;
const centerX = width / 2
const centerY = height / 2 - 40
const leftNodeX = centerX - nodeWidth;
const rightNodeX = centerX + nodeWidth;
const data = {
nodes: [
{ {
id: '1', label: `hhhhhhhhhhhhhhhhhh`, id: 5,
size: [250, 80] label: "商务部",
}, },
{ {
id: '2', label: `kkkkkkkkkkkkkkkkkkkk`, id: 6,
size: [250, 80] label: "国务院",
}, },
],
edges: [
{ target: '1', source: '2' }
] ]
}; },
{
graph = new G6.Graph({ id: 2,
container: container, label: "商务部长应向由产业界主导的联合体发布公开征集提案,以纳入该计划。提案必须包括全栈人工智能技术包、目标出口国家或区域集团、数据中心及基础设施的建设运营模式、所需联邦激励和支持机制,并遵守所有相关美国出口管制制度、对外投资法规和最终用户政策。执行期限:提案须在公开征集发布后90天内提交,商务部将滚动审议提案。",
width, children: [
height, {
defaultNode: { id: 7,
type: 'rect', label: "白宫科学和技术政策办公室",
anchorPoints: [[0, 0.5], [1, 0.5]],
style: {
cursor: "pointer",
radius: 4,
fill: '#f6faff',
stroke: '#B9DCFF',
}, },
labelCfg: { {
style: { id: 8,
cursor: "pointer", label: "国防部",
fill: "#333333",
fontSize: 15,
fontWeight: "bold",
fontFamily: "Microsoft YaHei",
textAlign: "center",
}
}
},
defaultEdge: {
type: "line",
style: {
lineWidth: 1,
endArrow: true,
}, },
}, ]
layout: { },
type: null // 等同于 null {
}, id: 3,
fitView: false, label: "制定并执行统一的联邦政府战略,以促进美国人工智能技术和标准的出口;协调技术、金融和外交资源以加速优先人工智能出口项目包的部署;协调美国参与多边倡议和国别伙伴关系;支持伙伴国家营造有利于美国人工智能系统部署的监管、数据和基础设施环境;分析市场准入障碍;并与小企业管理局投资与创新办公室协调促进对美国小型企业的人工智能技术研发和基础设施制造的投资。执行期限:持续执行,无明确终止日期",
}); children: [
{
console.log("graph", graph); id: 9,
// 加载数据并渲染 label: "能源部",
graph.data(data); },
graph.render(); {
id: 10,
label: "美国贸易代表办公室",
},
]
},
{
id: 4,
label: "商务部长应与国务卿、国防部长、能源部长和OSTP主任协商,评估提交的提案。经选定的提案将被指定为优先人工智能出口包,并通过优先获得本命令第4节所述工具予以支持。执行期限:提案评估将在提案提交后持续进行,无明确截止日期。",
children: [
{
id: 11,
label: "小企业管理局",
},
]
},
]
const headerTreeDialog = () => {
isTreeDialog.value = true;
let labelCfg = {
position: 'left',
offset: -20,
style: {
fontWeight: "normal",
fontSize: 16,
textAlign: 'left',
autoWrap: true
}
}
let nodes = [{id: "0", label: "人工智能领域举措"}]
let edges = []
mindData.forEach(item => {
nodes.push({id:String(item.id), label:item.label, maxWidth:600, labelCfg})
item.children?.forEach(child => {
nodes.push({id:String(child.id), label:child.label})
edges.push({id:`${child.id}-${item.id}`, source:String(child.id), target:String(item.id)})
edges.push({id:`0-${child.id}`, source:"0", target:String(child.id)})
})
})
console.log("思维导图数据", nodes, edges)
setTimeout(() => { refMindGraph.value.onMindGraphData(nodes, edges) }, 300)
} }
onMounted(() => {
setTimeout(() => {
onRelationChart();
}, 1000)
});
// 相关实体 // 相关实体
const entityList = ref([]); const entityList = ref([]);
const onRelatedEntityData = async () => { const onRelatedEntityData = async () => {
...@@ -456,6 +467,31 @@ onMounted(() => { ...@@ -456,6 +467,31 @@ onMounted(() => {
flex: auto; flex: auto;
.box1 { .box1 {
.mind-bnt {
background-color: var(--color-primary-10);
height: 28px;
border-radius: 14px;
display: flex;
align-items: center;
padding: 0 16px;
cursor: pointer;
.mind-icon {
width: 16px;
height: 13px;
font-size: 0px;
img {
width: 100%;
height: 100%;
}
}
.mind-text {
color: var(--color-primary-100);
font-family: Source Han Sans CN;
font-size: 16px;
line-height: 16px;
margin-left: 6px;
}
}
.analysis-box { .analysis-box {
display: flex; display: flex;
......
...@@ -53,8 +53,9 @@ export default defineConfig({ ...@@ -53,8 +53,9 @@ export default defineConfig({
rewrite: (path) => path.replace(/^\/reportData/, '') rewrite: (path) => path.replace(/^\/reportData/, '')
}, },
'/api': { '/api': {
target: 'http://8.140.26.4:9085/', target: 'http://8.140.26.4:9085/',
// target: 'http://192.168.0.4:28080/',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') rewrite: (path) => path.replace(/^\/api/, '')
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论