提交 b16fe10d authored 作者: 张伊明's avatar 张伊明

合并分支 'zy-dev' 到 'master'

从 zy-dev 合并到 master 查看合并请求 !187
...@@ -235,12 +235,26 @@ body { ...@@ -235,12 +235,26 @@ body {
display: none; display: none;
} }
/* #region 公共样式类名 */
/* 单行文本溢出隐藏显示省略号 */ /* 单行文本溢出隐藏显示省略号 */
.one-line-ellipsis { .one-line-ellipsis {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
/* 多行文本两端对齐 最后一行正常显示 */
.text-align-justify {
text-align: justify;
text-align-last: left;
-webkit-text-align-last: left;
}
/* 可点击文本 鼠标悬浮样式 */
#app .text-click-hover:hover {
text-decoration: underline;
color: rgb(5, 95, 194);
cursor: pointer;
}
/* #endregion 公共样式类名 */
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
......
import request from "@/api/request.js"; import request from "@/api/request.js";
// 最新科技政令 // 最新科技政令
export function getDepartmentList() { export function getDepartmentList(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/administrativeDict/department`, url: `/api/administrativeDict/department`,
...@@ -43,10 +43,10 @@ export function getDecreeArea(params) { ...@@ -43,10 +43,10 @@ export function getDecreeArea(params) {
} }
// 关键行政令 // 关键行政令
export function getKeyDecree() { export function getKeyDecree(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/administrativeOrderOverview/action`, url: `/api/administrativeOrderOverview/action?pageSize=${params.pageSize}&pageNum=${params.pageNum}`,
}) })
} }
......
...@@ -18,7 +18,7 @@ const decreeRoutes = [ ...@@ -18,7 +18,7 @@ const decreeRoutes = [
name: "Decree", name: "Decree",
component: Decree, component: Decree,
meta: { meta: {
title: "政令概览" title: "科技政令概况"
} }
}, },
{ {
......
...@@ -63,12 +63,18 @@ ...@@ -63,12 +63,18 @@
</div> </div>
</div> </div>
</div> --> </div> -->
<div class="home-main-header-item-box"> <div class="home-main-header-item-box" v-if="govInsList.length">
<div class="item" v-for="(item, index) in govInsList" :key="index" @click="handleToInstitution(item)"> <div class="item" v-for="(item, index) in govInsList.slice(0, 7)" :key="index" @click="handleToInstitution(item)">
<div class="item-left"> <div class="item-left">
<img :src="item.img ? item.img : DefaultIcon2" alt="" /> <img :src="item.orgImage || DefaultIcon2" alt="" />
</div>
<div class="item-right one-line-ellipsis">{{ item.orgName }}</div>
<div class="item-num">{{ item.total }}</div>
<el-icon color="var(--color-primary-100)"><ArrowRightBold /></el-icon>
</div> </div>
<div class="item-right">{{ item.name }}</div> <div class="item">
<div class="item-num item-more">查看全部机构 ({{govInsList.length+1}}家)</div>
<el-icon color="var(--color-primary-100)"><ArrowRightBold /></el-icon>
</div> </div>
</div> </div>
</div> </div>
...@@ -115,15 +121,7 @@ ...@@ -115,15 +121,7 @@
{{ item.name }} {{ item.name }}
</div> </div>
<div class="box1-main-right-info"> <div class="box1-main-right-info">
<!-- <div class="tag" :class="{ <AreaTag v-for="(tag, index) in item.industryList" :key="index" :tagName="tag.industryName" />
tag1: tag.status == 1,
tag2: tag.status == 2,
tag3: tag.status == 3
}" v-for="(tag, index) in item.industryList" :key="index">
{{ tag.industryName }}
</div> -->
<AreaTag v-for="(tag, index) in item.industryList" :key="index" :tagName="tag.industryName">
</AreaTag>
</div> </div>
<div class="box1-main-right-center"> <div class="box1-main-right-center">
{{ item.describe }} {{ item.describe }}
...@@ -243,6 +241,7 @@ ...@@ -243,6 +241,7 @@
<div class="header-title">{{ "关键行政令" }}</div> <div class="header-title">{{ "关键行政令" }}</div>
</div> </div>
<div class="box7-main"> <div class="box7-main">
<div class="box7-list">
<div class="box7-item" v-for="(item, index) in keyDecreeList" :key="index" @click="handleKeyDecree(item)"> <div class="box7-item" v-for="(item, index) in keyDecreeList" :key="index" @click="handleKeyDecree(item)">
<div class="icon"> <div class="icon">
<img src="./assets/images/warning.png" alt="" /> <img src="./assets/images/warning.png" alt="" />
...@@ -253,14 +252,11 @@ ...@@ -253,14 +252,11 @@
<div class="time">{{ item.time }}</div> <div class="time">{{ item.time }}</div>
</div> </div>
<div class="info-content">{{ item.content ? item.content : "暂无数据" }}</div> <div class="info-content">{{ item.content ? item.content : "暂无数据" }}</div>
<!-- <el-popover effect="dark" :width="800" :content="item.content" placement="top-start">
<template #reference>
<div class="info-content">{{ item.content ? item.content : "暂无数据" }}</div>
</template>
</el-popover> -->
</div> </div>
</div> </div>
</div> </div>
<SimplePagination v-model:current-page="keyDecreeInfo.page" :page-size="keyDecreeInfo.size" :total="keyDecreeInfo.total" @page-change="handleGetKeyDecree" />
</div>
</div> </div>
<div class="box8"> <div class="box8">
<div class="box8-header"> <div class="box8-header">
...@@ -269,17 +265,19 @@ ...@@ -269,17 +265,19 @@
</div> </div>
<div class="header-title">{{ "政令重点条款" }}</div> <div class="header-title">{{ "政令重点条款" }}</div>
</div> </div>
<div class="box8-main" id="wordCloudChart"></div> <div class="box8-content" v-if="wordCloudData?.length">
<WordCloudChart :data="wordCloudData" width="100%" height="100%" />
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="home-main-footer"> <div class="home-main-footer">
<DivideHeader id="position4" class="divide4" :titleText="'资源库'"></DivideHeader> <DivideHeader id="position4" class="divide4" :titleText="'科技政令库'"></DivideHeader>
<div class="home-main-footer-header"> <div class="home-main-footer-header">
<div class="search-box"> <div class="search-box">
<el-select v-model="searchType" :empty-values="[null, undefined]" style="width: 100%"> <el-select v-model="searchType" :empty-values="[null, undefined]" style="width: 100%" filterable>
<el-option label="全部政府部门" value="" /> <el-option label="全部政府部门" value="" />
<el-option v-for="item in govInsList" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in govInsList" :key="item.orgId" :label="item.orgName" :value="item.orgId" />
</el-select> </el-select>
</div> </div>
<div style="flex: auto;"></div> <div style="flex: auto;"></div>
...@@ -306,38 +304,23 @@ ...@@ -306,38 +304,23 @@
</div> </div>
<div class="select-main"> <div class="select-main">
<div class="checkbox-group"> <div class="checkbox-group">
<el-checkbox v-for="type in decreeTypeList" :key="type.id" v-model="checkedDecreeType" <el-checkbox v-for="type in decreeTypeList" :key="type.id" v-model="checkedDecreeType" :label="type.typeId"
:label="type.typeId" style="width: 180px" class="filter-checkbox" style="width: 180px" class="filter-checkbox"
@change="handleChangeCheckedDecreeType"> @change="handleChangeCheckedDecreeType">
{{ type.typeName }} {{ type.typeName }}
</el-checkbox> </el-checkbox>
</div> </div>
</div> </div>
</div> </div>
<!-- <div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "发布机构" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox v-for="cate in govInsList" :key="cate.id" v-model="checkedGovIns"
:label="cate.id" style="width: 180px" class="filter-checkbox"
@change="handleChangeCheckedGovIns">
{{ cate.name }}
</el-checkbox>
</div>
</div>
</div> -->
<div class="select-box"> <div class="select-box">
<div class="select-box-header"> <div class="select-box-header">
<div class="icon"></div> <div class="icon"></div>
<div class="title">{{ "科技领域" }}</div> <div class="title">{{ "科技领域" }}</div>
</div> </div>
<div class="select-main select-main1"> <div class="select-main">
<div class="checkbox-group"> <div class="checkbox-group">
<el-checkbox v-for="area in areaList" :key="area.id" v-model="activeAreaList" :label="area.id" <el-checkbox v-for="area in areaList" :key="area.id" v-model="activeAreaList" :label="area.id"
style="width: 100px" @change="checked => handleAreaChange(area.id, checked)"> style="width: 100px" class="filter-checkbox" @change="checked => handleAreaChange(area.id, checked)">
{{ area.name }} {{ area.name }}
</el-checkbox> </el-checkbox>
</div> </div>
...@@ -351,28 +334,12 @@ ...@@ -351,28 +334,12 @@
<div class="select-main"> <div class="select-main">
<div class="checkbox-group"> <div class="checkbox-group">
<el-checkbox v-for="time in pubTime" :key="time.id" v-model="activePubTime" :label="time.id" <el-checkbox v-for="time in pubTime" :key="time.id" v-model="activePubTime" :label="time.id"
style="width: 100px" class="filter-checkbox" style="width: 100px" class="filter-checkbox" @change="checked => handlePubTimeChange(time.id, checked)">
@change="checked => handlePubTimeChange(time.id, checked)">
{{ time.name }} {{ time.name }}
</el-checkbox> </el-checkbox>
</div> </div>
</div> </div>
</div> </div>
<!-- <div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "涉及领域" }}</div>
</div>
<div class="select-main select-main1">
<div class="checkbox-group">
<el-checkbox v-for="area in areaList" :key="area.id" v-model="activeAreaList"
:label="area.id" style="width: 100px"
@change="checked => handleAreaChange(area.id, checked)">
{{ area.name }}
</el-checkbox>
</div>
</div>
</div> -->
</div> </div>
<div class="right"> <div class="right">
<div class="content-header"> <div class="content-header">
...@@ -403,9 +370,7 @@ ...@@ -403,9 +370,7 @@
</div> </div>
<div class="desc">{{ item.desc }}</div> <div class="desc">{{ item.desc }}</div>
<div class="tag-box"> <div class="tag-box">
<div class="tag" v-for="(val, idx) in item.tagList" :key="idx"> <AreaTag v-for="(tag, index) in item.tagList" :key="index" :tagName="tag.industryName" />
{{ val.industryName }}
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -427,9 +392,10 @@ ...@@ -427,9 +392,10 @@
</template> </template>
<script setup> <script setup>
import NewsList from "@/components/base/NewsList/index.vue"; import { onMounted, ref, watch, nextTick, reactive } from "vue";
import { onMounted, ref, watch, nextTick } from "vue";
import router from "@/router"; import router from "@/router";
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
import SimplePagination from "@/components/SimplePagination.vue";
import { import {
getDepartmentList, getDepartmentList,
getLatestDecree, getLatestDecree,
...@@ -448,7 +414,6 @@ import DivideHeader from "@/components/DivideHeader.vue"; ...@@ -448,7 +414,6 @@ import DivideHeader from "@/components/DivideHeader.vue";
import { useContainerScroll } from "@/hooks/useScrollShow"; import { useContainerScroll } from "@/hooks/useScrollShow";
import getBarChart from "./utils/barChart"; import getBarChart from "./utils/barChart";
import getPieChart from "./utils/piechart"; import getPieChart from "./utils/piechart";
import getWordCloudChart from "./utils/wordCloudChart";
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
...@@ -458,11 +423,11 @@ import { ElMessage } from "element-plus"; ...@@ -458,11 +423,11 @@ import { ElMessage } from "element-plus";
// 跳转行政机构主页 // 跳转行政机构主页
const handleToInstitution = item => { const handleToInstitution = item => {
window.sessionStorage.setItem("curTabName", item.name); window.sessionStorage.setItem("curTabName", item.orgName);
const curRoute = router.resolve({ const curRoute = router.resolve({
path: "/institution", path: "/institution",
query: { query: {
id: item.id id: item.orgId
} }
}); });
window.open(curRoute.href, "_blank"); window.open(curRoute.href, "_blank");
...@@ -480,32 +445,15 @@ const handleCurrentChange = page => { ...@@ -480,32 +445,15 @@ const handleCurrentChange = page => {
}; };
// 页面 header // 页面 header
const govInsList = ref([ const govInsList = ref([]);
// {
// img: Gov1,
// name: "美国白宫"
// },
// {
// img: Gov2,
// name: "美国财政部"
// },
]);
const checkedGovIns = ref([]); const checkedGovIns = ref([]);
const handleChangeCheckedGovIns = val => { };
const handleGetDepartmentList = async () => { const handleGetDepartmentList = async () => {
try { try {
const res = await getDepartmentList(); const res = await getDepartmentList();
console.log("机构列表", res); console.log("机构列表", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
govInsList.value = res.data.map(item => { govInsList.value = res.data;
return {
id: item.orgId,
name: item.orgName,
img: item.orgImage
};
});
} }
} catch (error) { } catch (error) {
console.error("获取机构列表error", error); console.error("获取机构列表error", error);
...@@ -649,11 +597,8 @@ const newsList = ref([ ...@@ -649,11 +597,8 @@ const newsList = ref([
// } // }
]); ]);
const handleGetNews = async () => { const handleGetNews = async () => {
const params = {
moduleId: "0101"
};
try { try {
const res = await getNews(params); const res = await getNews({moduleId: "0101"});
console.log("新闻资讯", res); console.log("新闻资讯", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
// newsList.value = res.data || [] // newsList.value = res.data || []
...@@ -671,7 +616,6 @@ const handleGetNews = async () => { ...@@ -671,7 +616,6 @@ const handleGetNews = async () => {
console.error("新闻资讯error", error); console.error("新闻资讯error", error);
} }
}; };
handleGetNews();
// 点击新闻条目,跳转到新闻分析页 // 点击新闻条目,跳转到新闻分析页
const handleToNewsAnalysis = news => { const handleToNewsAnalysis = news => {
const route = router.resolve({ const route = router.resolve({
...@@ -914,13 +858,19 @@ const handleBox6YearChange = () => { ...@@ -914,13 +858,19 @@ const handleBox6YearChange = () => {
// 关键行政令 // 关键行政令
const keyDecreeList = ref([]); const keyDecreeList = ref([]);
const keyDecreeInfo = reactive({
total: 0,
page: 1,
size: 3,
})
const handleGetKeyDecree = async () => { const handleGetKeyDecree = async () => {
try { try {
const res = await getKeyDecree(); const res = await getKeyDecree({pageSize:keyDecreeInfo.size, pageNum:keyDecreeInfo.page-1});
console.log("关键行政令", res); console.log("关键行政令", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data?.total) {
keyDecreeList.value = res.data.map(item => { keyDecreeInfo.total = res.data.total || 0;
keyDecreeList.value = res.data.list.map(item => {
return { return {
title: item.name, title: item.name,
content: item.describe, content: item.describe,
...@@ -935,38 +885,17 @@ const handleGetKeyDecree = async () => { ...@@ -935,38 +885,17 @@ const handleGetKeyDecree = async () => {
handleGetKeyDecree(); handleGetKeyDecree();
// 政令重点条款 // 政令重点条款
const wordCloudData = [ const wordCloudData = ref([]);
// { name: "与马斯克公开冲突", value: 100 },
// { name: "传统能源", value: 5 },
// { name: "共和党财政鹰派", value: 77 },
// { name: "未实现赤字控制目标", value: 35 },
// { name: "得克萨斯州", value: 88 },
// { name: "选举压力", value: 57 },
// { name: "主张财政紧缩", value: 72 },
// { name: "财政保守", value: 18 },
];
const handleGetDecreeKeyInstruction = async () => { const handleGetDecreeKeyInstruction = async () => {
try { try {
const res = await getDecreeKeyInstruction(); const res = await getDecreeKeyInstruction();
console.log("政令重点条款", res); console.log("政令重点条款", res);
wordCloudData.value = res.data.map(item => { wordCloudData.value = res.data.map(item => ({name: item.clause, value: item.count}));
return {
name: item.clause,
value: item.count
};
});
} catch (error) { } catch (error) {
console.error("政令重点条款error", error); console.error("政令重点条款error", error);
} }
}; };
const handleBox8 = async () => {
await handleGetDecreeKeyInstruction();
let chart3 = getWordCloudChart(wordCloudData.value);
setChart(chart3, "wordCloudChart");
};
// 资源库 // 资源库
const searchType = ref(""); const searchType = ref("");
const isChina = ref(false); const isChina = ref(false);
...@@ -995,22 +924,6 @@ const handleToPosi = id => { ...@@ -995,22 +924,6 @@ const handleToPosi = id => {
} }
}; };
// const handleGetAreaList = async () => {
// try {
// const res = await getDecreehylyList();
// console.log("行业领域列表", res);
// if (res.code === 200 && res.data) {
// areaList.value = res.data.map(item => {
// return {
// name: item.name,
// id: item.id
// };
// });
// console.log("areaList", areaList.value);
// }
// } catch (error) { }
// };
// 政令类型 // 政令类型
const decreeTypeList = ref([]); const decreeTypeList = ref([]);
const checkedDecreeType = ref([]); const checkedDecreeType = ref([]);
...@@ -1101,7 +1014,7 @@ const handleGetAreaList = async () => { ...@@ -1101,7 +1014,7 @@ const handleGetAreaList = async () => {
console.log("行业领域列表", res); console.log("行业领域列表", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
areaList.value = [ areaList.value = [
{ name: "全", id: "all" }, { name: "全部领域", id: "all" },
...res.data.map(item => { ...res.data.map(item => {
return { return {
name: item.name, name: item.name,
...@@ -1218,13 +1131,14 @@ const handleSearch = () => { ...@@ -1218,13 +1131,14 @@ const handleSearch = () => {
}; };
onMounted(async () => { onMounted(async () => {
handleGetNews();
handleGetDecreeTypeList(); handleGetDecreeTypeList();
handleGetAreaList(); handleGetAreaList();
handleGetDecreeOrderList(); handleGetDecreeOrderList();
handleBox1(); // 最新科技政令 handleBox1(); // 最新科技政令
handleBox5(); handleBox5();
handleBox6(); handleBox6();
handleBox8(); handleGetDecreeKeyInstruction();
}); });
</script> </script>
...@@ -1476,15 +1390,17 @@ onMounted(async () => { ...@@ -1476,15 +1390,17 @@ onMounted(async () => {
} }
.home-main-header-item-box { .home-main-header-item-box {
margin-top: 48px; margin: 48px 0 64px;
margin-bottom: 64px;
width: 1600px; width: 1600px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 16px;
.item { .item {
width: 254px; width: 20%;
height: 72px; flex: auto;
height: 80px;
padding: 0 16px;
display: flex; display: flex;
box-sizing: border-box; box-sizing: border-box;
background: rgba(255, 255, 255, 0.65); background: rgba(255, 255, 255, 0.65);
...@@ -1492,8 +1408,7 @@ onMounted(async () => { ...@@ -1492,8 +1408,7 @@ onMounted(async () => {
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
align-items: center; align-items: center;
gap: 17px; justify-content: center;
margin: 0 6px 16px 6px;
cursor: pointer; cursor: pointer;
transition: transition:
transform 0.3s ease, transform 0.3s ease,
...@@ -1505,10 +1420,9 @@ onMounted(async () => { ...@@ -1505,10 +1420,9 @@ onMounted(async () => {
} }
.item-left { .item-left {
margin-left: 24px;
width: 48px; width: 48px;
height: 48px; height: 48px;
font-size: 0px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -1516,12 +1430,28 @@ onMounted(async () => { ...@@ -1516,12 +1430,28 @@ onMounted(async () => {
} }
.item-right { .item-right {
width: 140px; width: 20px;
flex: auto;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
line-height: 24px; line-height: 20px;
margin: 0 16px;
}
.item-num {
white-space: nowrap;
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 20px;
margin-right: 2px;
color: var(--color-primary-100);
}
.item-more {
margin-right: 12px;
font-size: 16px;
} }
} }
} }
...@@ -1708,15 +1638,10 @@ onMounted(async () => { ...@@ -1708,15 +1638,10 @@ onMounted(async () => {
line-height: 26px; line-height: 26px;
display: -webkit-box; display: -webkit-box;
/* 将元素设置为弹性盒模型 */
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
/* 限制文本显示的行数 */
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
/* 设置弹性盒子的子元素垂直排列 */
overflow: hidden; overflow: hidden;
/* 隐藏溢出的内容 */
text-overflow: ellipsis; text-overflow: ellipsis;
/* 文本溢出时显示省略号 */
} }
.box1-main-right-info { .box1-main-right-info {
...@@ -1724,58 +1649,21 @@ onMounted(async () => { ...@@ -1724,58 +1649,21 @@ onMounted(async () => {
display: flex; display: flex;
height: 24px; height: 24px;
gap: 8px; gap: 8px;
.tag {
height: 24px;
line-height: 24px;
padding: 0 8px;
box-sizing: border-box;
border-radius: 4px;
margin-right: 5px;
border: 1px solid rgba(255, 163, 158, 1);
color: rgba(245, 34, 45, 1);
background: rgba(255, 241, 240, 1);
}
.tag1 {
border: 1px solid rgba(135, 232, 222, 1);
color: rgba(19, 168, 168, 1);
background: rgba(230, 255, 251, 1);
}
.tag2 {
border: 1px solid rgba(186, 224, 255, 1);
background: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1);
}
.tag3 {
border: 1px solid rgba(255, 229, 143, 1);
color: rgba(250, 173, 20, 1);
background: rgba(255, 251, 230, 1);
}
.tag4 {
border: 1px solid rgba(255, 163, 158, 1);
color: rgba(245, 34, 45, 1);
background: rgba(255, 241, 240, 1);
}
} }
.box1-main-right-center { .box1-main-right-center {
margin-top: 10px; margin-top: 10px;
height: 200px; height: 180px;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
line-height: 30px; line-height: 30px;
display: -webkit-box; display: -webkit-box;
/* 将元素设置为弹性盒模型 */ -webkit-line-clamp: 6;
-webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
/* 隐藏溢出的内容 */
text-overflow: ellipsis; text-overflow: ellipsis;
/* 文本溢出时显示省略号 */
} }
.box1-main-right-footer { .box1-main-right-footer {
...@@ -2730,15 +2618,6 @@ onMounted(async () => { ...@@ -2730,15 +2618,6 @@ onMounted(async () => {
font-weight: 400; font-weight: 400;
} }
} }
.info-content {
margin-top: 3px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
}
} }
} }
} }
...@@ -2912,10 +2791,9 @@ onMounted(async () => { ...@@ -2912,10 +2791,9 @@ onMounted(async () => {
.box7-main { .box7-main {
margin-top: 10px; margin-top: 10px;
height: 380px;
box-sizing: border-box; .box7-list {
overflow-y: auto; height: 310px;
overflow-x: hidden;
.box7-item { .box7-item {
width: 730px; width: 730px;
...@@ -2985,7 +2863,12 @@ onMounted(async () => { ...@@ -2985,7 +2863,12 @@ onMounted(async () => {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
line-height: 24px; line-height: 24px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
}
} }
} }
} }
...@@ -2999,9 +2882,11 @@ onMounted(async () => { ...@@ -2999,9 +2882,11 @@ onMounted(async () => {
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2); box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
display: flex;
flex-direction: column;
.box8-header { .box8-header {
width: 792px; width: 100%;
height: 48px; height: 48px;
display: flex; display: flex;
border-bottom: 1px solid rgba(240, 242, 244, 1); border-bottom: 1px solid rgba(240, 242, 244, 1);
...@@ -3030,6 +2915,12 @@ onMounted(async () => { ...@@ -3030,6 +2915,12 @@ onMounted(async () => {
} }
} }
.box8-content {
width: 100%;
height: 20px;
flex: auto;
}
.box8-main { .box8-main {
height: 401px; height: 401px;
} }
...@@ -3039,7 +2930,6 @@ onMounted(async () => { ...@@ -3039,7 +2930,6 @@ onMounted(async () => {
.home-main-footer { .home-main-footer {
margin-top: 34px; margin-top: 34px;
max-height: 1860px;
padding-bottom: 160px; padding-bottom: 160px;
background: rgba(248, 249, 250, 1); background: rgba(248, 249, 250, 1);
overflow: hidden; overflow: hidden;
...@@ -3133,16 +3023,21 @@ onMounted(async () => { ...@@ -3133,16 +3023,21 @@ onMounted(async () => {
.left { .left {
width: 360px; width: 360px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2); height: 100%;
background: rgba(255, 255, 255, 1); padding-bottom: 24px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.select-box { .select-box {
margin-top: 16px; margin-top: 16px;
.select-box-header { .select-box-header {
display: flex; display: flex;
gap: 16px; gap: 17px;
.icon { .icon {
margin-top: 4px; margin-top: 4px;
...@@ -3155,7 +3050,7 @@ onMounted(async () => { ...@@ -3155,7 +3050,7 @@ onMounted(async () => {
.title { .title {
height: 24px; height: 24px;
color: var(--color-main-active); color: var(--color-main-active);
font-family: Microsoft YaHei; font-family: "Source Han Sans CN";
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
line-height: 24px; line-height: 24px;
...@@ -3165,11 +3060,20 @@ onMounted(async () => { ...@@ -3165,11 +3060,20 @@ onMounted(async () => {
} }
.select-main { .select-main {
margin-left: 25px; margin-left: 24px;
} margin-top: 12px;
.select-main1 { .checkbox-group {
width: 260px; display: grid;
grid-template-columns: repeat(2, 160px);
gap: 8px 4px;
.filter-checkbox {
width: 160px;
height: 24px;
margin-right: 0 !important;
}
}
} }
} }
} }
...@@ -3177,7 +3081,6 @@ onMounted(async () => { ...@@ -3177,7 +3081,6 @@ onMounted(async () => {
.right { .right {
width: 20px; width: 20px;
flex: auto; flex: auto;
max-height: 1489px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2); box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
box-sizing: border-box; box-sizing: border-box;
...@@ -3213,12 +3116,9 @@ onMounted(async () => { ...@@ -3213,12 +3116,9 @@ onMounted(async () => {
} }
.content-box { .content-box {
max-height: 1367px;
border-bottom: 1px solid rgba(234, 236, 238, 1); border-bottom: 1px solid rgba(234, 236, 238, 1);
overflow: hidden; overflow: hidden;
min-height: 790px; min-height: 790px;
overflow: hidden;
overflow-y: auto;
box-sizing: border-box; box-sizing: border-box;
.main-item { .main-item {
...@@ -3349,20 +3249,6 @@ onMounted(async () => { ...@@ -3349,20 +3249,6 @@ onMounted(async () => {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 8px;
.tag {
height: 28px;
line-height: 28px;
text-align: center;
padding: 0 8px;
border-radius: 4px;
background: rgba(231, 243, 255, 1);
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
letter-spacing: 0px;
}
} }
} }
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="box1"> <div class="box1">
<AnalysisBox title="相关政令" :showAllBtn="false"> <AnalysisBox title="相关政令" :showAllBtn="false">
<div class="box1-main"> <div class="box1-main">
<el-empty v-if="siderList.length===0" style="padding-top: 30%" description="暂无数据" :image-size="100" /> <el-empty v-if="!siderList?.length" style="padding-top: 40%;" description="暂无数据" :image-size="100" />
<el-scrollbar height="100%" always> <el-scrollbar height="100%" always>
<div class="left-item" :class="{ 'item-active': false }" v-for="(item, index) in siderList" :key="index" @click="handleClickDecree(item)"> <div class="left-item" :class="{ 'item-active': false }" v-for="(item, index) in siderList" :key="index" @click="handleClickDecree(item)">
<div class="item-head"> <div class="item-head">
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
</div> </div>
<div class="box2"> <div class="box2">
<AnalysisBox title="政令关系挖掘" :showAllBtn="false"> <AnalysisBox title="政令关系挖掘" :showAllBtn="false">
<el-empty v-if="!siderList?.length" style="padding-top: 20%;" description="暂无数据" :image-size="100" />
<div class="box2-main"> <div class="box2-main">
<div ref="containerRef" class="graph-container"></div> <div ref="containerRef" class="graph-container"></div>
</div> </div>
......
...@@ -8,22 +8,16 @@ ...@@ -8,22 +8,16 @@
<div class="layout-main-header-left-box"> <div class="layout-main-header-left-box">
<div class="left-box-top"> <div class="left-box-top">
<div class="icon"> <div class="icon">
<img <img :src="summaryInfo.imageUrl || USALogo" alt="" />
v-if="summaryInfo.imageUrl"
:src="summaryInfo.imageUrl"
alt=""
style="height: 40px; margin-top: 12px"
/>
<img v-else :src="USALogo" alt="" />
</div> </div>
<div class="info"> <div class="info">
<div class="info-box1">{{ summaryInfo.name }}</div> <div class="info-box1 one-line-ellipsis">{{ summaryInfo.name || "--" }}</div>
<div class="info-box2"> <div class="info-box2">
<div class="info-box2-item item1">{{ summaryInfo.postDate }}</div> <div class="info-box2-item">{{ summaryInfo.postDate || "--" }}</div>
| |
<div class="info-box2-item item2">{{ summaryInfo.orgName }}</div> <div class="info-box2-item">{{ summaryInfo.orgName || "--" }}</div>
| |
<div class="info-box2-item item3">{{ summaryInfo.ename }}</div> <div class="info-box2-item one-line-ellipsis">{{ summaryInfo.ename || "--" }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -47,8 +41,8 @@ ...@@ -47,8 +41,8 @@
</div> </div>
<div class="layout-main-header-right-box"> <div class="layout-main-header-right-box">
<div class="right-box-top"> <div class="right-box-top">
<div class="time">{{ summaryInfo.postDate }}</div> <div class="time">{{ summaryInfo.postDate || "--" }}</div>
<div class="name">{{ summaryInfo.orgName }}</div> <div class="name">{{ summaryInfo.orgName || "--" }}</div>
</div> </div>
<div class="right-box-bottom"> <div class="right-box-bottom">
<div class="btn" @click="handleShowReport"> <div class="btn" @click="handleShowReport">
...@@ -203,12 +197,12 @@ const mainHeaderBtnList = ref([ ...@@ -203,12 +197,12 @@ const mainHeaderBtnList = ref([
name: "深度挖掘", name: "深度挖掘",
path: "/decreeLayout/deepDig" path: "/decreeLayout/deepDig"
}, },
// { {
// icon: icon3, icon: icon3,
// activeIcon: icon3Active, activeIcon: icon3Active,
// name: "影响分析", name: "影响分析",
// path: "/decreeLayout/influence" path: "/decreeLayout/influence"
// }, },
]); ]);
const activeTitle = ref("政令概况"); const activeTitle = ref("政令概况");
...@@ -377,9 +371,9 @@ onMounted(() => { ...@@ -377,9 +371,9 @@ onMounted(() => {
flex-direction: column; flex-direction: column;
.header-main { .header-main {
width: 100%; width: 100%;
background-color: #fff; border-bottom: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.05); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
overflow: hidden; background: rgba(255, 255, 255, 1);
} }
.layout-main-header { .layout-main-header {
width: 1600px; width: 1600px;
...@@ -396,30 +390,32 @@ onMounted(() => { ...@@ -396,30 +390,32 @@ onMounted(() => {
margin: 0 auto; margin: 0 auto;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center;
} }
.layout-main-header-left-box { .layout-main-header-left-box {
width: 1100px; width: 20px;
margin-top: 13px; flex: auto;
margin-top: 12px;
.left-box-top { .left-box-top {
height: 64px; height: 64px;
display: flex; display: flex;
align-items: center; align-items: center;
.icon { .icon {
width: 64px; width: 64px;
height: 64px; height: 40px;
border-radius: 4px;
overflow: hidden; overflow: hidden;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: fill;
} }
} }
.info { .info {
width: 700px; margin-left: 10px;
margin-left: 9px; margin-right: 40px;
width: 20px;
flex: auto;
.info-box1 { .info-box1 {
width: 700px; width: 100%;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-size: 20px; font-size: 20px;
...@@ -428,9 +424,6 @@ onMounted(() => { ...@@ -428,9 +424,6 @@ onMounted(() => {
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
margin-top: 5px; margin-top: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.info-box2 { .info-box2 {
margin-top: 5px; margin-top: 5px;
...@@ -444,15 +437,13 @@ onMounted(() => { ...@@ -444,15 +437,13 @@ onMounted(() => {
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
display: flex; display: flex;
margin-left: -10px;
.info-box2-item { .info-box2-item {
white-space: nowrap;
padding: 0 10px; padding: 0 10px;
} }
.item3 { .info-box2-item:first-child {
max-width: 420px; padding-left: 0px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
} }
} }
...@@ -498,9 +489,9 @@ onMounted(() => { ...@@ -498,9 +489,9 @@ onMounted(() => {
} }
} }
.layout-main-header-right-box { .layout-main-header-right-box {
width: 450px;
margin-top: 19px;
.right-box-top { .right-box-top {
white-space: nowrap;
padding-top: 11px;
.time { .time {
height: 24px; height: 24px;
line-height: 24px; line-height: 24px;
...@@ -710,49 +701,5 @@ onMounted(() => { ...@@ -710,49 +701,5 @@ onMounted(() => {
} }
} }
} }
// .tool-box {
// position: fixed;
// z-index: 10000;
// bottom: 80px;
// left: 0;
// width: 48px;
// height: 144px;
// border-radius: 0px 10px 10px 0px;
// box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
// background: rgba(255, 255, 255, 1);
// .tool1 {
// width: 17px;
// height: 18px;
// margin-top: 17px;
// margin-left: 16px;
// cursor: pointer;
// img {
// width: 100%;
// height: 100%;
// }
// }
// .tool2 {
// width: 22px;
// height: 20px;
// margin-top: 26px;
// margin-left: 14px;
// cursor: pointer;
// img {
// width: 100%;
// height: 100%;
// }
// }
// .tool3 {
// width: 20px;
// height: 20px;
// margin-top: 25px;
// margin-left: 15px;
// cursor: pointer;
// img {
// width: 100%;
// height: 100%;
// }
// }
// }
} }
</style> </style>
\ No newline at end of file
<template>
<div class="relation-graph-wrapper">
<div class="graph-controls">
<!-- 这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。 -->
<div v-for="item in controlBtns" :key="item.type" :class="['control-btn', { 'control-btn-active': currentLayoutType === item.type }]" @click="handleClickControlBtn(item.type)">
<img :src="item.icon" alt="" />
</div>
</div>
<div ref="containerRef" class="graph-container"></div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
import G6 from '@antv/g6'
import { Close } from '@element-plus/icons-vue'
import echartsIcon01 from './assets/images/echartsicon01.png'
import echartsIcon02 from './assets/images/echartsicon02.png'
import echartsIcon03 from './assets/images/echartsicon03.png'
const props = defineProps({
graphData: {
type: Object,
default: () => ({ nodes: [], links: [] })
},
treeData: {
type: Object,
default: () => null
},
controlActive: {
type: Number,
default: 1
}
})
const emit = defineEmits(['nodeClick', 'layoutChange'])
const containerRef = ref(null)
const graphInstance = ref(null)
const currentLayoutType = ref(1)
const controlBtns = [
{ type: 1, icon: echartsIcon01, name: '力导向布局' },
{ type: 2, icon: echartsIcon02, name: '树布局' },
{ type: 3, icon: echartsIcon03, name: '环状布局' }
]
const initGraph = (layoutType = 1) => {
if (!containerRef.value) return
destroyGraph()
nextTick(() => {
const width = containerRef.value.offsetWidth || 800
const height = containerRef.value.offsetHeight || 600
switch (layoutType) {
case 1:
initNormalGraph(layoutType, width, height)
break
case 2:
initTreeGraph(width, height)
break
case 3:
initCircularGraph(width, height)
break
}
})
}
const initNormalGraph = (layoutType, width, height) => {
const data = processGraphData(props.graphData)
if (!data.nodes || data.nodes.length === 0) return
const layout = {
type: 'force',
center: [width / 2, height / 2],
preventOverlap: true,
nodeSpacing: 80,
linkDistance: 250,
nodeStrength: -800,
edgeStrength: 0.1,
collideStrength: 0.8,
alphaDecay: 0.01,
alphaMin: 0.001
}
graphInstance.value = new G6.Graph({
container: containerRef.value,
width,
height,
fitView: true,
fitViewPadding: 100,
fitCenter: true,
animate: true,
animateCfg: {
duration: 300,
easing: 'easeLinear'
},
minZoom: 0.1,
maxZoom: 10,
modes: {
default: [
'drag-canvas',
'zoom-canvas',
'drag-node',
{
type: 'activate-relations',
trigger: 'mouseenter',
resetSelected: true
}
]
},
layout,
defaultNode: {
type: 'image',
size: 40,
clipCfg: {
show: true,
type: 'circle',
r: 20
},
labelCfg: {
position: 'bottom',
offset: 10,
style: {
fill: '#333',
fontSize: 11,
fontFamily: 'Microsoft YaHei',
textAlign: 'center',
background: {
fill: 'rgba(255, 255, 255, 0.95)',
padding: [4, 6, 4, 6],
radius: 4
}
}
}
},
defaultEdge: {
type: 'quadratic',
style: {
stroke: '#5B8FF9',
lineWidth: 3,
opacity: 0.9,
endArrow: {
path: 'M 0,0 L 12,6 L 12,-6 Z',
fill: '#5B8FF9'
}
},
labelCfg: {
autoRotate: true,
style: {
fill: '#333',
fontSize: 10,
fontFamily: 'Microsoft YaHei',
background: {
fill: '#fff',
padding: [2, 4, 2, 4],
radius: 2
}
}
}
},
nodeStateStyles: {
active: {
shadowColor: '#1459BB',
shadowBlur: 15,
stroke: '#1459BB',
lineWidth: 3
},
inactive: {
opacity: 0.3
}
},
edgeStateStyles: {
active: {
stroke: '#1459BB',
lineWidth: 4
},
inactive: {
opacity: 0.15
}
}
})
graphInstance.value.data(data)
graphInstance.value.render()
bindGraphEvents()
}
const initCircularGraph = (width, height) => {
const data = processGraphData(props.graphData)
if (!data.nodes || data.nodes.length === 0) return
const centerX = width / 2
const centerY = height / 2
const radius = Math.min(width, height) / 2 - 120
const otherNodes = data.nodes.filter(n => !n.isCenter)
const nodeCount = otherNodes.length
otherNodes.forEach((node, index) => {
const angle = (2 * Math.PI * index) / nodeCount - Math.PI / 2
node.x = centerX + radius * Math.cos(angle)
node.y = centerY + radius * Math.sin(angle)
})
const centerNode = data.nodes.find(n => n.isCenter)
if (centerNode) {
centerNode.x = centerX
centerNode.y = centerY
centerNode.fx = centerX
centerNode.fy = centerY
}
graphInstance.value = new G6.Graph({
container: containerRef.value,
width,
height,
fitView: false,
fitCenter: false,
animate: true,
animateCfg: {
duration: 300,
easing: 'easeLinear'
},
minZoom: 0.1,
maxZoom: 10,
modes: {
default: [
'drag-canvas',
'zoom-canvas',
'drag-node',
{
type: 'activate-relations',
trigger: 'mouseenter',
resetSelected: true
}
]
},
defaultNode: {
type: 'image',
size: 40,
clipCfg: {
show: true,
type: 'circle',
r: 20
},
labelCfg: {
position: 'bottom',
offset: 10,
style: {
fill: '#333',
fontSize: 11,
fontFamily: 'Microsoft YaHei',
textAlign: 'center',
background: {
fill: 'rgba(255, 255, 255, 0.95)',
padding: [4, 6, 4, 6],
radius: 4
}
}
}
},
defaultEdge: {
type: 'quadratic',
style: {
stroke: '#5B8FF9',
lineWidth: 3,
opacity: 0.9,
endArrow: {
path: 'M 0,0 L 12,6 L 12,-6 Z',
fill: '#5B8FF9'
}
},
labelCfg: {
autoRotate: true,
style: {
fill: '#333',
fontSize: 10,
fontFamily: 'Microsoft YaHei',
background: {
fill: '#fff',
padding: [2, 4, 2, 4],
radius: 2
}
}
}
},
nodeStateStyles: {
active: {
shadowColor: '#1459BB',
shadowBlur: 15,
stroke: '#1459BB',
lineWidth: 3
},
inactive: {
opacity: 0.3
}
},
edgeStateStyles: {
active: {
stroke: '#1459BB',
lineWidth: 4
},
inactive: {
opacity: 0.15
}
}
})
graphInstance.value.data(data)
graphInstance.value.render()
bindGraphEvents()
}
const initTreeGraph = (width, height) => {
const treeDataSource = convertGraphToTree(props.graphData)
if (!treeDataSource) return
graphInstance.value = new G6.TreeGraph({
container: containerRef.value,
width,
height,
fitView: true,
fitViewPadding: 80,
animate: true,
animateCfg: {
duration: 300,
easing: 'easeLinear'
},
minZoom: 0.1,
maxZoom: 10,
modes: {
default: [
'drag-canvas',
'zoom-canvas',
'drag-node',
{
type: 'collapse-expand',
onChange: function onChange(item, collapsed) {
const data = item.getModel()
data.collapsed = collapsed
return true
}
}
]
},
layout: {
type: 'compactBox',
direction: 'LR',
getId: function getId(d) {
return d.id
},
getHeight: function getHeight() {
return 16
},
getWidth: function getWidth() {
return 16
},
getVGap: function getVGap() {
return 30
},
getHGap: function getHGap() {
return 120
}
},
defaultNode: {
type: 'image',
size: 40,
clipCfg: {
show: true,
type: 'circle',
r: 20
},
labelCfg: {
position: 'right',
offset: 10,
style: {
fill: '#333',
fontSize: 11,
fontFamily: 'Microsoft YaHei',
background: {
fill: 'rgba(255, 255, 255, 0.95)',
padding: [4, 6, 4, 6],
radius: 4
}
}
}
},
defaultEdge: {
type: 'cubic-horizontal',
style: {
stroke: '#5B8FF9',
lineWidth: 3
}
},
nodeStateStyles: {
active: {
shadowColor: '#1459BB',
shadowBlur: 15,
stroke: '#1459BB',
lineWidth: 3
}
}
})
graphInstance.value.data(treeDataSource)
graphInstance.value.render()
graphInstance.value.fitView()
bindGraphEvents()
}
const convertGraphToTree = (graphData) => {
if (!graphData || !graphData.nodes || graphData.nodes.length === 0) {
return null
}
const nodes = graphData.nodes
const links = graphData.links || graphData.edges || []
const centerNode = nodes[0]
const centerId = String(centerNode.id || '0')
const childIdSet = new Set()
const childrenNodes = []
links.forEach((link) => {
const source = String(link.source)
const target = String(link.target)
if (source === centerId && !childIdSet.has(target)) {
const node = nodes.find(n => String(n.id) === target)
if (node) {
childIdSet.add(target)
childrenNodes.push({
id: target,
label: node.name || '',
img: node.image || echartsIcon03,
size: node.symbolSize || 40,
name: node.name,
image: node.image,
isSanctioned: node.isSanctioned
})
}
} else if (target === centerId && !childIdSet.has(source)) {
const node = nodes.find(n => String(n.id) === source)
if (node) {
childIdSet.add(source)
childrenNodes.push({
id: source,
label: node.name || '',
img: node.image || echartsIcon03,
size: node.symbolSize || 40,
name: node.name,
image: node.image,
isSanctioned: node.isSanctioned
})
}
}
})
if (childrenNodes.length === 0) {
nodes.slice(1).forEach((node) => {
const nodeId = String(node.id)
if (!childIdSet.has(nodeId)) {
childIdSet.add(nodeId)
childrenNodes.push({
id: nodeId,
label: node.name || '',
img: node.image || echartsIcon03,
size: node.symbolSize || 40,
name: node.name,
image: node.image,
isSanctioned: node.isSanctioned
})
}
})
}
return {
id: centerId,
label: centerNode.name || '',
img: centerNode.image || echartsIcon03,
size: centerNode.symbolSize || 60,
name: centerNode.name,
image: centerNode.image,
isSanctioned: centerNode.isSanctioned,
children: childrenNodes
}
}
const processGraphData = (rawData) => {
if (!rawData || !rawData.nodes || rawData.nodes.length === 0) {
return { nodes: [], edges: [] }
}
const nodeMap = new Map()
const nodes = []
rawData.nodes.forEach((node, index) => {
const nodeId = String(node.id || index)
if (nodeMap.has(nodeId)) {
return
}
nodeMap.set(nodeId, true)
const isCenter = index === 0
const size = node.symbolSize || (isCenter ? 60 : 40)
nodes.push({
id: nodeId,
label: node.name || '',
img: node.image || echartsIcon03,
size,
isCenter,
clipCfg: {
show: true,
type: 'circle',
r: size / 2
},
style: {
cursor: 'pointer'
},
labelCfg: {
position: 'bottom',
offset: 12,
style: {
fill: isCenter ? '#1459BB' : '#333',
fontSize: isCenter ? 13 : 11,
fontWeight: isCenter ? 'bold' : 'normal',
fontFamily: 'Microsoft YaHei',
textAlign: 'center'
}
},
...node,
id: nodeId
})
})
const edgeMap = new Map()
const edges = []
const rawEdges = rawData.links || rawData.edges || []
rawEdges.forEach((edge, index) => {
const source = String(edge.source)
const target = String(edge.target)
const edgeKey = `${source}-${target}`
if (edgeMap.has(edgeKey)) {
return
}
if (!nodeMap.has(source) || !nodeMap.has(target)) {
return
}
edgeMap.set(edgeKey, true)
edges.push({
id: `edge-${index}`,
source,
target,
label: edge.name || ''
})
})
return { nodes, edges }
}
const bindGraphEvents = () => {
if (!graphInstance.value) return
graphInstance.value.on('node:click', (evt) => {
const node = evt.item
const model = node.getModel()
emit('nodeClick', model)
})
graphInstance.value.on('canvas:click', () => {
})
}
const handleClickControlBtn = (btn) => {
currentLayoutType.value = btn
emit('layoutChange', btn)
initGraph(btn)
}
const destroyGraph = () => {
if (graphInstance.value) {
graphInstance.value.destroy()
graphInstance.value = null
}
}
const handleResize = () => {
if (graphInstance.value && containerRef.value) {
const width = containerRef.value.offsetWidth
const height = containerRef.value.offsetHeight
graphInstance.value.changeSize(width, height)
graphInstance.value.fitView()
}
}
watch(
() => props.graphData,
() => {
initGraph(currentLayoutType.value)
}
)
watch(
() => props.treeData,
() => {
if (currentLayoutType.value === 2) {
initGraph(2)
}
}
)
watch(
() => props.controlActive,
(newVal) => {
if (newVal !== currentLayoutType.value) {
handleClickControlBtn(newVal)
}
}
)
onMounted(() => {
initGraph(1)
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
destroyGraph()
})
defineExpose({
refresh: () => initGraph(currentLayoutType.value),
changeLayout: (type) => handleClickControlBtn(type),
getGraph: () => graphInstance.value
})
</script>
<style lang="scss" scoped>
.relation-graph-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.graph-container {
width: 100%;
height: 100%;
}
.graph-controls {
position: absolute;
top: 16px;
right: 16px;
display: flex;
gap: 8px;
z-index: 10;
.control-btn {
width: 32px;
height: 32px;
border-radius: 4px;
border: 1px solid rgba(234, 236, 238, 1);
background: rgba(255, 255, 255, 1);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
img {
width: 16px;
height: 16px;
}
&:hover {
border-color: rgba(5, 95, 194, 0.5);
}
}
.control-btn-active {
border-color: rgba(5, 95, 194, 1);
background: rgba(231, 243, 255, 1);
}
}
</style>
\ No newline at end of file
<template>
<div class="view-box">
<div class="icon-left">
<img src="../../assets/icons/ai.png" alt="">
</div>
<div class="tips-content">{{ props.tips }}</div>
<div class="icon-right">
<img src="../../assets/icons/right.png" alt="">
</div>
</div>
</template>
<script setup lang="ts" name="AiTips">
const props = defineProps({
tips: {
type: String,
default: ''
}
});
</script>
<style scoped lang="scss">
.view-box {
width: 100%;
display: flex;
align-items: center;
padding: 7px 12px;
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
background: rgba(246, 250, 255, 1);
.icon-left {
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.tips-content {
color: rgb(5, 95, 194);
font-size: 16px;
font-weight: 400;
line-height: 24px;
margin-left: 13px;
flex: 1;
}
.icon-right {
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
</style>
\ No newline at end of file
<template> <template>
<div class="view-box"> <div class="view-box">
<div class="right-main"> <el-empty v-if="!dataList?.length" style="padding-top: 15%;" description="暂无数据" :image-size="100" />
<div class="right-main-content"> <div v-if="dataList.length" class="main-content-main">
<div class="hintWrap"> <div class="main-mask"
<div class="icon1"></div> @wheel.prevent="handleWheel"
<div class="title"> @mousedown="handleMouseDown"
这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。 @mouseup="handleMouseUp"
</div> @mouseleave="handleMouseUp"
<div class="icon2Wrap"> @mousemove="handleMouseMove"
<div class="icon2"></div> ></div>
</div> <div class="fishbone-container" :style="{ transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`, transformOrigin: 'center center' }">
</div>
<div class="right-main-content-main">
<div class="fishbone-wrapper">
<div class="fishbone-scroll-container" ref="scrollContainerRef">
<div class="fishbone" v-if="dataList.length > 0">
<div class="main-line" :style="{ width: dataList.length * 200 + 300 + 'px' }">
<!-- 主轴上的标签 --> <!-- 主轴上的标签 -->
<div class="main-line" :style="{ width: dataList.length * 200 + 300 + 'px' }">
<div class="main-line-text" v-for="(item, index) in dataList" :key="'label-' + index" <div class="main-line-text" v-for="(item, index) in dataList" :key="'label-' + index"
:class="{ :class="{
'blue-theme': index < 2, 'blue-theme': index < 2,
...@@ -27,36 +22,35 @@ ...@@ -27,36 +22,35 @@
</div> </div>
</div> </div>
<!-- 奇数索引的数据组放在上方 --> <!-- 奇数索引的数据组放在上方 -->
<div v-for="(causeGroup, groupIndex) in onFilterData(1)" :key="'top-' + groupIndex" <div v-for="(causeGroup, groupIndex) in onFilterData(1)" :key="groupIndex"
:class="getTopBoneClass(groupIndex)" :style="{ left: groupIndex * 400 + 420 + 'px' }"> class="top-bone" :style="{ left: groupIndex * 400 + 510 + 'px', height: (causeGroup.causes?.length) * 22 + 100 + 'px' }">
<div class="left-bone"> <div class="left-bone">
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="'left-' + index"> <div class="left-bone-item" v-for="item in getLeftItems(causeGroup.causes)" :key="item.id">
<img :src="defaultIcon2 || item.picture" alt="" class="company-icon" /> <img :src="defaultIcon2 || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div> <div class="text" :title="item.name">{{ item.name }}</div>
<div class="line"></div> <div class="line"></div>
</div> </div>
</div> </div>
<div class="right-bone"> <div class="right-bone">
<div class="right-bone-item" v-for="(item, index) in getRightItems(causeGroup.causes)" :key="'right-' + index"> <div class="right-bone-item" v-for="item in getRightItems(causeGroup.causes)" :key="item.id">
<div class="line"></div> <div class="line"></div>
<img :src="defaultIcon2 || item.picture" alt="" class="company-icon" /> <img :src="defaultIcon2 || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div> <div class="text" :title="item.name">{{ item.name }}</div>
</div> </div>
</div> </div>
</div> </div>
<!-- 偶数索引的数据组放在下方 --> <!-- 偶数索引的数据组放在下方 -->
<div v-for="(causeGroup, groupIndex) in onFilterData(0)" :key="'bottom-' + groupIndex" <div v-for="(causeGroup, groupIndex) in onFilterData(0)" :key="groupIndex"
:class="getBottomBoneClass(groupIndex)" :style="{ left: groupIndex * 400 + 220 + 'px' }"> class="bottom-bone" :style="{ left: groupIndex * 400 + 310 + 'px', height: (causeGroup.causes?.length) * 22 + 100 + 'px' }">
<div class="left-bone"> <div class="left-bone">
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="'left-bottom-' + index"> <div class="left-bone-item" v-for="item in getRightItems(causeGroup.causes)" :key="item.id">
<img :src="defaultIcon2 || item.picture" alt="" class="company-icon" /> <img :src="defaultIcon2 || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div> <div class="text" :title="item.name">{{ item.name }}</div>
<div class="line"></div> <div class="line"></div>
</div> </div>
</div> </div>
<div class="right-bone"> <div class="right-bone">
<div class="right-bone-item" v-for="(item, index) in getRightItems(causeGroup.causes)" :key="'right-bottom-' + index"> <div class="right-bone-item" v-for="item in getLeftItems(causeGroup.causes)" :key="item.id">
<div class="line"></div> <div class="line"></div>
<img :src="defaultIcon2 || item.picture" alt="" class="company-icon" /> <img :src="defaultIcon2 || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div> <div class="text" :title="item.name">{{ item.name }}</div>
...@@ -64,14 +58,8 @@ ...@@ -64,14 +58,8 @@
</div> </div>
</div> </div>
</div> </div>
<div v-else
style="display: flex; justify-content: center; align-items: center; height: 200px; width: 100%">
<el-empty description="暂无相关数据" />
</div>
</div> </div>
</div> <div v-if="dataList.length" class="main-content-footer">
</div>
<div class="right-main-content-footer">
<div class="footer-item footer-item1"> <div class="footer-item footer-item1">
<div class="footer-item-bottom"> <div class="footer-item-bottom">
<div class="icon"> <div class="icon">
...@@ -119,16 +107,49 @@ ...@@ -119,16 +107,49 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</template> </template>
<script setup name="ChartChain"> <script setup name="ChartChain">
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import defaultIcon2 from "@/assets/icons/default-icon2.png"; import defaultIcon2 from "@/assets/icons/default-icon2.png";
import noticeIcon from "./assets/images/notice-icon.png"; import noticeIcon from "../assets/images/notice-icon.png";
import { getDeepMiningSelect, getDeepMiningIndustry, getDeepMiningIndustryFishbone, getDeepMiningIndustryEntity } from "@/api/exportControlV2.0"; import { getDeepMiningSelect, getDeepMiningIndustry, getDeepMiningIndustryFishbone, getDeepMiningIndustryEntity } from "@/api/exportControlV2.0";
// 缩放功能处理
const scale = ref(1)
const minScale = 0.1
const maxScale = 10
const handleWheel = (e) => {
if (e.deltaY < 0) {
// 放大:不超过最大值
scale.value = Math.min(scale.value + 0.1, maxScale)
} else {
// 缩小:不低于最小值
scale.value = Math.max(scale.value - 0.1, minScale)
}
}
// 移动功能处理
const translateX = ref(0) // X轴位移
const translateY = ref(0) // Y轴位移
let isDragging = false
let startX = 0
let startY = 0
const handleMouseMove = (e) => {
if (!isDragging) return
translateX.value = e.clientX - startX
translateY.value = e.clientY - startY
}
const handleMouseDown = (e) => {
// 排除右键/中键,只响应左键(e.button=0为左键)
if (e.button !== 0) return
isDragging = true
startX = e.clientX - translateX.value
startY = e.clientY - translateY.value
}
const handleMouseUp = () => {
isDragging = false
}
// 实体清单-深度挖掘-产业链中国企业实体信息查询 // 实体清单-深度挖掘-产业链中国企业实体信息查询
const cnEntityOnChainData = ref({}); const cnEntityOnChainData = ref({});
const getCnEntityOnChainData = async () => { const getCnEntityOnChainData = async () => {
...@@ -173,16 +194,6 @@ const getRightItems = items => { ...@@ -173,16 +194,6 @@ const getRightItems = items => {
const midpoint = Math.ceil(items.length / 2); const midpoint = Math.ceil(items.length / 2);
return items.slice(midpoint); return items.slice(midpoint);
}; };
// 获取上方鱼骨图位置类名
const getTopBoneClass = index => {
const positions = ["top-bone", "top-bone1", "top-bone2"];
return positions[index % 3] || "top-bone";
};
// 获取下方鱼骨图位置类名
const getBottomBoneClass = index => {
const positions = ["bottom-bone", "bottom-bone1", "bottom-bone2"];
return positions[index % 3] || "bottom-bone";
};
const getFishboneData = async () => { const getFishboneData = async () => {
const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value); const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value);
const date = currentSanction ? currentSanction.date : ''; const date = currentSanction ? currentSanction.date : '';
...@@ -305,126 +316,35 @@ onMounted(() => { ...@@ -305,126 +316,35 @@ onMounted(() => {
.view-box { .view-box {
width: 100%; width: 100%;
height: 100%; height: 100%;
}
.right-main {
height: 100%;
padding: 11px 16px 20px;
.right-main-content {
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.hintWrap { .main-content-main {
display: flex; position: relative;
align-items: center;
padding: 7px 12px;
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
background: rgba(246, 250, 255, 1);
margin-bottom: 9px;
.icon1 {
width: 19px;
height: 20px; height: 20px;
background-image: url("../assets/icons/ai.png"); flex: auto;
background-size: 100% 100%;
flex-shrink: 0;
}
.title {
color: rgb(5, 95, 194);
font-size: 16px;
font-weight: 400;
line-height: 24px;
margin-left: 13px;
flex: 1;
}
.icon2Wrap {
width: 24px;
height: 24px;
background-color: rgba(231, 243, 255, 1);
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
border-radius: 12px; justify-content: center;
margin-left: 20px;
flex-shrink: 0;
.icon2 {
width: 24px;
height: 24px;
background-image: url("../assets/icons/right.png");
background-size: 100% 100%;
}
}
}
.right-main-content-main {
flex: 1;
position: relative;
overflow: hidden; overflow: hidden;
.main-mask {
.fishbone-wrapper { position: absolute;
position: relative; top: 0;
width: 100%; left: 0;
height: 100%;
}
.fishbone-scroll-container {
display: flex;
align-items: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow-x: auto; z-index: 3;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(144, 202, 249, 0.5) transparent;
&::-webkit-scrollbar {
height: 6px;
} }
&::-webkit-scrollbar-track { .fishbone-container {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(144, 202, 249, 0.5);
border-radius: 3px;
}
}
.fishbone {
position: relative; position: relative;
width: fit-content;
height: 100%;
margin-top: 40px;
min-width: 100%;
padding-left: 275px;
margin-left: 40px;
.main-line { .main-line {
margin-top: 280px;
width: 1888px;
height: 3px; height: 3px;
background: rgb(230, 231, 232); background: rgb(230, 231, 232);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0 100px;
// 虚线
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
// 添加中间的文字块 // 添加中间的文字块
.main-line-text { .main-line-text {
...@@ -472,28 +392,28 @@ onMounted(() => { ...@@ -472,28 +392,28 @@ onMounted(() => {
.top-bone { .top-bone {
position: absolute; position: absolute;
top: 20px; bottom: 0px;
right: 200px;
width: 3px; width: 3px;
height: 260px;
background: rgb(230, 231, 232); background: rgb(230, 231, 232);
transform-origin: bottom center;
transform: skew(30deg); transform: skew(30deg);
z-index: 1; z-index: 1;
.left-bone { .left-bone {
color: #777; color: #777;
position: absolute; position: absolute;
top: 0; top: -20px;
left: -150px; right: 0;
width: 150px; width: 180px;
height: 50px; height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
// overflow: hidden;
.left-bone-item { .left-bone-item {
transform: skew(-30deg); transform: skew(-30deg);
height: 45px; height: 40px;
margin-bottom: 2px; margin: 4px 0;
margin-top: 2px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
...@@ -519,17 +439,18 @@ onMounted(() => { ...@@ -519,17 +439,18 @@ onMounted(() => {
.right-bone { .right-bone {
color: #777; color: #777;
position: absolute; position: absolute;
top: 0; top: -44px;
right: -150px; left: 0;
width: 150px; width: 180px;
height: 210px; height: 100%;
overflow: hidden; display: flex;
flex-direction: column;
justify-content: flex-end;
.right-bone-item { .right-bone-item {
transform: skew(-30deg); transform: skew(-30deg);
height: 39px; height: 40px;
margin-bottom: 2px; margin: 4px 0;
margin-top: 2px;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
...@@ -554,39 +475,30 @@ onMounted(() => { ...@@ -554,39 +475,30 @@ onMounted(() => {
} }
} }
.top-bone1 {
@extend .top-bone;
right: 500px;
}
.top-bone2 {
@extend .top-bone;
right: 800px;
}
.bottom-bone { .bottom-bone {
position: absolute; position: absolute;
top: 280px; top: 0px;
right: 360px;
width: 3px; width: 3px;
height: 260px;
background: rgb(230, 231, 232); background: rgb(230, 231, 232);
transform-origin: top center;
transform: skew(-30deg); transform: skew(-30deg);
z-index: 1; z-index: 1;
.left-bone { .left-bone {
color: #777; color: #777;
position: absolute; position: absolute;
top: 50px; bottom: -44px;
left: -150px; right: 0;
width: 150px; width: 180px;
height: 260px; height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
.left-bone-item { .left-bone-item {
transform: skew(30deg); transform: skew(30deg);
height: 39px; height: 40px;
margin-bottom: 2px; margin: 4px 0;
margin-top: 2px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
...@@ -613,16 +525,18 @@ onMounted(() => { ...@@ -613,16 +525,18 @@ onMounted(() => {
.right-bone { .right-bone {
color: #777; color: #777;
position: absolute; position: absolute;
top: 50px; bottom: -20px;
right: -150px; left: 0;
width: 150px; width: 180px;
height: 260px; height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
.right-bone-item { .right-bone-item {
transform: skew(30deg); transform: skew(30deg);
height: 35px; height: 40px;
margin-bottom: 2px; margin: 4px 0;
margin-top: 2px;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
...@@ -646,19 +560,9 @@ onMounted(() => { ...@@ -646,19 +560,9 @@ onMounted(() => {
} }
} }
} }
.bottom-bone1 {
@extend .bottom-bone;
right: 660px;
}
.bottom-bone2 {
@extend .bottom-bone;
right: 960px;
}
} }
.right-main-content-footer { .main-content-footer {
margin-top: 16px; margin-top: 16px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
...@@ -725,6 +629,5 @@ onMounted(() => { ...@@ -725,6 +629,5 @@ onMounted(() => {
} }
} }
} }
}
} }
</style> </style>
\ No newline at end of file
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
</div> </div>
<div class="data-title">实体名称</div> <div class="data-title">实体名称</div>
<div style="height: 20px; flex: auto;"> <div style="height: 20px; flex: auto;">
<el-empty v-if="showCompanyList.length === 0" style="padding-top: 30%" description="暂无数据" :image-size="100" /> <el-empty v-if="!showCompanyList?.length" style="padding-top: 35%;" description="暂无数据" :image-size="100" />
<el-scrollbar height="100%" always> <el-scrollbar height="100%" always>
<div class="list-data"> <div class="list-data">
<div class="list-item" v-for="item in showCompanyList" :key="item.id" :class="{ 'item-active': activeEntityId === item.id }" @click="handleToCompanyDetail(item)"> <div class="list-item" v-for="item in showCompanyList" :key="item.id" :class="{ 'item-active': activeEntityId === item.id }" @click="handleToCompanyDetail(item)">
...@@ -75,19 +75,15 @@ ...@@ -75,19 +75,15 @@
</div> </div>
</div> </div>
</template> </template>
<div class="box2-main" v-if="contentType==1"> <div class="box2-main">
<AiTips :tips="tips" />
<div class="graph-box" v-if="contentType==1">
<ChartChain /> <ChartChain />
</div> </div>
<div class="box2-main" v-if="contentType==2"> <div class="graph-box" v-if="contentType==2">
<!-- <ChartRelation
:graph-data="graphData"
:tree-data="treeData"
:control-active="1"
@node-click="handleNodeClick"
@layout-change="handleLayoutChange"
/> -->
<GraphChart :nodes="testData.nodes" :links="testData.links" layoutType="force" /> <GraphChart :nodes="testData.nodes" :links="testData.links" layoutType="force" />
</div> </div>
</div>
</AnalysisBox> </AnalysisBox>
</div> </div>
</div> </div>
...@@ -101,9 +97,9 @@ import getBarChart from "./utils/barChart"; ...@@ -101,9 +97,9 @@ import getBarChart from "./utils/barChart";
import { getDecreeIndustry, getDecreehylyList, getDecreeCompany } from "@/api/decree/influence"; import { getDecreeIndustry, getDecreehylyList, getDecreeCompany } from "@/api/decree/influence";
import { getCnEntityOnChain, getChainInfoByDomainId } from "@/api/exportControl"; import { getCnEntityOnChain, getChainInfoByDomainId } from "@/api/exportControl";
import { getSingleSanctionEntitySupplyChain } from "@/api/exportControlV2.0"; import { getSingleSanctionEntitySupplyChain } from "@/api/exportControlV2.0";
import ChartChain from "./ChartChain.vue"; import ChartChain from "./com/ChartChain.vue";
import AiTips from "./com/AiTips.vue";
import GraphChart from "@/components/base/GraphChart/index.vue"; import GraphChart from "@/components/base/GraphChart/index.vue";
import ChartRelation from "./ChartRelation.vue";
import defaultIcon2 from "@/assets/icons/default-icon2.png"; import defaultIcon2 from "@/assets/icons/default-icon2.png";
import noticeIcon from "./assets/images/notice-icon.png"; import noticeIcon from "./assets/images/notice-icon.png";
import icon422 from "./assets/images/icon422.png"; import icon422 from "./assets/images/icon422.png";
...@@ -113,191 +109,65 @@ import icon1621 from "./assets/images/icon1621.png"; ...@@ -113,191 +109,65 @@ import icon1621 from "./assets/images/icon1621.png";
import company from "./assets/images/company.png"; import company from "./assets/images/company.png";
import companyActive from "./assets/images/company-active.png"; import companyActive from "./assets/images/company-active.png";
const tips = "这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。"
// 关系图数据 // 关系图数据
const testData = { const testData = {
// 节点数据 // 节点数据
nodes: [ nodes: [
{ id: 0, name: "泰丰先行", symbolSize: 60, symbol: `image://${company}`, x:0, y:0 },
{ id: 1, name: "国轩高科", symbolSize: 40, symbol: `image://${company}` },
{ id: 2, name: "智方纳米", symbolSize: 40, symbol: `image://${company}` },
{ id: 3, name: "香百科技", symbolSize: 40, symbol: `image://${company}` },
{ id: 4, name: "格林滨", symbolSize: 40, symbol: `image://${company}` },
{ id: 5, name: "江西紫宸", symbolSize: 40, symbol: `image://${company}` },
{ id: 6, name: "紫江企业", symbolSize: 40, symbol: `image://${company}` },
{ id: 7, name: "大而美法案", symbolSize: 40, symbol: `image://${company}` },
{ id: 8, name: "比亚迪", symbolSize: 40, symbol: `image://${company}` },
],
// 关系数据
links: [
{ {
id: 0, source: 1, target: 0,
name: "泰丰先行", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
// category: 0, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 8,
symbol: `image://${company}`,
x: 50,
y: 10
},
{
id: 1,
name: "国轩高科",
// category: 0,
symbolSize: 30,
value: 9,
symbol: `image://${company}`,
x: 150,
y: 10
},
{
id: 2,
name: "智方纳米",
// category: 2,
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 250,
y: 10
},
{
id: 3,
name: "香百科技",
// category: 1,
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 350,
y: 10
},
{
id: 4,
name: "格林滨",
// category: 2,
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 450,
y: 10
},
{
id: 5,
name: "江西紫宸",
// category: 2,
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 550,
y: 10
},
{
id: 6,
name: "紫江企业",
// category: 4,
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 650,
y: 10
},
{
id: 7,
name: "大而美法案",
// category: 4,
symbolSize: 50,
value: 5,
symbol: `image://${company}`,
x: 300,
y: 200
},
{
id: 8,
name: "比亚迪",
// category: 0,
symbolSize: 30,
value: 10,
symbol: `image://${company}`,
x: 50,
y: 400
}, },
{ {
id: 9, source: 2, target: 0,
name: "铜陵有色", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
// category: 3, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 8,
symbol: `image://${company}`,
x: 150,
y: 400
}, },
{ {
id: 10, source: 3, target: 0,
name: "长盛精密", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
// category: 1, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 250,
y: 400
}, },
{ {
id: 11, source: 4, target: 0,
name: "天合光能", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '从属' },
// category: 0, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 8,
symbol: `image://${company}`,
x: 350,
y: 400
}, },
{ {
id: 12, source: 5, target: 0,
name: "昆仑化学", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
// category: 2, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 250,
y: 400
}, },
{ {
id: 13, source: 6, target: 0,
name: "嘉源科技", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
// category: 1, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 450,
y: 400
}, },
{ {
id: 14, source: 7, target: 0,
name: "华阳集团", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
// category: 4, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 550,
y: 400
}, },
{ {
id: 15, source: 8, target: 0,
name: "海辰智能", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
// category: 1, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 650,
y: 400
}, },
], ],
// 关系数据
links: [
{ source: 1, target: 7, label: { show: true, formatter: '合作' } },
{ source: 2, target: 7, label: { show: true, formatter: '持股' } },
{ source: 3, target: 7, label: { show: true, formatter: '合作' } },
{ source: 4, target: 7, lineStyle: { type: 'dashed', color: '#d32f2f' }, label: { show: true, formatter: '从属' } },
{ source: 5, target: 7, label: { show: true, formatter: '合作' } },
{ source: 6, target: 7, label: { show: true, formatter: '持股' } },
{ source: 0, target: 7, label: { show: true, formatter: '持股' } },
{ source: 8, target: 7, label: { show: true, formatter: '合作' } },
{ source: 9, target: 7, lineStyle: { type: 'dashed', color: '#d32f2f' }, label: { show: true, formatter: '从属' } },
{ source: 10, target: 7, lineStyle: { type: 'dashed', color: '#d32f2f' }, label: { show: true, formatter: '合作' } },
{ source: 11, target: 7, label: { show: true, formatter: '合作' } },
{ source: 12, target: 7, label: { show: true, formatter: '合作' } },
{ source: 13, target: 7, label: { show: true, formatter: '合作' } },
{ source: 14, target: 7, label: { show: true, formatter: '合作' } },
{ source: 15, target: 7, label: { show: true, formatter: '合作', color: 'red', borderColor: 'red' } },
],
}; };
// 受影响实体 // 受影响实体
...@@ -374,7 +244,7 @@ const handleGetHylyList = async () => { ...@@ -374,7 +244,7 @@ const handleGetHylyList = async () => {
}; };
// 产业链/实体关系 // 产业链/实体关系
const contentType = ref(2); const contentType = ref(1);
const headerContentType = (type) => { const headerContentType = (type) => {
contentType.value = type; contentType.value = type;
}; };
...@@ -693,7 +563,7 @@ onMounted(() => { ...@@ -693,7 +563,7 @@ onMounted(() => {
align-items: flex-end; align-items: flex-end;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 0 16px; padding: 0 20px;
.title-left { .title-left {
display: flex; display: flex;
border: 1px solid rgb(5, 95, 194); border: 1px solid rgb(5, 95, 194);
...@@ -734,6 +604,14 @@ onMounted(() => { ...@@ -734,6 +604,14 @@ onMounted(() => {
.box2-main { .box2-main {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex;
flex-direction: column;
padding: 16px 20px;
.graph-box {
height: 20px;
flex: auto;
margin-top: 16px;
}
} }
} }
} }
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<div class="box1-main"> <div class="box1-main">
<div class="box1-item" v-for="(item, index) in backgroundList" :key="index"> <div class="box1-item" v-for="(item, index) in backgroundList" :key="index">
<div class="id">{{ index + 1 }}</div> <div class="id">{{ index + 1 }}</div>
<div class="title">{{ item.content }}</div> <div class="title text-align-justify">{{ item.content }}</div>
<div class="open"> <div class="open">
<img src="./assets/images/open-icon.png" alt="" /> <img src="./assets/images/open-icon.png" alt="" />
</div> </div>
...@@ -37,14 +37,17 @@ ...@@ -37,14 +37,17 @@
<div class="box2-main"> <div class="box2-main">
<div class="custom-collapse"> <div class="custom-collapse">
<el-collapse v-model="dependActive"> <el-collapse v-model="dependActive">
<el-collapse-item v-for="(item, index) in dependList" :key="item.billId" title="Consistency" :name="item.billId"> <el-collapse-item v-for="(item, index) in dependList" :key="item.billId" :name="item.billId">
<template #icon> <template #icon>
<el-icon><ArrowDownBold /></el-icon> <el-icon v-if="dependActive.includes(item.billId)"><ArrowDownBold /></el-icon>
<el-icon v-else><ArrowUpBold /></el-icon>
</template> </template>
<template #title> <template #title>
<div class="custom-collapse-title"> <div class="custom-collapse-title">
<div class="custom-collapse-index">{{ index + 1 }}</div> <div class="custom-collapse-index">{{ index + 1 }}</div>
<div class="custom-collapse-name one-line-ellipsis">{{ item.title }}</div> <div class="custom-collapse-name one-line-ellipsis">
<span class="text-click-hover" @click.stop="handleClickDecree(item)">{{ item.title }}</span>
</div>
</div> </div>
</template> </template>
<div class="custom-collapse-content"> <div class="custom-collapse-content">
...@@ -192,6 +195,20 @@ const handleGetLaws = async () => { ...@@ -192,6 +195,20 @@ const handleGetLaws = async () => {
console.error("获取法律依据数据失败", error); console.error("获取法律依据数据失败", error);
} }
}; };
// 跳转科技法案详情页
const handleClickDecree = decree => {
window.sessionStorage.setItem("billId", decree.billId);
window.sessionStorage.setItem("curTabName", decree.title);
const route = router.resolve({
path: "/billLayout",
query: {
billId: decree.billId
}
});
console.log(route);
window.open(route.href, "_blank");
};
onMounted(() => { onMounted(() => {
handleGetBackground(); handleGetBackground();
...@@ -261,29 +278,31 @@ onMounted(() => { ...@@ -261,29 +278,31 @@ onMounted(() => {
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
display: flex; display: flex;
align-items: center; align-items: center;
padding: 12px 0; padding: 18px 0;
.id { .id {
margin-right: 16px;
margin-left: 15px; margin-left: 15px;
width: 24px; width: 24px;
height: 24px; height: 24px;
text-align: center; text-align: center;
line-height: 30px;
border-radius: 12px;
background: #e7f3ff; background: #e7f3ff;
color: #0a57a6; color: #0a57a6;
font-size: 15px;
line-height: 24px;
border-radius: 50%;
} }
.title { .title {
width: 914px; width: 20px;
line-height: 24px; flex: auto;
margin-left: 13px; line-height: 30px;
} }
.open { .open {
width: 16px; width: 16px;
height: 16px; height: 16px;
margin-left: 16px; margin: 0 16px;
img { img {
width: 100%; width: 100%;
...@@ -332,7 +351,8 @@ onMounted(() => { ...@@ -332,7 +351,8 @@ onMounted(() => {
.custom-collapse-title { .custom-collapse-title {
position: relative; position: relative;
.custom-collapse-index { .custom-collapse-index {
font-size: 15px; font-family: Microsoft YaHei;
font-size: var(--font-size-base);
position: absolute; position: absolute;
top: 12px; top: 12px;
left: -32px; left: -32px;
......
...@@ -37,9 +37,6 @@ ...@@ -37,9 +37,6 @@
<div class="item"> <div class="item">
<div class="item-left">{{ "相关领域:" }}</div> <div class="item-left">{{ "相关领域:" }}</div>
<div class="item-right tag-box"> <div class="item-right tag-box">
<!-- <div class="tag" v-for="(area, index) in basicInfo.areaList" :key="index">
{{ area.industryName }}
</div> -->
<AreaTag v-for="(area, index) in basicInfo.areaList" :key="index" :tagName="area.industryName"></AreaTag> <AreaTag v-for="(area, index) in basicInfo.areaList" :key="index" :tagName="area.industryName"></AreaTag>
</div> </div>
</div> </div>
...@@ -120,7 +117,7 @@ ...@@ -120,7 +117,7 @@
<img :src="item.avatar ? item.avatar : DefaultIcon1" alt="" /> <img :src="item.avatar ? item.avatar : DefaultIcon1" alt="" />
</div> </div>
<div class="box3-top-bottom-item-right"> <div class="box3-top-bottom-item-right">
<div class="name" @click="handleClickUser(item)">{{ item.name }}</div> <div class="name text-click-hover one-line-ellipsis" @click="handleClickUser(item)">{{ item.name }}</div>
<div class="position">{{ item.job }}</div> <div class="position">{{ item.job }}</div>
</div> </div>
</div> </div>
...@@ -336,22 +333,20 @@ onMounted(() => { ...@@ -336,22 +333,20 @@ onMounted(() => {
.box1-main { .box1-main {
display: flex; display: flex;
padding: 0 24px;
.box1-main-left { .box1-main-left {
width: 395px; width: 395px;
height: 332px; height: 332px;
margin-left: 24px;
img { img {
width: 100%; width: 100%;
// height: 100%;
} }
} }
.box1-main-left-img-mock { .box1-main-left-img-mock {
width: 240px; width: 240px;
height: 332px; height: 332px;
margin-left: 24px;
background-color: #0b1932; background-color: #0b1932;
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -378,7 +373,8 @@ onMounted(() => { ...@@ -378,7 +373,8 @@ onMounted(() => {
} }
.box1-main-right { .box1-main-right {
width: 590px; width: 20px;
flex: auto;
margin-left: 20px; margin-left: 20px;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
...@@ -387,24 +383,22 @@ onMounted(() => { ...@@ -387,24 +383,22 @@ onMounted(() => {
line-height: 24px; line-height: 24px;
.item { .item {
height: 30px;
display: flex; display: flex;
margin-bottom: 17px; margin-bottom: 22px;
.item-left { .item-left {
width: 100px; width: 100px;
} }
.item-right { .item-right {
width: 470px; width: 20px;
overflow: hidden; flex: auto;
text-overflow: ellipsis;
white-space: nowrap;
} }
.tag-box { .tag-box {
display: flex; display: flex;
gap: 8px; gap: 8px;
flex-wrap: wrap;
.tag { .tag {
height: 24px; height: 24px;
...@@ -423,6 +417,9 @@ onMounted(() => { ...@@ -423,6 +417,9 @@ onMounted(() => {
} }
.text { .text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: normal !important; font-weight: normal !important;
} }
} }
...@@ -697,10 +694,6 @@ onMounted(() => { ...@@ -697,10 +694,6 @@ onMounted(() => {
line-height: 24px; line-height: 24px;
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
} }
.position { .position {
......
<template> <template>
<div class="introduction-wrap"> <div class="introduction-wrap">
<div class="left"> <div class="page-left">
<div class="box1"> <div class="box1">
<AnalysisBox title="主要指令" :showAllBtn="false"> <AnalysisBox title="主要指令" :showAllBtn="false">
<div class="analysis-box"> <div class="analysis-box">
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<el-empty v-if="!contentList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" /> <el-empty v-if="!contentList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<div v-for="(section, index) in contentList" :key="index" class="section"> <div v-for="(section, index) in contentList" :key="index" class="section">
<div class="section-header"> <div class="section-header">
<div class="section-title">({{ simpleNumToChinese(index+1) }}) {{ section.content }}</div> <div class="section-title text-align-justify" v-html="section.content"></div>
<div class="section-icon"> <div class="section-icon">
<img src="./assets/images/open-icon.png" alt="" /> <img src="./assets/images/open-icon.png" alt="" />
</div> </div>
...@@ -30,19 +30,19 @@ ...@@ -30,19 +30,19 @@
<div class="numbered-list"> <div class="numbered-list">
<div v-for="(item, itemIndex) in section.slaver" :key="itemIndex" class="list-item"> <div v-for="(item, itemIndex) in section.slaver" :key="itemIndex" class="list-item">
<div class="list-item-dot">{{itemIndex+1}}.</div> <div class="list-item-dot">{{itemIndex+1}}.</div>
<div class="list-item-word">{{ item.content }}</div> <div class="list-item-word" v-html="item.content"></div>
<!-- 渲染二级列表 --> <!-- 渲染二级列表 -->
<div v-if="item.slaver" class="sub-list"> <div v-if="item.slaver" class="sub-list">
<div v-for="(subItem, subIndex) in item.slaver" :key="subIndex" class="sub-item"> <div v-for="(subItem, subIndex) in item.slaver" :key="subIndex" class="sub-item">
<div class="sub-item-dot">({{subIndex+1}})</div> <div class="sub-item-dot">({{subIndex+1}})</div>
<div class="sub-item-word">{{ subItem.content }}</div> <div class="sub-item-word" v-html="subItem.content"></div>
<!-- 渲染三级列表 --> <!-- 渲染三级列表 -->
<div v-if="subItem.slaver" class="sub-sub-list"> <div v-if="subItem.slaver" class="sub-sub-list">
<div v-for="(subSubItem, subSubIndex) in subItem.slaver" :key="subSubIndex" class="sub-sub-item"> <div v-for="(subSubItem, subSubIndex) in subItem.slaver" :key="subSubIndex" class="sub-sub-item">
<div class="sub-sub-item-dot">{{ALPHABET[subSubIndex%26]}}.</div> <div class="sub-sub-item-dot">{{ALPHABET[subSubIndex%26]}}.</div>
<div class="sub-sub-item-word">{{ subSubItem.content }}</div> <div class="sub-sub-item-word" v-html="subItem.content"></div>
</div> </div>
</div> </div>
</div> </div>
...@@ -55,17 +55,22 @@ ...@@ -55,17 +55,22 @@
</AnalysisBox> </AnalysisBox>
</div> </div>
</div> </div>
<div class="right"> <div class="page-right">
<div class="box3"> <div class="box3">
<AnalysisBox title="发布机构" :showAllBtn="false"> <AnalysisBox title="执行机构" :showAllBtn="false">
<div class="box3-top"> <div class="box3-top">
<div class="box3-top-top" @click="handleToInstitution(box3TopTopData)"> <div class="organization-list">
<div class="organization-item" v-for="item in organizationInfo.list" :key="item.id">
<ActionButton @click="handleOrganization(item)" :name="item.obb" :type="item.id==organizationInfo.node.id?'active':'normal'" />
</div>
</div>
<div class="box3-top-top" @click="handleToInstitution()">
<div class="left"> <div class="left">
<img :src="box3TopTopData.logo ? box3TopTopData.logo : DefaultIcon2" alt="" /> <img :src="organizationInfo.node.logo || DefaultIcon2" alt="" />
</div> </div>
<div class="right"> <div class="right">
<div class="name">{{ box3TopTopData.name + " >" }}</div> <div class="name">{{ organizationInfo.node.name + " >" }}</div>
<div class="ename">{{ box3TopTopData.eName }}</div> <div class="ename">{{ organizationInfo.node.ename }}</div>
</div> </div>
</div> </div>
<div class="box3-top-bottom"> <div class="box3-top-bottom">
...@@ -76,17 +81,21 @@ ...@@ -76,17 +81,21 @@
<div class="text">{{ "关键人物" }}</div> <div class="text">{{ "关键人物" }}</div>
</div> </div>
<div class="box3-top-bottom-main"> <div class="box3-top-bottom-main">
<div class="box3-top-bottom-item" v-for="(item, index) in box3TopBottomData" :key="index"> <div class="box3-top-bottom-item" v-for="(item, index) in organizationInfo.node.leaders" :key="index">
<div class="box3-top-bottom-item-left"> <div class="box3-top-bottom-item-left">
<img :src="item.avatar ? item.avatar : DefaultIcon1" alt="" /> <img :src="item.avatar || DefaultIcon1" alt="" />
</div> </div>
<div class="box3-top-bottom-item-right"> <div class="box3-top-bottom-item-right">
<div class="name" @click="handleClickUser(item)">{{ item.name }}</div> <div class="name one-line-ellipsis text-click-hover" @click="handleClickUser(item)">{{ item.name }}</div>
<div class="position">{{ item.job }}</div> <div class="position">{{ item.job }}</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="organization-button">
<div class="button-text">查看政令执行情况</div>
<el-icon size="16"><Right /></el-icon>
</div>
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
...@@ -109,14 +118,14 @@ ...@@ -109,14 +118,14 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted, reactive } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import router from "@/router"; import router from "@/router";
import { Search } from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
import { getDecreeIssueOrganization } 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";
import ActionButton from '@/components/base/ActionButton/index.vue'
import DefaultIcon1 from "@/assets/icons/default-icon1.png"; import DefaultIcon1 from "@/assets/icons/default-icon1.png";
import DefaultIcon2 from "@/assets/icons/default-icon2.png"; import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import defaultCom from "@/views/coopRestriction/assets/images/default-icon2.png" import defaultCom from "@/views/coopRestriction/assets/images/default-icon2.png"
...@@ -211,14 +220,16 @@ const contentList = ref([ ...@@ -211,14 +220,16 @@ const contentList = ref([
const ALPHABET = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]; const ALPHABET = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
const onMainContentData = async () => { const onMainContentData = async () => {
try { try {
const res = await getDecreeMainContent({ const keyword = commandWord.value;
id: route.query.id, const res = await getDecreeMainContent({ id: route.query.id, keyword, domainId: areaType.value });
keyword: commandWord.value,
domainId: areaType.value
});
console.log("主要指令", res); console.log("主要指令", res);
if (res && res.code === 200) { if (res && res.code === 200) {
contentList.value = res.data; contentList.value = res.data || [];
contentList.value.forEach((item, index) => { item.content = `(${simpleNumToChinese(index+1)}) ${item.content}` })
if (keyword) {
let word = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
contentList.value.forEach(item => {onHighlight(word, item)})
}
} else { } else {
contentList.value = [] contentList.value = []
} }
...@@ -227,12 +238,19 @@ const onMainContentData = async () => { ...@@ -227,12 +238,19 @@ const onMainContentData = async () => {
console.error("获取主要指令数据失败:", error); console.error("获取主要指令数据失败:", error);
} }
}; };
// 搜索高亮效果
const onHighlight = (word, row) => {
row.content = String(row.content).replace(new RegExp(word, "gi"), (match) => {
return `<span class="highlight">${match}</span>`;
});
if (row.slaver?.length) {
row.slaver.forEach(item => { onHighlight(word, item) })
}
}
// 数字转中文(支持 0-99 整数) // 数字转中文(支持 0-99 整数)
const simpleNumToChinese = (num) => { const simpleNumToChinese = (num) => {
// 1. 基础校验:只处理 0-99 的整数 // 1. 基础校验:只处理 0-99 的整数
if (!Number.isInteger(num) || num < 0 || num > 99) { if (!Number.isInteger(num) || num < 0 || num > 99) return '100';
return '仅支持 0-99 之间的整数';
}
// 2. 定义基础字符 // 2. 定义基础字符
const singleChars = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; const singleChars = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
const tenChar = '十'; const tenChar = '十';
...@@ -271,20 +289,34 @@ const onRelatedEntityData = async () => { ...@@ -271,20 +289,34 @@ const onRelatedEntityData = async () => {
} }
}; };
// 发布机构 // 执行机构
const box3TopTopData = ref({ const organizationInfo = reactive({
id: "", list : [],
logo: "", node: {id: "", obb: "", logo: "", name: "", ename: "", leaders: []},
name: "", })
eName: "" const handleGetOrgnization = async () => {
}); try {
const box3TopBottomData = ref([]); const res = await getDecreeOrganization({id: route.query.id});
// 跳转行政机构主页 console.log("执行机构", res);
const handleToInstitution = item => { if (res.code === 200 && res.data?.length) {
organizationInfo.list = res.data;
organizationInfo.node = res.data[0];
}
} catch (error) {
organizationInfo.node = {id: "", obb: "", logo: "", name: "", ename: "", leaders: []};
console.error("获取执行机构数据失败", error);
}
};
// 切换执行机构
const handleOrganization = (node) => {
organizationInfo.node = node;
};
// 跳转机构主页
const handleToInstitution = () => {
const curRoute = router.resolve({ const curRoute = router.resolve({
path: "/institution", path: "/institution",
query: { query: {
id: item.id id: organizationInfo.node.id
} }
}); });
window.open(curRoute.href, "_blank"); window.open(curRoute.href, "_blank");
...@@ -300,21 +332,6 @@ const handleClickUser = item => { ...@@ -300,21 +332,6 @@ const handleClickUser = item => {
}); });
window.open(routeData.href, "_blank"); window.open(routeData.href, "_blank");
}; };
const handleGetOrgnization = async () => {
try {
const res = await getDecreeIssueOrganization({id: route.query.id});
console.log("发布机构", res);
if (res.code === 200 && res.data) {
let { id, image, name, ename } = res.data
Object.assign(box3TopTopData.value, { id, logo: image, name, eName: ename });
box3TopBottomData.value = res.data.personList;
}
} catch (error) {
box3TopTopData.value = { id: "", logo: "", name: "", eName: "" };
box3TopBottomData.value = [];
console.error("执行机构error", error);
}
};
onMounted(() => { onMounted(() => {
handleGetAreaList(); handleGetAreaList();
...@@ -325,13 +342,19 @@ onMounted(() => { ...@@ -325,13 +342,19 @@ onMounted(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.analysis-content {
:deep(span.highlight) {
background-color: #ffff00;
}
}
.introduction-wrap { .introduction-wrap {
display: flex; display: flex;
width: 1600px; width: 1600px;
padding: 16px 0; padding: 16px 0;
gap: 16px; gap: 16px;
.left { .page-left {
width: 20px; width: 20px;
flex: auto; flex: auto;
...@@ -387,7 +410,6 @@ onMounted(() => { ...@@ -387,7 +410,6 @@ onMounted(() => {
.section-title { .section-title {
font-size: 18px; font-size: 18px;
line-height: 30px; line-height: 30px;
font-weight: 600;
letter-spacing: 1px; letter-spacing: 1px;
width: 20px; width: 20px;
flex: auto; flex: auto;
...@@ -457,7 +479,7 @@ onMounted(() => { ...@@ -457,7 +479,7 @@ onMounted(() => {
} }
} }
.right { .page-right {
width: 520px; width: 520px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -466,10 +488,34 @@ onMounted(() => { ...@@ -466,10 +488,34 @@ onMounted(() => {
.box3 { .box3 {
.box3-top { .box3-top {
margin-top: 2px; margin-top: 2px;
padding: 0 22px 20px;
border-bottom: 1px solid rgba(234, 236, 238, 1); border-bottom: 1px solid rgba(234, 236, 238, 1);
.organization-list {
display: flex;
flex-wrap: wrap;
margin-bottom: 16px;
gap: 8px 16px;
}
.organization-button {
height: 36px;
background-color: var(--color-primary-100);
color: var(--bg-white-100);
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
cursor: pointer;
.button-text {
margin-right: 8px;
font-family: Microsoft YaHei;
font-size: 16px;
line-height: 16px;
}
}
.box3-top-top { .box3-top-top {
width: 473px;
height: 88px; height: 88px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1); border: 1px solid rgba(234, 236, 238, 1);
...@@ -477,42 +523,13 @@ onMounted(() => { ...@@ -477,42 +523,13 @@ onMounted(() => {
background: rgba(247, 248, 249, 1); background: rgba(247, 248, 249, 1);
display: flex; display: flex;
align-items: center; align-items: center;
margin: 0 auto;
position: relative;
cursor: pointer; cursor: pointer;
.more {
position: absolute;
right: 17px;
top: 17px;
display: flex;
gap: 3px;
.text {
height: 16px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 16px;
}
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
}
.left { .left {
width: 64px; width: 64px;
height: 64px; height: 64px;
margin-left: 17px; margin-left: 17px;
font-size: 0px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -520,7 +537,8 @@ onMounted(() => { ...@@ -520,7 +537,8 @@ onMounted(() => {
} }
.right { .right {
width: 370px; width: 20px;
flex: auto;
margin-left: 15px; margin-left: 15px;
.name { .name {
...@@ -545,9 +563,7 @@ onMounted(() => { ...@@ -545,9 +563,7 @@ onMounted(() => {
} }
.box3-top-bottom { .box3-top-bottom {
width: 473px;
height: 193px; height: 193px;
margin: 0 auto;
.box3-top-bottom-header { .box3-top-bottom-header {
height: 40px; height: 40px;
...@@ -624,10 +640,6 @@ onMounted(() => { ...@@ -624,10 +640,6 @@ onMounted(() => {
line-height: 24px; line-height: 24px;
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
} }
.position { .position {
......
...@@ -135,7 +135,6 @@ onMounted(() => { ...@@ -135,7 +135,6 @@ onMounted(() => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
.header-main { .header-main {
padding: 17px 0; padding: 17px 0;
width: 100%; width: 100%;
......
...@@ -53,7 +53,7 @@ export default defineConfig({ ...@@ -53,7 +53,7 @@ export default defineConfig({
'/api': { '/api': {
target: 'http://8.140.26.4:9085/', target: 'http://8.140.26.4:9085/',
// target: 'http://192.168.0.5:28080/', // target: 'http://192.168.0.6:28080/',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') rewrite: (path) => path.replace(/^\/api/, '')
}, },
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论