提交 20ebbfed authored 作者: 朱政's avatar 朱政

Merge branch 'master' into zz-dev

......@@ -321,6 +321,13 @@ body {
.el-popper[data-popper-placement^="top"] > .el-popper__arrow:before {
display: none;
}
/* 单行文本溢出隐藏显示省略号 */
.one-line-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
<style lang="scss" scoped>
......
......@@ -12,6 +12,18 @@ export function getDecreeBackground(params) {
})
}
// 前序政令
/**
* @param {id}
*/
export function getDecreePrev(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderInfo/prev/${params.id}`,
params
})
}
// 相关事件
/**
* @param { id }
......@@ -35,3 +47,27 @@ export function getDecreeDepend(params) {
params
})
}
// 主要指令
/**
* @param { id }
*/
export function getDecreeMainContent(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderInfo/mainContent/${params.id}`,
params
})
}
// 相关实体
/**
* @param { id }
*/
export function getDecreeRelatedEntity(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderInfo/relatedEntity/${params.id}`,
params
})
}
\ No newline at end of file
import request from "@/api/request.js";
// 获取相关政令
/**
* @param { id }
*/
export function getDecreeRelatedOrder(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderInfo/relatedOrder/${params.id}`,
params
})
}
// 根据政令ID获取领域公司信息
/**
* @param {cRelated, id}
......
......@@ -60,16 +60,17 @@ export function getDecreeSummary(params) {
})
}
// 获取报告原文
// 获取风险信号
/**
* @param {id}
*/
// export function getDecreeReport(params) {
// return request({
// method: 'GET',
// url: `/api/administrativeOrderInfo/contentUrl/${params.id}`,
// })
// }
export function getDecreeRiskSignal(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderInfo/riskSignal/${params.id}`,
params
})
}
export function getDecreeReport(params) {
return request({
......
export const TAGTYPE = ["primary", "success", "warning", "danger", "info"];
// 双色图表
export const DOUBLECHARTCOLORS = [
'#055FC2',
'#CE4F51'
]
// 多色图表预设颜色列表 20种
export const MUTICHARTCOLORS = [
"#69B1FF",
"#FF7875",
"#B37FEB",
"#FFC069",
"#1677FF",
"#87E8DE",
"#ADC6FF",
"#FFBB96",
"#BAE0FF",
"#FFD591",
"#6691FF",
"#FFB2AF",
"#81D0FF",
"#D8E5FB",
"#7981F1",
"#FF9696",
"#6678A1",
"#273C57",
"#E8B8FF",
"#DF812E"
];
......@@ -41,6 +41,7 @@ const classObject = computed(() => ({
height: 24px;
padding: 0 8px;
line-height: 24px;
width: fit-content;
text-align: center;
font-family: Microsoft YaHei;
font-style: Regular;
......
<template>
<div class="wordcloud-wrapper" :style="{ width: width ? width : '520px', height: height ? height : '400px' }">
<div class="chart-box" id="wordcloud-chart">
</div>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
import setChart from '@/utils/setChart';
import getWordCloudChart from './wordCloudChart';
const props = defineProps(({
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
},
data: {
type: Array,
default: [
// { name: "与马斯克公开冲突", value: 100 },
// { name: "传统能源", value: 5 },
// { name: "共和党财政鹰派", value: 77 },
// { name: "未实现赤字控制目标", value: 35 },
// { name: "得克萨斯州", value: 88 },
// { name: "选举压力", value: 57 },
// { name: "主张财政紧缩", value: 72 },
// { name: "财政保守", value: 18 },
]
}
}))
onMounted(() => {
let chart = getWordCloudChart(props.data);
setChart(chart, "wordcloud-chart");
})
</script>
<style lang="scss">
.chart-box {
width: 100%;
height: 100%;
}
</style>
\ No newline at end of file
import 'echarts-wordcloud';
const getWordCloudChart = (data) => {
const option = {
grid: {
left: 5,
top: 5,
right: 5,
bottom: 5,
},
series: [
{
type: "wordCloud",
shape: 'circle',
width: '100%',
height: '100%',
// 其他形状你可以使用形状路径
// shape: 'circle', // 示例
// 或者自定义路径
gridSize: 35, // 网格大小,影响词间距。
sizeRange: [16, 36], // 定义词云中文字大小的范围
rotationRange: [0, 0],
rotationStep: 0,
drawOutOfBound: false, // 是否超出画布
shrinkToFit: true, // 是否自动缩小以适应容器
// 字体
textStyle: {
// normal: {
// color: function () {
// return 'rgb(' + [
// Math.round(Math.random() * 160),
// Math.round(Math.random() * 160),
// Math.round(Math.random() * 160)
// ].join(',') + ')';
// }
// },
color: function () {
let colors = [
"rgba(189, 33, 33, 1)",
"rgba(232, 151, 21, 1)",
"rgba(220, 190, 68, 1)",
"rgba(96, 58, 186, 1)",
"rgba(32, 121, 69, 1)",
"rgba(22, 119, 255, 1)",
];
return colors[parseInt(Math.random() * colors.length)];
},
emphasis: {
shadowBlur: 5,
shadowColor: "#333",
},
},
// 设置词云数据
data: data,
},
],
};
return option
}
export default getWordCloudChart
\ No newline at end of file
......@@ -2,7 +2,10 @@
<div class="analysis-box-wrapper" :style="{ width: width ? width : '100%', height: height ? height : '100%' }">
<div class="wrapper-header">
<div class="header-icon"></div>
<div class="header-title">{{ title }}</div>
<div class="header-title">
<div v-if="title">{{ title }}</div>
<slot v-else name="custom-title"></slot>
</div>
<div class="header-btn" v-if="!showAllBtn">
<slot name="header-btn"></slot>
</div>
......@@ -98,54 +101,51 @@ const emit = defineEmits(['save', 'download', 'collect'])
.wrapper-header {
height: 45px;
display: flex;
padding-right: 14px;
align-items: center;
box-sizing: border-box;
position: relative;
.header-icon {
margin-top: 18px;
width: 8px;
height: 20px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
margin-right: 14px;
}
.header-title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
flex: auto;
width: 20px;
// color: var(--color-main-active);
// font-family: Source Han Sans CN;
// font-size: 20px;
// font-weight: 700;
// line-height: 26px;
// letter-spacing: 0px;
height: 26px;
height: 100%;
&>div {
height: 100%;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
line-height: 45px;
font-weight: 700;
line-height: 26px;
}
}
.header-btn {
position: absolute;
top: 14px;
right: 84px;
// .header-btn {
// display: flex;
// justify-content: flex-end;
// gap: 8px;
}
// }
.header-btn1 {
position: absolute;
top: 14px;
right: 116px;
}
// .header-btn1 {
// position: absolute;
// top: 14px;
// right: 116px;
// }
.header-right {
position: absolute;
top: 14px;
right: 14px;
height: 28px;
display: flex;
justify-content: flex-end;
......
......@@ -323,10 +323,12 @@ onMounted(() => {
.nav-right {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 21px;
.info-box {
height: 40px;
display: flex;
justify-content: flex-end;
align-items: center;
......
<template>
<el-space alignment="flex-start" :size="10">
<img :width="64" :height="52" :src="news ?? DefaultIconNews" alt="" />
<el-space direction="vertical" alignment="flex-start" :size="0">
<common-text :line-limit="1" class="text-regular text-hover" color="var(--text-primary-80-color)">
<img :width="97" :height="72" :src="img ?? DefaultIconNews" alt="" />
<el-space direction="vertical" alignment="flex-start" :size="0" fill>
<div class="full-width flex-display">
<common-text :line-limit="1" class="text-title-3-bold text-hover flex-fill"
color="var(--text-primary-80-color)">
{{ title }}
</common-text>
<common-text :line-limit="1" class="text-tip-1" color="var(--text-primary-65-color)">
<common-text class="text-tip-2" color="var(--text-primary-65-color)">
{{ from }}
</common-text>
</div>
<common-text :line-limit="contentLineLimit" class="text-compact" color="var(--text-primary-65-color)">
{{ content }}
</common-text>
</el-space>
</el-space>
</template>
......@@ -20,7 +26,7 @@ import CommonText from "../texts/CommonText.vue";
const props = defineProps({
img: {
type: String,
default: ''
default: null,
},
title: {
type: String,
......@@ -34,14 +40,13 @@ const props = defineProps({
type: String,
default: ""
},
contentLineLimit: {
type: Number,
default: 2
}
});
const emit = defineEmits(['item-click', 'more-click']);
const handleToNewsAnalysis = (item, index) => {
emit('item-click', item, index)
};
</script>
<style lang="scss" scoped>
@use '@/styles/common.scss';
@use '@/styles/container.scss';
</style>
\ No newline at end of file
<template>
<el-space alignment="flex-start" :size="10">
<img :width="64" :height="52" :src="img ?? DefaultIconNews" alt="" />
<el-space direction="vertical" alignment="flex-start" :size="0">
<common-text :line-limit="1" class="text-regular text-hover" color="var(--text-primary-80-color)">
{{ title }}
</common-text>
<common-text :line-limit="1" class="text-tip-1" color="var(--text-primary-65-color)">
{{ from }}
</common-text>
</el-space>
</el-space>
</template>
<script setup>
import DefaultIconNews from "@/assets/icons/default-icon-news.png";
import { ElSpace } from "element-plus";
import CommonText from "../texts/CommonText.vue";
const props = defineProps({
img: {
type: String,
default: null
},
title: {
type: String,
default: ""
},
from: {
type: String,
default: ""
}
});
</script>
<style lang="scss" scoped>
@use '@/styles/common.scss';
</style>
\ No newline at end of file
<template>
<div class="news-item">
<el-space direction="vertical" class="flex-fill" alignment='flex-start'>
<common-text :lineLimit="1" class="text-bold" color="var(--text-primary-80-color)">{{
<common-text :lineLimit="1" class="text-bold text-hover" color="var(--text-primary-80-color)">{{
props.title
}}</common-text>
<common-text class="text-tip-2" color="var(--text-primary-65-color)">
......@@ -11,8 +11,7 @@
<area-tag v-for="(tag, index) in props.aeraTags" :key="index" :tagName="tag" />
</el-space>
</el-space>
<img style="width: 122px; height: 82px" :src="props.img">
<!-- <img v-else style="width: 122px; height: 82px" :src="DefaultIconNews"> -->
<img style="width: 122px; height: 82px" :src="props.img ? props.img : DefaultIconNews">
</div>
</template>
......@@ -27,7 +26,7 @@ const props = defineProps({
img: {
type: String,
default: 'img'
default: null
},
title: {
type: String,
......
......@@ -12,6 +12,7 @@
.left-btn-wrapper {
width: 24px;
height: 48px;
cursor: pointer
img {
width: 100%;
......
......@@ -12,7 +12,7 @@
.right-btn-wrapper {
width: 24px;
height: 48px;
cursor: pointer
img {
width: 100%;
height: 100%;
......
......@@ -26,8 +26,8 @@ const props = defineProps({
text: {
type: String,
default: ''
}
, entities: {
},
entities: {
type: Array<TextEntity>,
default: () => []
}
......@@ -37,8 +37,9 @@ const props = defineProps({
const processedText = ref<ProcessedTextSegment[]>([])
// 处理文本,识别并替换实体
const processText = () => {
console.log('props.entities.length', props.entities.length)
if (!props.text || !props.entities) {
console.log('props.text', props.entities.length)
// console.log('props.text', props.entities.length)
processedText.value = [{ text: '', isEntity: false }]
return
}
......@@ -120,5 +121,6 @@ onMounted(processText)
.p-regular-rereg {
text-indent: 2em;
margin: 4px 0;
}
</style>
\ No newline at end of file
<template>
<div class="full-width">
<div class="flex-display" style="align-items: center;">
<common-text class="text-title-3-bold" color="var(--text-primary-80-color)">{{ isOpenTranslation
? '中文' : '原文' }}</common-text>
<div class="flex-fill" style="margin: 0 10px;">
<el-divider></el-divider>
</div>
<el-button v-if="showMoreVisible" @click="() => { showMore = !showMore; updateText() }">
{{ showMore ? '收起' : '展开' }}
<el-icon>
<arrow-up v-if="showMore" />
<arrow-down v-else />
</el-icon>
</el-button>
</div>
<el-row :gutter="32">
<el-col :span="textColSpan" v-for="(item, index) in allTexts" :key="index">
<!-- <p class="p-news-content"> {{ item }}</p> -->
<intelligent-entity-text :text="item"
:entities="isHighlightEntity ? textEntities : []"></intelligent-entity-text>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import '@/styles/container.scss';
import '@/styles/common.scss';
import { ref, watch, onMounted } from 'vue';
import { TextEntity } from '@/api/intelligent';
import IntelligentEntityText from '@/components/base/texts/IntelligentEntityText.vue';
import { ElIcon, ElButton, ElDivider, ElRow, ElCol } from 'element-plus';
import CommonText from './CommonText.vue';
const allTexts = ref([]);
const textColSpan = ref(12);
const hasTranslation = ref(false);
const showMore = ref(false);
const showMoreVisible = ref(false);
const props = defineProps({
//段落列表: 原始文本
textsRaw: {
type: Array<String>,
default: () => []
},
//段落列表: 翻译文本
textsTranslate: {
type: Array<String>,
default: () => []
},
//是否显示翻译
isOpenTranslation: {
type: Boolean,
default: true
},
//是否高亮实体
isHighlightEntity: {
type: Boolean,
default: true
},
//实体列表
textEntities: {
type: Array<TextEntity>,
default: () => []
}
})
function updateText() {
const tempTexts = []
const tempRaws = props.textsRaw ?? []
const tempTranslates = props.textsTranslate ?? []
hasTranslation.value = tempTranslates.length > 0
if (hasTranslation.value && props.isOpenTranslation) {
// 遍历原始文本和翻译文本,将它们交替添加到 tempTexts 中,并保持原始文本和翻译文本的的数量一致
const maxCount = Math.max(tempRaws.length, tempTranslates.length)
for (let i = 0; i < maxCount; i++) {
if (i < tempTranslates.length) {
tempTexts.push(tempTranslates[i]);
} else {
tempTexts.push('');
}
if (i < tempRaws.length) {
tempTexts.push(tempRaws[i]);
} else {
tempTexts.push('');
}
}
console.log(tempTexts.length)
textColSpan.value = 12;
showMoreVisible.value = tempTexts.length > 6;
allTexts.value = showMore.value ? tempTexts : tempTexts.slice(0, 6);
} else {
textColSpan.value = 24;
showMoreVisible.value = tempRaws.length > 3;
allTexts.value = showMore.value ? tempRaws : tempRaws.slice(0, 3);
}
}
watch(() => [props.textsRaw, props.textsTranslate, props.isOpenTranslation], () => {
updateText();
})
onMounted(() => {
updateText();
})
</script>
\ No newline at end of file
......@@ -2,7 +2,7 @@ import { useRouter } from "vue-router";
export function useGotoPage() {
const router = useRouter();
return (path, data, isNewTabs = true) => {
console.log('path', path);
console.log("path", path);
if (isNewTabs) {
// 打开新页面
const url = new URL(window.location.origin + path);
......@@ -11,10 +11,17 @@ export function useGotoPage() {
url.searchParams.append(key, value);
});
}
window.open(url.toString(), '_blank');
window.open(url.toString(), "_blank");
} else {
// 当前页面打开
router.push({ path, query: data });
}
}
};
}
// 滚动到指定元素
export function scrollToElement(elementId) {
document.getElementById(elementId).scrollIntoView({
behavior: "smooth", // 平滑滚动
block: "start" // 滚动到元素顶部
});
}
......@@ -33,4 +33,15 @@ const comprehensiveSearchRoutes = [
]
import { useGotoPage } from "../common.js";
export function useGotoComprehensiveSearch() {
const gotoPage = useGotoPage();
return (isNewTabs = true) => gotoPage("/comprehensiveSearch/", {}, isNewTabs)
}
export function useGotoSearchResults() {
const gotoPage = useGotoPage();
return (isNewTabs = true) => gotoPage("/searchResults/", {searchText, areaName}, isNewTabs)
}
export default comprehensiveSearchRoutes
\ No newline at end of file
......@@ -4,6 +4,7 @@ const DecreeLayoutContainer = () => import('@/views/decree/decreeLayout/index.vu
const DecreeOverviewLayout = () => import('@/views/decree/decreeLayout/overview/index.vue')
const DecreeIntroduction = () => import('@/views/decree/decreeLayout/overview/introduction/index.vue')
const DecreeBackground = () => import('@/views/decree/decreeLayout/overview/background/index.vue')
const DecreeMeasures = () => import('@/views/decree/decreeLayout/overview/measures/index.vue')
const DecreeDeepDig = () => import('@/views/decree/decreeLayout/deepdig/index.vue')
const DecreeInfluence = () => import('@/views/decree/decreeLayout/influence/index.vue')
const Institution = () => import('@/views/decree/institution/index.vue')
......@@ -50,7 +51,13 @@ const decreeRoutes = [
name: "DecreeBackground",
component: DecreeBackground,
// meta: { title: "政令背景" }
}
},
{
path: "measures",
name: "DecreeMeasures",
component: DecreeMeasures,
// meta: { title: "政令举措" }
},
]
},
// 深度挖掘路由
......
......@@ -75,6 +75,7 @@
&:hover {
color: rgb(5, 95, 194) !important;
font-weight: 700;
cursor: pointer;
}
}
......
<template>
<el-row class="wrapper layout-grid-line">
<el-col :span="span">
<pre>
{{
`
import ActionButton from '@/components/base/ActionButton/index.vue'
<ActionButton name="全部选项" type="active" />
<ActionButton name="选项A" type="normal" />
<ActionButton name="选项B" type="normal" />
<ActionButton name="选项C" type="normal" />
选项按钮
`
}}
</pre>
<div class="button-box">
<ActionButton name="全部选项" type="active" />
<ActionButton name="选项A" type="normal" />
<ActionButton name="选项B" type="normal" />
<ActionButton name="选项C" type="normal" />
</div>
</el-col>
<el-col :span="span">
<pre>
{{
`
import LeftBtn from '@/components/base/PageBtn/LeftBtn.vue'
import RightBtn from '@/components/base/PageBtn/RightBtn.vue'
<LeftBtn />
<RightBtn />
左右按钮
`
}}
</pre>
<div class="button-box1">
<LeftBtn />
<RightBtn />
</div>
</el-col>
</el-row>
</template>
<script setup>
import '@/styles/common.scss'
import LeftBtn from '@/components/base/pageBtn/leftBtn.vue'
import RightBtn from '@/components/base/pageBtn/rightBtn.vue'
const span = 12
</script>
<style lang="scss" scoped>
.button-box {
margin-left: 40px;
;
display: flex;
gap: 8px
}
.button-box1 {
width: 200px;
display: flex;
justify-content: space-between;
margin-left: 40px;
margin-bottom: 10px;
gap: 8px
}
</style>
\ No newline at end of file
<template>
<el-row class="wrapper layout-grid-line">
<el-col :span="span">
<pre>
{{
`
import AreaTag from '@/components/base/AreaTag/index.vue'
<AreaTag tagName="人工智能" />
<AreaTag tagName="生物科技" />
<AreaTag tagName="新一代通信网络" />
<AreaTag tagName="量子科技" />
<AreaTag tagName="新能源" />
`
}}
</pre>
<div class="tag-box">
<AreaTag tagName="人工智能" />
<AreaTag tagName="生物科技" />
<AreaTag tagName="新一代通信网络" />
<AreaTag tagName="量子科技" />
<AreaTag tagName="新能源" />
</div>
</el-col>
</el-row>
</template>
<script setup>
import '@/styles/common.scss'
import AreaTag from '@/components/base/AreaTag/index.vue'
const span = 12
</script>
<style lang="scss" scoped>
.tag-box{
display: flex;
gap: 8px
}
</style>
\ No newline at end of file
......@@ -38,6 +38,14 @@ const span = 12
</pre>
<div class="mouse-hover">鼠标悬停</div>
</el-col>
<el-col :span="span">
<pre>{{ `import '@/styles/common.scss';\n<template>
<div class="text-hover"></div>
</template>
`}}
</pre>
<div class="text-hover">文字悬停</div>
</el-col>
</el-row>
</template>
......
<script setup lang="ts">
import { ElRow, ElCol } from 'element-plus';
import '@/styles/common.scss'
import NewsItemMini from '@/components/base/newsList/NewsItemMini.vue'
import NewsItem from '@/components/base/newsList/NewsItem.vue'
import NewsItemWithTag from '@/components/base/newsList/NewsItemWithTag.vue'
const span = 12
const news = {
title: '新闻标题-老长了老长了老长了老长了老长了老长长了老长了',
content: '新闻内容-老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了老长了',
from: "2025.1.1 · 来源:中国",
aeraTags: ['人工智能', '深海']
}
</script>
<template>
<el-row class="wrapper layout-grid-line">
<el-col :span="span">
<pre>
{{ `import NewsItem from '@/components/base/newsList/NewsItem.vue'` }}
</pre>
<news-item class="common-padding" :title="news.title" :content="news.content" :from="news.from"></news-item>
</el-col>
<el-col :span="span">
<pre>
{{ `import NewsItemMini from '@/components/base/newsList/NewsItemMini.vue'` }}
</pre>
<news-item-mini class="common-padding" :title="news.title" :content="news.content"
:from="news.from"></news-item-mini>
</el-col>
<el-col :span="span">
<pre>
{{ `import NewsItemWithTag from '@/components/base/newsList/NewsItemWithTag.vue'` }}
</pre>
<news-item-with-tag class="common-padding" :title="news.title" :content="news.content" :from="news.from"
:aeraTags="news.aeraTags"></news-item-with-tag>
</el-col>
</el-row>
</template>
<style lang="scss" scoped></style>
\ No newline at end of file
<script setup lang="ts">
import { ElRadioGroup, ElRadioButton, ElRow, ElCol } from 'element-plus';
import { ElRadioGroup, ElRadioButton, ElRow, ElCol, ElSpace } from 'element-plus';
import { ref } from 'vue';
import '@/styles/radio.scss';
......@@ -13,16 +13,36 @@ const span = 12
<pre>
{{ `import '@/styles/radio.scss';
<template>
<el-radio-group class="radio-group-as-gap-btn">
<el-radio-group v-model="radio" class="radio-group-as-gap-btn">
<el-radio-button :value="1">选项1</el-radio-button>
<el-radio-button :value="2">选项2</el-radio-button>
<el-radio-button :value="3">选项3</el-radio-button>
</el-radio-group>
<el-radio-group v-model="radio" class="radio-group-as-gap-btn">
<el-space :size="24">
<el-radio-button :value="1">选项1</el-radio-button>
<el-radio-button :value="2">选项2</el-radio-button>
<el-radio-button :value="3">选项3</el-radio-button>
</el-space>
</el-radio-group>
</template>
`}}
</pre>
<el-space direction="vertical" fill>
<el-radio-group v-model="radio" class="radio-group-as-gap-btn">
<el-radio-button :value="1">选项1</el-radio-button>
<el-radio-button :value="2">选项2</el-radio-button>
<el-radio-button :value="3">选项3</el-radio-button>
</el-radio-group>
<el-radio-group v-model="radio" class="radio-group-as-gap-btn">
<el-space :size="24">
<el-radio-button :value="1">选项1</el-radio-button>
<el-radio-button :value="2">选项2</el-radio-button>
<el-radio-button :value="3">选项3</el-radio-button>
</el-space>
</el-radio-group>
</el-space>
</el-col>
<el-col :span="span">
<pre>{{ `import '@/styles/radio.scss';\n <template>
......
<template>
<el-space direction="vertical" class="common-padding" fill alignment="flex-start">
<el-space>
<el-switch v-model="isHightLightEntity" active-text="高亮实体" @change="console.log(isHightLightEntity)" />
<el-switch v-model="isOpenTranslation" active-text="译文" />
</el-space>
<text-translate-pane :texts-raw="textEns" :texts-translate="textZns" :text-entities="textEntities"
:is-open-translation="isOpenTranslation" :is-highlight-entity="isHightLightEntity">
</text-translate-pane>
</el-space>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import TextTranslatePane from '@/components/base/texts/TextTranslatePane.vue'
import { ElSwitch, ElButton, ElSpace } from 'element-plus'
const isOpenTranslation = ref(true);
const isHightLightEntity = ref(true);
const textEntities = ref([{
text_span: '美国',
type: '国家'
},
{
text_span: 'U.S.',
type: '国家'
}]);
const textZns = ref(['华盛顿当地时间2024年7月2日,美国商务部产业与安全局(BIS)',
'发布一项最终规则,宣布修订《出口管理条例》(EAR) ,',
'以违反美国国家安全或外交政策利益为由在实体清单中增列来自四个国家的6个实体,其中包括2家中国企业。[1]',
'多余段落-测试']);
const textEns = ref(['Washington local time on July 2, 2024, the U.S. Department of Commerce, Industry and Security (BIS) released a final rule, announcing the revision of the Export Administration Regulations (EAR) ,',
'Violating the U.S. national security or diplomatic policy interests, BIS increased the list of 6 entities from four countries, including 2 Chinese companies, on the entity list. [1]',
'Your company has a large amount of money in the U.S.',
]);
</script>
<style scoped>
:deep(.el-col) {
border: 0px !important;
}
</style>
......@@ -4,6 +4,9 @@ import '@/styles/tabs.scss'
import ColorPrefixTitle from '@/components/base/texts/ColorPrefixTitle.vue';
import AiTipPane from '@/components/base/panes/AiTipPane.vue';
import CommonText from '@/components/base/texts/CommonText.vue';
import IntelligentEntityText from '@/components/base/texts/IntelligentEntityText.vue';
import TranslateExample from './TranslateExample.vue';
const span = 12
</script>
......@@ -50,6 +53,40 @@ const span = 12
</pre>
<ai-tip-pane>huidadadadadasda</ai-tip-pane>
</el-col>
<el-col :span="span">
<pre>{{ `import IntelligentEntityText from '@/components/base/texts/IntelligentEntityText.vue';\n<template>
<intelligent-entity-text
text="华盛顿当地时间2024年7月2日,美国商务部产业与安全局(BIS) 发布一项最终规则,宣布修订《出口管理条例》(EAR) ,以违反美国国家安全或外交政策利益为由在实体清单 中增列来自四个国家的6个实体,其中包括2家中国企业。[1]"
:entities="[{
text_span: '华盛顿',
type: 'location'
}, {
text_span: '美国商务部产业与安全局',
type: 'organization'
}]">
</intelligent-entity-text>
</template>
`}}
</pre>
<intelligent-entity-text
text="华盛顿当地时间2024年7月2日,美国商务部产业与安全局(BIS) 发布一项最终规则,宣布修订《出口管理条例》(EAR) ,以违反美国国家安全或外交政策利益为由在实体清单 中增列来自四个国家的6个实体,其中包括2家中国企业。[1]"
:entities="[{
text_span: '华盛顿',
type: 'location'
}, {
text_span: '美国商务部产业与安全局',
type: 'organization'
}]">
</intelligent-entity-text>
</el-col>
<el-col :span="span">
<pre>{{ `参考src/styles/components/TextPage/TranslateExample.vue` }}
</pre>
<translate-example>
</translate-example>
</el-col>
</el-row>
</template>
......
<template>
<el-row class="wrapper layout-grid-line">
<el-col :span="span">
<pre>
{{
`
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
<div class="chart-box">
<WordCloudChart :data="data" />
</div>
`
}}
</pre>
<div class="chart-box">
<WordCloudChart :data="data" />
</div>
</el-col>
</el-row>
</template>
<script setup>
import {ref} from 'vue'
import '@/styles/common.scss'
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
const span = 12
const data = 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 },
])
</script>
<style lang="scss" scoped>
.chart-box {
width: 520px;
height: 400px;
border: 1px solid var(--bg-black-5);
}
</style>
\ No newline at end of file
.layout-grid-line {
.el-col {
.el-col:not(disinheritance) {
border: 1px double var(--bg-black-5);
}
......
......@@ -28,15 +28,27 @@
<el-tab-pane label="人物" lazy>
<people-page />
</el-tab-pane>
<el-tab-pane label="新闻" lazy>
<news-page />
</el-tab-pane>
<el-tab-pane label="预警面板" lazy>
<WarnningPane />
</el-tab-pane>
<el-tab-pane label="领域标签" lazy>
<AreaTag />
</el-tab-pane>
<el-tab-pane label="按钮" lazy>
<ActionButton />
</el-tab-pane>
<el-tab-pane label="层级关系图" lazy>
<GraphChart />
</el-tab-pane>
<el-tab-pane label="引力关系图" lazy>
<GraphTreeChart />
</el-tab-pane>
<el-tab-pane label="词云图" lazy>
<WordCloudChart />
</el-tab-pane>
</el-tabs>
</div>
</el-space>
......@@ -59,6 +71,10 @@ import PeoplePage from './People/index.vue';
import WarnningPane from './WarnningPane/index.vue'
import GraphChart from './GraphChart/index.vue'
import GraphTreeChart from './GraphTreeChart/index.vue'
import AreaTag from './AreaTag/index.vue'
import ActionButton from './ActionButton/index.vue'
import WordCloudChart from './WordCloudChart/index.vue'
import NewsPage from './News/index.vue'
</script>
<style lang="scss" scoped>
......
......@@ -17,3 +17,11 @@
.common-padding {
padding: 20px 24px;
}
.common-padding-h {
padding: 0px 24px;
}
.common-padding-v {
padding: 20px 0px;
}
\ No newline at end of file
......@@ -3,23 +3,14 @@
<div class="content-wrapper">
<div class="policy-monitoring">
<div class="header">
<div
class="section"
v-for="(section, index) in sectionTab"
:key="index"
:style="{
<div class="section" v-for="(section, index) in sectionTab" :key="index" :style="{
width: sections[index].waveBall.length === 2 ? '350px' : '503px',
background: section.background
}"
>
}">
<img class="section-title" :src="section.title" />
<div class="stats">
<div
class="ball-item"
v-for="(value, idx) in sections[index].waveBall"
:key="idx"
@click="highLight(value.type)"
>
<div class="ball-item" v-for="(value, idx) in sections[index].waveBall" :key="idx"
@click="highLight(value.type)">
<WaveBall :percent="value.percent" :data="value" :color="section.waterColor" :size="128" />
<div class="text-box">
<div class="waveBall-text">
......@@ -29,16 +20,10 @@
</div>
</div>
</div>
<div
class="bottm-box"
:style="sections[index].waveBall.length === 2 ? 'width: 350px' : 'width:503px'"
@click="handleClickCardBottomInfo(cardBottomInfo[index])"
>
<div class="bottm-box" :style="sections[index].waveBall.length === 2 ? 'width: 350px' : 'width:503px'"
@click="handleClickCardBottomInfo(cardBottomInfo[index])">
<img src="./icon/title-icon-1.png" />
<div
class="bottm-box-text"
:style="sections[index].waveBall.length === 2 ? 'width: 225px' : 'width:378px'"
>
<div class="bottm-box-text" :style="sections[index].waveBall.length === 2 ? 'width: 225px' : 'width:378px'">
{{ cardBottomInfo[index]?.title || "暂无新增数据" }}
</div>
<div style="width: 115px; color: #ffffff">
......@@ -58,24 +43,16 @@
<div style="display: flex">
<!-- 风险信号列表 -->
<div class="risk-signals" ref="riskSignalsRef">
<div
class="risk-signals-item"
v-for="(item, index) in warningList"
:key="index"
@mouseenter="onMouseEnter(item, index)"
@mouseleave="onMouseLeave"
:class="['risk-signals-item', { 'risk-signals-item-hightLight': riskSignalActiveIndex === index }]"
>
<div
class="item-left"
:class="{
<div class="risk-signals-item" v-for="(item, index) in warningList" :key="index"
@mouseenter="onMouseEnter(item, index)" @mouseleave="onMouseLeave"
:class="['risk-signals-item', { 'risk-signals-item-hightLight': riskSignalActiveIndex === index }]">
<div class="item-left" :class="{
'item-status-1': item.signalLevel === '特别重大',
'item-status-2': item.signalLevel === '重大风险',
'item-status-3': item.signalLevel === '较大风险',
'item-status-4': item.signalLevel === '一般风险',
'item-status-5': item.signalLevel === '低风险'
}"
>
}">
{{ item.signalLevel ? item.signalLevel : "一般风险" }}
</div>
<div class="item-right">
......@@ -87,31 +64,16 @@
</div>
</div>
<div class="news">
<div
class="box1-left"
@click="handleSwithCurNews('left')"
@mouseenter="handleInBtn"
@mouseleave="handleOutBtn"
>
<div class="box1-left" @click="handleSwithCurNews('left')" @mouseenter="handleInBtn"
@mouseleave="handleOutBtn">
<img :src="leftBtn" alt="" />
</div>
<div
class="box1-right"
@click="handleSwithCurNews('right')"
@mouseenter="handleInBtn"
@mouseleave="handleOutBtn"
>
<div class="box1-right" @click="handleSwithCurNews('right')" @mouseenter="handleInBtn"
@mouseleave="handleOutBtn">
<img :src="rightBtn" alt="" />
</div>
<el-carousel
ref="carouselRef"
style="height: 443px; width: 664px"
:autoplay="true"
:interval="30000"
arrow="never"
indicator-position="none"
@change="handleCarouselChange"
>
<el-carousel ref="carouselRef" style="height: 443px; width: 664px" :autoplay="true" :interval="30000"
arrow="never" indicator-position="none" @change="handleCarouselChange">
<el-carousel-item v-for="(News, NewsIndex) in filteredHotNewsList" :key="NewsIndex">
<div class="carousel-item" @click="toDetaile(News.hotspotID, News.hotspotType)">
<div class="carousel-title">
......@@ -128,9 +90,7 @@
style="width: 96px; height: 96px"
/> -->
</div>
<div
style="/* 矩形 351 */ width: 664px; height: 1px; background: rgba(234, 236, 238, 1)"
></div>
<div style="/* 矩形 351 */ width: 664px; height: 1px; background: rgba(234, 236, 238, 1)"></div>
<div class="news-carousel-content">{{ News.hotspotDesc }}</div>
<div class="carousel-bottom">
......@@ -1146,6 +1106,7 @@ onUnmounted(() => {
align-items: center;
position: relative;
position: relative;
.red-dot {
position: absolute;
right: -15px;
......@@ -1233,8 +1194,8 @@ onUnmounted(() => {
}
.item-status-2 {
color: var(--color-orange-100) !important;
background: var(--color-orange-10) !important;
color: #FA8C16 !important;
background: #FFF7E6 !important;
}
.item-status-3 {
......@@ -1243,8 +1204,8 @@ onUnmounted(() => {
}
.item-status-4 {
color: var(--color-green-100) !important;
background: var(--color-green-10) !important;
color: #52C41A !important;
background: #F6FFED !important;
}
.item-status-5 {
......@@ -1286,6 +1247,7 @@ onUnmounted(() => {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
color: var(--color-main-active);
font-weight: bold;
......@@ -1410,6 +1372,7 @@ onUnmounted(() => {
width: 24px;
height: 48px;
cursor: pointer;
img {
width: 100%;
height: 100%;
......@@ -1423,6 +1386,7 @@ onUnmounted(() => {
width: 24px;
height: 48px;
cursor: pointer;
img {
width: 100%;
height: 100%;
......@@ -1448,6 +1412,7 @@ onUnmounted(() => {
text-align: justify;
display: flex;
justify-content: space-between;
.title-text {
/* 美国白宫发布关于进一步延长TikTok执法宽限期的行政令 */
width: 660px;
......@@ -1513,6 +1478,7 @@ onUnmounted(() => {
display: flex;
gap: 8px;
justify-content: flex-end;
.tag {
/* 数据展示/Tag标签/亮色/绿 */
height: 24px;
......@@ -1538,6 +1504,7 @@ onUnmounted(() => {
}
}
}
.carousel-footer {
margin-top: 10px;
width: 664px;
......@@ -1551,8 +1518,10 @@ onUnmounted(() => {
align-items: center;
justify-content: space-between;
cursor: pointer;
.footer-left {
display: flex;
.type {
color: var(--color-main-active);
font-family: Source Han Sans CN;
......@@ -1563,6 +1532,7 @@ onUnmounted(() => {
letter-spacing: 0px;
text-align: left;
}
.text {
width: 500px;
color: var(--color-main-active);
......@@ -1578,9 +1548,11 @@ onUnmounted(() => {
white-space: nowrap;
}
}
.footer-right {
width: 12px;
height: 11px;
img {
width: 100%;
height: 100%;
......
......@@ -35,7 +35,7 @@
<script setup>
// 导入组件
import AnalysisBox from '@/components/base/BoxBackground/AnalysisBox.vue';
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue';
import { ref, watch } from 'vue';
import { getEnterpriseBranch, getEnterpriseKeyPerson } from '@/api/companyPages';
import PersonAvatar from '@/components/base/people/PersonAvatar.vue';
......
......@@ -5,8 +5,9 @@
<el-divider></el-divider>
<!-- 新闻列表 -->
<el-space :size="0" direction="vertical">
<news-item class="list-item" v-for="(t, i) in newsPage.content" :key="i" :news="t"></news-item>
<el-space class="full-width" :size="0" direction="vertical" fill alignment="flex-start">
<news-item class="list-item" v-for="(t, i) in newsPage.content" :key="i.id" :title="t.title" :from="t.from"
:content="t.content" @click="gotoNewsDetail(t.id)"></news-item>
</el-space>
<!-- 底部分隔线 -->
......@@ -20,14 +21,17 @@
</template>
<script setup>
import { ref, watch } from 'vue';
// 导入API
import { getEnterpriseNewDynamicPage } from '@/api/companyPages';
// 导入组件
import AnalysisBox from '@/components/base/BoxBackground/AnalysisBox.vue';
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue';
import NewsItem from '@/components/base/newsList/NewsItem.vue';
import { useGotoNewsDetail } from '@/router/modules/news';
import { ElDivider, ElSpace, ElPagination } from 'element-plus';
const gotoNewsDetail = useGotoNewsDetail()
// 导入Vue组合式API
import { ref, watch } from 'vue';
// 响应式数据
const newsPage = ref({}) // 新闻分页数据
......@@ -78,7 +82,11 @@ const onCurrentChange = e => {
<style lang="css" scoped>
/* 新闻列表项样式 */
.list-item {
margin-bottom: 20px;
width: 100%;
padding-bottom: 25px;
margin-bottom: 25px;
margin-left: 25px;
margin-right: 25px;
/* 底部外边距 */
border-bottom: 1px solid rgba(240, 242, 244, 1);
/* 底部边框 */
......
......@@ -3,7 +3,7 @@ import { ref, onMounted, onUnmounted, watch } from 'vue';
import { ElRadioGroup, ElRadioButton, ElSpace } from 'element-plus';
import * as echarts from 'echarts';
import { getSanctionList } from '@/api/companyPages';
import AnalysisBox from '@/components/base/BoxBackground/AnalysisBox.vue';
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue';
import AiTipPane from '@/components/base/panes/AiTipPane.vue'
export interface LineDataItem {
......
......@@ -64,8 +64,7 @@
</div>
</div> -->
<div class="home-main-header-item-box">
<div class="item" v-for="(item, index) in govInsList" :key="index"
@click="handleToInstitution(item)">
<div class="item" v-for="(item, index) in govInsList" :key="index" @click="handleToInstitution(item)">
<div class="item-left">
<img :src="item.img ? item.img : DefaultIcon2" alt="" />
</div>
......@@ -123,8 +122,7 @@
}" 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 v-for="(tag, index) in item.industryList" :key="index" :tagName="tag.industryName">
</AreaTag>
</div>
<div class="box1-main-right-center">
......@@ -187,17 +185,16 @@
<div class="text">{{ "查看更多" }}</div>
</div>
</div> -->
<RiskSignal :list="warningList" @item-click="handleClickToDetail"
@more-click="handleToMoreRiskSignal" riskLevel="signalLevel" postDate="signalTime"
name="signalTitle">
<RiskSignal :list="warningList" @item-click="handleClickToDetail" @more-click="handleToMoreRiskSignal"
riskLevel="signalLevel" postDate="signalTime" name="signalTitle">
</RiskSignal>
</div>
<DivideHeader id="position2" class="divide2" :titleText="'资讯要闻'"></DivideHeader>
<div class="center-center">
<NewsList :newsList="newsList" @item-click="handleToNewsAnalysis" @more-click="handleToMoreNews" />
<!-- <NewsList :newsList="newsList" /> -->
<MessageBubble :messageList="messageList" @person-click="handleClickPerson"
@info-click="handleGetMessage" imageUrl="img" @more-click="handleToSocialDetail" />
<MessageBubble :messageList="messageList" @person-click="handleClickPerson" @info-click="handleGetMessage"
imageUrl="img" @more-click="handleToSocialDetail" />
</div>
<DivideHeader id="position3" class="divide3" :titleText="'数据总览'"></DivideHeader>
<div class="center-footer">
......@@ -212,8 +209,7 @@
<div class="box5-selectbox">
<el-select @change="handleBox5YearChange" v-model="box5SelectedYear" placeholder="选择时间"
style="width: 120px">
<el-option v-for="item in box5YearList" :key="item.value" :label="item.label"
:value="item.value" />
<el-option v-for="item in box5YearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
......@@ -227,12 +223,11 @@
<div class="header-icon">
<img src="./assets/images/box4-header-icon.png" alt="" />
</div>
<div class="header-title">{{ "政令涉及领域" }}</div>
<div class="header-title">{{ "政令科技领域" }}</div>
<div class="box6-selectbox">
<el-select @change="handleBox6YearChange" v-model="box6SelectedYear" placeholder="选择时间"
style="width: 120px">
<el-option v-for="item in box6YearList" :key="item.value" :label="item.label"
:value="item.value" />
<el-option v-for="item in box6YearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
......@@ -248,8 +243,7 @@
<div class="header-title">{{ "关键行政令" }}</div>
</div>
<div class="box7-main">
<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">
<img src="./assets/images/warning.png" alt="" />
</div>
......@@ -282,18 +276,25 @@
<div class="home-main-footer">
<DivideHeader id="position4" class="divide4" :titleText="'资源库'"></DivideHeader>
<div class="home-main-footer-header">
<div class="search-box">
<el-select v-model="searchType" :empty-values="[null, undefined]" style="width: 100%">
<el-option label="全部政府部门" value="" />
<el-option v-for="item in govInsList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
<div style="flex: auto;"></div>
<el-checkbox v-model="isChina">只看涉华政令</el-checkbox>
<div class="select-box">
<div class="paixu-btn" @click="handleSwithSort">
<el-select v-model="isSort" placeholder="发布时间" style="width:120px; margin-left:8px;">
<template #prefix>
<div class="icon1">
<img v-if="isSort" src="@/assets/icons/shengxu1.png" alt="" />
<img v-else src="@/assets/icons/jiangxu1.png" alt="" />
</div>
<div class="text">{{ "发布时间" }}</div>
<div class="icon2">
<img v-if="isSort" src="@/assets/icons/shengxu2.png" alt="" />
<img v-else src="@/assets/icons/jiangxu2.png" alt="" />
</div>
</div>
</template>
<el-option label="正序" :value="true" />
<el-option label="倒序" :value="false" />
</el-select>
</div>
</div>
<div class="home-main-footer-main">
......@@ -305,15 +306,15 @@
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox v-for="type in decreeTypeList" :key="type.id"
v-model="checkedDecreeType" :label="type.typeId" style="width: 180px"
class="filter-checkbox" @change="handleChangeCheckedDecreeType">
<el-checkbox v-for="type in decreeTypeList" :key="type.id" v-model="checkedDecreeType"
:label="type.typeId" style="width: 180px" class="filter-checkbox"
@change="handleChangeCheckedDecreeType">
{{ type.typeName }}
</el-checkbox>
</div>
</div>
</div>
<div class="select-box">
<!-- <div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "发布机构" }}</div>
......@@ -327,6 +328,20 @@
</el-checkbox>
</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 class="select-box">
<div class="select-box-header">
......@@ -335,15 +350,15 @@
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox v-for="time in pubTime" :key="time.id" v-model="activePubTime"
:label="time.id" style="width: 100px" class="filter-checkbox"
<el-checkbox v-for="time in pubTime" :key="time.id" v-model="activePubTime" :label="time.id"
style="width: 100px" class="filter-checkbox"
@change="checked => handlePubTimeChange(time.id, checked)">
{{ time.name }}
</el-checkbox>
</div>
</div>
</div>
<div class="select-box">
<!-- <div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "涉及领域" }}</div>
......@@ -357,7 +372,7 @@
</el-checkbox>
</div>
</div>
</div>
</div> -->
</div>
<div class="right">
<div class="content-header">
......@@ -367,17 +382,16 @@
<div class="title">{{ "政令库" }}</div>
</div>
<div class="content-box" v-show="decreeList">
<div class="main-item" v-for="(item, index) in decreeList" :key="index"
@click="handleClickDecree(item)">
<div class="main-item" v-for="(item, index) in decreeList" :key="index" @click="handleClickDecree(item)">
<div class="main-item-left">
<div class="left-left">
{{ item.time.split("-")[0] }}<br />{{ item.time.split("-")[1] }}月{{
item.time.split("-")[2]
{{ item.time?.split("-")[0] }}<br />{{ item.time?.split("-")[1] }}月{{
item.time?.split("-")[2]
}}日
</div>
<div class="left-right">
<div class="icon">
<img :src="item.orgImage ? item.orgImage : DefaultIcon2" alt="" />
<img :src="item.img ? item.img : DefaultIcon2" alt="" />
</div>
<div class="line" v-if="index !== 9 && index !== totalDecreesNum - 1"></div>
</div>
......@@ -385,16 +399,7 @@
<div class="main-item-center">
<div class="center-header">
<div class="title">{{ item.title }}</div>
<!-- <div
class="type-box"
:class="{
type1: item.status === 1,
type2: item.status === 2,
type3: item.status === 3
}"
>
{{ item.type }}
</div> -->
<!-- <div class="type-box type1">{{ item.type }}</div> -->
</div>
<div class="desc">{{ item.desc }}</div>
<div class="tag-box">
......@@ -410,9 +415,8 @@
{{ `共 ${totalDecreesNum} 项` }}
</div>
<div class="footer-right">
<el-pagination @current-change="handleCurrentChange" :pageSize="10"
:current-page="currentPage" background layout="prev, pager, next"
:total="totalDecreesNum" />
<el-pagination @current-change="handleCurrentChange" :pageSize="10" :current-page="currentPage"
background layout="prev, pager, next" :total="totalDecreesNum" />
</div>
</div>
</div>
......@@ -423,8 +427,8 @@
</template>
<script setup>
import NewsList from "@/components/base/newsList/index.vue";
import { onMounted, ref, computed, watch, nextTick } from "vue";
import NewsList from "@/components/base/NewsList/index.vue";
import { onMounted, ref, watch, nextTick } from "vue";
import router from "@/router";
import {
getDepartmentList,
......@@ -438,55 +442,17 @@ import {
getDecreehylyList,
getDecreeTypeList
} from "@/api/decree/home";
import RiskSignal from "@/components/base/riskSignal/index.vue";
// import RiskSignal from "@/components/base/RiskSignal/index.vue";
import { getPersonSummaryInfo } from "@/api/common/index";
import { getNews, getSocialMedia } from "@/api/general/index";
import DivideHeader from "@/components/DivideHeader.vue";
import { useContainerScroll } from "@/hooks/useScrollShow";
import getMultiLineChart from "./utils/multiLineChart";
import getBarChart from "./utils/barChart";
import getPieChart from "./utils/piechart";
import getWordCloudChart from "./utils/wordCloudChart";
import getCalendarHeatChart from "./utils/cleandarHeat";
import setChart from "@/utils/setChart";
import DefaultIcon1 from "@/assets/icons/default-icon1.png";
import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import DefaultIconNews from "@/assets/icons/default-icon-news.png";
import p1 from "./assets/images/iconp1.png";
import p2 from "./assets/images/iconp2.png";
import p3 from "./assets/images/iconp3.png";
import p4 from "./assets/images/iconp4.png";
import p5 from "./assets/images/iconp5.png";
import p6 from "./assets/images/iconp6.png";
import p7 from "./assets/images/iconp7.png";
import p8 from "./assets/images/iconp8.png";
import p9 from "./assets/images/iconp9.png";
import p10 from "./assets/images/iconp10.png";
import Gov1 from "./assets/images/gov1.png";
import Gov2 from "./assets/images/gov2.png";
import Gov3 from "./assets/images/gov3.png";
import Gov4 from "./assets/images/gov4.png";
import Gov5 from "./assets/images/gov5.png";
import Gov6 from "./assets/images/gov6.png";
import Gov7 from "./assets/images/gov7.png";
import Gov8 from "./assets/images/gov8.png";
import Gov9 from "./assets/images/gov9.png";
import Gov10 from "./assets/images/gov10.png";
import News1 from "./assets/images/news1.png";
import News2 from "./assets/images/news2.png";
import News3 from "./assets/images/news3.png";
import News4 from "./assets/images/news4.png";
import News5 from "./assets/images/news5.png";
import Message1 from "./assets/images/message-icon1.png";
import Message2 from "./assets/images/message-icon2.png";
import Message3 from "./assets/images/message-icon3.png";
import { ElMessage } from "element-plus";
......@@ -509,6 +475,7 @@ const pageSize = ref(10);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
handleToPosi('position4')
handleGetDecreeOrderList();
};
......@@ -658,7 +625,7 @@ const handlegetDecreeRiskSignal = async () => {
const res = await getDecreeRiskSignal(params);
console.log("风险信号", res);
if (res.code === 200 && res.data) {
warningList.value = res.data;
warningList.value = res.data.map(item => ({ ...item, id: item.orderId }));
}
} catch (error) {
console.error("风险信号error", error);
......@@ -875,7 +842,7 @@ const handleBox5 = async () => {
setChart(chart1, "chart1");
};
// 政令涉及领域
// 政令科技领域
const chart2Data = ref([
// {
// name: "集成电路",
......@@ -922,7 +889,7 @@ const handleGetDecreeArea = async () => {
};
try {
const res = await getDecreeArea(params);
console.log("政令涉及领域", res);
console.log("政令科技领域", res);
if (res.code === 200 && res.data) {
chart2Data.value = res.data.map(item => {
return {
......@@ -932,7 +899,7 @@ const handleGetDecreeArea = async () => {
});
}
} catch (error) {
console.error("政令涉及领域error", error);
console.error("政令科技领域error", error);
}
};
const handleBox6 = async () => {
......@@ -1001,10 +968,9 @@ const handleBox8 = async () => {
};
// 资源库
const isSort = ref(false); // true 升序 false 倒序
const handleSwithSort = () => {
isSort.value = !isSort.value;
};
const searchType = ref("");
const isChina = ref(false);
const isSort = ref(false); // true 升序 false 降序
const handleToPosi = id => {
const element = document.getElementById(id);
......@@ -1029,13 +995,6 @@ const handleToPosi = id => {
}
};
const areaList = ref([
// { id: "人工智能", name: "人工智能" },
// { id: "集成电路", name: "集成电路" },
// { id: "通信网络", name: "通信网络" },
// { id: "量子科技", name: "量子科技" }
]);
const activeAreaList = ref(["1"]);
// const handleGetAreaList = async () => {
// try {
// const res = await getDecreehylyList();
......@@ -1080,7 +1039,7 @@ const handleChangeCheckedDecreeType = () => {
};
const pubTime = ref([
{ id: "all", name: "全选" },
{ id: "all", name: "全选时间" },
{ id: "2026", name: "2026年" },
{ id: "2025", name: "2025年" },
{ id: "2024", name: "2024年" },
......@@ -1128,7 +1087,14 @@ const handleAreaChange = (id, checked) => {
}
};
// 修改获取涉及领域列表,添加全选选项
const areaList = ref([
// { id: "人工智能", name: "人工智能" },
// { id: "集成电路", name: "集成电路" },
// { id: "通信网络", name: "通信网络" },
// { id: "量子科技", name: "量子科技" }
]);
const activeAreaList = ref(["1"]);
// 修改获取科技领域列表,添加全选选项
const handleGetAreaList = async () => {
try {
const res = await getDecreehylyList();
......@@ -1160,7 +1126,7 @@ const decreeList = ref([]);
const handleGetDecreeOrderList = async () => {
const p0 = checkedGovIns.value.join(",");
// 处理涉及领域:如果包含 all 或全选,则 researchTypeIds 为空(不传)
// 处理科技领域:如果包含 all 或全选,则 researchTypeIds 为空(不传)
let p1 = "";
const allAreaIds = areaList.value.filter(item => item.id !== "all").map(item => item.id);
const selectedAreaIds = activeAreaList.value.filter(id => id !== "all");
......@@ -1190,6 +1156,8 @@ const handleGetDecreeOrderList = async () => {
proposeName: p0,
researchTypeIds: p1, // 全选时不传(为空)
sortFun: isSort.value,
isCN: isChina.value ? 1 : 0,
proposeName: searchType.value,
years: p2, // 全选时不传(为空)
typeIds: checkedDecreeType.value.toString()
};
......@@ -1220,33 +1188,11 @@ const handleGetDecreeOrderList = async () => {
}
};
watch(
() => activePubTime.value,
val => {
watch([activePubTime, activeAreaList, checkedGovIns, isSort, isChina, searchType], val => {
// 切换页码到第一页
currentPage.value = 1;
handleGetDecreeOrderList();
}
);
watch(
() => activeAreaList.value,
val => {
handleGetDecreeOrderList();
}
);
watch(
() => isSort.value,
val => {
handleGetDecreeOrderList();
}
);
watch(
() => checkedGovIns.value,
val => {
handleGetDecreeOrderList();
}
);
});
// 切换当前政令
const handleSwithCurDecree = name => {
......@@ -3109,7 +3055,7 @@ onMounted(async () => {
height: 50px;
margin: 0 auto 16px;
display: flex;
justify-content: flex-end;
align-items: center;
.btn-box {
margin-top: 10px;
......@@ -3150,32 +3096,20 @@ onMounted(async () => {
}
}
.search-box {
width: 360px;
}
.select-box {
margin-top: 5px;
margin-left: 10px;
height: 42px;
box-sizing: border-box;
padding: 5px 0;
.paixu-btn {
display: flex;
width: 120px;
height: 32px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
&:hover {
background: var(--color-bg-hover);
}
cursor: pointer;
.icon1 {
width: 11px;
height: 14px;
margin-top: 10px;
margin-left: 9px;
font-size: 0px;
img {
width: 100%;
......@@ -3183,30 +3117,10 @@ onMounted(async () => {
}
}
.text {
height: 19px;
color: rgba(95, 101, 108, 1);
:deep(.el-select__selected-item) {
text-align: center;
font-size: 16px;
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0px;
text-align: left;
margin-top: 7px;
margin-left: 9px;
}
.icon2 {
width: 10px;
height: 5px;
margin-top: 5px;
margin-left: 13px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
......@@ -3219,18 +3133,16 @@ onMounted(async () => {
.left {
width: 360px;
// height: 666px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
border-radius: 10px;
.select-box {
margin-top: 21px;
margin-top: 16px;
.select-box-header {
display: flex;
gap: 17px;
gap: 16px;
.icon {
margin-top: 4px;
......@@ -3263,7 +3175,8 @@ onMounted(async () => {
}
.right {
width: 1284px;
width: 20px;
flex: auto;
max-height: 1489px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
......@@ -3306,9 +3219,11 @@ onMounted(async () => {
min-height: 790px;
overflow: hidden;
overflow-y: auto;
box-sizing: border-box;
.main-item {
display: flex;
width: 100%;
min-height: 100px;
// height: 136px;
// background: orange;
......@@ -3348,6 +3263,7 @@ onMounted(async () => {
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
......@@ -3361,13 +3277,18 @@ onMounted(async () => {
}
.main-item-center {
margin-left: 21px;
margin-bottom: 16px;
width: 1086px;
padding-left: 21px;
padding-bottom: 16px;
box-sizing: border-box;
width: 20px;
flex: auto;
.center-header {
width: 100%;
overflow: hidden;
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
.title {
height: 26px;
......@@ -3377,10 +3298,14 @@ onMounted(async () => {
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: left;
flex: auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.type-box {
flex: none;
height: 28px;
line-height: 28px;
padding: 0 8px;
......@@ -3393,18 +3318,13 @@ onMounted(async () => {
}
.type1 {
color: rgba(22, 119, 255, 1);
background: rgba(230, 244, 255, 1);
color: rgba(232, 189, 11, 1);
background: rgba(232, 189, 11, 0.1);
}
.type2 {
color: rgba(19, 168, 168, 1);
background: rgba(230, 255, 251, 1);
}
.type3 {
color: rgba(250, 140, 22, 1);
background: rgba(255, 247, 230, 1);
color: rgba(255, 149, 77, 1);
background: rgba(255, 149, 77, 0.1);
}
}
......
......@@ -17,8 +17,8 @@ const getWordCloudChart = (data) => {
// 其他形状你可以使用形状路径
// shape: 'circle', // 示例
// 或者自定义路径
gridSize: 30, // 网格大小,影响词间距。
sizeRange: [15, 40], // 定义词云中文字大小的范围
gridSize: 35, // 网格大小,影响词间距。
sizeRange: [16, 36], // 定义词云中文字大小的范围
rotationRange: [0, 0],
rotationStep: 0,
drawOutOfBound: false, // 是否超出画布
......
<template>
<div class="wrap">
<div class="box1">
<AnalysisBox title="相关政令关联分析" :showAllBtn="false">
<AnalysisBox title="相关政令" :showAllBtn="false">
<div class="box1-main">
<div class="left">
<el-empty v-if="siderList.length === 0" style="padding-top: 240px" description="暂无数据" :image-size="100" />
<div class="left-item" :class="{ leftItemActive: siderActiveIndex === index }"
v-for="(item, index) in siderList" :key="index" @click="handleClickSider(index)">
<div class="time">{{ item.time }}</div>
<div class="title">{{ item.title }}</div>
<el-empty v-if="siderList.length===0" style="padding-top: 30%" description="暂无数据" :image-size="100" />
<el-scrollbar height="100%" always>
<div class="left-item" :class="{ 'item-active': false }" v-for="(item, index) in siderList" :key="index" @click="handleClickDecree(item)">
<div class="item-head">
<div class="itme-name one-line-ellipsis">{{ item.label }}</div>
<div class="item-tag">政令</div>
</div>
<div class="itme-time one-line-ellipsis">{{ item.time }} · {{ item.name }} </div>
</div>
<div class="right">
<div class="info-box">
<div class="info-left">
<img v-if="decreeInfo.img" :src="decreeInfo.img" alt="" />
<div v-else class="box1-main-left-img-mock">
<img class="img-mock-badge-img" src="./assets/icons/badge.png" />
<div class="img-mock-badge-title">{{ decreeInfo.eTotalTitle }}</div>
</div>
</div>
<div class="info-right">
<div class="info-item">
<div class="item-left">{{ "政令全称:" }}</div>
<div class="item-right1">
<div class="item-right-text">
{{ decreeInfo.totalTitle }}
</div>
<div class="item-right-icon" v-if="decreeInfo.totalTitle" @click="handleToDecreeDetail(decreeInfo)">
<img src="./assets/icons/open-icon.png" alt="" />
</div>
</div>
</div>
<div class="info-item">
<div class="item-left">{{ "英文全称:" }}</div>
<div class="item-right">
{{ decreeInfo.eTotalTitle }}
</div>
</div>
<div class="info-item">
<div class="item-left">{{ "签署时间:" }}</div>
<div class="item-right">
{{ decreeInfo.signTime }}
</div>
</div>
<div class="info-item">
<div class="item-left">{{ "发布机构:" }}</div>
<div class="item-right">
{{ decreeInfo.signOrg }}
</div>
</div>
</div>
</div>
<div class="list-box">
<div class="list-header">
<div class="icon">
<img src="./assets/icons/box1-list-header-icon.png" alt="" />
</div>
<div class="title">{{ "政令主要内容" }}</div>
</div>
<div class="list-main">
<el-empty v-if="showList.length === 0" style="padding-top: 150px" description="暂无数据"
:image-size="100" />
<div class="list-item" v-for="(val, idx) in showList" :key="idx">
<div class="id">{{ idx + 1 }}</div>
<div class="title">{{ val.content }}</div>
<!-- <div class="open">
<img src="./assets/icons/open-icon.png" alt="" />
</div> -->
</el-scrollbar>
</div>
</AnalysisBox>
</div>
<div class="box2">
<AnalysisBox title="政令关系挖掘" :showAllBtn="false">
<div class="box2-main">
<div ref="containerRef" class="graph-container"></div>
</div>
<div class="list-footer">
<div class="footer-left">
{{ `共 ${decreeInfo.list.length} 项` }}
</AnalysisBox>
</div>
<div class="footer-right">
<el-pagination @current-change="handleCurrentChange" :pageSize="pageSize" :current-page="currentPage"
background layout="prev, pager, next" :total="decreeInfo.list.length" />
<el-dialog v-model="dialogVisible" width="1000px" class="viewpoint-dialog">
<template #header>
<div class="viewpoint-header">
<div class="viewpoint-title">冲突关系</div>
</div>
</template>
<div class="viewpoint-body">
<div class="graph-tag">
<div class="icon1"></div>
<div class="title">
{{ `${mainInfo.time}-${mainInfo.label} ${nodeInfo.relation} ${nodeInfo.time}-${nodeInfo.label}, 属于${nodeInfo.relation}关系` }}
</div>
<div class="icon2Wrap">
<div class="icon2"></div>
</div>
</div>
</AnalysisBox>
<div ref="graphContainer" class="graph-container"></div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import { ref, onMounted, onBeforeUnmount } from "vue";
import { useRoute } from "vue-router";
import router from "@/router";
import { getDecreeMainContent } from "@/api/decree/introduction";
import { getDecreeRelateOrder } from "@/api/decree/deepdig";
import box2InfoImg from "./assets/icons/box1-info.png";
import * as G6 from '@antv/g6';
import { getDecreeRelatedOrder } from "@/api/decree/deepdig";
import { getDecreeSummary } from "@/api/decree/introduction";
const allData = ref([]);
const relateId = ref(0);
import icon1628 from "./assets/icons/icon1628.png";
import icon1629 from "./assets/icons/icon1629.png";
const route = useRoute();
const pageSize = ref(10);
const currentPage = ref(1);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
};
const showList = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return decreeInfo.value.list.slice(startIndex, endIndex);
});
// 冲突关系
const dialogVisible = ref(false);
const siderList = ref([
// {
// time: "",
// title: ""
// },
// {
// time: "2023",
// title: "拜登人工智能政令"
// },
// {
// time: "2025",
// title: "特朗普撤销拜登AI规则"
// },
// {
// time: "2023",
// title: "美国AI行动计划"
// },
// {
// time: "2024",
// title: "对中国AI芯片限制"
// }
]);
const siderActiveIndex = ref(0);
const handleClickSider = async index => {
siderActiveIndex.value = index;
decreeInfo.value.id = allData.value[index].id;
decreeInfo.value.img = allData.value[index].imageUrl;
decreeInfo.value.totalTitle = allData.value[index].name;
decreeInfo.value.eTotalTitle = allData.value[index].ename;
decreeInfo.value.signTime = allData.value[index].postDate;
decreeInfo.value.signOrg = allData.value[index].proposeOrgName;
relateId.value = allData.value[index].id;
const params1 = {
currentPage: 0,
pageSize: 999999,
id: relateId.value
};
// 基本信息
const mainInfo = ref({});
const nodeInfo = ref({});
const onDecreeSummaryData = async () => {
try {
const res = await getDecreeMainContent(params1);
console.log("政令主要内容", res);
if (res.code === 200 && res.data) {
decreeInfo.value.list = res.data.content;
} else {
decreeInfo.value.list = [];
const res = await getDecreeSummary({id: route.query.id});
console.log("基本信息", res);
if (res.code===200 && res.data) {
mainInfo.value.label = res.data.name;
mainInfo.value.time = res.data.postDate;
mainInfo.value.id = route.query.id;
mainInfo.value.isCenter = true
}
} catch (error) {
mainInfo.value = {};
console.log("获取基本信息数据失败:", error);
}
} catch (error) { }
};
const decreeInfo = ref({
id: 0,
img: "",
totalTitle: "",
eTotalTitle: "",
signTime: "",
signOrg: "",
list: [
// {
// content:
// "要求强大AI系统开发者与政府分享安全测试结果(“红队测试”);制定生物合成筛查标准防范风险;建立AI生成内容鉴别标准"
// },
// {
// content: "优先支持隐私保护技术(PET)研发;评估各机构如何收集和使用商业信息;制定评估隐私保护技术有效性的指南。"
// },
// {
// content: "为解决算法歧视提供明确指导;确保刑事司法系统中AI使用的公平性;协调调查和起诉AI相关的民权侵犯行为。"
// }
]
});
// 相关政令
const siderList = ref([]);
const handleClickSider = async (item) => {
dialogVisible.value = true;
nodeInfo.value = item
setTimeout(onRelationChart, 300)
};
const handleGetRelateOrder = async () => {
const params = {
id: route.query.id
};
try {
const res = await getDecreeRelateOrder(params);
console.log("相关政令关联分析", res);
if (res.code === 200 && res.data) {
allData.value = res.data;
siderList.value = res.data.map(item => {
return {
time: item.year,
title: item.name
};
});
decreeInfo.value.id = allData.value[0].id;
decreeInfo.value.img = allData.value[0].imageUrl;
decreeInfo.value.totalTitle = allData.value[0].name;
decreeInfo.value.eTotalTitle = allData.value[0].ename;
decreeInfo.value.signTime = allData.value[0].postDate;
decreeInfo.value.signOrg = allData.value[0].proposeOrgName;
relateId.value = allData.value[0].id;
const params1 = {
currentPage: 0,
pageSize: 999999,
id: relateId.value
};
try {
const res = await getDecreeMainContent(params1);
console.log("政令主要内容", res);
if (res.code === 200 && res.data) {
decreeInfo.value.list = res.data.content;
const res = await getDecreeRelatedOrder({id: route.query.id});
console.log("相关政令", res);
if (res.code===200 && res.data?.length) {
siderList.value = res.data
siderList.value.forEach(item => {
item.label = item.proposeOrgName;
item.time = item.postDate;
})
} else {
decreeInfo.value.list = [];
}
} catch (error) { }
} else {
allData.value = [];
siderList.value = [];
decreeInfo.value.id = 0;
decreeInfo.value.img = "";
decreeInfo.value.totalTitle = "";
decreeInfo.value.eTotalTitle = "";
decreeInfo.value.signTime = "";
decreeInfo.value.signOrg = "";
decreeInfo.value.list = [];
siderList.value = []
}
} catch (error) {
allData.value = [];
siderList.value = [];
decreeInfo.value.id = 0;
decreeInfo.value.img = "";
decreeInfo.value.totalTitle = "";
decreeInfo.value.eTotalTitle = "";
decreeInfo.value.signTime = "";
decreeInfo.value.signOrg = "";
decreeInfo.value.list = [];
console.log("获取相关政令数据失败:", error);
}
};
const handleToDecreeDetail = item => {
console.log("item", item.id);
window.sessionStorage.setItem("curTabName", item.totalTitle);
// 政令关系挖掘
const containerRef = ref();
let graphInstance = null;
// 文本插入换行符
const onWordWrap = (word, num) => {
const list = word.split('');
let label = "";
for (let i = 0; i < list.length; i++) {
if (i % num === 0 && i !== 0) {
label += "\n";
}
label += list[i];
}
return label;
}
const onFormatNode = (item) => {
let isCenter = item.isCenter || false
return {
id: item.id+'', label:onWordWrap(item.label, 15), isCenter,
img: isCenter ? icon1628 : icon1629,
clipCfg: { r: isCenter ? 40 : 30 },
labelCfg: {
style: {
fill: isCenter ? "#1459BB" : "#333333",
fontSize: isCenter ? 13 : 13,
fontWeight: isCenter ? "bold" : "normal"
}
}
}
}
const onFormatEdge = (item, index) => {
return {
id: `edge-${index+1}`,
target: item.id+'',
source: route.query.id,
// label: ["", "相似", "继承", "冲突"][1],
label: item.relation,
style: {
stroke: ["", "#B9DCFF", "#87E8DE", "#FFCCC7"][1],
},
labelCfg: {
style: {
fill: ["", "#055FC2", "#13A8A8", "#CE4F51"][1],
background: {
fill: ["", "#E7F3FF", "#E6FFFB", "#FFE0E0"][1],
}
}
}
}
}
const initChart = () => {
let edgeList = siderList.value.map(onFormatEdge)
let nodeList = siderList.value.map(onFormatNode)
nodeList.unshift(onFormatNode(mainInfo.value))
console.log(nodeList)
const width = containerRef.value.offsetWidth || 800
const height = containerRef.value.offsetHeight || 600
const centerX = width / 2
const centerY = height / 2
const radius = Math.min(width, height) / 2 - 120
const otherNodes = nodeList.filter(n => !n.isCenter)
const nodeCount = otherNodes.length
otherNodes.forEach((node, index) => {
const angle = (2 * Math.PI * index) / nodeCount - Math.PI / 2
node.x = centerX + radius * Math.cos(angle)
node.y = centerY + radius * Math.sin(angle)
})
const centerNode = nodeList.find(n => n.isCenter)
if (centerNode) {
centerNode.x = centerX
centerNode.y = centerY
centerNode.fx = centerX
centerNode.fy = centerY
}
graphInstance = new G6.Graph({
container: containerRef.value,
width,
height,
fitView: false,
fitCenter: false,
animate: true,
animateCfg: {
duration: 300,
easing: 'easeLinear'
},
minZoom: 0.1,
maxZoom: 10,
modes: {
default: [ 'drag-canvas', 'zoom-canvas', 'drag-node' ]
},
defaultNode: {
type: 'image',
size: 50,
style: { cursor: "pointer" },
clipCfg: { show: true, type: 'circle' },
labelCfg: {
position: "bottom", offset: 12,
style: {
fill: '#333',
fontSize: 11,
fontFamily: 'Microsoft YaHei',
textAlign: 'center',
background: {
fill: 'rgba(255, 255, 255, 0.95)',
padding: [4, 6, 4, 6],
radius: 4
}
}
}
},
defaultEdge: {
type: "line",
style: { lineWidth: 1, endArrow: true },
labelCfg: {
autoRotate: true,
style: {
cursor: "pointer",
fontSize: 12,
fontFamily: 'Microsoft YaHei',
background: { padding: [4, 4, 4, 4] }
}
}
}
})
// 节点点击处理
graphInstance.on('node:click', (evt) => {
let node = siderList.value.find(item => item.id==evt.item._cfg.model.id)
if (node) handleClickDecree(node)
});
graphInstance.on('edge:click', (evt) => {
let node = siderList.value.find(item => item.id==evt.item._cfg.model.target)
if (node) handleClickSider(node)
});
graphInstance.data({nodes: nodeList, edges: edgeList})
graphInstance.render()
}
const handleClickDecree = decree => {
window.sessionStorage.setItem("curTabName", decree.name);
const route = router.resolve({
path: "/decreeLayout/overview/introduction",
path: "/decreeLayout",
query: {
id: item.id
id: decree.id
}
});
window.open(route.href, "_blank");
};
onMounted(() => {
handleGetRelateOrder();
});
</script>
<style lang="scss" scoped>
.wrap {
height: 100%;
overflow: hidden;
.box-header {
height: 56px;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
// 冲突关系
const graphContainer = ref(null);
let graph = null;
const onRelationChart = () => {
const container = graphContainer.value;
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: mainInfo.value.id+'', label: `${mainInfo.value.time}\n${onWordWrap(mainInfo.value.label, 15)}`,
size: [250, 80], x: leftNodeX, y: centerY,
},
{
id: nodeInfo.value.id+'', label: `${nodeInfo.value.time}\n${onWordWrap(nodeInfo.value.label, 15)}`,
size: [250, 80], x: rightNodeX, y: centerY,
},
],
edges: [
{
id: `edge-1`, target: nodeInfo.value.id+'', source: mainInfo.value.id+'', label: nodeInfo.value.relation,
style: {
stroke: ["", "#B9DCFF", "#87E8DE", "#FFCCC7"][1],
},
labelCfg: {
style: {
fill: ["", "#055FC2", "#13A8A8", "#CE4F51"][1],
background: {
fill: ["", "#E7F3FF", "#E6FFFB", "#FFE0E0"][1],
}
}
.title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
line-height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
}
}
]
};
.header-btn-box {
position: absolute;
top: 14px;
right: 52px;
display: flex;
// 创建图实例
if (!graph) {
graph = new G6.Graph({
container: container,
width,
height,
defaultNode: {
type: 'rect',
anchorPoints: [[0, 0.5], [1, 0.5]],
style: {
cursor: "pointer",
radius: 4,
fill: '#f6faff',
stroke: '#B9DCFF',
},
labelCfg: {
style: {
cursor: "pointer",
fill: "#333333",
fontSize: 15,
fontWeight: "bold",
fontFamily: "Microsoft YaHei",
textAlign: "center",
}
}
},
defaultEdge: {
type: "line",
style: {
lineWidth: 1,
endArrow: true,
},
labelCfg: {
autoRotate: true,
style: {
fontSize: 14,
fontFamily: 'Microsoft YaHei',
background: {
padding: [6, 4, 4, 4],
}
}
}
},
layout: null,
modes: { default: [] },
fitView: false,
});
.btn {
margin-left: 8px;
}
graph.on('node:click', (evt) => {
let node = siderList.value.find(item => item.id==evt.item._cfg.model.id)
if (node) handleClickDecree(node)
});
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
height: 28px;
display: flex;
gap: 4px;
// 加载数据并渲染
graph.data(data);
graph.render();
}
.icon {
width: 28px;
height: 28px;
onMounted(() => {
Promise.all([onDecreeSummaryData(), handleGetRelateOrder()]).then(() => {
if (mainInfo.value.id && siderList.value.length) initChart()
})
});
img {
width: 100%;
onBeforeUnmount(() => {
graphInstance?.destroy()
graph?.destroy()
})
</script>
<style lang="scss" scoped>
.wrap {
height: 100%;
}
}
}
}
width: 1600px;
padding: 16px 0;
display: flex;
gap: 16px;
margin: 0 auto;
.box1 {
margin: 16px auto;
width: 1600px;
max-height: 898px;
min-height: 788px;
width: 480px;
.box1-main {
display: flex;
margin-top: 5px;
margin-bottom: 10px;
.left {
margin-left: 21px;
width: 300px;
padding: 10px 16px;
height: 100%;
.left-item {
width: 300px;
height: 64px;
border-radius: 4px;
display: flex;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
border-bottom: 1px solid rgba(240, 242, 244, 1);
padding: 10px;
cursor: pointer;
&:first-child {
border-top: 1px solid rgba(240, 242, 244, 1);
}
&:hover {
background: rgba(246, 250, 255, 1);
}
.time {
width: 45px;
height: 24px;
line-height: 24px;
margin-left: 18px;
margin-top: 20px;
.item-head {
display: flex;
align-items: center;
margin-bottom: 6px;
}
.title {
width: 200px;
margin-left: 17px;
margin-top: 17px;
height: 30px;
line-height: 30px;
.itme-name {
width: 20px;
flex: auto;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 600;
letter-spacing: 0px;
}
.item-tag{
height: 24px;
line-height: 24px;
text-align: center;
padding: 0 8px;
border-radius: 12px;
background: rgba(231, 243, 255, 1);
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
letter-spacing: 0px;
margin-left: 6px;
}
.itme-time {
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
letter-spacing: 0px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.leftItemActive {
color: var(--color-main-active);
font-weight: 700;
.item-active {
background: rgba(246, 250, 255, 1);
position: relative;
&::after {
position: relative;
position: absolute;
content: "";
width: 5px;
height: 48px;
height: 100%;
background: var(--color-main-active);
right: -15px;
top: 8px;
right: 0px;
top: 0px;
}
}
}
.right {
margin-left: 36px;
.info-box {
margin-left: 28px;
width: 1180px;
height: 188px;
box-sizing: border-box;
border: 1px solid rgba(231, 243, 255, 1);
background: rgba(246, 250, 255, 1);
display: flex;
.info-left {
width: 242px;
height: 136px;
margin-top: 25px;
margin-left: 28px;
img {
// width: 100%;
height: 100%;
}
.box1-main-left-img-mock {
width: 100%;
.box2 {
width: 20px;
flex: auto;
.box2-main {
height: 100%;
border-radius: 4px;
background-color: #0b1932;
display: flex;
align-items: center;
flex-direction: column;
justify-content: space-around;
padding: 15px;
.img-mock-badge-img {
width: 50px;
height: 50px;
padding: 10px;
.graph-container {
width: 100%;
height: 600px;
}
}
.img-mock-badge-title {
text-align: center;
font-size: 14px;
line-height: 20px;
color: #fff;
height: 40px;
display: -webkit-box;
/* 2. 设置排列方向为垂直 */
-webkit-box-orient: vertical;
/* 3. 设置显示的行数(这里设为2行) */
-webkit-line-clamp: 2;
/* 4. 处理溢出和换行 */
overflow: hidden;
text-overflow: ellipsis;
word-break: break-word;
}
}
// .img-mock-badge-org {
// text-align: center;
// font-size: 14px;
// color: #fff;
// }
// 修改element-plus弹出框样式
:deep(.viewpoint-dialog) {
padding: 0;
border-radius: 4px;
.el-dialog__body {
padding: 0;
}
.el-dialog__header {
padding: 0;
margin: 0;
position: relative;
height: 48px;
}
.info-right {
margin-left: 20px;
margin-top: 22px;
.info-item {
.el-dialog__headerbtn {
top: 50%;
transform: translateY(-50%);
right: 12px;
}
.viewpoint-header {
height: 48px;
display: flex;
min-height: 30px;
max-height: 60px;
margin-bottom: 8px;
.item-left {
// margin-top: 3px;
width: 100px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
align-items: center;
padding: 0 24px;
border-bottom: 1px solid rgb(234, 236, 238);
}
.viewpoint-title {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
}
.item-right {
width: 769px;
// height: 30px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 25px;
}
.item-right1 {
.viewpoint-body {
padding: 16px;
height: 560px;
overflow: hidden;
display: flex;
.item-right-text {
height: 30px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
.item-right-icon {
margin-left: 13px;
margin-top: 7px;
width: 16px;
height: 16px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
}
}
}
.list-box {
margin-left: 36px;
.list-header {
flex-direction: column;
.graph-tag {
display: flex;
height: 56px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
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;
.icon {
margin-top: 21px;
margin-left: 17px;
.icon1 {
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
height: 20px;
background-image: url("../assets/icons/ai.png");
background-size: 100% 100%;
flex-shrink: 0;
}
.title {
margin-top: 16px;
margin-left: 16px;
height: 30px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
color: rgb(5, 95, 194);
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
}
.list-main {
min-height: 420px;
max-height: 540px;
overflow-x: hidden;
overflow-y: auto;
.list-item {
width: 1180px;
min-height: 65px;
box-sizing: border-box;
border-radius: 4px;
background: rgba(255, 255, 255, 1);
border-bottom: 1px solid rgba(234, 236, 238, 1);
display: flex;
cursor: pointer;
&:hover {
background: var(--color-bg-hover);
font-weight: 400;
line-height: 24px;
margin-left: 13px;
flex: 1;
}
.id {
.icon2Wrap {
width: 24px;
height: 24px;
margin-left: 15px;
margin-top: 15px;
background-color: rgba(231, 243, 255, 1);
display: flex;
justify-content: center;
align-items: center;
border-radius: 12px;
background: #e7f3ff;
text-align: center;
line-height: 24px;
color: #0a57a6;
}
.title {
margin-left: 13px;
margin-top: 12px;
margin-bottom: 12px;
// height: 30px;
width: 1100px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 25px;
}
.open {
width: 16px;
height: 16px;
margin-top: 20px;
margin-left: 20px;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
.icon2 {
width: 24px;
height: 24px;
background-image: url("../assets/icons/right.png");
background-size: 100% 100%;
}
}
}
.graph-container {
width: 100%;
height: 20px;
flex: auto;
}
}
}
.list-footer {
margin-left: 35px;
height: 32px;
margin-top: 10px;
display: flex;
justify-content: space-between;
.footer-left {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 32px;
}
}
}
}
// 修改element-plus滚动条样式
:deep(.el-scrollbar__bar.is-vertical) {
right: 0px;
width: 4px;
background: transparent;
border-radius: 2px;
&>div {
background: #c5c7c9;
opacity: 1;
}
&>div:hover {
background: #505357;
}
}
</style>
......@@ -19,14 +19,11 @@
<div class="info">
<div class="info-box1">{{ summaryInfo.name }}</div>
<div class="info-box2">
<div class="info-box2-item item1" v-if="summaryInfo.order">
{{ summaryInfo.order + " | " }}
</div>
<div class="info-box2-item item2" v-if="summaryInfo.orgName">
{{ summaryInfo.orgName + " | " }}
</div>
<div class="info-box2-item item3" v-if="summaryInfo.ename">{{ summaryInfo.ename }}</div>
<div class="info-box2-item item1">{{ summaryInfo.postDate }}</div>
|
<div class="info-box2-item item2">{{ summaryInfo.orgName }}</div>
|
<div class="info-box2-item item3">{{ summaryInfo.ename }}</div>
</div>
</div>
</div>
......@@ -60,6 +57,12 @@
</div>
<div class="text">{{ "政令原文" }}</div>
</div>
<!-- <div class="btn" @click="handleToInstitution">
<div class="icon">
<img src="./assets/icons/link-icon.png" alt="" />
</div>
<div class="text">{{ "查看官网" }}</div>
</div> -->
<div class="btn-active" @click="handleAnalysisClick">
<div class="icon-active">
<img src="./assets/icons/edit-icon.png" alt="" />
......@@ -77,7 +80,7 @@
<router-view />
</div>
</div>
<div class="layout-report-box" v-if="activeName === '法案原文'">
<!-- <div class="layout-report-box" v-if="activeName === '法案原文'">
<div class="report-close" @click="handleSwitchActiveName('分析报告')">
<img src="./assets/images/report-close-icon.png" alt="" />
</div>
......@@ -127,8 +130,8 @@
</div>
</div>
</div>
</div>
<div class="report" v-if="isShowReport">
</div> -->
<!-- <div class="report" v-if="isShowReport">
<div class="report-close" @click="handleCloseReport">
<img src="@/assets/icons/close.png" alt="" />
</div>
......@@ -145,7 +148,7 @@
<iframe v-else :src="reportUrlEn" style="border: none" width="100%" height="100%"> </iframe>
</div>
</div>
</div>
</div> -->
</div>
</template>
......@@ -170,30 +173,22 @@ const route = useRoute();
const decreeId = ref(route.query.id);
const isShowReport = ref(false);
const reportUrl = ref("");
const reportUrlEn = ref("");
const activeName = ref("分析报告");
const summaryInfo = ref({});
const handleSwitchActiveName = name => {
activeName.value = name;
};
const curBill = ref("公法(2025年7月4日)");
// const activeName = ref("分析报告");
// const handleSwitchActiveName = name => {
// activeName.value = name;
// };
const billList = ref([
{
label: "公法(2025年7月4日)",
value: "公法(2025年7月4日)"
},
{
label: "公法(2025年7月2日)",
value: "公法(2025年7月2日)"
}
]);
// const curBill = ref("公法(2025年7月4日)");
// const billList = ref([
// {
// label: "公法(2025年7月4日)",
// value: "公法(2025年7月4日)"
// },
// {
// label: "公法(2025年7月2日)",
// value: "公法(2025年7月2日)"
// }
// ]);
const mainHeaderBtnList = ref([
{
......@@ -207,17 +202,16 @@ const mainHeaderBtnList = ref([
activeIcon: icon2Active,
name: "深度挖掘",
path: "/decreeLayout/deepDig"
}
},
// {
// icon: icon3,
// activeIcon: icon3Active,
// name: "影响分析",
// path: "/decreeLayout/influence"
// }
// },
]);
const activeTitle = ref("政令概况");
const handleClickMainHeaderBtn = item => {
activeTitle.value = item.name;
router.push({
......@@ -229,12 +223,10 @@ const handleClickMainHeaderBtn = item => {
};
// 获取全局信息
const summaryInfo = ref({});
const handleGetSummary = async () => {
const params = {
id: route.query.id
};
try {
const res = await getDecreeSummary(params);
const res = await getDecreeSummary({id: route.query.id});
console.log("全局信息", res);
if (res.code === 200 && res.data) {
summaryInfo.value = res.data;
......@@ -243,24 +235,24 @@ const handleGetSummary = async () => {
};
// 获取报告原文
const handleGetReport = async () => {
const params = {
id: route.query.id
};
// const reportUrl = ref("");
// const reportUrlEn = ref("");
// const handleGetReport = async () => {
// try {
// const res = await getDecreeReport({id: route.query.id});
// console.log("报告原文", res);
// if (res.code === 200 && res.data) {
// reportUrl.value = res.data.content;
// reportUrlEn.value = res.data.contentEn;
// }
// } catch (error) {}
// };
try {
const res = await getDecreeReport(params);
console.log("报告原文", res);
if (res.code === 200 && res.data) {
reportUrl.value = res.data.content;
reportUrlEn.value = res.data.contentEn;
}
} catch (error) {}
};
// const isShowReport = ref(false);
// const handleCloseReport = () => {
// isShowReport.value = false;
// };
const handleCloseReport = () => {
isShowReport.value = false;
};
const handleShowReport = () => {
const curRoute = router.resolve({
path: "/decree/decreeOriginal",
......@@ -271,6 +263,16 @@ const handleShowReport = () => {
window.open(curRoute.href, "_blank");
};
const handleToInstitution = () => {
// const curRoute = router.resolve({
// path: "/institution",
// query: {
// id: route.query.id
// }
// });
// window.open(curRoute.href, "_blank");
};
const handleAnalysisClick = () => {
router.push({
path: "/writtingAsstaint",
......@@ -284,14 +286,14 @@ const handleAnalysisClick = () => {
onMounted(() => {
handleGetSummary();
console.log(route.path);
if (route.path === "/decreeLayout/overview/introduction" || route.path === "/decreeLayout/overview/background") {
if (route.path === "/decreeLayout/overview/introduction" || route.path === "/decreeLayout/overview/background" || route.path === "/decreeLayout/overview/measures") {
activeTitle.value = "政令概况";
} else if (route.path === "/decreeLayout/deepDig") {
activeTitle.value = "深度挖掘";
} else {
} else if (route.path === "/decreeLayout/influence") {
activeTitle.value = "影响分析";
}
handleGetReport();
// handleGetReport();
});
</script>
......@@ -370,11 +372,10 @@ onMounted(() => {
}
.layout-main {
width: 100%;
overflow-y: auto;
height: 100%;
display: flex;
flex-direction: column;
.header-main {
position: sticky;
top: 0;
z-index: 1000;
width: 100%;
background-color: #fff;
box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.05);
......@@ -447,18 +448,6 @@ onMounted(() => {
.info-box2-item {
padding: 0 10px;
}
.item1 {
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item2 {
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item3 {
max-width: 420px;
overflow: hidden;
......@@ -509,7 +498,7 @@ onMounted(() => {
}
}
.layout-main-header-right-box {
width: 300px;
width: 450px;
margin-top: 19px;
.right-box-top {
.time {
......@@ -609,6 +598,9 @@ onMounted(() => {
}
}
.layout-main-center {
height: 20px;
flex: auto;
background-color: #f7f8f9;
}
}
.layout-report-box {
......
<template>
<div class="view-box">
<div class="right-main">
<div class="right-main-content">
<div class="hintWrap">
<div class="icon1"></div>
<div class="title">
这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。
</div>
<div class="icon2Wrap">
<div class="icon2"></div>
</div>
</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-text" v-for="(item, index) in dataList" :key="'label-' + index"
:class="{
'blue-theme': index < 2,
'green-theme': index >= 2 && index < 4,
'purple-theme': index >= 4
}" :style="{ left: index * 200 + 220 + 'px' }">
{{ item.text }}
</div>
</div>
<!-- 奇数索引的数据组放在上方 -->
<div v-for="(causeGroup, groupIndex) in onFilterData(1)" :key="'top-' + groupIndex"
:class="getTopBoneClass(groupIndex)" :style="{ left: groupIndex * 400 + 420 + 'px' }">
<div class="left-bone">
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="'left-' + index">
<img :src="defaultIcon2 || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div class="right-bone-item" v-for="(item, index) in getRightItems(causeGroup.causes)" :key="'right-' + index">
<div class="line"></div>
<img :src="defaultIcon2 || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
</div>
</div>
</div>
<!-- 偶数索引的数据组放在下方 -->
<div v-for="(causeGroup, groupIndex) in onFilterData(0)" :key="'bottom-' + groupIndex"
:class="getBottomBoneClass(groupIndex)" :style="{ left: groupIndex * 400 + 220 + 'px' }">
<div class="left-bone">
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="'left-bottom-' + index">
<img :src="defaultIcon2 || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div class="right-bone-item" v-for="(item, index) in getRightItems(causeGroup.causes)" :key="'right-bottom-' + index">
<div class="line"></div>
<img :src="defaultIcon2 || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
</div>
</div>
</div>
</div>
<div v-else
style="display: flex; justify-content: center; align-items: center; height: 200px; width: 100%">
<el-empty description="暂无相关数据" />
</div>
</div>
</div>
</div>
<div class="right-main-content-footer">
<div class="footer-item footer-item1">
<div class="footer-item-bottom">
<div class="icon">
<img :src="noticeIcon" alt="" />
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.upstreamInternalCount ||
0}家(${formatRate(cnEntityOnChainData.upstreamInternalRate)}%),受制裁${cnEntityOnChainData.upstreamEntityCount
|| 0}家(${formatRate(cnEntityOnChainData.upstreamEntityRate)}%)`
}}
</div>
</div>
<div class="footer-item-top">{{ "上游" }}</div>
</div>
<div class="footer-item footer-item2">
<div class="footer-item-bottom">
<div class="icon">
<img :src="noticeIcon" alt="" />
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.midstreamInternalCount ||
0}家(${formatRate(cnEntityOnChainData.midstreamInternalRate)}%),受制裁${cnEntityOnChainData.midstreamEntityCount
|| 0}家(${formatRate(cnEntityOnChainData.midstreamEntityRate)}%)`
}}
</div>
</div>
<div class="footer-item-top">{{ "中游" }}</div>
</div>
<div class="footer-item footer-item3">
<div class="footer-item-bottom">
<div class="icon">
<img :src="noticeIcon" alt="" />
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.downstreamInternalCount ||
0}家(${formatRate(cnEntityOnChainData.downstreamInternalRate)}%),受制裁${cnEntityOnChainData.downstreamEntityCount
|| 0}家(${formatRate(cnEntityOnChainData.downstreamEntityRate)}%)`
}}
</div>
</div>
<div class="footer-item-top">{{ "下游" }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup name="ChartChain">
import { ref, onMounted } from "vue";
import defaultIcon2 from "@/assets/icons/default-icon2.png";
import noticeIcon from "./assets/images/notice-icon.png";
import { getDeepMiningSelect, getDeepMiningIndustry, getDeepMiningIndustryFishbone, getDeepMiningIndustryEntity } from "@/api/exportControlV2.0";
// 实体清单-深度挖掘-产业链中国企业实体信息查询
const cnEntityOnChainData = ref({});
const getCnEntityOnChainData = async () => {
const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value);
const date = currentSanction ? currentSanction.date : '';
// 确保 date 格式正确
const formattedDate = date && date.includes('年') ? date.replace('年', '-').replace('月', '-').replace('日', '') : date;
const params = {
date: formattedDate
};
if (selectedIndustryId.value) {
params.chainId = selectedIndustryId.value;
}
try {
const res = await getDeepMiningIndustryEntity(params);
console.log("企业信息", res)
if (res.code === 200 && res.data) {
cnEntityOnChainData.value = res.data;
} else {
cnEntityOnChainData.value = {};
}
} catch (error) {
console.error("获取产业链中国企业实体信息失败:", error);
cnEntityOnChainData.value = {};
}
}
// 产业链鱼骨数据
const dataList = ref([]);
// 奇数索引的数据组放在上方, 偶数索引的数据组放在下方
const onFilterData = (num) => {
return dataList.value.filter((_, index) => index % 2 === num);
};
// 获取左侧显示的项目(前半部分)
const getLeftItems = items => {
const midpoint = Math.ceil(items.length / 2);
return items.slice(0, midpoint);
};
// 获取右侧显示的项目(后半部分)
const getRightItems = items => {
const midpoint = Math.ceil(items.length / 2);
return items.slice(midpoint);
};
// 获取上方鱼骨图位置类名
const getTopBoneClass = index => {
const positions = ["top-bone", "top-bone1", "top-bone2"];
return positions[index % 3] || "top-bone";
};
// 获取下方鱼骨图位置类名
const getBottomBoneClass = index => {
const positions = ["bottom-bone", "bottom-bone1", "bottom-bone2"];
return positions[index % 3] || "bottom-bone";
};
const getFishboneData = async () => {
const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value);
const date = currentSanction ? currentSanction.date : '';
// 确保 date 格式正确
const formattedDate = date && date.includes('年') ? date.replace('年', '-').replace('月', '-').replace('日', '') : date;
const params = {
date: formattedDate
};
if (selectedIndustryId.value) {
params.chainId = selectedIndustryId.value;
}
try {
const res = await getDeepMiningIndustryFishbone(params);
console.log("获取产业链数据:", res);
if (res.code === 200 && res.data?.causes?.length) {
dataList.value = res.data.causes;
} else {
dataList.value = [];
}
} catch (error) {
console.error("获取产业链鱼骨图数据失败:", error);
dataList.value = [];
}
}
// 实体清单-深度挖掘-产业链列表信息
const selectedIndustryId = ref(null);
const getIndustryList = async () => {
try {
const res = await getDeepMiningIndustry();
if (res.code === 200 && res.data && res.data.length > 0) {
selectedIndustryId.value = res.data[0].id;
getFishboneData();
getCnEntityOnChainData();
} else {
selectedIndustryId.value = null;
}
} catch (error) {
console.error("获取产业链列表数据失败:", error);
selectedIndustryId.value = null;
}
}
// 获取选择制裁
const loading = ref(false);
const currentPage = ref(1);
const pageSize = ref(10000);
const getDeepMiningSelectData = async () => {
loading.value = true;
const params = {
startDate: dateRange.value && dateRange.value[0] ? dateRange.value[0] : '',
endDate: dateRange.value && dateRange.value[1] ? dateRange.value[1] : '',
typeName: "实体清单",
isCn: false,
pageNum: currentPage.value,
pageSize: pageSize.value
};
try {
const res = await getDeepMiningSelect(params);
if (res.code === 200 && res.data && res.data.content) {
sanctionList.value = res.data.content.map(item => ({
id: item.id,
date: item.postDate,
title: item.name,
count: item.cnEntityCount,
unit: '家中国实体', // 接口未返回单位,暂时固定
summary: item.summary, // 保留额外信息备用
techDomainList: item.techDomainList // 保留额外信息备用
}));
// 默认选中第一条
if (sanctionList.value.length > 0) {
currentSanctionId.value = sanctionList.value[0].id;
// getFishboneData(); // 这里不需要调用,因为getIndustryList会调用
}
} else {
sanctionList.value = [];
}
} catch (error) {
console.error("获取选择制裁数据失败:", error);
sanctionList.value = [];
} finally {
loading.value = false;
}
}
const dateRange = ref(["2025-01-01", "2025-12-31"]);
const sanctionList = ref([
{ id: 1, date: "2025年2月8日", title: "实体清单更新", count: 2, unit: "家中国实体" },
{ id: 2, date: "2025年4月10日", title: "实体清单更新", count: 5, unit: "家中国实体" },
{ id: 3, date: "2025年6月29日", title: "实体清单更新", count: 6, unit: "家中国实体" },
{ id: 4, date: "2025年8月12日", title: "实体清单更新", count: 24, unit: "家中国实体" },
{ id: 5, date: "2025年8月19日", title: "实体清单更新", count: 11, unit: "家中国实体" },
{ id: 6, date: "2025年9月12日", title: "实体清单更新", count: 3, unit: "家中国实体" },
{ id: 7, date: "2025年9月26日", title: "实体清单更新", count: 6, unit: "家中国实体" },
{ id: 8, date: "2025年10月12日", title: "实体清单更新", count: 18, unit: "家中国实体" }
]);
const currentSanctionId = ref(5);
// 格式化比率
const formatRate = (rate, ratio=false) => {
if (!rate) return '0.00';
return (rate * 100).toFixed(2);
};
onMounted(() => {
// 获取选择制裁
getDeepMiningSelectData();
// 获取产业链信息
getIndustryList();
});
</script>
<style scoped lang="scss">
.view-box {
width: 100%;
height: 100%;
}
.right-main {
height: 100%;
padding: 11px 16px 20px;
.right-main-content {
height: 100%;
display: flex;
flex-direction: column;
.hintWrap {
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);
margin-bottom: 9px;
.icon1 {
width: 19px;
height: 20px;
background-image: url("../assets/icons/ai.png");
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;
justify-content: center;
align-items: center;
border-radius: 12px;
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;
.fishbone-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.fishbone-scroll-container {
display: flex;
align-items: center;
width: 100%;
height: 100%;
overflow-x: auto;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(144, 202, 249, 0.5) transparent;
&::-webkit-scrollbar {
height: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(144, 202, 249, 0.5);
border-radius: 3px;
}
}
.fishbone {
position: relative;
width: fit-content;
height: 100%;
margin-top: 40px;
min-width: 100%;
padding-left: 275px;
margin-left: 40px;
.main-line {
margin-top: 280px;
width: 1888px;
height: 3px;
background: rgb(230, 231, 232);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 100px;
// 虚线
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
// 添加中间的文字块
.main-line-text {
position: absolute;
// top: -14px;
font-size: 16px;
color: #055FC2;
font-weight: bold;
background-color: #f7f8f9;
padding: 0 10px;
z-index: 2;
// 箭头背景
height: 32px;
line-height: 32px;
width: 160px;
text-align: center;
background: rgba(231, 243, 255, 1);
clip-path: polygon(0% 0%, 90% 0%, 100% 50%, 90% 100%, 0% 100%, 10% 50%);
&.blue-theme {
background: rgba(231, 243, 255, 1);
color: rgba(22, 119, 255, 1);
}
&.green-theme {
background: rgba(225, 255, 251, 1);
color: rgba(19, 168, 168, 1);
}
&.purple-theme {
background: rgba(246, 235, 255, 1);
color: rgba(146, 84, 222, 1);
}
}
}
}
.company-icon {
width: 16px;
height: 16px;
margin: 0 4px;
object-fit: contain;
}
.top-bone {
position: absolute;
top: 20px;
right: 200px;
width: 3px;
height: 260px;
background: rgb(230, 231, 232);
transform: skew(30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 50px;
// overflow: hidden;
.left-bone-item {
transform: skew(-30deg);
height: 45px;
margin-bottom: 2px;
margin-top: 2px;
display: flex;
justify-content: flex-end;
align-items: center;
.text {
margin-left: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
width: 40px;
height: 2px;
background: rgb(230, 231, 232);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 210px;
overflow: hidden;
.right-bone-item {
transform: skew(-30deg);
height: 39px;
margin-bottom: 2px;
margin-top: 2px;
display: flex;
justify-content: flex-start;
align-items: center;
.line {
margin-right: 7px;
width: 30px;
height: 2px;
background: rgb(230, 231, 232);
}
.text {
max-width: 100px;
margin-right: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.top-bone1 {
@extend .top-bone;
right: 500px;
}
.top-bone2 {
@extend .top-bone;
right: 800px;
}
.bottom-bone {
position: absolute;
top: 280px;
right: 360px;
width: 3px;
height: 260px;
background: rgb(230, 231, 232);
transform: skew(-30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 50px;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(30deg);
height: 39px;
margin-bottom: 2px;
margin-top: 2px;
display: flex;
justify-content: flex-end;
align-items: center;
.text {
margin-left: 4px;
height: 25px;
max-width: 130px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
width: 40px;
height: 2px;
background: rgb(230, 231, 232);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 50px;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 2px;
margin-top: 2px;
display: flex;
justify-content: flex-start;
align-items: center;
.line {
margin-right: 7px;
width: 30px;
height: 2px;
background: rgb(230, 231, 232);
}
.text {
max-width: 100px;
margin-right: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.bottom-bone1 {
@extend .bottom-bone;
right: 660px;
}
.bottom-bone2 {
@extend .bottom-bone;
right: 960px;
}
}
.right-main-content-footer {
margin-top: 16px;
display: flex;
justify-content: space-between;
.footer-item {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.footer-item {
.footer-item-top {
height: 28px;
text-align: center;
line-height: 28px;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
margin-top: 10px;
position: relative;
z-index: 1;
}
.footer-item-bottom {
display: flex;
justify-content: center;
align-items: center;
.text {
color: rgba(206, 79, 81, 1);
font-size: 14px;
line-height: 14px;
margin-left: 6px;
}
.icon {
width: 16px;
height: 16px;
font-size: 0;
img {
width: 100%;
height: 100%;
}
}
}
}
.footer-item1 {
color: rgba(22, 119, 255, 1);
.footer-item-top {
background: rgba(231, 243, 255, 1);
}
}
.footer-item2 {
color: rgba(19, 168, 168, 1);
.footer-item-top {
background: rgba(225, 255, 251, 1);
}
}
.footer-item3 {
color: rgba(146, 84, 222, 1);
.footer-item-top {
background: rgba(246, 235, 255, 1);
}
}
}
}
}
</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="wrapper">
<div class="left">
<div class="box1">
<div class="box-header">
<div class="icon"></div>
<div class="title">{{ "涉及行业" }}</div>
<div class="header-right1">
<el-checkbox v-model="isCRelated" label="只看中国企业" size="large" />
<AnalysisBox title="受影响实体" :showAllBtn="false">
<div class="box1-main">
<div class="data-filter">
<div class="filter-select">
<el-select v-model="curAreaId" :empty-values="[null, undefined]" style="width: 100%">
<el-option label="全部领域" value="" />
<el-option v-for="item in areaList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
<div class="header-right">
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
<div class="filter-input">
<el-input v-model="commandWord" @keyup.enter="handleSearch" style="width: 100%; height: 100%;" :suffix-icon="Search" placeholder="搜索实体" />
</div>
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
<div class="data-title">实体名称</div>
<div style="height: 20px; flex: auto;">
<el-empty v-if="showCompanyList.length === 0" style="padding-top: 30%" description="暂无数据" :image-size="100" />
<el-scrollbar height="100%" always>
<div class="list-data">
<div class="list-item" v-for="item in showCompanyList" :key="item.id" :class="{ 'item-active': activeEntityId === item.id }" @click="handleToCompanyDetail(item)">
<div class="item-icon">
<img :src="defaultIcon2" alt="" class="item-img" />
</div>
<div class="item-name one-line-ellipsis">{{ item.name }}</div>
<div class="item-icon item-icon-tag">
<img :src="noticeIcon" alt="" class="item-img" />
</div>
<div class="box1-top" id="chart1"></div>
<div class="box1-tab-box">
<div
class="tab"
:class="{ tabActive: box1BtnActiveName === item.name }"
v-for="(item, index) in box1BtnList"
:key="index"
@click="handleClickBox1Btn(item)"
>
{{ item.name }}
<div class="item-tag">提及</div>
</div>
</div>
<div class="box1-list-box">
<div class="box1-item" v-for="(item, index) in showCompanyList" :key="index" @click="handleToCompanyDetail">
<div class="id">{{ index + 1 }}</div>
<div class="title">{{ item.name }}</div>
<div class="icon">
<img v-if="item.status >= 0" src="./assets/images/up.png" alt="" />
<img v-else src="./assets/images/down.png" alt="" />
</el-scrollbar>
</div>
</div>
</div>
<div class="box1-footer">
<div class="box1-footer-left">{{ `共 ${companyTotalNum} 项` }}</div>
<div class="box1-footer-right">
<div class="pagination-info">
<div class="pagination-left">{{ `共 ${companyTotalNum} 家企业` }}</div>
<div class="pagination-right">
<el-pagination
@current-change="handleCurrentChange"
:pageSize="pageSize"
......@@ -54,338 +47,474 @@
</div>
</div>
</div>
</AnalysisBox>
</div>
<div class="right">
<div class="box2">
<div class="box-header">
<div class="icon"></div>
<div class="title">{{ "产业链分析" }}</div>
<div class="header-right">
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box2-main">
<div class="box2-main-header">
<div class="box2-main-header-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box2-main-header-center">
{{
"法案核心意图在于通过税收优惠吸引制造业回流美国​,并在关键科技领域对中国进行遏制,限制中国获取先进技术、资本和市场渠道,从而延缓中国科技产业的发展速度。给半导体、新能源、人工智能等相关科技行业带来不小的短期压力。"
}}
</div>
<div class="box2-main-header-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="" />
</div>
<AnalysisBox :showAllBtn="false">
<template #custom-title>
<div class="custom-title">
<div class="title-left">
<div :class="['title-item', {'title-active': contentType==1}]" @click="headerContentType(1)">
<div class="title-icon">
<img :src="contentType==1 ? icon1620 : icon1621" alt="">
</div>
<div class="box2-main-main">
<Fishbone :chainId="chainId" />
<div>产业链</div>
</div>
<div class="box2-main-footer">
<div class="box2-main-footer-left">
<div class="top">
<div class="icon">
<img src="@/assets/icons/warning.png" alt="" />
<div :class="['title-item', {'title-active': contentType==2}]" @click="headerContentType(2)">
<div class="title-icon">
<img :src="contentType==2 ? icon422 : icon423" alt="">
</div>
<div class="text">
{{
`中国企业${chainInfo.upstreamInternalCount}家(${
Number(chainInfo.upstreamInternalRate * 100).toFixed(2)
}%),受制裁${chainInfo.upstreamEntityCount}家(${Number(chainInfo.upstreamEntityRate * 100).toFixed(2)}%)`
}}
<div>实体关系</div>
</div>
</div>
<div class="bottom">{{ "基础支撑" }}</div>
</div>
<div class="box2-main-footer-center">
<div class="top">
<div class="icon">
<img src="@/assets/icons/warning.png" alt="" />
</div>
<div class="text">
{{
`中国企业${chainInfo.midstreamInternalCount}家(${
Number(chainInfo.midstreamInternalRate * 100).toFixed(2)
}%),受制裁${chainInfo.midstreamEntityCount}家(${
Number(chainInfo.midstreamEntityRate * 100).toFixed(2)
}%)`
}}
</div>
</div>
<div class="bottom">{{ "软件算法" }}</div>
</div>
<div class="box2-main-footer-right">
<div class="top">
<div class="icon">
<img src="@/assets/icons/warning.png" alt="" />
</div>
<div class="text">
{{
`中国企业${chainInfo.downstreamInternalCount}家(${
Number(chainInfo.downstreamInternalRate * 100).toFixed(2)
}%),受制裁${chainInfo.downstreamEntityCount}家(${
Number(chainInfo.downstreamEntityRate * 100).toFixed(2)
}%)`
}}
</div>
</div>
<div class="bottom">{{ "行业应用" }}</div>
<div class="title-right" v-if="contentType==1">
<el-select v-model="curAreaId" :empty-values="[null, undefined]" style="width: 100%">
<el-option label="全部领域" value="" />
<el-option v-for="item in areaList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
</div>
</template>
<div class="box2-main" v-if="contentType==1">
<ChartChain />
</div>
<div class="box2-main" 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" />
</div>
</AnalysisBox>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from "vue";
import Fishbone from "./fishbone.vue";
import { useRoute } from "vue-router";
import router from "@/router";
import { ref, onMounted } from "vue";
import setChart from "@/utils/setChart";
import { Search } from '@element-plus/icons-vue'
import getBarChart from "./utils/barChart";
import { getDecreeIndustry, getDecreehylyList, getDecreeCompany, getDecreeAction } from "@/api/decree/influence";
import { getCnEntityOnChain, getChainFishbone, getChainInfoByDomainId, getChainStructure } from "@/api/exportControl";
const route = useRoute();
import { getDecreeIndustry, getDecreehylyList, getDecreeCompany } from "@/api/decree/influence";
import { getCnEntityOnChain, getChainInfoByDomainId } from "@/api/exportControl";
import { getSingleSanctionEntitySupplyChain } from "@/api/exportControlV2.0";
import ChartChain from "./ChartChain.vue";
import GraphChart from "@/components/base/GraphChart/index.vue";
import ChartRelation from "./ChartRelation.vue";
import defaultIcon2 from "@/assets/icons/default-icon2.png";
import noticeIcon from "./assets/images/notice-icon.png";
import icon422 from "./assets/images/icon422.png";
import icon423 from "./assets/images/icon423.png";
import icon1620 from "./assets/images/icon1620.png";
import icon1621 from "./assets/images/icon1621.png";
import company from "./assets/images/company.png";
import companyActive from "./assets/images/company-active.png";
// 跳转企业详情
const handleToCompanyDetail = () => {
const route = router.resolve("/companyPages");
window.open(route.href, "_blank");
};
// 关系图数据
const testData = {
// 节点数据
nodes: [
{
id: 0,
name: "泰丰先行",
// category: 0,
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,
name: "铜陵有色",
// category: 3,
symbolSize: 30,
value: 8,
symbol: `image://${company}`,
x: 150,
y: 400
},
{
id: 10,
name: "长盛精密",
// category: 1,
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 250,
y: 400
},
{
id: 11,
name: "天合光能",
// category: 0,
symbolSize: 30,
value: 8,
symbol: `image://${company}`,
x: 350,
y: 400
},
{
id: 12,
name: "昆仑化学",
// category: 2,
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 250,
y: 400
// 企业影响分析
const companyTotalNum = ref(0); // 企业数量
const isCRelated = ref(false); // 只看中国企业
},
{
id: 13,
name: "嘉源科技",
// category: 1,
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 450,
y: 400
},
{
id: 14,
name: "华阳集团",
// category: 4,
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 550,
y: 400
},
{
id: 15,
name: "海辰智能",
// category: 1,
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 650,
y: 400
},
],
const chart1Data = ref({
// title: ["集成电路", "新能源", "人工智能", "先进制造", "量子科技"],
// value: [109, 95, 79, 25, 11]
});
// 关系数据
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' } },
],
};
const handleGetChart1Data = async () => {
// 受影响实体
const companyList = ref([]);
const activeEntityId = ref(1);
const currentPage = ref(1);
const pageSize = ref(10);
const handleToCompanyDetail = (item) => {
activeEntityId.value = item.id;
};
const handleCurrentChange = page => {
currentPage.value = page;
};
// const showCompanyList = computed(() => {
// const startIndex = (currentPage.value - 1) * pageSize.value;
// const endIndex = startIndex + pageSize.value;
// return companyList.value.slice(startIndex, endIndex);
// });
const showCompanyList = ref([
{ id: 1, name: "北京市", status: "上市" },
{ id: 2, name: "上海市", status: "上市" },
{ id: 3, name: "广州市广州市广州市广州市广州市广州市广州市广州市", status: "上市" },
{ id: 4, name: "深圳市", status: "上市" },
{ id: 5, name: "成都市", status: "上市" },
{ id: 7, name: "天津市", status: "上市" },
{ id: 9, name: "武汉市", status: "上市" },
{ id: 10, name: "西安市", status: "上市" },
]);
const handleGetCompanyListByArea = async () => {
const params = {
cRelated: isCRelated.value,
id: 147
id: curAreaId.value
};
try {
const res = await getDecreeIndustry(params);
console.log("企业影响分析", res);
const res = await getDecreeCompany(params);
console.log("行业领域公司列表", res);
if (res.code === 200 && res.data) {
chart1Data.value.title = res.data.map(item => {
return item.hylyName;
});
chart1Data.value.value = res.data.map(item => {
return item.companyNum;
companyList.value = res.data.map(item => {
return {
name: item.name,
id: item.id,
status: item.marketChange
};
});
companyTotalNum.value = companyList.value.length;
if (res.data?.length) handleToCompanyDetail(res.data[0])
} else {
chart1Data.value.title = [];
chart1Data.value.value = [];
companyList.value = [];
companyTotalNum.value = 0;
}
} catch (error) {
chart1Data.value.title = [];
chart1Data.value.value = [];
companyList.value = [];
companyTotalNum.value = 0;
}
};
const handelBox1 = async () => {
await handleGetChart1Data();
let chart1 = getBarChart(chart1Data.value.title, chart1Data.value.value);
setChart(chart1, "chart1");
};
// 指令搜索
const commandWord = ref("");
const handleSearch = () => {
const box1BtnActiveName = ref("");
const curAreaId = ref(0);
const box1BtnList = ref([
// "集成电路",
// "新能源",
// "人工智能",
// "先进制造",
// "量子科技"
]);
const handleClickBox1Btn = btn => {
box1BtnActiveName.value = btn.name;
curAreaId.value = btn.id;
handleGetCompanyListByArea();
handleGetCompanyListByArea();
handleGetChainId();
};
// 获取行业领域列表
// 行业领域
const curAreaId = ref("");
const areaList = ref([]);
const handleGetHylyList = async () => {
try {
const res = await getDecreehylyList();
console.log("行业领域列表", res);
if (res.code === 200 && res.data) {
box1BtnList.value = res.data;
box1BtnActiveName.value = box1BtnList.value[0].name;
curAreaId.value = box1BtnList.value[0].id;
handleGetCompanyListByArea();
areaList.value = res.data;
handleGetChainId();
}
} catch (error) {}
};
const companyList = ref([
// {
// name: "宁德时代新能源科技股份有限公司",
// status: "down"
// },
// {
// name: "比亚迪股份有限公司",
// status: "down"
// },
// {
// name: "隆基绿能科技股份有限公司",
// status: "down"
// },
// {
// name: "晶科能源控股有限公司",
// status: "down"
// },
// {
// name: "厦门海辰储能科技股份有限公司",
// status: "down"
// }
]);
const currentPage = ref(1);
const pageSize = ref(10);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
// 产业链/实体关系
const contentType = ref(2);
const headerContentType = (type) => {
contentType.value = type;
};
const chainId = ref(0);
const handleGetChainId = async () => {
try {
const res = await getChainInfoByDomainId(curAreaId.value);
console.log("获取chainId", res);
if (res && res.length) {
chainId.value = res[0].id;
// handleGetChainInfoByChainId();
}
} catch (error) {
console.error("chainId error", error);
}
};
const showCompanyList = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return companyList.value.slice(startIndex, endIndex);
});
const handleNodeClick = (node) => {
const handleGetCompanyListByArea = async () => {
const params = {
cRelated: isCRelated.value,
id: curAreaId.value
};
};
const handleLayoutChange = (type) => {
};
const treeData = ref(null);
const graphData = ref({ nodes: [], links: [] });
const singleSanctionEntitySupplyChainData = ref(null);
const updateGraphData = () => {
const data = singleSanctionEntitySupplyChainData.value;
if (!data) return;
const nodes = [];
const links = [];
nodes.push({
id: "0",
name: data.orgName,
image: companyActive,
symbolSize: 60,
isSanctioned: true
});
const parentList = data.parentOrgList || [];
parentList.forEach((item, index) => {
nodes.push({
id: `p-${item.id || index}`,
name: item.name,
image: item.isSanctioned ? companyActive : company,
symbolSize: 40,
isSanctioned: item.isSanctioned
});
links.push({
source: `p-${item.id || index}`,
target: "0",
name: "供应商"
});
});
const childList = data.childrenOrgList || [];
childList.forEach((item, index) => {
nodes.push({
id: `c-${item.id || index}`,
name: item.name,
image: item.isSanctioned ? companyActive : company,
symbolSize: 40,
isSanctioned: item.isSanctioned
});
links.push({
source: "0",
target: `c-${item.id || index}`,
name: "客户"
});
});
graphData.value = { nodes, links };
};
const getSingleSanctionEntitySupplyChainRequest = async () => {
try {
const res = await getDecreeCompany(params);
console.log("行业领域公司列表", res);
const res = await getSingleSanctionEntitySupplyChain({
orgId: "91370102723265504D"
});
console.log("data1", res)
if (res.code === 200 && res.data) {
companyList.value = res.data.map(item => {
return {
singleSanctionEntitySupplyChainData.value = res.data;
updateGraphData();
treeData.value = {
id: res.data.orgId,
name: res.data.orgName,
image: companyActive,
symbolSize: 50,
children: (res.data.parentOrgList || []).map((item, index) => ({
id: item.id || `p-${index}`,
name: item.name,
id: item.id,
status: item.marketChange
image: item.isSanctioned ? companyActive : company,
symbolSize: 30
}))
};
});
companyTotalNum.value = companyList.value.length;
} else {
companyList.value = [];
companyTotalNum.value = 0;
}
} catch (error) {
companyList.value = [];
companyTotalNum.value = 0;
console.log(error);
}
};
// 政令举措落实分析
const timeLineList = ref([
// {
// time: "2025年7月25日",
// content: "商务部已成立AI出口计划办公室,并开始招募专业人员。"
// },
// {
// time: "2025年7月31日",
// content: "英伟达、微软、谷歌等企业已提交初步技术栈提案。"
// }
]);
// 企业影响分析
const companyTotalNum = ref(0); // 企业数量
const handleGetAction = async () => {
const chart1Data = ref({
// title: ["集成电路", "新能源", "人工智能", "先进制造", "量子科技"],
// value: [109, 95, 79, 25, 11]
});
const handleGetChart1Data = async () => {
const params = {
id: route.query.id
id: 147
};
try {
const res = await getDecreeAction(params);
console.log("政令举措落实分析", res);
const res = await getDecreeIndustry(params);
console.log("企业影响分析", res);
if (res.code === 200 && res.data) {
timeLineList.value = res.data.map(item => {
return {
time: item.time,
content: item.describe
};
chart1Data.value.title = res.data.map(item => {
return item.hylyName;
});
chart1Data.value.value = res.data.map(item => {
return item.companyNum;
});
} else {
timeLineList.value = [];
chart1Data.value.title = [];
chart1Data.value.value = [];
}
} catch (error) {
timeLineList.value = [];
chart1Data.value.title = [];
chart1Data.value.value = [];
}
};
// 历史相似举措及落实情况
const box3List = ref([
// {
// type: "科技法案",
// title: "瓦森纳安排",
// content: "落实情况:持续有效,但面临技术快速迭代挑战。",
// time: "1996-至今",
// tag: "人工智能"
// },
// {
// type: "科技法案",
// title: "云计算出口管制",
// content: "落实情况:部分有效,但执行难度大。",
// time: "1996-至今",
// tag: "人工智能"
// },
// {
// type: "科技法案",
// title: "芯片与科学法案",
// content: "落实情况:正在实施,效果待观察。",
// time: "2022-至今",
// tag: "人工智能"
// },
// {
// type: "科技法案",
// title: "AI芯片出口管制",
// content: "落实情况:部分有效,但催生中国自主创新。",
// time: "1996-至今",
// tag: "人工智能"
// },
// {
// type: "科技法案",
// title: "瓦森纳安排",
// content: "落实情况:持续有效,但面临技术快速迭代挑战。",
// time: "1996-至今",
// tag: "人工智能"
// },
// {
// type: "科技法案",
// title: "瓦森纳安排",
// content: "落实情况:持续有效,但面临技术快速迭代挑战。",
// time: "1996-至今",
// tag: "人工智能"
// }
]);
watch(
() => isCRelated.value,
val => {
handleGetCompanyListByArea();
handelBox1();
}
);
const handelBox1 = async () => {
await handleGetChart1Data();
let chart1 = getBarChart(chart1Data.value.title, chart1Data.value.value);
setChart(chart1, "chart1");
};
const chainInfo = ref({
upstreamInternalCount: 0,
......@@ -402,24 +531,6 @@ const chainInfo = ref({
downstreamEntityRate: 0
});
const chainId = ref(0);
// 根据领域id获取chainId
const handleGetChainId = async () => {
try {
const res = await getChainInfoByDomainId(curAreaId.value);
console.log("获取chainId", res);
if (res && res.length) {
chainId.value = res[0].id;
console.log("chainId", chainId.value);
handleGetChainInfoByChainId();
}
} catch (error) {
console.error("chainId error", error);
}
};
// 根据chainId获取chainInfo
const handleGetChainInfoByChainId = async () => {
try {
......@@ -434,168 +545,127 @@ const handleGetChainInfoByChainId = async () => {
};
onMounted(() => {
// handleGetCompanyListByArea();
handleGetChart1Data();
handleGetHylyList();
// handleGetAction();
handelBox1();
getSingleSanctionEntitySupplyChainRequest()
});
</script>
<style lang="scss" scoped>
.wrapper {
width: 100%;
height: 100%;
width: 1600px;
padding: 16px 0;
display: flex;
justify-content: center;
.box-header {
display: flex;
height: 48px;
position: relative;
.icon {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
height: 26px;
margin-left: 14px;
margin-top: 14px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
display: flex;
justify-content: flex-end;
gap: 4px;
.header-right-icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
}
.header-right1 {
position: absolute;
top: 8px;
right: 84px;
}
}
.left {
margin-top: 16px;
gap: 16px;
margin: 0 auto;
.box1 {
width: 480px;
height: 845px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.box1-top {
width: 446px;
height: 188px;
margin: 7px auto 0;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 4px;
background: rgba(247, 248, 249, 1);
}
.box1-tab-box {
height: 60px;
width: 446px;
margin: 0 auto;
overflow-x: auto;
.box1-main {
height: 100%;
padding: 10px 16px;
display: flex;
flex-direction: column;
.data-filter {
display: flex;
white-space: nowrap;
gap: 8px;
.tab {
min-width: min-content;
margin-top: 18px;
padding: 0 8px;
height: 28px;
align-items: center;
gap: 16px;
margin-bottom: 10px;
.filter-select {
width: 150px;
}
.filter-input {
width: 20px;
flex: auto;
background-color: var(--el-fill-color-blank);
border-radius: var(--el-border-radius-base);
box-shadow: 0 0 0 1px var(--el-border-color) inset;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
cursor: pointer;
&:hover {
background: var(--btn-active-bg-color);
border: 1px solid var(--btn-active-border-color);
height: 32px;
}
}
.tabActive {
color: var(--btn-active-text-color);
background: var(--btn-active-bg-color);
border: 1px solid var(--btn-active-border-color);
}
.data-title {
border-top: 1px solid rgba(240, 242, 244, 1);
padding: 12px;
font-weight: bold;
}
.box1-list-box {
height: 480px;
width: 446px;
margin: 0 auto;
.box1-item {
.list-data {
width: 446px;
height: 48px;
box-sizing: border-box;
border-bottom: 1px solid rgba(234, 236, 238, 1);
border-radius: 4px;
.list-item {
display: flex;
align-items: center;
border: 1px solid transparent;
border-bottom: 1px solid rgba(240, 242, 244, 1);
padding: 12px;
cursor: pointer;
&:first-child {
border-top: 1px solid rgba(240, 242, 244, 1);
}
&:hover {
background: var(--color-bg-hover);
background-color: #f7f8fa;
}
.id {
.item-icon {
width: 24px;
height: 24px;
margin-top: 12px;
margin-left: 12px;
border-radius: 12px;
background: #e7f3ff;
font-size: 0px;
.item-img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.item-icon-tag {
width: 20px;
height: 20px;
}
.item-tag {
color: rgb(206, 79, 81);
font-size: 14px;
font-family: Microsoft YaHei;
text-align: center;
line-height: 24px;
color: var(--color-main-active);
line-height: 14px;
}
.title {
margin-left: 10px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
.item-name {
width: 20px;
flex: auto;
padding: 0 10px;
font-size: 16px;
color: rgb(59, 65, 75);
font-weight: 400;
line-height: 48px;
font-family: "Microsoft YaHei";
}
.icon {
width: 8px;
height: 6px;
margin-top: 14px;
margin-left: 6px;
img {
width: 100%;
height: 100%;
}
.list-item.item-active {
background-color: rgba(5, 95, 194, 0.05);
border-color: rgba(174, 214, 255, 1);
.item-name {
color: rgb(5, 95, 194);
font-weight: bold;
}
}
}
.box1-footer {
.pagination-info {
height: 65px;
width: 446px;
margin: 0 auto;
display: flex;
justify-content: space-between;
box-sizing: border-box;
.box1-footer-left {
.pagination-left {
margin-top: 25px;
height: 18px;
color: rgba(95, 101, 108, 1);
......@@ -604,217 +674,66 @@ onMounted(() => {
font-weight: 400;
line-height: 18px;
}
.box1-footer-right {
.pagination-right {
margin-top: 23px;
}
}
}
}
.right {
margin-top: 16px;
margin-left: 16px;
.box2 {
width: 1105px;
height: 847px;
background: rgba(255, 255, 255);
border-radius: 10px;
position: relative;
.box2-main {
height: 799px;
.box2-main-header {
margin: 14px auto 36px;
width: 1066px;
height: 64px;
box-sizing: border-box;
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
background: rgba(246, 250, 255, 1);
padding: 6px 12px;
display: flex;
align-items: center;
justify-content: center;
gap: 13px;
.box2-main-header-left {
width: 19px;
height: 20px;
img {
width: 100%;
height: 100%;
width: 20px;
flex: auto;
:deep(.header-icon) {
display: none;
}
}
.box2-main-header-center {
width: 973px;
height: 48px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.box2-main-header-right {
width: 24px;
height: 24px;
img {
.custom-title {
display: flex;
justify-content: space-between;
align-items: flex-end;
width: 100%;
height: 100%;
}
}
}
.box2-main-main {
width: 1053px;
height: 568px;
margin: 0 auto;
}
.box2-main-footer {
margin-left: 24px;
width: 1053px;
height: 117px;
padding: 0 16px;
.title-left {
display: flex;
box-sizing: border-box;
padding-top: 36px;
overflow-x: auto;
.box2-main-footer-left {
width: 408px;
text-align: center;
position: relative;
z-index: 3;
.top {
height: 22px;
border: 1px solid rgb(5, 95, 194);
color: rgb(5, 95, 194);
border-radius: 16px;
width: 240px;
height: 32px;
overflow: hidden;
cursor: pointer;
.title-item {
display: flex;
justify-content: center;
gap: 6px;
align-items: center;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
color: rgba(206, 79, 81, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
}
.bottom {
margin-top: 13px;
width: 408px;
height: 28px;
color: rgba(22, 119, 255, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: center;
background: url("./assets/images/bg1.png");
}
}
.box2-main-footer-center {
width: 408px;
text-align: center;
margin-left: -10px;
position: relative;
z-index: 2;
.top {
height: 22px;
display: flex;
justify-content: center;
gap: 6px;
align-items: center;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
color: rgba(206, 79, 81, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
}
.bottom {
margin-top: 13px;
width: 408px;
height: 28px;
color: rgba(19, 168, 168, 1);
font-family: Microsoft YaHei;
font-style: Bold;
width: 50%;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: center;
background: url("./assets/images/bg2.png");
}
}
.box2-main-footer-right {
width: 408px;
text-align: center;
margin-left: -10px;
position: relative;
z-index: 1;
.top {
height: 22px;
display: flex;
justify-content: center;
gap: 6px;
align-items: center;
.icon {
width: 16px;
height: 16px;
line-height: 16px;
font-family: "Microsoft YaHei";
.title-icon {
width: 14px;
height: 14px;
font-size: 0;
img {
width: 100%;
height: 100%;
}
}
.text {
color: rgba(206, 79, 81, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
}
.bottom {
margin-top: 13px;
width: 408px;
height: 28px;
color: rgba(146, 84, 222, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: center;
background: url("./assets/images/bg3.png");
.title-active {
background-color: rgb(5, 95, 194);
color: white;
}
}
.title-right {
width: 180px;
}
}
.box2-main {
width: 100%;
height: 100%;
}
}
}
......
<template>
<div class="introduction-wrap">
<div class="hover-dialog"
:style="{ position: 'absolute', zIndex: 9999, top: `${mouseY - 220}px`, left: `${mouseX - 600}px` }"
v-if="isShowBox2Dialog">
{{ box2ContentAll }}
</div>
<div class="left">
<div class="box1">
<!-- <div class="box-header">
<div class="header-left"></div>
<div class="title">提出背景</div>
<div class="header-btn-box">
<div
class="btn"
:class="{ btnActive: box1ActiveBtn === item }"
v-for="(item, index) in box1BtnList"
:key="index"
@click="handleClickBox1Btn(item)"
>
{{ item }}
</div>
</div>
<div class="header-right">
<div class="icon">
<img src="../assets/icons/header-right-icon1.png" alt="" />
</div>
<div class="icon">
<img src="../assets/icons/header-right-icon2.png" alt="" />
</div>
</div>
</div>
<div class="box1-main">
<el-empty v-if="backgroundList.length === 0" style="padding-top: 60px;" description="暂无数据" :image-size="100" />
<div class="box1-item" v-for="(item, index) in backgroundList" :key="index">
<div class="id">{{ index + 1 }}</div>
<div class="title">{{ item.content }}</div>
</div>
</div>
<div class="box1-footer">
<div class="box1-footer-left">{{ `共 ${backgroundListNum} 项` }}</div>
<div class="box1-footer-right">
<el-pagination :page-size="5" background layout="prev, pager, next" :total="backgroundListNum" />
</div>
</div> -->
<AnalysisBox title="提出背景" :showAllBtn="false">
<template #header-btn>
<div class="header-btn-box">
......@@ -52,75 +11,47 @@
</div>
</div>
</template>
<div class="box1-container">
<el-empty v-if="!backgroundList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<div class="box1-main">
<el-empty v-if="backgroundList.length === 0" style="padding-top: 60px;" description="暂无数据"
:image-size="100" />
<div class="box1-item" v-for="(item, index) in backgroundList" :key="index">
<div class="id">{{ index + 1 }}</div>
<div class="title">{{ item.content }}</div>
<!-- <div class="open">
<div class="open">
<img src="./assets/images/open-icon.png" alt="" />
</div> -->
</div>
</div>
<div class="box1-footer">
</div>
<div class="box1-footer" v-if="backgroundListNum > 10">
<div class="box1-footer-left">{{ `共 ${backgroundListNum} 项` }}</div>
<div class="box1-footer-right">
<el-pagination :page-size="5" background layout="prev, pager, next" :total="backgroundListNum" />
<el-pagination :page-size="10" @current-change="handleCurrentChange" :current-page="currentPage" background layout="prev, pager, next" :total="backgroundListNum" />
</div>
</div>
</div>
</AnalysisBox>
</div>
<div class="box2">
<!-- <div class="box-header">
<div class="header-left"></div>
<div class="title">相关事件</div>
<div class="header-right">
<div class="icon">
<img src="../assets/icons/header-right-icon1.png" alt="" />
</div>
<div class="icon">
<img src="../assets/icons/header-right-icon2.png" alt="" />
</div>
</div>
</div>
<AnalysisBox title="法律依据" :showAllBtn="false">
<el-empty v-if="!dependList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<div class="box2-main">
<el-empty v-if="!relatedEvents.length" description="暂无数据" :image-size="100" />
<div class="box2-item" v-for="(item, index) in relatedEvents" :key="index">
<div class="item-left">
<img :src="item.image" alt="" />
</div>
<div class="item-center">
<div class="title">{{ item.title }}</div>
<el-popover effect="dark" :width="1000" :content="item.content" placement="top-start">
<template #reference>
<div class="content">
{{ item.content }}
</div>
<div class="custom-collapse">
<el-collapse v-model="dependActive">
<el-collapse-item v-for="(item, index) in dependList" :key="item.billId" title="Consistency" :name="item.billId">
<template #icon>
<el-icon><ArrowDownBold /></el-icon>
</template>
</el-popover>
</div>
<div class="item-right">{{ item.time }}</div>
</div>
</div> -->
<AnalysisBox title="相关事件" :showAllBtn="false">
<div class="box2-main">
<el-empty v-if="!relatedEvents.length" description="暂无数据" :image-size="100" />
<div class="box2-item" v-for="(item, index) in relatedEvents" :key="index">
<div class="item-left">
<img :src="item.image" alt="" />
</div>
<div class="item-center">
<div class="title">{{ item.title }}</div>
<el-popover effect="dark" :width="1000" :content="item.content" placement="top-start">
<template #reference>
<div class="content">
{{ item.content }}
<template #title>
<div class="custom-collapse-title">
<div class="custom-collapse-index">{{ index + 1 }}</div>
<div class="custom-collapse-name one-line-ellipsis">{{ item.title }}</div>
</div>
</template>
</el-popover>
<div class="custom-collapse-content">
{{ item.content }}
</div>
<div class="item-right">{{ item.time }}</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
</AnalysisBox>
......@@ -128,16 +59,23 @@
</div>
<div class="right">
<div class="box3">
<AnalysisBox title="法律依据" :showAllBtn="false">
<div class="box3-main">
<el-empty v-if="!laws.length" style="padding-top: 200px;" description="暂无数据" :image-size="100" />
<div class="box3-item" v-for="(item, index) in laws" :key="index" @click="handleToBillDetail(item)">
<div class="id">{{ index + 1 }}</div>
<div class="item-header">
<div class="name">{{ item.title }}</div>
<AnalysisBox title="前序政令" :showAllBtn="false">
<el-empty v-if="!prevList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<div class="box3-bottom-main">
<el-timeline>
<el-timeline-item v-for="(item, index) in prevList" :key="index">
<div>
<div class="time-line-top">
<div class="time-line-date">{{ item.postDate }}</div>
<div class="time-line-icon">
<img style="width: 100%; height: 100%;" :src="item.orgImage || DefaultIcon1" alt="">
</div>
<div class="time-line-name" @click="handleToInstitution(item)">{{ item.proposeOrgName }}</div>
</div>
<div class="item-content">{{ item.content }}</div>
<div class="timeline-content">{{ item.describe }}</div>
</div>
</el-timeline-item>
</el-timeline>
</div>
</AnalysisBox>
</div>
......@@ -148,31 +86,21 @@
<script setup>
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import { getDecreeBackground, getDecreeDepend, getDecreePrev } from "@/api/decree/background";
import router from "@/router";
import { getDecreeBackground, getDecreeRelatedEvent, getDecreeDepend } from "@/api/decree/background";
import Img1 from "./assets/images/box2-img1.png";
import Img2 from "./assets/images/box2-img2.png";
import Img3 from "./assets/images/box2-img3.png";
import Img4 from "./assets/images/box2-img4.png";
import Img5 from "./assets/images/box2-img5.png";
import { reduce } from "lodash";
import Index from "../index.vue";
import DefaultIcon1 from "@/assets/icons/default-icon1.png";
const route = useRoute();
const decreeId = ref(route.query.id);
// 基本鼠标位置
const mouseX = ref(0);
const mouseY = ref(0);
// 提出背景
const box1BtnList = ref(["涉华背景", "全部背景"]);
const box1ActiveBtn = ref("涉华背景");
const handleClickBox1Btn = btn => {
box1ActiveBtn.value = btn;
handleGetBackground();
handleCurrentChange(1)
};
const backgroundListNum = ref(0);
const backgroundList = ref([
......@@ -183,11 +111,16 @@ const backgroundList = ref([
// content: "要求美国不仅必须在开发通用和前沿AI能力方面领先,还必须确保美国的人工智能技术、标准和治理模式在全球范围内被采用"
// },
]);
const currentPage = ref(1);
const handleCurrentChange = page => {
currentPage.value = page;
handleGetBackground();
};
const handleGetBackground = async () => {
const params = {
cRelated: box1ActiveBtn.value === "涉华背景" ? true : false,
currentPage: 0,
pageSize: 999999,
currentPage: currentPage.value - 1,
pageSize: 10,
id: decreeId.value
};
try {
......@@ -200,147 +133,89 @@ const handleGetBackground = async () => {
backgroundListNum.value = 0;
backgroundList.value = [];
}
} catch (error) { }
} catch (error) {
backgroundListNum.value = 0;
backgroundList.value = [];
console.error("获取提出背景数据失败", error);
}
};
// 相关事件
const relatedEvents = ref([
// 前序政令
const prevList = ref([
// {
// image: Img1,
// title: "中美AI模型性能差距迅速缩小",
// content:
// "斯坦福大学《2025年人工智能指数报告》显示,中美顶尖AI模型在MMLU(大规模多任务语言理解)等主流基准测试中的性能...",
// time: "2025-08-30"
// }
// proposeOrgName: "白宫",
// postDate: "2025-07-31",
// describe: "美商务部发布指南,警告全球企业使用华为昇腾芯片可能违反美国出口管制。意在限制中国AI产业发展,阻碍其获得先进算力。"
// },
]);
const handleGetRelateEvents = async () => {
const params = {
id: decreeId.value
};
const handleGetPrev = async () => {
try {
const res = await getDecreeRelatedEvent(params);
console.log("相关事件", res);
const res = await getDecreePrev({id: decreeId.value});
console.log("前序政令", res);
if (res.code === 200 && res.data) {
relatedEvents.value = res.data.map(item => {
return {
image: item.imageUrl,
title: item.sjbt,
content: item.sjnr,
time: item.sjsj
};
});
prevList.value = res.data;
} else {
relatedEvents.value = [];
prevList.value = [];
}
} catch (error) {
prevList.value = [];
console.error("获取前序政令数据失败", error);
}
};
// 跳转行政机构主页
const handleToInstitution = item => {
window.sessionStorage.setItem("curTabName", item.proposeOrgName);
const curRoute = router.resolve({
path: "/institution",
query: {
id: item.id
}
} catch (error) { }
});
window.open(curRoute.href, "_blank");
};
// 法律依据
const laws = ref([]);
const dependList = ref([]);
const dependActive = ref([]);
const handleGetLaws = async () => {
const params = {
id: decreeId.value
};
try {
const res = await getDecreeDepend(params);
const res = await getDecreeDepend({id: decreeId.value});
console.log("法律依据", res);
if (res.code === 200 && res.data) {
laws.value = res.data;
dependList.value = res.data;
dependActive.value = res.data.map(item => item.billId);
} else {
laws.value = [];
dependList.value = [];
}
} catch (error) { }
};
const isShowBox2Dialog = ref(false);
const box2ContentAll = ref("");
const handleChangeShowBox2Dialog = (isShow, content) => {
isShowBox2Dialog.value = isShow;
box2ContentAll.value = content;
};
const handleMouseMove = event => {
mouseX.value = event.screenX;
mouseY.value = event.screenY;
};
const handleToBillDetail = item => {
window.sessionStorage.setItem("curTabName", item.title);
const route = router.resolve({
path: "/billLayout",
query: {
billId: item.billId
} catch (error) {
dependList.value = [];
console.error("获取法律依据数据失败", error);
}
});
window.open(route.href, "_blank");
};
onMounted(() => {
handleGetBackground();
handleGetRelateEvents();
handleGetLaws();
handleGetPrev();
});
</script>
<style lang="scss" scoped>
.introduction-wrap {
display: flex;
position: relative;
width: 1600px;
padding: 16px 0;
gap: 16px;
.header-btn-box {
display: flex;
justify-content: flex-end;
gap: 8px;
.btn {
height: 28px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 28px;
cursor: pointer;
}
.btnActive {
border: 1px solid var(--color-main-active);
color: var(--color-main-active);
background: rgba(231, 243, 255, 1);
}
}
.box-header {
height: 56px;
.left {
width: 20px;
flex: auto;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
line-height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
}
flex-direction: column;
gap: 16px;
.box1 {
.header-btn-box {
position: absolute;
top: 14px;
right: 84px;
display: flex;
justify-content: flex-end;
gap: 8px;
......@@ -367,59 +242,33 @@ onMounted(() => {
}
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
height: 28px;
.box1-container {
display: flex;
gap: 4px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
flex-direction: column;
padding: 16px;
}
}
.left {
width: 1064px;
.box1 {
margin-top: 16px;
width: 1064px;
height: 414px;
.box1-main {
margin-left: 22px;
width: 1034px;
height: 290px;
overflow: hidden;
overflow-y: auto;
.box1-item {
width: 1015px;
min-height: 48px;
margin-bottom: 8px;
font-family: Microsoft YaHei;
font-size: var(--font-size-base);
color: rgb(59, 65, 75);
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 2px;
background: rgba(255, 255, 255, 1);
display: flex;
align-items: center;
padding: 8px 0;
padding: 12px 0;
.id {
margin-left: 15px;
width: 24px;
height: 24px;
text-align: center;
line-height: 24px;
line-height: 30px;
border-radius: 12px;
background: #e7f3ff;
color: #0a57a6;
......@@ -429,15 +278,12 @@ onMounted(() => {
width: 914px;
line-height: 24px;
margin-left: 13px;
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
}
.open {
width: 16px;
height: 16px;
margin-top: 16px;
margin-left: 16px;
img {
width: 100%;
......@@ -445,12 +291,16 @@ onMounted(() => {
}
}
}
.box1-item:nth-child(odd) {
background-color: rgba(247, 248, 249, 1);
}
}
.box1-footer {
margin: 20px 22px;
display: flex;
justify-content: space-between;
margin-top: 10px;
.box1-footer-left {
height: 20px;
......@@ -464,173 +314,117 @@ onMounted(() => {
}
.box2 {
margin-top: 16px;
width: 1064px;
height: 415px;
.box2-main {
margin-top: 3px;
margin-left: 31px;
height: 330px;
width: 1004px;
overflow: hidden;
overflow-y: auto;
.box2-item {
display: flex;
height: 60px;
margin-bottom: 2px;
.item-left {
width: 64px;
height: 48px;
border-radius: 2px;
img {
width: 100%;
height: 100%;
padding: 16px 20px;
.custom-collapse {
padding-left: 32px;
:deep(.el-collapse),
:deep(.el-collapse-item__wrap) {
border: none !important;
}
:deep(.el-collapse-item__header) {
border-bottom: 1px solid rgba(234, 236, 238, 1);
}
.item-center {
width: 806px;
margin-left: 14px;
.title {
height: 30px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
margin-top: -5px;
:deep(.el-collapse-item__content) {
padding-bottom: 16px;
}
.content {
width: 806px;
height: 30px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
.custom-collapse-title {
position: relative;
.custom-collapse-index {
font-size: 15px;
position: absolute;
top: 12px;
left: -32px;
width: 24px;
height: 24px;
text-align: center;
line-height: 24px;
border-radius: 50%;
background: #e7f3ff;
color: #0a57a6;
}
.custom-collapse-name {
font-weight: 600;
font-size: 18px;
color: var(--el-collapse-header-text-color);
}
}
.item-right {
margin-top: 19px;
margin-left: 30px;
height: 22px;
color: rgba(95, 101, 108, 1);
.custom-collapse-content {
margin-top: 8px;
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
font-size: var(--font-size-base);
color: rgb(59, 65, 75);
}
}
}
// .box2-footer {
// margin: 5px auto 0;
// width: 108px;
// height: 32px;
// cursor: pointer;
// img {
// width: 100%;
// height: 100%;
// }
// }
}
}
.right {
margin-left: 16px;
.box3 {
margin-top: 16px;
width: 520px;
height: 845px;
.box3-main {
margin-top: 9px;
width: 464px;
height: 720px;
// overflow: hidden;
overflow-y: auto;
padding-left: 20px;
.box3-item {
margin-bottom: 20px;
position: relative;
cursor: pointer;
.box3 {
// border-bottom: 1px solid rgba(234, 236, 238, 1);
.id {
width: 24px;
height: 24px;
position: absolute;
left: 18px;
top: 1px;
z-index: 99;
text-align: center;
line-height: 24px;
margin-top: 5px;
border-radius: 12px;
background: #e7f3ff;
color: #0a57a6;
}
.box3-bottom-main {
width: 100%;
padding-right: 30px;
margin-top: 14px;
.item-header {
width: 384px;
height: 35px;
.time-line-top {
display: flex;
justify-content: space-between;
margin-left: 60px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
align-items: center;
margin-bottom: 10px;
.time-line-date {
color: rgba(5, 95, 194, 1);
flex: auto;
font-weight: bold;
font-size: 16px;
}
.name {
max-width: 384px;
.time-line-icon {
width: 20px;
height: 20px;
margin-right: 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 24px;
margin-top: 5px;
border-radius: 50%;
font-size: 0px;
}
.time-line-name {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
line-height: 24px;
font-size: 15px;
cursor: pointer;
}
}
.info {
text-align: right;
margin-left: 10px;
height: 24px;
margin-top: 5px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
:deep(.el-timeline) {
padding: 8px 0px 0px 25px !important;
}
:deep(.el-timeline-item__node) {
border: 4px solid var(--color-main-active) !important;
background-color: #fff;
}
.item-content {
margin-left: 66px;
border-top: 1px solid rgba(234, 236, 238, 1);
padding-top: 3px;
color: rgba(95, 101, 108, 1);
:deep(.el-timeline-item) {
padding-bottom: 12px !important;
}
:deep(.el-timeline-item__timestamp) {
color: var(--color-main-active) !important;
font-family: Microsoft YaHei !important;
font-size: 16px !important;
font-weight: 600 !important;
padding-top: 0px !important;
}
.timeline-content {
color: var(--text-primary-65-color);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
}
line-height: 26px;
}
}
}
......
<template>
<div class="decree-overview-wrap">
<div class="main">
<div class="left">
<div
class="sider-btn"
:class="{ siderBtnActive: siderBtnActive === item.name }"
@click="handleClickLeftSiderBtn(item)"
v-for="(item, index) in siderList"
:key="index"
>
<div class="btn-text">{{ item.name }}</div>
<div class="btn-icon">
<el-icon v-if="siderBtnActive === item.name" color="#fff"><CaretRight /></el-icon>
</div>
</div>
<SiderTabs :siderList="siderList" @clickSiderItem="handleClickLeftSiderBtn" />
</div>
<div class="main">
<router-view />
</div>
</div>
......@@ -23,6 +12,7 @@
import { ref, onMounted } from "vue";
import router from "@/router";
import { useRoute } from "vue-router";
import SiderTabs from "@/components/base/SiderTabs/index.vue";
const route = useRoute();
const decreeId = ref(route.query.id);
......@@ -30,20 +20,34 @@ const decreeId = ref(route.query.id);
const siderList = ref([
{
name: "政令简介",
active: true,
path: "/decreeLayout/overview/introduction"
},
{
name: "政令背景",
active: false,
path: "/decreeLayout/overview/background"
}
},
{
name: "政令举措",
active: false,
path: "/decreeLayout/overview/measures"
},
]);
const siderBtnActive = ref("政令简介");
const handleClickLeftSiderBtn = item => {
siderBtnActive.value = item.name;
const handleClickLeftSiderBtn = node => {
siderBtnActive.value = node.name;
siderList.value.forEach(item => {
if (item.name === node.name) {
item.active = true;
} else {
item.active = false;
}
});
router.push({
path: item.path,
path: node.path,
query: {
id: decreeId.value
}
......@@ -51,59 +55,34 @@ const handleClickLeftSiderBtn = item => {
};
onMounted(() => {
if (route.path === "/decreeLayout/overview/introduction") {
siderBtnActive.value = "政令简介";
siderList.value.forEach(item => {
if (item.path === route.path) {
siderBtnActive.value = item.name;
item.active = true;
} else {
siderBtnActive.value = "政令背景";
item.active = false;
}
});
});
</script>
<style lang="scss" scoped>
.decree-overview-wrap {
width: 100%;
height: 100%;
overflow: hidden;
background: rgba(247, 248, 249, 1);
display: flex;
justify-content: center;
.main {
position: relative;
width: 1600px;
margin: 0 auto;
.left {
position: absolute;
left: -160px;
top: 0px;
width: 160px;
padding-top: 8px;
.sider-btn {
margin-top: 20px;
margin-left: 20px;
width: 120px;
height: 32px;
display: flex;
border-radius: 16px;
cursor: pointer;
.btn-text {
margin-left: 28px;
width: 68px;
height: 32px;
line-height: 32px;
text-align: left;
box-sizing: border-box;
font-size: 16px;
font-family: Microsoft YaHei;
color: rgba(95, 101, 108, 1);
}
.btn-icon {
width: 22px;
padding-top: 9px;
padding: 0px 16px;
}
}
.siderBtnActive {
background: var(--color-main-active);
.btn-text {
color: #fff !important;
}
}
}
.main {
width: 1760px;
height: 901px;
}
}
</style>
\ No newline at end of file
<template>
<div class="page-box">
<div v-if="riskInfo.riskLevel" style="margin-bottom: 16px;">
<WarnningPane :warnningLevel="riskInfo.riskLevel" :warnningContent="riskInfo.content" />
</div>
<div class="introduction-wrap">
<div class="left">
<div class="box1">
......@@ -63,132 +67,31 @@
</div>
</div>
</div>
</AnalysisBox>
<!-- <div class="box-header">
<div class="header-left">
</div>
<div class="title">基本信息</div>
<div class="header-right">
<div class="icon">
<img src="../assets/icons/header-right-icon1.png" alt="" />
</div>
<div class="icon">
<img src="../assets/icons/header-right-icon2.png" alt="" />
</div>
</div>
</div>
<div class="box1-main">
<div class="box1-main-left" v-if="basicInfo.img">
<img :src="basicInfo.img" alt="" />
</div>
<div v-else class="box1-main-left-img-mock">
<img class="img-mock-badge-img" src="./assets/images/badge.png">
<p class="img-mock-badge-title">{{basicInfo.eName }}</p>
<p class="img-mock-badge-org">The White House</p>
</div>
<div class="box1-main-right">
<div class="item">
<div class="item-left">{{ "政令全称:" }}</div>
<div class="item-right">{{ basicInfo.name }}</div>
</div>
<div class="item">
<div class="item-left">{{ "英文全称:" }}</div>
<div class="item-right text" v-if="basicInfo.eName?.length < 60">
{{ basicInfo.eName }}
</div>
<el-popover v-else effect="dark" :width="500" :content="basicInfo.eName"
placement="top-start">
<template #reference>
<div class="item-right text">
{{ basicInfo.eName }}
</div>
</template>
</el-popover>
</div>
<div class="item">
<div class="item-left">{{ "相关领域:" }}</div>
<div class="item-right tag-box">
<div class="tag" v-for="(area, index) in basicInfo.areaList" :key="index">
{{ area.industryName }}
</div>
</div>
</div>
<div class="item">
<div class="item-left">{{ "签署时间:" }}</div>
<div class="item-right text">{{ basicInfo.signTime }}</div>
</div>
<div class="item">
<div class="item-left">{{ "发布机构:" }}</div>
<div class="item-right text">
{{ basicInfo.proposeOrgName }}
</div>
</div>
<div class="item">
<div class="item-left">{{ "政令编号:" }}</div>
<div class="item-right text">
{{ basicInfo.bh }}
</div>
</div>
<div class="item">
<div class="item-left">{{ "执行期限:" }}</div>
<div class="item-right text">
{{ basicInfo.deadline + " 天" }}
</div>
</div>
</div>
</div> -->
</div>
<div class="box2">
<AnalysisBox title="主要指令" :showAllBtn="false">
<AnalysisBox title="相关事件" :showAllBtn="false">
<div class="box2-main">
<el-empty v-if="!curmajorList.length" description="暂无数据" :image-size="100" />
<div class="box2-item" v-for="(item, index) in curmajorList" :key="index">
<div class="id">{{ index + 1 }}</div>
<div class="title">{{ item.content }}</div>
<!-- <div class="open">
<img src="./assets/images/open-icon.png" alt="" />
</div> -->
</div>
<el-empty v-if="!relatedData.length" description="暂无数据" :image-size="100" />
<div class="box2-item" v-for="(item, index) in relatedData" :key="index">
<div class="item-left">
<img :src="item.imageUrl || DefaultIconNews" alt="" />
</div>
<div class="item-center">
<div class="bubble-header" @click="handleClickToNewsDetail(item)">
<span class="name">{{ item.sjbt }}</span>
<span class="meta">{{ item.sjsj }} · {{ item.source }}</span>
</div>
<div class="content">{{ item.sjnr }}</div>
<!-- <el-popover effect="dark" :width="1000" :content="item.content" placement="top-start">
<template #reference>
<div class="content">{{ item.content }}</div>
</template>
</el-popover> -->
</div>
<div class="box2-footer">
<div class="box2-footer-left">{{ `共 ${majorListNum} 项` }}</div>
<div class="box2-footer-right">
<el-pagination @current-change="handleCurrentChange" :page-size="5" background layout="prev, pager, next"
:total="majorListNum" />
</div>
</div>
</AnalysisBox>
<!-- <div class="box-header">
<div class="header-left"></div>
<div class="title">主要指令</div>
<div class="header-right">
<div class="icon">
<img src="../assets/icons/header-right-icon1.png" alt="" />
</div>
<div class="icon">
<img src="../assets/icons/header-right-icon2.png" alt="" />
</div>
</div>
</div>
<div class="box2-main">
<el-empty v-if="!curmajorList.length" description="暂无数据" :image-size="100" />
<div class="box2-item" v-for="(item, index) in curmajorList" :key="index">
<div class="id">{{ index + 1 }}</div>
<div class="title">{{ item.content }}</div>
</div>
</div>
<div class="box2-footer">
<div class="box2-footer-left">{{ `共 ${majorListNum} 项` }}</div>
<div class="box2-footer-right">
<el-pagination @current-change="handleCurrentChange" :page-size="5" background layout="prev, pager, next"
:total="majorListNum" />
</div>
</div> -->
</div>
</div>
<div class="right">
......@@ -197,18 +100,12 @@
<div class="box3-top">
<div class="box3-top-top" @click="handleToInstitution(box3TopTopData)">
<div class="left">
<img :src="box3TopTopData.logo ? box3TopTopData.logo : DefaultIcon2" alt="" />
<img :src="box3TopTopData?.logo || DefaultIcon2" alt="" />
</div>
<div class="right">
<div class="name">{{ box3TopTopData.name + " >" }}</div>
<div class="ename">{{ box3TopTopData.eName }}</div>
</div>
<!-- <div class="more">
<div class="text">{{ "查看官网" }}</div>
<div class="icon">
<img src="./assets/images/open-icon.png" alt="" />
</div>
</div> -->
</div>
<div class="box3-top-bottom">
<div class="box3-top-bottom-header">
......@@ -223,7 +120,7 @@
<img :src="item.avatar ? item.avatar : DefaultIcon1" alt="" />
</div>
<div class="box3-top-bottom-item-right">
<div class="name">{{ item.name }}</div>
<div class="name" @click="handleClickUser(item)">{{ item.name }}</div>
<div class="position">{{ item.job }}</div>
</div>
</div>
......@@ -239,101 +136,58 @@
</div>
<div class="box3-bottom-main">
<el-timeline style="max-width: 500px">
<el-timeline-item :timestamp="item.newsDate" placement="top"
v-for="(item, index) in eventList?.slice(0, 3)" :key="index">
<div class="timeline-content">
{{ item.newsContent }}
</div>
<el-timeline-item :timestamp="item.postDate" placement="top" v-for="(item, index) in eventList?.slice(0, 3)" :key="index">
<div class="timeline-content">{{ item.describe }}</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</AnalysisBox>
<!-- <div class="box-header">
<div class="header-left"></div>
<div class="title">发布机构</div>
<div class="header-right">
<div class="icon">
<img src="../assets/icons/header-right-icon1.png" alt="" />
</div>
<div class="icon">
<img src="../assets/icons/header-right-icon2.png" alt="" />
</div>
</div>
</div>
<div class="box3-top">
<div class="box3-top-top" @click="handleToInstitution(box3TopTopData)">
<div class="left">
<img :src="box3TopTopData.logo ? box3TopTopData.logo : DefaultIcon2" alt="" />
</div>
<div class="right">
<div class="name">{{ box3TopTopData.name + " >" }}</div>
<div class="ename">{{ box3TopTopData.eName }}</div>
</div>
</div>
<div class="box3-top-bottom">
<div class="box3-top-bottom-header">
<div class="icon">
<img src="./assets/images/box3-icon1.png" alt="" />
</div>
<div class="text">{{ "关键人物" }}</div>
</div>
<div class="box3-top-bottom-main">
<div class="box3-top-bottom-item" v-for="(item, index) in box3TopBottomData" :key="index">
<div class="box3-top-bottom-item-left">
<img :src="item.avatar ? item.avatar : DefaultIcon1" alt="" />
</div>
<div class="box3-top-bottom-item-right">
<div class="name">{{ item.name }}</div>
<div class="position">{{ item.job }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="box3-bottom">
<div class="box3-bottom-header">
<div class="header-icon">
<img src="./assets/images/box3-bottom-header-icon.png" alt="" />
</div>
<div class="header-title">{{ "机构动态" }}</div>
</div>
<div class="box3-bottom-main">
<el-timeline style="max-width: 500px">
<el-timeline-item :timestamp="item.newsDate" placement="top"
v-for="(item, index) in eventList?.slice(0, 3)" :key="index">
<div class="timeline-content">
{{ item.newsContent }}
</div>
</el-timeline-item>
</el-timeline>
</div>
</div> -->
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import router from "@/router";
import box1Img from "./assets/images/box1-img.png";
import Box3Logo from "./assets/images/box3-img.png";
import WarnningPane from '@/components/base/WarningPane/index.vue'
import {
getDecreeBasicInfo,
getDecreeMainContent,
getDecreeOrganization,
getDecreeRiskSignal,
getDecreeIssueOrganization
} from "@/api/decree/introduction";
import { getDecreeRelatedEvent } from "@/api/decree/background";
import DefaultIcon1 from "@/assets/icons/default-icon1.png";
import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import { ElMessage } from "element-plus";
import DefaultIconNews from "@/assets/icons/default-icon-news.png";
const route = useRoute();
const decreeId = ref(route.query.id);
// 风险警告
const riskInfo = ref({
// riskLevel: "特别重大风险",
// content: "政令核心意图在于通过税收优惠吸引制造业回流美国,并在关键科技领域对中国进行遏制,限制中国获取先进技术、资本和市场渠道,从而延缓中国科技产业的发展速度。给半导体、新能源、人工智能等相关科技行业带来不小的短期压力。"
})
const onRiskSignalData = async () => {
try {
const res = await getDecreeRiskSignal({id: decreeId.value});
console.log("风险警告", res);
if (res.code === 200 && res.data) {
riskInfo.value = res.data;
} else {
riskInfo.value = { riskLevel: "", content: "" };
}
} catch (error) {
riskInfo.value = { riskLevel: "", content: "" };
console.error("获取风险警告数据失败:", error);
}
};
// 基本信息
const basicInfo = ref({
img: "",
......@@ -345,13 +199,9 @@ const basicInfo = ref({
bh: "",
deadline: ""
});
const handleGetBasicInfo = async () => {
const params = {
id: decreeId.value
};
try {
const res = await getDecreeBasicInfo(params);
const res = await getDecreeBasicInfo({id: decreeId.value});
console.log("基本信息", res);
if (res.code === 200 && res.data) {
basicInfo.value.img = res.data.picture;
......@@ -364,74 +214,44 @@ const handleGetBasicInfo = async () => {
basicInfo.value.proposeOrgName = res.data.proposeOrgName;
}
} catch (error) {
console.error("基本信息error", error);
console.error("获取基本信息数据失败", error);
}
};
handleGetBasicInfo();
// 主要指令
const majorList = ref([
// {
// id: 1,
// content: '要求商务部在90天内建立"全栈式"美国AI出口机制。'
// }
]);
const currentPage = ref(1);
const pageSize = ref(5);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
};
const curmajorList = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return majorList.value.slice(startIndex, endIndex);
});
const majorListNum = ref(0);
const handleMajorList = async () => {
const params = {
currentPage: 0,
pageSize: 999999,
id: decreeId.value
};
// 相关事件
const relatedData = ref([]);
const handleGetRelateEvents = async () => {
try {
const res = await getDecreeMainContent(params);
console.log("主要指令", res);
const res = await getDecreeRelatedEvent({id: decreeId.value});
console.log("相关事件", res);
if (res.code === 200 && res.data) {
majorList.value = res.data.content;
majorListNum.value = res.data.numberOfElements;
relatedData.value = res.data;
} else {
majorList.value = [];
majorListNum.value = 0;
relatedData.value = [];
}
} catch (error) {
relatedData.value = [];
console.error("获取相关事件数据失败", error);
}
} catch (error) { }
};
handleMajorList();
const handleClickToNewsDetail = news => {
const route = router.resolve({
path: "/newsAnalysis",
query: {
newsId: news.id
}
});
window.open(route.href, "_blank");
};
// 发布机构
const box3TopTopData = ref({
id: "",
logo: "",
name: "",
eName: ""
});
const box3TopBottomData = ref([]);
// 跳转行政机构主页
const handleToInstitution = item => {
const curRoute = router.resolve({
path: "/institution",
query: {
id: item.id
}
});
window.open(curRoute.href, "_blank");
};
// 机构动态
const eventList = ref([
// {
......@@ -447,134 +267,71 @@ const eventList = ref([
// title: "美商务部进一步收紧对华先进半导体出口管制,将更多中国实体列入“实体清单”。限制14纳米及以下先进芯片、DRAM等对华出口"
// }
]);
const box3TopBottomData = ref([]);
const handleGetOrgnization = async () => {
const params = {
id: decreeId.value
};
try {
const res = await getDecreeIssueOrganization(params);
console.log("发布机构", res);
const res = await getDecreeIssueOrganization({id: decreeId.value});
console.log("发布机构/关键人物/机构动态", res);
if (res.code === 200 && res.data) {
box3TopTopData.value.id = res.data.id;
box3TopTopData.value.logo = res.data.image;
box3TopTopData.value.name = res.data.name;
box3TopTopData.value.eName = res.data.ename;
eventList.value = res.data.newsList;
let { id, image, name, ename } = res.data
Object.assign(box3TopTopData.value, { id, logo: image, name, eName: ename });
// eventList.value = res.data.newsList;
eventList.value = res.data.orderInfoList;
box3TopBottomData.value = res.data.personList;
}
} catch (error) {
console.error("执行机构error", error);
box3TopTopData.value = {
id: "",
logo: "",
name: "",
eName: ""
};
box3TopTopData.value = { id: "", logo: "", name: "", eName: "" };
eventList.value = [];
box3TopBottomData.value = [];
console.error("获取发布机构数据失败", error);
}
};
// 跳转行政机构主页
const handleToInstitution = item => {
const curRoute = router.resolve({
path: "/institution",
query: { id: item.id }
});
window.open(curRoute.href, "_blank");
};
// 跳转人员详情
const handleClickUser = item => {
window.sessionStorage.setItem('curTabName', item.name)
const routeData = router.resolve({
path: "/characterPage",
query: {
personId: item.id
}
});
window.open(routeData.href, "_blank");
};
onMounted(() => {
onRiskSignalData()
handleGetRelateEvents();
handleGetOrgnization();
});
</script>
<style lang="scss" scoped>
.introduction-wrap {
display: flex;
.box-header {
height: 56px;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
line-height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
}
.header-btn-box {
position: absolute;
z-index: 9999;
width: 325px;
height: 64px;
top: 14px;
right: 82px;
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
gap: 5px 8px;
white-space: nowrap;
overflow: hidden;
overflow-y: auto;
padding-right: 5px;
.btn {
min-width: min-content;
height: 28px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 28px;
cursor: pointer;
}
.btnActive {
border: 1px solid var(--color-main-active);
color: var(--color-main-active);
background: rgba(231, 243, 255, 1);
}
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
height: 28px;
.page-box {
width: 1600px;
display: flex;
gap: 4px;
.icon {
width: 28px;
height: 28px;
flex-direction: column;
padding: 16px 0;
}
img {
.introduction-wrap {
width: 100%;
height: 100%;
}
}
}
}
display: flex;
.left {
width: 1064px;
display: flex;
flex-direction: column;
flex: auto;
margin-right: 16px;
.box1 {
margin-top: 16px;
width: 1064px;
height: 414px;
.box1-main {
......@@ -675,63 +432,83 @@ onMounted(() => {
.box2 {
margin-top: 16px;
width: 1064px;
height: 415px;
height: 420px;
.box2-main {
margin-left: 22px;
height: 280px;
margin-top: 3px;
margin-left: 31px;
height: 100%;
width: 1004px;
overflow: hidden;
overflow-y: auto;
.box2-item {
width: 1015px;
// height: 48px;
margin-bottom: 8px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
display: flex;
height: 60px;
width: 100%;
margin-bottom: 2px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin-top: 10px;
.item-left {
width: 64px;
height: 48px;
border-radius: 2px;
background: rgba(255, 255, 255, 1);
img {
width: 100%;
height: 100%;
}
}
.item-center {
flex: auto;
width: 20px;
margin-left: 14px;
.bubble-header {
display: flex;
align-items: center;
padding: 12px 0;
cursor: pointer;
&:nth-child(2n-1) {
background: rgba(247, 248, 249, 1);
.name {
flex: auto;
width: 20px;
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
text-decoration: underline;
color: var(--color-main-active);
}
.id {
margin-left: 15px;
width: 24px;
height: 24px;
text-align: center;
line-height: 24px;
border-radius: 12px;
background: #e7f3ff;
color: #0a57a6;
}
.title {
width: 1020px;
line-height: 24px;
.meta {
margin-left: 10px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 700;
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
font-weight: 400;
letter-spacing: 0px;
white-space: nowrap;
}
}
.open {
width: 16px;
height: 16px;
margin-top: 16px;
img {
.content {
width: 100%;
height: 100%;
height: 30px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
......@@ -756,16 +533,12 @@ onMounted(() => {
.right {
width: 520px;
margin-left: 16px;
.box3 {
margin-top: 16px;
width: 520px;
height: 845px;
height: 100%;
.box3-top {
margin-top: 2px;
// height: 241px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
.box3-top-top {
......@@ -927,6 +700,7 @@ onMounted(() => {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.position {
......@@ -1023,81 +797,5 @@ onMounted(() => {
}
}
}
.report {
padding: 10px 150px;
position: absolute;
left: 0;
top: 0;
z-index: 999999;
width: 100%;
height: 100%;
background: #f7f8f9;
.report-close {
position: absolute;
top: 20px;
right: 230px;
width: 20px;
height: 20px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.report-header {
width: 100%;
height: 50px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 20px;
font-weight: 700;
line-height: 50px;
letter-spacing: 0px;
text-align: left;
padding-left: 30px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
}
.report-main {
display: flex;
height: calc(100% - 100px);
justify-content: space-between;
.left {
width: 800px;
.noContent {
height: 100px;
line-height: 100px;
text-align: center;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 20px;
font-weight: 400;
}
}
.right {
width: 800px;
.noContent {
height: 100px;
line-height: 100px;
text-align: center;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 20px;
font-weight: 400;
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="introduction-wrap">
<div class="left">
<div class="box1">
<AnalysisBox title="主要指令" :showAllBtn="false">
<div class="analysis-box">
<div class="analysis-top">
<el-select v-model="areaType" :empty-values="[null, undefined]" @change="onMainContentData()" style="width: 200px;">
<el-option label="全部领域" value="" />
<el-option v-for="item in areaList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<div style="flex: auto; width: 20px;"></div>
<!-- <el-switch v-model="isHighlight" />
<div style="margin-left: 6px;">高亮实体</div> -->
<div class="select-input">
<el-input v-model="commandWord" @keyup.enter="onMainContentData()" style="width: 100%; height: 100%;" :suffix-icon="Search" placeholder="指令搜索" />
</div>
</div>
<div class="analysis-content">
<!-- 遍历文档章节 -->
<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 class="section-header">
<div class="section-title">({{ simpleNumToChinese(index+1) }}) {{ section.content }}</div>
<div class="section-icon">
<img src="./assets/images/open-icon.png" alt="" />
</div>
</div>
<!-- 渲染一级列表 -->
<div class="numbered-list">
<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-word">{{ item.content }}</div>
<!-- 渲染二级列表 -->
<div v-if="item.slaver" class="sub-list">
<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-word">{{ subItem.content }}</div>
<!-- 渲染三级列表 -->
<div v-if="subItem.slaver" class="sub-sub-list">
<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-word">{{ subSubItem.content }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
<div class="right">
<div class="box3">
<AnalysisBox title="发布机构" :showAllBtn="false">
<div class="box3-top">
<div class="box3-top-top" @click="handleToInstitution(box3TopTopData)">
<div class="left">
<img :src="box3TopTopData.logo ? box3TopTopData.logo : DefaultIcon2" alt="" />
</div>
<div class="right">
<div class="name">{{ box3TopTopData.name + " >" }}</div>
<div class="ename">{{ box3TopTopData.eName }}</div>
</div>
</div>
<div class="box3-top-bottom">
<div class="box3-top-bottom-header">
<div class="icon">
<img src="./assets/images/box3-icon1.png" alt="" />
</div>
<div class="text">{{ "关键人物" }}</div>
</div>
<div class="box3-top-bottom-main">
<div class="box3-top-bottom-item" v-for="(item, index) in box3TopBottomData" :key="index">
<div class="box3-top-bottom-item-left">
<img :src="item.avatar ? item.avatar : DefaultIcon1" alt="" />
</div>
<div class="box3-top-bottom-item-right">
<div class="name" @click="handleClickUser(item)">{{ item.name }}</div>
<div class="position">{{ item.job }}</div>
</div>
</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
<div class="box4">
<AnalysisBox title="相关实体" :showAllBtn="false">
<el-empty v-if="!entityList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<div class="left-bottom-main">
<div v-for="(item, index) in entityList" :key="index" class="main-box">
<div class="icon">
<img style="width: 100%; height: 100%;" :src="item.imgUrl || defaultCom" alt="" />
</div>
<div class="name one-line-ellipsis">{{ item.name }}</div>
<div class="type">{{ item.entityType }}</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import router from "@/router";
import { Search } from '@element-plus/icons-vue'
import { getDecreeIssueOrganization } from "@/api/decree/introduction";
import { getDecreeRelatedEntity, getDecreeMainContent } from "@/api/decree/background";
import { getDecreehylyList } from "@/api/decree/home";
import DefaultIcon1 from "@/assets/icons/default-icon1.png";
import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import defaultCom from "@/views/coopRestriction/assets/images/default-icon2.png"
const route = useRoute();
// 科技领域
const areaType = ref("");
const areaList = ref([]);
const handleGetAreaList = async () => {
try {
const res = await getDecreehylyList();
console.log("行业领域列表", res);
if (res.code === 200 && res.data) {
areaList.value = res.data;
} else {
areaList.value = []
}
} catch (error) {
areaList.value = []
console.error("获取科技领域数据失败:", error);
}
};
// 主要指令
const isHighlight = ref(false);
const commandWord = ref("");
const contentList = ref([
// {
// content: "建立美国人工智能出口计划建立美国人工智能出口计划建立美国人工智能出口计划建立美国人工智能出口计划建立美国人工智能出口计划",
// slaver: [
// {
// content: '在本命令发布之日起 90 天内,商务部长应与国务卿及科学技术政策办公室(OSTP)主任协商,建立并实施美国人工智能出口计划(计划),以支持美国全栈人工智能出口软件包的开发和部署。'
// },
// {
// content: '商务部长应公开征集由行业主导的联盟提案,以纳入该计划。公开征集要求每项提案必须:',
// slaver: [
// {
// content: '包含一套全栈人工智能技术包,涵盖:',
// slaver: [
// {
// content: 'AI 优化的计算机硬件(如芯片、服务器和加速器)、数据中心存储、云服务和网络,以及这些设备是否以及在多大程度上在美国制造的描述;'
// },
// {
// content: '数据管道和标签系统;'
// },
// {
// content: '人工智能模型与系统;'
// },
// {
// content: '采取措施保障人工智能模型和系统的安全性和网络安全;'
// },
// {
// content: '针对特定用例的人工智能应用(如软件工程、教育、医疗保健、农业或交通运输);'
// }
// ]
// },
// {
// content: '确定出口参与者的具体目标国家或区域集团;'
// },
// {
// content: '描述一个业务和运营模型,以在高层次上说明哪些实体将建设、拥有和运营数据中心及相关基础设施;'
// },
// {
// content: '联邦激励和支持机制请求的细节;'
// },
// {
// content: '遵守所有相关的美国出口管制制度、出境投资法规和终端用户政策,包括美国法典第 50 编第 58 章及商务部工业与安全局的相关指导。'
// }
// ]
// },
// {
// content: '商务部要求提案须在公开征集后不超过 90 天内提交,并应滚动考虑提案纳入项目。'
// },
// {
// content: '商务部长应与国务卿、国防部长、能源部长及 OSTP 主任协商,评估提交的纳入计划提案。商务部长与国务卿、国防部长、能源部长及 OSTP 主任协商后选定的提案,将被指定为优先 AI 出口包,并通过优先访问本命令第 4 节指定的工具予以支持,符合适用法律。'
// }
// ]
// },
// {
// content: "动员联邦融资工具",
// slaver: [
// {
// content: '经济外交行动小组(EDAG),于 2024 年 6 月 21 日总统备忘录中成立,由国务卿主持,并与商务部长和美国贸易代表协商,并根据 2019 年《通过外交倡导美国企业法案》(公共法 116-94 J 部分第七章)第 708 条(CABDA)所述,应协调联邦融资工具的动员,以支持优先的人工智能出口方案。'
// },
// {
// content: '我将根据 CABDA α 第 708 (c) (3) 条授权小企业管理局局长和 OSTP 主任任命各自执行部门和机构的高级官员担任 EDAG 。'
// }
// ]
// }
]);
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 () => {
try {
const res = await getDecreeMainContent({
id: route.query.id,
keyword: commandWord.value,
domainId: areaType.value
});
console.log("主要指令", res);
if (res && res.code === 200) {
contentList.value = res.data;
} else {
contentList.value = []
}
} catch (error) {
contentList.value = []
console.error("获取主要指令数据失败:", error);
}
};
// 数字转中文(支持 0-99 整数)
const simpleNumToChinese = (num) => {
// 1. 基础校验:只处理 0-99 的整数
if (!Number.isInteger(num) || num < 0 || num > 99) {
return '仅支持 0-99 之间的整数';
}
// 2. 定义基础字符
const singleChars = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
const tenChar = '十';
// 3. 核心转换逻辑
if (num < 10) {
// 0-9 直接返回对应字符
return singleChars[num];
} else if (num === 10) {
// 10 特殊处理
return tenChar;
} else if (num < 20) {
// 11-19:十 + 个位(如十一、十九)
return tenChar + singleChars[num - 10];
} else {
// 20-99:十位 + 十 + 个位(个位为0则省略,如二十、二十九)
const ten = Math.floor(num / 10); // 十位数字
const unit = num % 10; // 个位数字
return singleChars[ten] + tenChar + (unit === 0 ? '' : singleChars[unit]);
}
}
// 相关实体
const entityList = ref([]);
const onRelatedEntityData = async () => {
try {
const res = await getDecreeRelatedEntity({id: route.query.id});
console.log("相关实体", res);
if (res && res.code === 200) {
entityList.value = res.data;
} else {
entityList.value = []
}
} catch (error) {
entityList.value = []
console.error("获取相关实体数据失败:", error);
}
};
// 发布机构
const box3TopTopData = ref({
id: "",
logo: "",
name: "",
eName: ""
});
const box3TopBottomData = ref([]);
// 跳转行政机构主页
const handleToInstitution = item => {
const curRoute = router.resolve({
path: "/institution",
query: {
id: item.id
}
});
window.open(curRoute.href, "_blank");
};
// 跳转人员详情
const handleClickUser = item => {
window.sessionStorage.setItem('curTabName', item.name)
const routeData = router.resolve({
path: "/characterPage",
query: {
personId: item.id
}
});
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(() => {
handleGetAreaList();
onMainContentData();
handleGetOrgnization();
onRelatedEntityData();
});
</script>
<style lang="scss" scoped>
.introduction-wrap {
display: flex;
width: 1600px;
padding: 16px 0;
gap: 16px;
.left {
width: 20px;
flex: auto;
.box1 {
.analysis-box {
display: flex;
flex-direction: column;
.analysis-top {
font-family: var(--font-family-base);
padding: 0 20px;
color: var(--text-primary-65-color);
height: 60px;
display: flex;
align-items: center;
.select-input {
background-color: var(--el-fill-color-blank);
border-radius: var(--el-border-radius-base);
box-shadow: 0 0 0 1px var(--el-border-color) inset;
box-sizing: border-box;
margin-left: 20px;
width: 200px;
height: 32px;
}
}
}
.analysis-content {
:deep(.el-scrollbar__bar.is-vertical) {
right: 0px;
width: 4px;
background: transparent;
border-radius: 2px;
&>div {
background: #c5c7c9;
opacity: 1;
}
&>div:hover {
background: #505357;
}
}
.section {
padding: 0 20px;
.section-header {
display: flex;
padding: 12px 16px;
align-items: center;
background-color: rgba(247, 248, 249, 1);
border-radius: 2px;
.section-title {
font-size: 18px;
line-height: 30px;
font-weight: 600;
letter-spacing: 1px;
width: 20px;
flex: auto;
}
.section-icon {
width: 16px;
height: 16px;
margin-left: 16px;
img {
width: 100%;
height: 100%;
}
}
}
.numbered-list {
margin-bottom: 12px;
font-size: 16px;
line-height: 1.7;
color: #374151;
.list-item {
position: relative;
.list-item-dot {
position: absolute;
left: 50px;
top: 10px;
}
.list-item-word {
border-bottom: 1px solid rgba(234, 236, 238, 1);
padding: 10px 16px 10px 68px;
}
}
}
.sub-list {
.sub-item {
position: relative;
.sub-item-dot {
position: absolute;
left: 66px;
top: 10px;
}
.sub-item-word {
border-bottom: 1px solid rgba(234, 236, 238, 1);
padding: 10px 16px 10px 90px;
}
}
}
.sub-sub-list {
border-bottom: 1px solid rgba(234, 236, 238, 1);
.sub-sub-item {
position: relative;
.sub-sub-item-dot {
position: absolute;
left: 82px;
top: 6px;
}
.sub-sub-item-word {
padding: 6px 16px 6px 100px;
}
}
}
}
}
}
}
.right {
width: 520px;
display: flex;
flex-direction: column;
gap: 16px;
.box3 {
.box3-top {
margin-top: 2px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
.box3-top-top {
width: 473px;
height: 88px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 4px;
background: rgba(247, 248, 249, 1);
display: flex;
align-items: center;
margin: 0 auto;
position: relative;
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 {
width: 64px;
height: 64px;
margin-left: 17px;
img {
width: 100%;
height: 100%;
}
}
.right {
width: 370px;
margin-left: 15px;
.name {
height: 26px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.ename {
margin-top: 6px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
.box3-top-bottom {
width: 473px;
height: 193px;
margin: 0 auto;
.box3-top-bottom-header {
height: 40px;
display: flex;
padding-top: 14px;
box-sizing: border-box;
gap: 12px;
.icon {
margin-top: 5px;
width: 14px;
height: 14px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 100px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
}
.box3-top-bottom-main {
margin-top: 2px;
height: 130px;
overflow: hidden;
overflow-y: auto;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.box3-top-bottom-item {
margin-top: 12px;
height: 48px;
width: 200px;
display: flex;
justify-content: flex-start;
.box3-top-bottom-item-left {
width: 48px;
height: 48px;
border-radius: 24px;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
.box3-top-bottom-item-right {
margin-left: 8px;
width: 144px;
.name {
width: 144px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.position {
margin-top: 1px;
width: 144px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
}
.box3-bottom {
.box3-bottom-header {
height: 59px;
display: flex;
.header-icon {
margin-left: 22px;
margin-top: 27px;
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.header-title {
margin-left: 12px;
margin-top: 23px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
}
.box3-bottom-main {
width: 510px;
height: 440px;
overflow: hidden;
overflow-y: auto;
:deep(.el-timeline) {
padding: 8px 0px 0px 25px !important;
}
:deep(.el-timeline-item__node) {
border: 4px solid var(--color-main-active) !important;
background-color: #fff;
}
:deep(.el-timeline-item) {
padding-bottom: 12px !important;
}
:deep(.el-timeline-item__timestamp) {
color: var(--color-main-active) !important;
font-family: Microsoft YaHei !important;
font-size: 16px !important;
font-weight: 600 !important;
padding-top: 0px !important;
}
.timeline-content {
color: var(--text-primary-65-color);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 26px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.box4 {
.left-bottom-main {
padding: 20px;
.main-box {
height: 48px;
border-radius: 50px;
border: 1px solid rgb(234, 236, 238);
background-color: rgba(247, 248, 249, 1);
margin-bottom: 12px;
padding: 0 15px;
display: flex;
align-items: center;
cursor: pointer;
.icon {
width: 24px;
height: 24px;
font-size: 0px;
}
.name {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
flex: auto;
width: 20px;
margin: 0 10px;
}
.type {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
}
}
}
}
}
}
</style>
\ No newline at end of file
......@@ -2,22 +2,19 @@
<div class="layout-container">
<!-- 导航菜单 -->
<div class="layout-main">
<div class="layout-main-box">
<div class="layout-main-header">
<div class="layout-main-header-left-box">
<div class="left-box-top">
<div class="icon">
<img :src="summaryInfo.imageUrl" alt="" />
</div>
<div class="info">
<div class="info-box1">{{ summaryInfo.name }}</div>
<div class="info-box1 one-line-ellipsis">{{ summaryInfo.name }}</div>
<div class="info-box2">
<div class="info-box2-item item1">{{ summaryInfo.order }}</div>
<div class="info-box2-item">{{ summaryInfo.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>
</div>
<div class="info-box2-item one-line-ellipsis">{{ summaryInfo.ename }}</div>
</div>
</div>
<div class="layout-main-header-right-box">
......@@ -27,22 +24,48 @@
</div>
</div>
</div>
</div>
<div class="layout-main-center">
<div class="report-box">
<div class="report-header">
{{ "政令原文" }}
<div class="report-title">政令原文</div>
<!-- <el-switch v-model="isHighlight" />
<div style="margin-left: 6px; margin-right: 10px;">高亮实体</div> -->
<el-switch v-model="isTranslate" />
<div style="margin-left: 6px;">原文显示</div>
<!-- <div class="btn" @click="handleDownload">
<el-icon><Document /></el-icon>
<div class="text">下载</div>
</div> -->
<div class="btn" @click="handleFindWord('open')">
<el-icon><Search /></el-icon>
<div class="text">查找</div>
</div>
<div class="find-word-box" v-if="findWordBox">
<div class="find-word-input">
<el-input v-model="findWordTxt" placeholder="查找原文内容" @input="handleUpdateWord()" />
</div>
<div class="find-word-limit">{{ findWordNum }}/{{ findWordMax }}</div>
<div class="find-word-icon" @click="handleFindWord('last')">
<el-icon><ArrowUp /></el-icon>
</div>
<div class="find-word-icon" @click="handleFindWord('next')">
<el-icon><ArrowDown /></el-icon>
</div>
<div class="find-word-icon" @click="handleFindWord('close')">
<el-icon><Close /></el-icon>
</div>
</div>
</div>
<div class="report-main">
<div v-if="!reportData.length" class="noContent">{{ "暂无数据" }}</div>
<template v-else>
<div v-for="(item, index) in reportData" :key="index" class="content-row">
<!-- 左侧:英文 -->
<div class="content-en">{{ item.contentEn }}</div>
<el-scrollbar height="100%" v-else>
<div v-for="(item, index) in reportData" :key="index" :class="['content-row', {'high-light':isHighlight}]">
<!-- 右侧:中文 -->
<div class="content-cn">{{ item.content }}</div>
</div>
</template>
<div :class="['content-cn', {'translate-cn':!isTranslate}]" v-html="item.content"></div>
<!-- 左侧:英文 -->
<div class="content-en" v-html="item.contentEn" v-if="isTranslate"></div>
</div>
</el-scrollbar>
</div>
</div>
</div>
......@@ -50,18 +73,131 @@
</template>
<script setup>
import { ref, onMounted } from "vue";
import { ref, onMounted, nextTick } from "vue";
import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { debounce } from "lodash";
import { getDecreeSummary } from "@/api/decree/introduction";
import { getDecreeReport } from "@/api/decree/introduction";
const route = useRoute();
// 修改为数组存储分段数据
const reportData = ref([]);
const summaryInfo = ref({});
// 政令原文操作
const isHighlight = ref(false);
const isTranslate = ref(true);
const findWordTxt = ref("")
const findWordBox = ref(false);
const findWordNum = ref(0);
const findWordMax = ref(0);
const handleDownload = async () => {
if (summaryInfo.value?.url) {
try {
const response = await fetch(summaryInfo.value.url, {
method: 'GET',
headers: { 'Content-Type': 'application/pdf' },
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const blob = await response.blob();
// 创建 Blob URL
const blobUrl = window.URL.createObjectURL(blob);
// 创建隐藏的下载链接
const link = document.createElement('a');
link.href = blobUrl;
link.download = `${summaryInfo.value.name}.pdf`;
// 添加到文档中并点击
document.body.appendChild(link);
link.click();
// 清理
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
} catch (error) {
console.error('下载失败:', error);
// 可以在这里提示用户下载失败
alert('PDF 下载失败,请稍后重试');
}
} else {
ElMessage.warning("暂无下载链接!");
}
}
const handleHighlight = () => {
let spans = document.querySelectorAll(`span.highlight`);
spans.forEach((span, index) => {
if (index+1 === findWordNum.value) {
// 平滑滚动 behavior: 'smooth'
span.scrollIntoView({})
span.style.backgroundColor = "#ff9632";
} else {
span.style.backgroundColor = "#ffff00";
}
})
}
const handleFindWord = (event) => {
switch (event) {
case "open":
findWordBox.value = true;
break;
case "last":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value==1 ? findWordMax.value : findWordNum.value-1;
handleHighlight()
}
break;
case "next":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value==findWordMax.value ? 1 : findWordNum.value+1;
handleHighlight()
}
break;
case "close":
findWordBox.value = false;
findWordTxt.value = "";
handleUpdateWord()
break;
}
}
const handleUpdateWord = debounce(() => {
console.log("更新查找词", findWordTxt.value);
findWordNum.value = 0;
findWordMax.value = 0;
if (findWordTxt.value) {
originData.forEach((item, index) => {
if (item.content) {
reportData.value[index].content = highlightText(item.content, findWordTxt.value);
}
if (isTranslate.value && item.contentEn) {
reportData.value[index].contentEn = highlightText(item.contentEn, findWordTxt.value);
}
});
if (findWordMax.value > 0) {
nextTick(() => { handleFindWord('next') })
}
} else {
originData.forEach((item, index) => {
reportData.value[index].content = item.content;
reportData.value[index].contentEn = item.contentEn;
});
}
}, 300)
const highlightText = (text, searchTerm) => {
const escapedTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return text.replace(new RegExp(escapedTerm, 'g'), (match) => {
findWordMax.value++;
return `<span class="highlight">${match}</span>`;
});
}
// 获取全局信息
const summaryInfo = ref({});
const handleGetSummary = async () => {
const params = {
id: route.query.id
......@@ -76,17 +212,24 @@ const handleGetSummary = async () => {
};
// 获取报告原文 - 修改为获取分段数组
const reportData = ref([]);
let originData = [];
const handleGetReport = async () => {
const params = {
id: route.query.id
};
try {
const res = await getDecreeReport(params);
const res = await getDecreeReport({id: route.query.id});
console.log("报告原文", res);
if (res.code === 200 && res.data) {
// 假设后端返回的是数组格式,如果返回的是对象包含数组,请改为 res.data.list
reportData.value = res.data || [];
originData = [];
let num = Math.max(res.data.content.length, res.data.contentEn.length)
for (let i = 0; i < num; i++) {
let obj = {
content: res.data.content[i] || "",
contentEn: res.data.contentEn[i] || "",
num: i + 1,
}
originData.push(obj);
}
reportData.value = JSON.parse(JSON.stringify(originData));
}
} catch (error) { }
};
......@@ -98,42 +241,39 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
.high-light {
:deep(span.highlight) {
// color: #055FC2;
background-color: #ffff00;
}
}
.layout-container {
width: 100%;
height: 100%;
overflow: hidden;
overflow-y: auto;
background-color: #F7F8F9;
.layout-main {
width: 100%;
.layout-main-header {
height: 120px;
background: rgba(255, 255, 255, 1);
height: 100%;
display: flex;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 99999999;
box-sizing: border-box;
flex-direction: column;
align-items: center;
.layout-main-box {
padding: 16px 0;
width: 100%;
border-bottom: 1px solid rgba(234, 236, 238, 1);
border-top: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.layout-main-header-left-box {
width: 1100px;
margin-left: 160px;
margin-top: 13px;
.left-box-top {
height: 64px;
}
.layout-main-header {
width: 1600px;
display: flex;
align-items: center;
margin: 0 auto;
.icon {
width: 122px;
height: 64px;
border-radius: 4px;
width: 64px;
height: 40px;
overflow: hidden;
img {
......@@ -143,11 +283,12 @@ onMounted(() => {
}
.info {
width: 700px;
margin-left: 9px;
margin-left: 10px;
margin-right: 40px;
width: 20px;
flex: auto;
.info-box1 {
width: 700px;
width: 100%;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
......@@ -156,9 +297,6 @@ onMounted(() => {
letter-spacing: 0px;
text-align: left;
margin-top: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.info-box2 {
......@@ -173,89 +311,20 @@ onMounted(() => {
letter-spacing: 0px;
text-align: left;
display: flex;
margin-left: -10px;
.info-box2-item {
padding: 0 10px;
}
.item1 {
width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item2 {
width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 10px;
}
.item3 {
width: 420px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.left-box-bottom {
display: flex;
height: 40px;
margin-top: 21px;
.left-box-bottom-item {
display: flex;
margin-right: 32px;
margin-top: 3px;
height: 35px;
cursor: pointer;
.icon {
margin-top: 4px;
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.name {
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
margin-left: 3px;
}
.nameActive {
color: var(--color-main-active);
font-weight: 700;
}
}
.leftBoxBottomItemActive {
border-bottom: 3px solid var(--color-main-active);
.info-box2-item:first-child {
padding-left: 0px;
}
}
}
.layout-main-header-right-box {
width: 300px;
margin-right: 150px;
margin-top: 19px;
.right-box-top {
white-space: nowrap;
.time {
height: 24px;
line-height: 24px;
......@@ -280,117 +349,91 @@ onMounted(() => {
text-align: right;
}
}
}
}
.right-box-bottom {
margin-top: 24px;
text-align: right;
.layout-main-center {
width: 1600px;
background-color: white;
padding: 0 60px;
height: 20px;
flex: auto;
display: flex;
justify-content: flex-end;
gap: 8px;
.btn {
width: 120px;
height: 36px;
box-sizing: border-box;
flex-direction: column;
.report-header {
height: 80px;
display: flex;
align-items: center;
border-bottom: solid 1px rgba(234, 236, 238, 1);
margin: 0 20px 10px;
position: relative;
.find-word-box {
position: absolute;
top: -50px;
right: 0px;
width: 430px;
height: 60px;
border: 1px solid rgba(230, 231, 232, 1);
background-color: white;
border-radius: 6px;
background: rgba(255, 255, 255, 1);
display: flex;
justify-content: center;
gap: 8px;
align-items: center;
.find-word-input {
width: 20px;
flex: auto;
}
.find-word-limit {
border-right: solid 1px rgba(230, 231, 232, 1);
color: #5F656C;
padding-right: 16px;
}
.find-word-icon {
padding: 10px 12px;
margin: 0 2px;
cursor: pointer;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 64px;
height: 24px;
color: rgba(95, 101, 108, 1);
.report-title {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
font-size: 20px;
line-height: 20px;
font-weight: 700;
width: 20px;
flex: auto;
}
.btn-active {
width: 120px;
height: 36px;
.btn {
margin-left: 10px;
width: 88px;
height: 32px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 6px;
background: var(--color-main-active);
background: rgba(255, 255, 255, 1);
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
align-items: center;
cursor: pointer;
.icon-active {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text-active {
width: 64px;
.text {
height: 24px;
color: rgba(255, 255, 255, 1);
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-size: 14px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: center;
}
}
text-align: left;
}
}
}
.layout-main-center {
.report-box {
margin: 0 auto;
width: 1600px;
height: 926px;
background: rgba(248, 249, 250, 1);
.report-header {
height: 80px;
line-height: 80px;
padding-left: 69px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 20px;
font-weight: 700;
letter-spacing: 0px;
text-align: left;
}
.report-main {
width: 1600px;
margin-top: 24px;
background: #fff;
height: 20px;
flex: auto;
box-sizing: border-box;
padding: 24px 69px;
height: calc(100% - 100px);
overflow-y: auto; // 改为单一滚动容器,天然同步
padding-top: 10px;
// 滚动条样式
&::-webkit-scrollbar {
......@@ -421,9 +464,9 @@ onMounted(() => {
.content-row {
display: flex;
width: 100%;
padding: 0 20px;
min-height: 100px;
// border-bottom: 1px solid rgba(234, 236, 238, 1);
align-items: stretch; // 默认stretch,确保左右等高,头部对齐
gap: 80px;
&:last-child {
border-bottom: none;
......@@ -431,24 +474,38 @@ onMounted(() => {
.content-en,
.content-cn {
width: 800px;
padding: 24px 30px;
width: 50%;
flex: auto;
padding-bottom: 40px;
box-sizing: border-box;
font-size: 16px;
line-height: 1.8;
color: rgba(59, 65, 75, 1);
color: #3B414B;
font-family: Microsoft YaHei;
text-align: justify;
white-space: pre-wrap; // 保留换行格式
}
.content-en {
// border-right: 1px solid rgba(234, 236, 238, 1); // 中间分隔线
.translate-cn {
padding-bottom: 10px;
}
}
}
}
}
}
// 修改element-plus滚动条样式
:deep(.el-scrollbar__bar.is-vertical) {
right: 0px;
width: 4px;
background: transparent;
border-radius: 2px;
&>div {
background: #c5c7c9;
opacity: 1;
}
&>div:hover {
background: #505357;
}
}
</style>
\ No newline at end of file
......@@ -390,7 +390,7 @@
</template>
<script setup>
import { onMounted, ref } from "vue";
import { onMounted, ref, nextTick } from "vue";
import LeftBtn from "@/components/base/PageBtn/LeftBtn.vue";
import RightBtn from "@/components/base/PageBtn/RightBtn.vue";
......@@ -426,6 +426,29 @@ import {
import { getRiskSignal, getNews, getRemarks } from "@/api/common/index";
import { ElMessage } from "element-plus";
const handleToPosi = id => {
const element = document.getElementById(id);
if (element && containerRef.value) {
// // 1. 如果是从完整搜索框跳转,先强制切换状态稳定布局
// if (!isShow.value) {
// isShow.value = true;
// }
// 2. 使用 nextTick 等待 DOM 布局(如高度切换)完成后再进行坐标计算
nextTick(() => {
const containerRect = containerRef.value.getBoundingClientRect();
const elementRect = element.getBoundingClientRect();
// 使用 getBoundingClientRect 计算元素相对于容器顶部的绝对距离
const top = elementRect.top - containerRect.top + containerRef.value.scrollTop;
containerRef.value.scrollTo({
top: top,
behavior: "smooth"
});
});
}
};
const formatDate = (dateStr, dateType) => {
if (!dateStr) return "";
const date = new Date(dateStr);
......@@ -1052,6 +1075,7 @@ const pageSize = ref(10);
const currentPage = ref(1);
const totalDiscussNum = ref(0);
const handleCurrentChange = page => {
handleToPosi("position4")
currentPage.value = page;
handleGetSurveyList();
};
......
......@@ -118,9 +118,9 @@
</AnalysisBox>
</div>
</div>
<div class="graph-box" id="graphChart">
<!-- <div class="graph-box" id="graphChart">
</div>
</div> -->
</div>
</template>
<script setup>
......
......@@ -4,7 +4,7 @@
<back />
</el-icon> 返回</el-button>
<el-space style="width: 993px;" direction="vertical" alignment="flex-start">
<div style="margin-top: 50px; margin-bottom: 24px; margin-left: 24px;">
<div id="ref-news-list" style="margin-top: 50px; margin-bottom: 24px; margin-left: 24px;">
<common-text class="text-title-0-show" color="var(--text-primary-90-color)">{{
moduleName
}}</common-text>
......@@ -21,9 +21,12 @@
</el-space>
</el-radio-group>
<el-divider style="margin: 10px 0px;"></el-divider>
<div class="">
<news-list :news="NewsData" />
<div v-if="NewsData?.content?.length > 0">
<news-list :news="NewsData.content" />
</div>
<el-empty v-else></el-empty>
<el-pagination background layout="total, ->, prev, pager, next" :current-page="modulePage"
:total="NewsData?.totalElements ?? 0" v-on:current-change="onCurrentChange" />
</el-space>
</el-space>
</el-space>
......@@ -36,32 +39,44 @@ import '@/styles/container.scss';
import '@/styles/radio.scss';
import NewsList from "./NewsList.vue";
import { getAreaList, getHotNewsByArea } from "@/api/news/newsBrief";
import { ElSpace, ElDivider, ElRadioButton, ElRadioGroup, ElButton, ElIcon } from "element-plus";
import { ElSpace, ElDivider, ElRadioButton, ElRadioGroup, ElButton, ElIcon, ElEmpty, ElPagination } from "element-plus";
import CommonText from "@/components/base/texts/CommonText.vue";
import { useGotoNewsBrief } from "@/router/modules/news";
import { scrollToElement } from "@/router/common";
import { number } from "echarts";
const route = useRoute();
const gotoNewsBrief = useGotoNewsBrief();
const moduleId = ref(route.params.id);
const moduleName = ref(route.query.name ?? "");
const NewsData = ref([]);
const modulePage = ref(1);
const NewsData = ref({});
const AreaList = ref([]);
const currentAreaId = ref("");
onMounted(async () => {
console.log(route.query.name, moduleId.value, moduleName.value);
const { data: areaList } = await getAreaList();
AreaList.value = areaList ?? [];
await changeArea("")
await updateDate("")
});
async function changeArea(id) {
const onCurrentChange = async e => {
await updateDate(currentAreaId.value, e - 1)
scrollToElement("ref-news-list");
}
async function updateDate(id, page = 0) {
const { data } = await getHotNewsByArea({
moduleId: moduleId.value,
industryId: id ? id : null,
currentPage: page,
});
data?.forEach(item => {
data?.content?.forEach(item => {
item.newsImage = item.coverUrl ?? ""
})
NewsData.value = data ?? [];
modulePage.value = (data?.number ?? 0) + 1;
}
async function changeArea(id) {
await updateDate(id, 0)
}
</script>
......
......@@ -34,49 +34,34 @@
</color-prefix-title>
<div class="flex-fill"></div>
<el-switch v-model="isHightLightEntity" active-text="高亮实体" @change="handleHighlightEntity" />
<el-button v-if="hasTranslation" :type="isOpenTranslation ? 'primary' : ''" plain
<el-button v-if="textZns.length > 0" :type="isOpenTranslation ? 'primary' : ''" plain
@click="handleTranslation">
<color-svg :svg-url="TranslationSvg" color="var(--color-primary-100)" :size="18"
style="margin-right:10px"></color-svg>
译文
</el-button>
</div>
<div class="common-padding">
<div class="flex-display" style="align-items: center;">
<common-text class="text-title-3-bold" color="var(--text-primary-80-color)">{{ isOpenTranslation
? '中文' : '原文' }}</common-text>
<div class="flex-fill" style="margin: 0 10px;">
<el-divider></el-divider>
</div>
<el-button v-if="zhEnTexts.length > 6" @click="() => showMore = !showMore">
{{ showMore ? '收起' : '展开' }}
<el-icon>
<arrow-down v-if="showMore" />
<arrow-up v-else />
</el-icon>
</el-button>
</div>
<el-row :gutter="32">
<el-col :span="znEnColSpan"
v-for="(item, index) in showMore ? zhEnTexts : zhEnTexts.slice(0, 6)" :key="index">
<!-- <p class="p-news-content"> {{ item }}</p> -->
<intelligent-entity-text :text="item"
:entities="isHightLightEntity ? textEntities : []"></intelligent-entity-text>
</el-col>
</el-row>
<text-translate-pane class="common-padding-h" :texts-raw="textEns" :texts-translate="textZns"
:text-entities="textEntities" :is-open-translation="isOpenTranslation"
:is-highlight-entity="isHightLightEntity">
</text-translate-pane>
<div>
<img v-if="newsDetail.coverUrl" class="common-padding" :src="newsDetail.coverUrl" :width="320"
:height="240" />
</div>
</el-space>
<el-space direction="vertical" class="background-as-card relation-news-box" alignment="flex-start">
<el-space direction="vertical" class="background-as-card relation-news-box" fill>
<el-space style="margin-top: 10px;">
<color-prefix-title height="20px">
<div class="text-title-2-bold">相关新闻</div>
</color-prefix-title>
</el-space>
<el-space direction="vertical" fill class="common-padding">
<news-item v-for="item in relationNews" :key="item.newsId" :news="item" :img="item.newsImage"
<el-space v-if="relationNews?.length > 0" direction="vertical" fill class="common-padding">
<news-item-mini v-for="item in relationNews" :key="item.newsId" :news="item" :img="item.newsImage"
:title="item.newsTitle" :from="`${item.newsDate} · ${item.newsOrg}`"
@click="gotoNewsDetail(item.newsId)" />
</el-space>
<el-empty v-else style=""></el-empty>
</el-space>
</div>
</el-scrollbar>
......@@ -87,28 +72,26 @@ import '@/styles/container.scss';
import '@/styles/common.scss';
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import { ElSpace, ElImage, ElButton, ElIcon, ElScrollbar, ElRow, ElCol, ElDivider, ElSwitch } from "element-plus";
import { ElSpace, ElButton, ElScrollbar, ElSwitch, ElEmpty, ElImage } from "element-plus";
import CommonText from "@/components/base/texts/CommonText.vue";
import AreaTag from "@/components/base/AreaTag/index.vue";
import ColorPrefixTitle from '@/components/base/texts/ColorPrefixTitle.vue';
import { getRelationNews } from "@/api/news/newsDetail";
import NewsItem from "@/components/base/newsList/NewsItem.vue";
import NewsItemMini from "@/components/base/newsList/NewsItemMini.vue";
import ColorSvg from "@/components/base/images/ColorSvg.vue";
import TranslationSvg from './assets/images/翻译 1.svg';
import NewsLogo from './assets/images/组合 293.svg';
import { extractTextEntity } from "@/api/intelligent";
import { useGotoNewsDetail } from "@/router/modules/news";
import IntelligentEntityText from "@/components/base/texts/IntelligentEntityText.vue";
import TextTranslatePane from "@/components/base/texts/TextTranslatePane.vue";
const newsDetail = ref({});
const relationNews = ref([]);
const zhEnTexts = ref([]);
const znEnColSpan = ref(12);
const hasTranslation = ref(false);
const isOpenTranslation = ref(true);
const isHightLightEntity = ref(true);
const textEntities = ref([]);
const showMore = ref(false);
const textZns = ref([]);
const textEns = ref([]);
const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail();
......@@ -118,50 +101,26 @@ onMounted(async () => {
}
const { data: newsDetailData } = await getNewsDetail(params);
newsDetail.value = newsDetailData ?? {};
textZns.value = newsDetail.value?.contentZh?.split('\n') ?? [];
textEns.value = newsDetail.value?.content?.split('\n') ?? [];
const { data: relationNewsData } = await getRelationNews(params);
relationNews.value = relationNewsData ?? [];
console.log(relationNews.value)
updateText();
await handleHighlightEntity();
});
async function handleHighlightEntity() {
if (textEntities.value.length > 0) return
const { result: entityDataZh } = await extractTextEntity(newsDetail.value.contentZh);
const { result: entityDataZh } = await extractTextEntity(newsDetail.value?.contentZh ?? '');
textEntities.value = [...entityDataZh ?? []]
if (newsDetail.value.contentZh !== newsDetail.value.content) {
const { result: entityData } = await extractTextEntity(newsDetail.value.content);
const { result: entityData } = await extractTextEntity(newsDetail.value?.content ?? '');
textEntities.value = [...textEntities.value, ...entityData ?? []]
}
console.log(isHightLightEntity.value)
}
function handleTranslation() {
isOpenTranslation.value = !isOpenTranslation.value;
updateText();
}
function updateText() {
const enTexts = newsDetail.value.content?.split('\n')
const zhTexts = newsDetail.value.contentZh?.split('\n')
console.log(enTexts.length, zhTexts.length)
const tempZhEnTexts = []
hasTranslation.value = enTexts.length === zhTexts.length
if (hasTranslation.value && isOpenTranslation.value) {
for (let i = 0; i < enTexts.length; i++) {
tempZhEnTexts.push(zhTexts[i]);
tempZhEnTexts.push(enTexts[i]);
}
znEnColSpan.value = 12;
} else {
for (let i = 0; i < enTexts.length; i++) {
tempZhEnTexts.push(enTexts[i]);
}
znEnColSpan.value = 24;
}
zhEnTexts.value = tempZhEnTexts;
}
</script>
......
<template>
<el-space direction="vertical" fill class="full-width mouse-hover" v-for="(item, index) in props.news" :key="index">
<news-item2 :img="item.newsImage" :title="item.newsTitle" :from="`新闻来源:${item.newsOrg} 发表时间:${item.newsDate}`"
:aeraTags="item.industryList?.map(t => t.industryName)" @click="gotoNewsDetail(item.newsId)" />
<news-item-with-tag :img="item.newsImage" :title="item.newsTitle"
:from="`新闻来源:${item.newsOrg} 发表时间:${item.newsDate}`" :aeraTags="item.industryList?.map(t => t.industryName)"
@click="gotoNewsDetail(item.newsId)" />
<div class="divider-news-list"></div>
</el-space>
</template>
<script setup>
import NewsItem2 from '@/components/base/newsList/NewsItem2.vue';
import NewsItemWithTag from '@/components/base/newsList/NewsItemWithTag.vue';
import { useGotoNewsDetail } from '@/router/modules/news';
import { ElSpace } from 'element-plus';
......
......@@ -53,6 +53,7 @@ export default defineConfig({
'/api': {
target: 'http://8.140.26.4:9085/',
// target: 'http://192.168.0.5:28080/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
......@@ -86,7 +87,7 @@ export default defineConfig({
'/aiAnalysis': {
target: 'http://8.140.26.4:15000/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/temporarySearch/, '')
rewrite: (path) => path.replace(/^\/aiAnalysis/, '')
},
'^/bill(?:/|$)': {
target: 'http://8.140.26.4:9085/',
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论