提交 d2be76a4 authored 作者: yanpeng's avatar yanpeng

bugfix-6

上级 9688726b
......@@ -178,3 +178,11 @@ export function getVertexInfo(sanRecordId) {
export function getEdgeInfo(edgeId) {
return http.get(`/api/sanctionList/invFin/getEdgeInfo?edgeId=${edgeId}`);
}
/**
* 获取字典列表-实体类型
* url:/commonDict/entity/san-type
*/
export function getEntityTypeList() {
return http.get("/api/commonDict/entity/san-type");
}
......@@ -18,7 +18,8 @@ const financeRoutes = [
name: "sdnlistOverview",
component: () => import("@/views/finance/entityList/index.vue"),
meta: {
title: "SDN制裁清单概览"
title: "SDN制裁清单概览",
dynamicTitle: true
}
},
// V2.0单条制裁详情
......
......@@ -85,6 +85,7 @@
import { ref, onMounted, shallowReactive, shallowRef, watch } from "vue";
import CardCustom from "../../components/CardCustom.vue";
import Echarts from "@/components/Chart/index.vue";
import { ElMessage } from "element-plus";
import Hint from "./hint.vue";
import ButtonList from "@/components/buttonList/buttonList.vue";
import Fishbone from "./fishbone.vue";
......@@ -164,6 +165,10 @@ const handleEttClick = item => {
// }
// });
// window.open(route.href, "_blank");
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
gotoCompanyPages(item.id);
};
// 处理点击事件
......
......@@ -89,6 +89,7 @@
import { ref, shallowRef, onMounted, watch } from "vue";
import CardCustom from "../../components/CardCustom.vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import Echarts from "@/components/Chart/index.vue";
import { getBarChart, getLineChart } from "../../utils/charts";
import Hint from "./hint.vue";
......@@ -350,6 +351,10 @@ const handleOrgClick = item => {
// }
// });
// window.open(route.href, "_blank");
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
gotoCompanyPages(item.id);
};
</script>
......
......@@ -68,6 +68,7 @@
import { ref, shallowRef, onMounted, watch } from "vue";
import CardCustom from "../../components/CardCustom.vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import Echarts from "@/components/Chart/index.vue";
import { getBarChart, getLineChart } from "../../utils/charts";
import _ from "lodash";
......@@ -313,6 +314,10 @@ const handleOrgClick = item => {
// }
// });
// window.open(route.href, "_blank");
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
gotoCompanyPages(item.id);
};
</script>
......
......@@ -80,6 +80,7 @@
<script setup>
import { ref, defineProps, defineEmits, computed, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import defaultIcon from "@/assets/icons/default-icon1.png";
import { useGotoCompanyPages } from "@/router/modules/company";
const gotoCompanyPages = useGotoCompanyPages();
......@@ -155,6 +156,10 @@ const getTagStyle = tag => {
// 跳转公司详情页
const handleCompClick = item => {
console.log("item", item);
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
window.sessionStorage.setItem("curTabName", item.name || item.orgName);
gotoCompanyPages(item.id);
};
......
<template>
<div class="home-wrapper">
<div class="home-main" ref="homeMainRef">
<div class="home-top-bg"></div>
<div class="home-main-header">
<SearchContainer
style="margin-bottom: 0; margin-top: 48px; height: fit-content"
v-if="homeMainRef"
placeholder="搜索出口管制"
:containerRef="homeMainRef"
areaName="实体清单"
/>
<div class="home-main-header-footer-info">
<InfoCard
v-for="(item, index) in infoList"
:key="item.id"
:title="item.nameZh"
:subtitle="item.nameAbbr"
:description="item.description"
:quantity="item.postCount"
:unit="item.unit"
:color="infoListColor[index]"
@click="handleToEntityListNoId(item)"
/>
</div>
</div>
<el-row :gutter="16" style="width: 1600px; margin: 0 auto; height: 528px; margin-top: 64px">
<CustomTitle id="position1" title="最新动态" />
<el-col :span="16" style="padding-left: 0px">
<custom-container titleType="primary" title="最新出口管制政策" :titleIcon="houseIcon" height="450px">
<template #header-right>
<el-button type="primary" @click="handleToEntityList" link>
{{ "查看详情 >" }}
</el-button>
</template>
<template #default>
<div class="box1">
<div class="box1-left-arrow" @click="handleSwithCurPolicy('left')">
<div class="icon">
<img src="./assets/images/box1-left.png" alt="" />
</div>
</div>
<div class="box1-right-arrow" @click="handleSwithCurPolicy('right')">
<div class="icon">
<img src="./assets/images/box1-right.png" alt="" />
</div>
</div>
<el-carousel
ref="carouselRef"
height="370px"
:autoplay="false"
:interval="3000"
arrow="never"
indicator-position="none"
@change="handleCarouselChange"
>
<el-carousel-item v-for="(item, index) in entitiesDataInfoList" :key="item.id + index">
<div>
<div class="box1-top">
<div class="box1-top-title">{{ item.postDate }}——{{ item.name }}</div>
<div class="box1-top-content">
<div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 发布机构:</span>
<span class="box1-top-content-item-content">{{ item.postOrgName }}</span>
</div>
<div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 生效日期:</span>
<span class="box1-top-content-item-content">{{ item.postDate }}</span>
</div>
<div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 涉及领域:</span>
<AreaTag
v-for="(domainItem, index) in item.domains"
:key="index"
:tagName="domainItem"
/>
</div>
</div>
</div>
<div class="box1-bottom">
<div class="box1-bottom-sanTypeId" v-if="item.sanEntities?.length">
<div class="box1-bottom-title">· 涉及主要实体:</div>
<div class="box1-bottom-content">
<div
class="box1-bottom-content-item"
v-for="(ett, index) in item.sanEntities"
:key="index"
@click="handleEntityClick(ett)"
>
<el-image
v-if="ett.img"
class="box1-bottom-content-item-img"
:src="ett.img"
alt=""
></el-image>
<div v-else class="box1-bottom-content-item-imgUndefined">
{{
(ett.orgName || ett.orgNameZh)?.match(
/[\u4e00-\u9fa5a-zA-Z0-9]/
)?.[0]
}}
</div>
<div class="box1-bottom-content-item-txt">
{{ ett.orgName || ett.orgNameZh }}
</div>
</div>
</div>
</div>
<div class="box1-bottom-sanTypeId" v-if="item.sanItems?.length > 0">
<div class="box1-bottom-title">· 涉及管制物项:</div>
<div class="box1-bottom-content__wx">
<div
class="box1-bottom-content__wx-item"
v-for="(ett, index) in item.sanItems"
:key="index"
@click="handleWxClick(item)"
>
<div class="box1-bottom-content__wx-item-id">
{{ ett.id }}
</div>
<div class="box1-bottom-content__wx-item-txt">
{{ ett.name }}
</div>
</div>
</div>
</div>
</div>
<div class="box1-absolute" @click="handleToDataLibrary(item)">
<div class="box1-absolute-des">
<el-icon>
<Warning color="rgba(206, 79, 81, 1)" />
</el-icon>
<span>{{
item.sanTypeId == allSanTypeIds[0] ? "新增中国实体" : "新增物项"
}}</span>
</div>
<div class="box1-absolute-num">
{{ item.cnEntityCount }}{{ item.sanTypeId == allSanTypeIds[0] ? "家" : "项" }}
</div>
</div>
</div>
</el-carousel-item>
</el-carousel>
</div>
</template>
</custom-container>
</el-col>
<el-col :span="8" style="padding-right: 0px">
<RiskSignal
:list="warningList"
@item-click="handleToRiskSignalDetail"
@more-click="handleToMoreRiskSignal"
riskLevel="signalLevel"
postDate="signalTime"
name="signalTitle"
/>
</el-col>
</el-row>
<el-row :gutter="16" style="width: 1600px; margin: 0 auto; height: 50px; margin-top: 64px">
<CustomTitle id="position2" title="资讯要闻" />
</el-row>
<div class="center-center">
<NewsList
:newsList="newsList"
@item-click="handleNewsInfoClick"
@more-click="handleToMoreNews"
content="newsContent"
/>
<MessageBubble
:messageList="socialMediaList"
@person-click="handlePerClick"
imageUrl="avatar"
@more-click="handleToSocialDetail"
/>
</div>
<el-row :gutter="16" style="width: 1600px; margin: 0 auto; height: 510px; margin-top: 64px">
<CustomTitle id="position3" title="数据总览" />
<el-col :span="24" style="padding: 0">
<custom-container title="发布频次统计" :titleIcon="box3Icon" height="420px">
<template #default>
<div class="box3">
<div class="box3-content">
<div class="box3-content-title">实体清单发布频次统计</div>
<el-table
:data="entityListReleaseFreq"
stripe
style="width: 100%"
@row-click="handleEntityRowClick"
>
<el-table-column prop="year" label="年份" width="200" />
<el-table-column label="发布次数" width="300">
<template #default="scope">
<div style="display: flex; align-items: center; cursor: pointer">
<span style="margin-right: 10px; width: 40px">{{ scope.row.num }}</span>
<el-progress
:percentage="scope.row.percent * 100"
:show-text="false"
:status="getStatus(scope.row.percent)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="重点领域" width="220" align="center">
<template #default="scope">
<div
style="display: flex; justify-content: center; align-items: center; gap: 5px"
>
<AreaTag v-for="tag in scope.row.tags" :key="tag" :tagName="tag" />
</div>
</template>
</el-table-column>
</el-table>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">数据来源:美国商务部官网</div>
</div>
<div class="ai-pane">
<AiButton @mouseenter="handleShowAiPane('entityListReleaseFreqChart')" />
<AiPane
v-if="aiPaneVisible?.entityListReleaseFreqChart"
:aiContent="overviewAiContent.entityListReleaseFreqChart"
@mouseleave="handleHideAiPane('entityListReleaseFreqChart')"
/>
</div>
</div>
<div class="box3-content">
<div class="box3-content-title">商业管制清单发布频次统计</div>
<el-table
:data="commerceControlListReleaseFreq"
stripe
style="width: 100%"
@row-click="handleCommercialRowClick"
>
<el-table-column prop="year" label="年份" width="200" />
<el-table-column label="发布次数" width="300">
<template #default="scope">
<div style="display: flex; align-items: center; cursor: pointer">
<span style="margin-right: 10px; width: 40px">{{ scope.row.num }}</span>
<el-progress
:percentage="scope.row.percent * 100"
:show-text="false"
:status="getStatus(scope.row.percent)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="重点领域" width="220" align="center">
<template #default="scope">
<div
style="display: flex; justify-content: center; align-items: center; gap: 5px"
>
<AreaTag v-for="tag in scope.row.tags" :key="tag" :tagName="tag" />
</div>
</template>
</el-table-column>
</el-table>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">数据来源:美国商务部官网</div>
</div>
<div class="ai-pane">
<AiButton @mouseenter="handleShowAiPane('commerceControlListReleaseFreqChart')" />
<AiPane
v-if="aiPaneVisible?.commerceControlListReleaseFreqChart"
:aiContent="overviewAiContent.commerceControlListReleaseFreqChart"
@mouseleave="handleHideAiPane('commerceControlListReleaseFreqChart')"
/>
</div>
</div>
<div class="box3-content" style="display: none">
<div class="box3-content-title">关键与新兴技术清单(CETs)</div>
<el-table :data="entityListReleaseFreq" stripe style="width: 100%">
<el-table-column prop="year" label="年份" width="100" />
<el-table-column label="发布次数" width="180">
<template #default="scope">
<div style="display: flex; align-items: center">
<span style="margin-right: 10px; width: 40px">{{ scope.row.num }}</span>
<el-progress
:percentage="scope.row.percent * 100"
:show-text="false"
:status="getStatus(scope.row.percent)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="重点领域" width="180">
<template #default="scope">
<div style="display: flex; align-items: center; gap: 5px">
<el-tag v-for="tag in scope.row.tags" :key="tag" :type="getTagType(tag)">{{
tag
}}</el-tag>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
</custom-container>
</el-col>
</el-row>
<el-row :gutter="16" style="width: 1600px; margin: 0 auto; height: 540px; margin-top: 16px">
<el-col :span="8" style="padding-left: 0">
<custom-container title="实体清单领域分布情况" :titleIcon="radarIcon" height="540px">
<template #header-right>
<el-checkbox v-model="domainChecked" label="50%规则" size="large" />
</template>
<template #default>
<EChart
:option="radarOption"
autoresize
:style="{ height: '420px' }"
@chart-click="handleRadarChartClick"
/>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">数据来源:美国商务部官网</div>
</div>
<div class="ai-pane">
<AiButton @mouseenter="handleShowAiPane('radarChart')" />
<AiPane :aiContent="overviewAiContent.radarChart" @mouseleave="handleHideAiPane('radarChart')" />
</div>
</template>
</custom-container>
</el-col>
<el-col :span="16" style="padding-right: 0">
<custom-container title="制裁清单数量增长趋势" :titleIcon="qushiIcon" height="540px">
<template #header-right>
<div style="display: flex; align-items: center; gap: 16px">
<el-checkbox
v-if="selectedEntityId != '13'"
v-model="trendChecked"
label="50%规则"
size="large"
/>
<el-select v-model="selectedEntityId" placeholder="请选择清单类型" style="width: 160px">
<el-option v-for="item in infoList" :key="item.id" :label="item.nameZh" :value="item.id" />
</el-select>
</div>
</template>
<template #default>
<EChart
:option="trendOption"
autoresize
:style="{ height: '420px' }"
@chart-click="handleMultiBarChartClick"
/>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">数据来源:美国商务部官网</div>
</div>
<div class="ai-pane">
<AiButton @mouseenter="handleShowAiPane('trendChart')" />
<AiPane
v-if="aiPaneVisible?.trendChart"
:aiContent="overviewAiContent.trendChart"
@mouseleave="handleHideAiPane('trendChart')"
/>
</div>
</template>
</custom-container>
</el-col>
</el-row>
<el-row :gutter="16" style="width: 1600px; margin: 0 auto; margin-top: 39px; padding-bottom: 60px">
<CustomTitle id="position4" title="出口管制数据库" style="margin-top: 0px" />
<div class="resource-tabs">
<div
v-for="tab in resourceTabs"
:key="tab.value"
class="resource-tab-item"
:class="{ active: activeResourceTab == tab.value, disabled: tab.disabled }"
@click="handleResourceTabClick(tab)"
>
{{ tab.label }}
</div>
</div>
<template v-if="activeResourceTab === 'entity'">
<el-col :span="8" style="padding-left: 0">
<custom-container title="历次制裁过程" :titleIcon="listIcon" height="845px">
<template #default>
<div class="box4">
<div style="height: 90%; overflow-y: auto; padding-top: 10px">
<div class="box4-item" v-for="(item, idx) in sanctionProcessList" :key="item.title">
<div class="box4-item-left">
<el-image :src="dotIcon" alt="图片" class="box4-item-left-icon" />
<div
class="box4-item-left-line"
v-if="idx + 1 != sanctionProcessList.length"
></div>
</div>
<div class="box4-item-right">
<div class="box4-item-right-header" @click="handleSanc(item)">
<span class="box4-item-right-header-title"
>{{ item.postDate }}{{ item.title }}</span
>
<span class="box4-item-right-header-desc">{{ item.desc }}</span>
</div>
<el-tooltip
effect="dark"
:content="item.content"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="box4-item-right-content">
{{ item.content }}
</div>
</el-tooltip>
</div>
</div>
</div>
<div
class="box4-footer"
:style="{ marginTop: sanctionProcessList.length > 0 ? '0px' : 'auto' }"
>
<el-button type="primary" link @click="handleGetMore"
>查看更多
<el-icon>
<DArrowRight />
</el-icon>
</el-button>
</div>
</div>
</template>
</custom-container>
</el-col>
<el-col :span="16" style="padding-right: 0">
<custom-container title="制裁实体清单" :titleIcon="entityIcon" height="845px">
<template #header-right>
<div class="box5-header-right">{{ total }}家实体</div>
</template>
<template #default>
<div class="box5">
<el-table
:data="entitiesList"
class="sanction-table"
stripe
empty-text="暂无数据"
height="700px"
header-row-class-name="table-header"
row-class-name="table-row"
>
<el-table-column prop="name" label="实体名称" min-width="200">
<template #default="scope">
<div class="tableName" @click="handleCompClick(scope.row)">
<el-image
v-if="scope.row.img"
class="box1-bottom-content-item-img"
:src="scope.row.img"
alt=""
></el-image>
<div v-else class="box1-bottom-content-item-imgUndefined">
{{
(scope.row.name || scope.row.enName)?.match(
/[\u4e00-\u9fa5a-zA-Z0-9]/
)?.[0]
}}
</div>
<CommonPrompt :content="scope.row.name" style="flex: 1; overflow: hidden" />
</div>
</template>
</el-table-column>
<el-table-column prop="domains" label="涉及领域" min-width="150">
<template #default="scope">
<div class="domain-tags">
<AreaTag v-for="tag in scope.row.domains" :key="tag" :tagName="tag" />
</div>
<!-- <div class="domain-tags">
<el-tag v-for="tag in scope.row.domains" :key="tag" :type="getTagType(tag)">{{
tag
}}</el-tag>
</div> -->
</template>
</el-table-column>
<el-table-column prop="sanctionDate" label="制裁时间" width="120" align="center">
<template #default="scope">
{{ scope.row.sanctionDate }}
</template>
</el-table-column>
<el-table-column prop="revenue" label="50%规则子企业" width="280" align="right">
<template #default="scope">
<div class="num-item" v-if="scope.row.ruleOrgCount > 0">
<div
class="name-item"
:class="[
'revenue-cell',
scope.row.revenue === '无营收数据' ? 'no-revenue' : ''
]"
>
{{ scope.row.ruleOrgList[0].orgName }}...等
</div>
<div
style="width: 50px; color: #409eff; cursor: pointer"
@click="handleOrgClick(scope.row)"
>
{{ scope.row.ruleOrgCount }}家>
</div>
</div>
</template>
</el-table-column>
</el-table>
<div class="table-footer">
<!-- <div class="pagination-info">
第{{ currentPage }}页,共{{ totalPages }}页
</div> -->
<el-pagination
v-model:current-page="currentPage"
:page-size="pageSize"
:total="total"
:pager-count="5"
layout="prev, pager, next"
background
@current-change="handlePageChange"
/>
</div>
</div>
</template>
</custom-container>
</el-col>
</template>
<template v-if="activeResourceTab === 'all'">
<el-col :span="24" style="padding: 0">
<!-- <div style="min-height: 500px; display: flex; justify-content: center; align-items: center; background: #fff; border-radius: 4px;">
暂无内容
</div> -->
<div class="all-content">
<div class="left">
<div class="title">
<div class="box"></div>
<div class="text">科技领域</div>
</div>
<div class="left-main">
<el-checkbox-group v-model="checkedTech">
<div class="checkbox-grid">
<el-checkbox v-for="item in techOptions" :key="item.value" :label="item.value">{{
item.label
}}</el-checkbox>
</div>
</el-checkbox-group>
</div>
<div class="title">
<div class="box"></div>
<div class="text">制裁时间</div>
</div>
<div class="left-main">
<el-checkbox-group v-model="checkedTime">
<div class="checkbox-grid">
<el-checkbox v-for="item in timeOptions" :key="item.value" :label="item.label">{{
item.label
}}</el-checkbox>
</div>
</el-checkbox-group>
<div
v-if="timeOptions.find(i => i.value === 'custom' && i.checked)"
class="custom-date-picker"
>
<el-date-picker
v-model="customDateRange"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</div>
</div>
</div>
<div class="right">
<div class="right-title">
<img :src="icon01" alt="" />
<div>出口管制制裁措施</div>
</div>
<div class="right-main">
<div class="sanction-list" v-for="item in sanctionList" :key="item.id">
<div class="time">
<div class="year">{{ item.year }}</div>
<div class="date">{{ item.dateStr }}</div>
</div>
<img :src="item.orgLogoUrl || comTitle" alt="" />
<div class="main">
<div class="main-title" @click="handleTitleClick(item)">{{ item.title }}</div>
<div class="main-desc">{{ item.desc }}</div>
<div class="tag-box">
<div v-for="tag in item.tags" :key="tag" class="tag-item">{{ tag }}</div>
</div>
<div :class="{ 'count-tag': item.countTag }">{{ item.countTag }}</div>
</div>
</div>
</div>
<div class="right-footer">
<div class="total-count">{{ totalAll }}</div>
<el-pagination
v-model:current-page="currentPageAll"
:page-size="pageSizeAll"
:total="totalAll"
layout="prev, pager, next"
background
@current-change="handlePageChangeAll"
/>
</div>
</div>
</div>
</el-col>
</template>
<template v-if="activeResourceTab === 'commerce'">
<div class="commerce-wrapper" :style="{ minHeight: '500px' }">
<listPage />
</div>
</template>
</el-row>
</div>
<RuleSubsidiaryDialog
v-model="dialogVisible"
:company-name="currentRuleCompany"
:total-count="currentRuleCount"
:data-list="currentOrgList"
/>
</div>
<el-dialog v-model="mediaVisible" title="社交媒体信息" width="500" :before-close="handleMediaClose">
<div class="dialog-content">
{{ currentMedia }}
</div>
<template #footer>
<div class="dialog-footer">
<!-- <el-button @click="mediaVisible = false">Cancel</el-button> -->
<el-button type="primary" @click="mediaVisible = false"> 确定 </el-button>
</div>
</template>
</el-dialog>
<RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</template>
<script setup>
//这是一个备注
import NewsList from "@/components/base/newsList/index.vue";
import RiskSignal from "@/components/base/riskSignal/index.vue";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/index.vue";
import { onMounted, ref, computed, reactive, shallowRef, watch, nextTick } from "vue";
import { useContainerScroll } from "@/hooks/useScrollShow";
const homeMainRef = ref(null);
const { isShow } = useContainerScroll(homeMainRef);
import setChart from "@/utils/setChart";
import listPage from "./v2.0CommercialControlList/components/sanctionsOverview/components/listPage/index.vue";
import EChart from "@/components/Chart/index.vue";
import tipsIcon from "./assets/icons/info-icon.png";
import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
import AreaTag from "@/components/base/AreaTag/index.vue";
import { useChartInterpretation } from "@/views/exportControl/utils/common";
import { TAGTYPE } from "@/public/constant";
import { useGotoCompanyPages } from "@/router/modules/company";
import { useGotoNewsDetail } from "@/router/modules/news";
const gotoCompanyPages = useGotoCompanyPages();
const gotoNewsDetail = useGotoNewsDetail();
const trendChart = useChartInterpretation();
const radarChart = useChartInterpretation();
const entityListReleaseFreqChart = useChartInterpretation();
const commerceControlListReleaseFreqChart = useChartInterpretation();
import { useRouter } from "vue-router";
import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate";
const router = useRouter();
import CustomContainer from "@/components/Container/index.vue";
import ClickableCard from "./components/link.vue";
import InfoCard from "./components/info.vue";
import CustomTitle from "./components/title.vue";
import CommonPrompt from "./commonPrompt/index.vue";
import RuleSubsidiaryDialog from "./components/RuleSubsidiaryDialog.vue";
import trumpAvatar from "@/assets/images/icon-trump.png";
import elongAvatar from "@/assets/images/icon-elong.png";
import newsIcon from "@/assets/images/icon-news.png";
import dialogIcon from "@/assets/images/icon-duihua.png";
import houseIcon from "@/assets/images/icon-house.png";
import dangerIcon from "./assets/images/box2-header-icon.png";
import box1Image from "./assets/images/box1-image.png";
import box3Icon from "./assets/images/box1-header-icon.png";
import radarIcon from "./assets/images/icon-radar.png";
import qushiIcon from "./assets/images/icon-qushi.png";
import listIcon from "./assets/images/icon-list.png";
import dotIcon from "./assets/images/info2-icon.png";
import entityIcon from "./assets/images/icon-entity.png";
import comTitle from "./assets/images/panel1_1.png";
import getMultiLineChart from "./utils/multiLineChart";
import icon01 from "./assets/images/jianzhu.png";
import {
getEntitiesDataCount,
getEntitiesDataInfo,
getIndustryCountByYear,
getCountDomainByYear,
getSanctionsInfoCount,
getEntitiesList,
getSanctionProcess,
getSanDomainCount,
getRiskSignal,
getSocialMediaInfo,
getNewsInfo,
getExportControlList
} from "@/api/exportControl";
import { getMultipleBarChart_m } from "./utils/charts";
import { formatAnyDateToChinese } from "./utils";
import _ from "lodash";
const currentRuleCompany = ref("");
const currentRuleCount = ref(0);
const currentRuleList = ref([]);
const handleToPosi = id => {
const element = document.getElementById(id);
if (element && homeMainRef.value) {
// 1. 如果是从完整搜索框跳转,先强制切换状态稳定布局
if (!isShow.value) {
isShow.value = true;
}
// 2. 使用 nextTick 等待 DOM 布局(如高度切换)完成后再进行坐标计算
nextTick(() => {
const containerRect = homeMainRef.value.getBoundingClientRect();
const elementRect = element.getBoundingClientRect();
// 使用 getBoundingClientRect 计算元素相对于容器顶部的绝对距离,不受嵌套布局影响
const top = elementRect.top - containerRect.top + homeMainRef.value.scrollTop;
homeMainRef.value.scrollTo({
top: top,
behavior: "smooth"
});
});
}
};
const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
const handleToRiskSignalDetail = item => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true;
};
const sanctionList = ref([]);
const techOptions = [
{ label: "全部领域", value: 0 },
{ label: "人工智能", value: 1 },
{ label: "生物科技", value: 2 },
{ label: "新一代信息技术", value: 3 },
{ label: "量子科技", value: 4 },
{ label: "新能源", value: 5 },
{ label: "集成电路", value: 6 },
{ label: "海洋", value: 7 },
{ label: "先进制造", value: 8 },
{ label: "新材料", value: 9 },
{ label: "航空航天", value: 10 },
{ label: "深海", value: 11 },
{ label: "极地", value: 12 },
{ label: "太空", value: 13 },
{ label: "核", value: 14 }
];
const customDateRange = ref("");
const timeOptions = [
{ label: "全部时间", value: "all", checked: true },
{ label: "2025年", value: "2025", checked: false },
{ label: "2024年", value: "2024", checked: false },
{ label: "2023年", value: "2023", checked: false },
{ label: "2022年", value: "2022", checked: false },
{ label: "2021年", value: "2021", checked: false },
{ label: "自定义", value: "custom", checked: false }
];
const checkedTech = ref([0]);
const checkedTime = ref(["全部时间"]);
// 跳转到单条制裁页面,单独打开一个新页面
const handleTitleClick = item => {
if (item.sanTypeId == "13") {
handleWxClick(item);
return;
}
window.sessionStorage.setItem("curTabName", `${item.year}-${item.dateStr}${item.title}》`);
const route = router.resolve({
path: "/exportControl/singleSanction",
query: {
id: item.id,
sanTypeId: item.sanTypeId,
date: `${item.year}-${item.dateStr}`
}
});
window.open(route.href, "_blank");
};
const handleCompClick = item => {
// console.log("item", item);
if (!item.id) return;
window.sessionStorage.setItem("curTabName", item.name);
gotoCompanyPages(item.entityId);
// const route = router.resolve({
// name: "companyPages",
// params: {
// id: item.id,
// sanTypeId: item.sanTypeId
// }
// });
// window.open(route.href, "_blank");
};
const tagsType = ["primary", "success", "warning", "danger"];
const getTagType = tag => {
if (!tag) return "info";
const strTag = String(tag).trim();
const tagColorMap = {
通信网络: "primary",
人工智能: "danger",
集成电路: "warning",
量子科技: "success",
生物技术: "info",
新一代信息技术: "primary",
新能源: "success",
航空航天: "primary",
先进制造: "warning",
海洋: "info",
新材料: "danger",
深海: "primary",
极地: "info",
: "danger",
其他: "info"
};
if (tagColorMap[strTag]) {
return tagColorMap[strTag];
}
const hash = strTag.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
return TAGTYPE[hash % TAGTYPE.length];
};
//数据定义
const entitiesDataInfoList = shallowRef([]);
// 趋势图
const trendOption = ref({});
const trendChecked = ref(false);
const selectedEntityId = ref(1);
// 发布频度
const entityListReleaseFreq = ref([]);
// CCL发布频度
const commerceControlListReleaseFreq = ref([]);
// 历次制裁过程
const sanctionProcessList = ref([]);
const sanctionPage = ref(1);
// 制裁实体清单
const entitiesList = ref([]);
// 风险信号
const riskSignalList = ref([]);
// 社交媒体信息
const socialMediaList = ref([]);
// 新闻资讯
const newsList = ref([]);
onMounted(async () => {
try {
const [dataCount, entitiesDataInfo, industryCountByYear, cclList] = await Promise.all([
getEntitiesDataCount(),
getEntitiesDataInfo(),
getIndustryCountByYear(1),
getIndustryCountByYear(13)
]);
// 交换第二个和第三个元素
// [dataCount[1], dataCount[2]] = [dataCount[2], dataCount[1]];
infoList.value = dataCount.slice(0, 2).map((item, idx) => {
return {
...item,
unit: idx == 0 ? "家" : "项"
};
});
allSanTypeIds.value = infoList.value.map(item => item.id);
resourceTabs.value = infoList.value.map(item => ({
label: item.nameZh,
value: tabMap[item.id],
id: item.id,
disabled: false
}));
resourceTabs.value.unshift({ label: "全部制裁", value: "all", id: "", disabled: false });
console.log("返回的数据结构 infoList =》", resourceTabs.value);
entitiesDataInfoList.value = entitiesDataInfo || [];
const list = _.chain(industryCountByYear).filter("year").orderBy("year", "desc").value().slice(0, 5);
const cclList1 = _.chain(cclList).filter("year").orderBy("year", "desc").value().slice(0, 5);
const total = _.sumBy(list, "count");
const maxCountItem = _.maxBy(list, "count");
const maxCountForList = maxCountItem ? maxCountItem.count : 0;
const maxCountItem1 = _.maxBy(cclList1, "count");
const maxCountForList1 = maxCountItem1 ? maxCountItem1.count : 0;
entityListReleaseFreq.value = _.map(list, item => {
return {
year: item.year,
num: item.count,
percent: item.count / maxCountForList,
tags: item.domain
};
});
entityListReleaseFreqChartData.value = entityListReleaseFreq.value;
// entityListReleaseFreqChart.interpret({
// type: "柱状图",
// name: "美国商务部发布实体清单的频次",
// data: entityListReleaseFreq.value
// });
commerceControlListReleaseFreq.value = _.map(cclList1, item => {
return {
year: item.year,
num: item.count,
percent: item.count / maxCountForList1,
tags: item.domain
};
});
commerceControlListReleaseFreqChartData.value = commerceControlListReleaseFreq.value;
// commerceControlListReleaseFreqChart.interpret({
// type: "柱状图",
// name: "美国商务部发布商业管制清单的频次",
// data: commerceControlListReleaseFreq.value
// });
// 获取趋势图数据
await fetchTrendData();
await fetchRiskSignals("0103");
// 获取社交媒体信息
await fetchSocialMediaInfo();
// 获取新闻资讯
await fetchNewsInfo();
await fetchEntitiesList(currentPage.value, pageSize.value);
await fetchSanctionProcess(sanctionPage.value, 10);
// 获取雷达图数据
await fetchRadarData(domainChecked.value);
// 获取出口管制制裁措施
await fetchSanctionList();
} catch (err) {
console.log(err);
}
});
// 查看社交媒体详情
const handleToSocialDetail = item => {
const route = router.resolve({
path: "/characterPage",
query: {
personId: item.id
}
});
window.open(route.href, "_blank");
};
// 获取趋势图数据
const fetchTrendData = async () => {
try {
const res = await getCountDomainByYear({
isRule: selectedEntityId.value != "13" && trendChecked.value,
startYear: "2020",
endYear: String(new Date().getFullYear()),
sanTypeId: selectedEntityId.value
});
if (res && res[0] && res[0].yearDomainCount) {
trendOption.value = processYearDomainCountData(res[0].yearDomainCount);
trendChartData.value = res[0].yearDomainCount;
// trendChart.interpret({ type: "柱状图", name: "制裁清单数量增长趋势", data: res[0].yearDomainCount });
}
} catch (error) {
console.error("获取趋势图数据失败:", error);
}
};
const requestAiPaneContent = async key => {
if (!key || aiPaneLoading.value[key] || aiPaneFetched.value[key]) return;
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: true };
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成中..." };
try {
const payload = buildAiChartPayload(key);
const res = await getChartAnalysis(
{ text: JSON.stringify(payload) },
{
onChunk: chunk => {
const current = overviewAiContent.value[key];
const base = current === "智能总结生成中..." ? "" : current;
overviewAiContent.value = {
...overviewAiContent.value,
[key]: base + chunk
};
}
}
);
const list = res?.data;
const first = Array.isArray(list) ? list[0] : null;
const interpretation = first?.解读 || first?.["解读"];
// 流式已渲染过内容,最终用解析出的解读覆盖(保证显示格式统一)
if (interpretation) {
overviewAiContent.value = {
...overviewAiContent.value,
[key]: interpretation
};
}
aiPaneFetched.value = { ...aiPaneFetched.value, [key]: true };
} catch (error) {
console.error("获取图表解读失败", error);
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成失败" };
} finally {
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: false };
}
};
const trendChartData = ref([]);
const radarChartData = ref([]);
const entityListReleaseFreqChartData = ref([]);
const commerceControlListReleaseFreqChartData = ref([]);
const aiPaneVisible = ref({
trendChart: false,
radarChart: false,
entityListReleaseFreqChart: false,
commerceControlListReleaseFreqChart: false
});
const overviewAiContent = ref({
trendChart: "智能总结生成中...",
radarChart: "智能总结生成中...",
entityListReleaseFreqChart: "智能总结生成中...",
commerceControlListReleaseFreqChart: "智能总结生成中..."
});
const aiPaneFetched = ref({
trendChart: false,
radarChart: false,
entityListReleaseFreqChart: false,
commerceControlListReleaseFreqChart: false
});
const aiPaneLoading = ref({
trendChart: false,
radarChart: false,
entityListReleaseFreqChart: false,
commerceControlListReleaseFreqChart: false
});
const chartLoading = ref({
trendChart: false,
radarChart: false,
entityListReleaseFreqChart: false,
commerceControlListReleaseFreqChart: false
});
const buildAiChartPayload = key => {
if (key === "trendChart") {
return { type: "柱状图", name: "制裁清单数量增长趋势", data: trendChartData.value };
}
if (key === "radarChart") {
return { type: "雷达图", name: "实体清单领域分布情况", data: radarChartData.value };
}
if (key === "entityListReleaseFreqChart") {
return {
type: "柱状图",
name: "美国商务部发布实体清单的频次",
data: entityListReleaseFreqChartData.value
};
}
if (key === "commerceControlListReleaseFreqChart") {
return {
type: "柱状图",
name: "美国商务部发布商业管制清单的频次",
data: commerceControlListReleaseFreqChartData.value
};
}
return { type: "", name: "", data: [] };
};
const handleShowAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: true
};
requestAiPaneContent(key);
};
const handleHideAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: false
};
};
watch(
() => [trendChecked.value, selectedEntityId.value],
() => {
fetchTrendData();
}
);
// 新增函数:处理 yearDomainCount 数据并使用 getMultipleBarChart_m 方法生成图表配置
const processYearDomainCountData = yearDomainCountData => {
// 提取所有年份并排序
const years = [...new Set(yearDomainCountData.map(item => item.year))].sort();
// 提取所有领域名称
const allDomains = [...new Set(yearDomainCountData.flatMap(item => item.domainCountInfo.map(domain => domain.name)))];
console.log("不同领域的数据 =>", allDomains);
// 构造 getMultipleBarChart_m 所需的数据结构
const chartData = {
domains: allDomains,
data: years.map(year => {
const yearData = yearDomainCountData.find(item => item.year === year);
const domainCounts = {};
// 初始化所有领域的计数为0
allDomains.forEach(domain => {
domainCounts[domain] = 0;
});
// 填充实际数据
if (yearData && yearData.domainCountInfo) {
yearData.domainCountInfo.forEach(domain => {
domainCounts[domain.name] = domain.count;
});
}
return {
year: year,
domainNum: domainCounts
};
})
};
console.log("不同领域的数据 chartData", chartData);
// 使用 getMultipleBarChart_m 生成图表配置
return getMultipleBarChart_m(chartData);
};
const handleEntityClick = item => {
console.log("item", item);
window.sessionStorage.setItem("curTabName", item.name || item.entityNameZh);
gotoCompanyPages(item.id);
};
const handleWxClick = item => {
const routeData = router.resolve({
path: "/exportControl/commercialControlList",
query: {
sanTypeId: item.sanTypeId,
key: item.id
}
});
// 打开一个新页面
window.open(routeData.href, "_blank");
};
const carouselRef = ref(null);
const currentCarouselIndex = ref(0);
const handleCarouselChange = index => {
currentCarouselIndex.value = index;
};
// 跳转到V2.0单次制裁
const handleToEntityList = item => {
console.log("这是什么数据1 =>", item);
let id = item?.id;
let sanTypeId = item?.sanTypeId || 1;
if (!id) {
const currentItem = entitiesDataInfoList.value[currentCarouselIndex.value];
id = currentItem?.id;
sanTypeId = currentItem?.sanTypeId || 1;
}
window.sessionStorage.setItem(
"curTabName",
entitiesDataInfoList.value[currentCarouselIndex.value].postDate + " 《实体清单新增条目》"
);
let date = entitiesDataInfoList.value[currentCarouselIndex.value].postDate;
const routeData = router.resolve({
path: "/exportControl/singleSanction",
query: {
id,
sanTypeId,
date
}
});
// 打开一个新页面
window.open(routeData.href, "_blank");
};
// 跳转到V2.0实体清单无ID
const handleToEntityListNoId = item => {
console.log("这是什么数据 =>", item);
if (item.nameZh == "实体清单") {
const routeData = router.resolve({
path: "/exportControl/entityList",
query: {
sanTypeId: item.id
}
});
// 打开一个新页面
window.open(routeData.href, "_blank");
} else if (item.nameZh == "商业管制清单") {
const routeData = router.resolve({
path: "/exportControl/commercialControlList",
query: {
sanTypeId: item.id
}
});
// 打开一个新页面
window.open(routeData.href, "_blank");
} else {
return;
}
};
// const billList = ref([]);
// const curBillListIndex = ref(0);
const searchExportControlText = ref("");
const infoListColor = ref(["rgba(206, 79, 81, 1)", "rgba(114, 46, 209, 1)", "rgba(132, 136, 142, 1)", "rgba(132, 136, 142, 1)"]);
const infoList = ref([]);
const allSanTypeIds = ref(["1", "13"]);
// 雷达图
const domainChecked = ref(false);
const radarOption = ref({
title: {
text: ""
},
tooltip: {
// trigger: "item",
confine: true,
trigger: "axis",
formatter: function (params) {
// params 包含所有系列的数据
if (!params || params.length === 0) return "";
const radarData = params[0];
const indicator = radarData.axisValue; // 当前角度对应的指标名
const value = radarData.value;
// 只显示当前角度对应的指标
return `${indicator}: ${value}`;
}
},
legend: {
show: false,
top: "0%",
icon: "circle",
itemWidth: 12,
itemHeight: 12,
textStyle: {
fontSize: 16,
fontWeight: 400,
fontFamily: "Microsoft YaHei",
color: "rgb(95, 101, 108)",
lineHeight: 24,
verticalAlign: "middle",
padding: [2, 0, 0, 0]
},
data: []
},
grid: {
top: "5%",
bottom: "5%"
},
radar: {
radius: "60%",
center: ["50%", "50%"],
// shape: 'circle',
indicator: [],
axisName: {
formatter: "{value}",
color: "rgba(59, 65, 75, 1)",
fontSize: 16,
fontWeight: 700
}
},
series: [
{
name: "",
type: "radar",
symbol: "none", // 隐藏节点圆圈
data: []
}
]
});
// 获取雷达图数据
const fetchRadarData = async checked => {
try {
const data = await getSanDomainCount(checked, allSanTypeIds.value.join(","));
if (data && Array.isArray(data) && data.length > 0) {
// 收集所有可能的领域名称
const allDomains = new Set();
data.forEach(item => {
if (item.domainCountInfo) {
item.domainCountInfo.forEach(domain => {
allDomains.add(domain.name);
});
}
});
const domainNames = Array.from(allDomains);
// 为每个制裁类型准备数据
const radarColors = [
"rgba(45, 123, 248, 1)",
"rgba(206, 79, 81, 1)",
"rgba(255, 197, 61, 1)",
"rgba(255, 182, 193, 1)",
"rgba(159, 122, 234, 1)",
"rgba(90, 200, 220, 1)"
];
const seriesData = data.map((sanItem, index) => {
// 创建一个映射,将领域名称映射到数量
const domainMap = {};
if (sanItem.domainCountInfo) {
sanItem.domainCountInfo.forEach(domain => {
domainMap[domain.name] = domain.count;
});
}
// 按照统一的领域顺序创建值数组
const values = domainNames.map(name => domainMap[name] || 0);
// 确定颜色
const solidColor = radarColors[index % radarColors.length];
const areaColor = solidColor.replace("1)", "0.2)");
return {
value: values,
name: sanItem.sanTypeName,
itemStyle: { color: solidColor },
areaStyle: { color: areaColor }
};
});
// 更新雷达图指标
let maxValue = Math.max(...seriesData.flatMap(item => item.value)) * 1.2;
// 向上取整到最近的100的倍数,避免小数导致 ticks 不可读警告
maxValue = Math.ceil(maxValue / 100) * 100;
const indicators = domainNames.map(name => ({
name: name,
max: maxValue || 100 // 防止max为0的情况
}));
// 更新雷达图配置
radarOption.value.radar.indicator = indicators;
radarOption.value.series[0].data = seriesData;
radarOption.value.legend.data = seriesData.map(item => {
return {
name: item.name,
itemStyle: {
color: item.itemStyle.color
}
};
});
radarChartData.value = data;
// radarChart.interpret({ type: "雷达图", name: "实体清单领域分布情况", data: data });
}
} catch (error) {
console.error("获取雷达图数据失败:", error);
}
};
watch(
() => domainChecked.value,
() => fetchRadarData(domainChecked.value)
);
// 进度条状态
const getStatus = _percent => {
const percent = _percent * 100;
if (percent >= 90) {
return "exception";
} else if (percent >= 50) {
return "warning";
} else {
return "success";
}
};
// 制裁实体
const currentPage = ref(1); // 默认显示第5页
const pageSize = ref(10);
const total = ref(0);
// 全部制裁分页
const currentPageAll = ref(1);
const pageSizeAll = ref(10);
const totalAll = ref(0);
const fetchSanctionList = async () => {
try {
const techDomains = checkedTech.value.includes(0) ? null : checkedTech.value.map(String);
let years = null;
if (!checkedTime.value.includes("全部时间")) {
years = checkedTime.value
.map(t => {
const match = t.match(/(\d{4})/);
return match ? parseInt(match[1]) : null;
})
.filter(y => y !== null);
if (years.length === 0) years = null;
const customTime = timeOptions.value.find(item => item.value === "custom");
if (customTime && customTime.checked && customDateRange.value && customDateRange.value.length === 2) {
const start = new Date(customDateRange.value[0]);
const end = new Date(customDateRange.value[1]);
startDate = `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, "0")}-${String(
start.getDate()
).padStart(2, "0")}`;
endDate = `${end.getFullYear()}-${String(end.getMonth() + 1).padStart(2, "0")}-${String(end.getDate()).padStart(
2,
"0"
)}`;
}
}
const params = {
pageNum: currentPageAll.value,
pageSize: pageSizeAll.value,
techDomainIds: techDomains,
years: years,
isCn: false,
// typeName: "实体清单"
sanTypeIds: allSanTypeIds.value
};
const res = await getExportControlList(params);
if (res && res.content) {
sanctionList.value = res.content.map(item => {
const tags = Array.isArray(item.techDomains)
? item.techDomains
: item.techDomain
? [item.techDomain]
: item.techDomainList || [];
const fullTime = item.startTime
? formatAnyDateToChinese(item.startTime)
: item.postDate || item.publishDate || item.date;
let year = "";
let dateStr = fullTime;
if (typeof fullTime === "string") {
if (fullTime.includes("年")) {
const parts = fullTime.split("年");
year = parts[0];
dateStr = parts[1].replace(/\s+/g, "");
} else if (fullTime.includes("-")) {
const parts = fullTime.split("-");
year = parts[0];
dateStr = parts.slice(1).join("-");
}
}
return {
...item,
time: fullTime,
year,
dateStr,
title: item.entityNameZh || item.entityName || item.title || item.name,
desc: item.sanReason || item.description || item.summary || item.content,
tags: tags,
countTag: item.cnEntityCount
? `${item.cnEntityCount}家中国实体`
: item.ruleOrgCount
? `${item.ruleOrgCount}家关联实体`
: item.countTag || ""
};
});
totalAll.value = res.totalElements;
}
} catch (error) {}
};
const handlePageChangeAll = val => {
currentPageAll.value = val;
fetchSanctionList();
handleToPosi("position4");
};
watch(
checkedTech,
(newVal, oldVal) => {
let isModified = false;
if (newVal.includes(0)) {
if (!oldVal.includes(0)) {
checkedTech.value = [0];
isModified = true;
} else if (newVal.length > 1) {
checkedTech.value = newVal.filter(v => v !== 0);
isModified = true;
}
} else if (newVal.length === 0) {
checkedTech.value = [0];
isModified = true;
}
if (isModified) return;
currentPageAll.value = 1;
fetchSanctionList();
},
{ deep: true }
);
watch(
checkedTime,
(newVal, oldVal) => {
let isModified = false;
if (newVal.includes("全部时间")) {
if (!oldVal.includes("全部时间")) {
checkedTime.value = ["全部时间"];
isModified = true;
} else if (newVal.length > 1) {
checkedTime.value = newVal.filter(v => v !== "全部时间");
isModified = true;
}
} else if (newVal.length === 0) {
checkedTime.value = ["全部时间"];
isModified = true;
}
if (isModified) return;
currentPageAll.value = 1;
fetchSanctionList();
},
{ deep: true }
);
// 获取实体清单数据
const fetchEntitiesList = async (page = 1, size = 10) => {
try {
console.log("activeResourceTabItem.value.id", activeResourceTabItem.value.id);
if (!activeResourceTabItem.value.id) return;
const res = await getEntitiesList(activeResourceTabItem.value.id, page, size);
if (res) {
entitiesList.value = res.content.map(item => ({
...item,
name: item.entityNameZh || item.entityName,
enName: item.entityName,
domains: item.techDomains,
sanctionDate: item.startTime
}));
total.value = res.totalElements;
currentPage.value = res.number + 1; // API返回的页码从0开始,前端从1开始
}
} catch (err) {
console.error(err);
}
};
const handleGetMore = async () => {
sanctionPage.value++;
try {
const sanTypeid = activeResourceTabItem.value.id ? [activeResourceTabItem.value.id] : allSanTypeIds.value;
const res = await getSanctionProcess(sanTypeid, sanctionPage.value, 10);
if (res && res.content) {
// 将新数据合并到现有列表中
const newData = res.content.map(item => ({
...item,
title: item.name,
desc: `${item.cnEntityCount} 家中国实体`,
content:
item.summary ||
"2025年3月25日,美国商务部工业与安全局以从事有悖于美国国家安全和外交政策利益的活动为由,宣布将来自中国的54家实体新增至“实体清单”。"
}));
// 合并新数据到现有列表
sanctionProcessList.value = [...sanctionProcessList.value, ...newData];
}
} catch (err) {
console.error(err);
// 如果请求失败,回退页码
sanctionPage.value--;
}
};
// 获取历次制裁过程数据
const fetchSanctionProcess = async (page = 1, size = 10) => {
try {
const res = await getSanctionProcess(
activeResourceTabItem.value.id ? [activeResourceTabItem.value.id] : allSanTypeIds.value,
page,
size
);
if (res) {
// 暂无商业管制清单数据
sanctionProcessList.value = res.content.map(item => ({
...item,
title: item.name,
desc: `${item.cnEntityCount} 家中国实体`,
content:
item.summary ||
"2025年3月25日,美国商务部工业与安全局以从事有悖于美国国家安全和外交政策利益的活动为由,宣布将来自中国的54家实体新增至“实体清单”。"
}));
}
} catch (err) {
console.error(err);
}
};
// 分页改变时的处理函数
const handlePageChange = page => {
currentPage.value = page;
fetchEntitiesList(page, pageSize.value);
};
const searchKeyword = ref("");
// 资源库 Tab 数据
const resourceTabs = ref([
// { label: "全部制裁", value: "all", disabled: false },
// { label: "实体清单", value: "entity", disabled: false },
// { label: "商业管制清单", value: "commerce", disabled: true }
// { label: "关键与新兴技术清单", value: "tech", disabled: true },
// { label: "军事最终用户清单", value: "military", disabled: true }
]);
const activeResourceTab = ref("all");
const activeResourceTabItem = ref({});
// 数据对应,便宜行事
const tabMap = {
1: "entity",
13: "commerce"
};
const handleResourceTabClick = tab => {
// if (tab.disabled) return;
console.log("选项点击", tab);
activeResourceTab.value = tab.value;
activeResourceTabItem.value = tab;
fetchSanctionProcess();
console.log("tabMap[tab.id]", tabMap[tab.id]);
if (tab.value === "entity") {
fetchEntitiesList();
}
};
const strengthLabels = {
strong: "强",
medium: "中",
weak: "弱",
none: "无"
};
// 获取风险信号数据
const fetchRiskSignals = async () => {
try {
const data = await getRiskSignal();
if (data && Array.isArray(data)) {
console.log(data);
warningList.value = data.map(item => ({
...item,
title: item.signalTitle,
time: item.signalTime,
status: item.signalLevel,
id: item.signalId,
sanId: item.sanId
}));
}
} catch (err) {
console.error("获取风险信号数据失败:", err);
}
};
// 添加获取社交媒体信息的方法
const fetchSocialMediaInfo = async () => {
try {
const data = await getSocialMediaInfo();
if (data && Array.isArray(data)) {
// console.log(data);
socialMediaList.value = data.map(item => ({
...item,
avatar: item.personImage,
name: item.personName,
time: formatTime(item.time),
source: item.orgName,
content: item.remarks,
personId: item.personId
}));
}
} catch (err) {
console.error("获取社交媒体信息失败:", err);
}
};
// 添加获取新闻资讯的方法
const fetchNewsInfo = async () => {
try {
const data = await getNewsInfo();
if (data && Array.isArray(data)) {
newsList.value = data.map(item => ({
...item,
img: item.newsImage,
title: item.newsTitle,
content: item.newsContent,
from: item.newsDate + (item.newsOrg ? " · " + item.newsOrg : "")
}));
}
} catch (err) {
console.error("获取新闻资讯失败:", err);
}
};
const handlePerClick = item => {
console.log("点击了社交媒体消息:", item);
window.sessionStorage.setItem("curTabName", item.name);
const route = router.resolve({
path: "/characterPage",
query: {
type: item.personType || [1, 2, 3][Math.floor(Math.random() * 3)],
personId: item.personId
}
});
window.open(route.href, "_blank");
};
// 处理点击社交媒体消息的方法
// const handleInfoClick = item => {
// console.log("点击了社交媒体消息的更多信息:", item);
// // 这里可以添加打开详情页的逻辑
// ElMessageBox.alert(`${item.content}`, "信息详情", {
// confirmButtonText: "确定",
// callback: action => {
// ElMessage({
// type: "info",
// message: `action: ${action}`
// });
// }
// });
// };
// 添加格式化时间的方法
const formatTime = timeStr => {
// 空值兜底,避免报错
if (!timeStr) return "暂无时间";
// 核心:替换T为空格
return timeStr.replace("T", " ");
};
const warningList = ref([]);
const curBillList = ref([]);
const releaseTime = ref("近一年发布");
const categoryList = ref([]);
const activeCate = ref("全部分类");
const activeHylyId = ref("");
// 获取领域分类
const handleGetHylyList = async () => {
try {
const res = await getHylyList();
console.log("行业领域列表");
categoryList.value = res.data;
const obj = {
id: 0,
hylyid: "",
hylymc: "全部分类"
};
categoryList.value = [obj, ...categoryList.value];
} catch (error) {}
};
const chart1Data = ref({
title: [
"2024-09",
"2024-10",
"2024-11",
"2024-12",
"2025-01",
"2025-02",
"2025-03",
"2025-04",
"2025-05",
"2025-06",
"2025-07",
"2025-08"
],
data: [
{
name: "提出法案",
value: [145, 52, 84, 99, 71, 96, 128, 144, 140, 168, 188, 172]
},
{
name: "通过法案",
value: [6, 3, 4, 6, 11, 5, 2, 14, 16, 27, 28, 44]
}
]
});
const handleSanc = item => {
console.log("activeResourceTabItem.value.id", activeResourceTabItem.value.id);
window.sessionStorage.setItem("curTabName", `${item.postDate}${item.title}》`);
const route = router.resolve({
path: "/exportControl/singleSanction",
query: {
id: item.id,
sanTypeId: activeResourceTabItem.value.id,
date: item.postDate
}
});
window.open(route.href, "_blank");
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
navigateToViewRiskSignal(router);
};
// 查看更多新闻资讯
const handleToMoreNews = () => {
const route = router.resolve("/newsBrief");
window.open(route.href, "_blank");
// router.push("/newsBrief")
};
const handleNewsInfoClick = item => {
console.log("点击了社交媒体消息的更多信息:", item);
// 应该跳转至哪儿???
// const route = router.resolve({
// path: "/newsAnalysis",
// query: {
// newsId: item.newsId
// }
// });
// window.open(route.href, "_blank");
gotoNewsDetail(item.newsId);
};
// 切换当前出口管制政策
const handleSwithCurPolicy = name => {
if (name === "left") {
carouselRef.value.prev();
} else {
carouselRef.value.next();
}
};
const handleSearch = () => {
window.sessionStorage.setItem("curTabName", `搜索-${searchExportControlText.value}`);
const curRoute = router.resolve({
path: "/searchResults",
query: {
searchText: searchExportControlText.value,
areaName: "实体清单"
}
});
window.open(curRoute.href, "_blank");
};
// 点击实体清单发布频次统计
const handleEntityRowClick = row => {
console.log("row", row);
const params = {
// domains: row.tags[0],
selectedDate: JSON.stringify([row.year + "-01-01", row.year + "-12-31"])
};
const route = router.resolve({
path: "/dataLibrary/dataEntityListEvent",
query: params
});
window.open(route.href, "_blank");
};
// 点击商业管制清单发布频次统计
const handleCommercialRowClick = row => {
console.log("row", row);
const params = {
// domains: row.tags[0],
selectedDate: JSON.stringify([row.year + "-01-01", row.year + "-12-31"])
};
const route = router.resolve({
path: "/dataLibrary/dataCommerceControlListEvent",
query: params
});
window.open(route.href, "_blank");
};
// 点击实体清单领域分布情况
const handleRadarChartClick = value => {
// console.log('value', value);
// alert(domainChecked.value)
const params = {
isHalfRule: domainChecked.value
};
const route = router.resolve({
path: "/dataLibrary/dataEntityList",
query: params
});
window.open(route.href, "_blank");
};
// 点击制裁清单数量增长趋势
const handleMultiBarChartClick = val => {
const params = {
isHalfRule: trendChecked.value,
domains: val.seriesName,
isCnEntityOnly: true,
selectedDate: JSON.stringify([val.name + "-01-01", val.name + "-12-31"])
};
const route = router.resolve({
path: selectedEntityId.value === 1 ? "/dataLibrary/dataEntityList" : "/dataLibrary/dataCommerceControlList",
query: params
});
window.open(route.href, "_blank");
};
// 跳转到数据资源库
const handleToDataLibrary = item => {
const route = router.resolve({
path: "/dataLibrary/dataEntityList",
query: {
isCnEntityOnly: true,
selectedDate: JSON.stringify([item.postDate, item.postDate])
}
});
window.open(route.href, "_blank");
};
onMounted(async () => {
handleGetHylyList();
let chart1 = getMultiLineChart(chart1Data.value.title, chart1Data.value.data[0].value, chart1Data.value.data[1].value);
setChart(chart1, "chart1");
});
const dialogVisible = ref(false);
const currentOrgList = ref([]);
const handleClose = () => {
dialogVisible.value = false;
};
const handleOrgClick = item => {
// console.log(item, item.name);
currentRuleCompany.value = item.name;
currentRuleCount.value = item.ruleOrgCount;
currentOrgList.value = item.ruleOrgList;
dialogVisible.value = true;
};
const mediaVisible = ref(false);
const currentMedia = ref("");
const handleMediaClose = () => {
mediaVisible.value = false;
};
const handleMediaClick = item => {
// console.log(item, item.name);
currentMedia.value = item.content;
mediaVisible.value = true;
};
</script>
<style lang="scss" scoped>
.list-page {
padding-top: 0;
}
.home-header {
height: 64px;
background: url("@/assets/images/nav-bg.png");
box-sizing: border-box;
padding-left: 160px;
display: flex;
justify-content: space-between;
padding: 0 160px;
}
.box1 {
display: flex;
flex-direction: column;
gap: 20px;
position: relative;
width: 1036px;
.box1-left-arrow {
position: absolute;
z-index: 9999;
left: -24px;
top: 135px;
width: 24px !important;
height: 48px;
background: #e7f1ff;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.icon {
width: 11px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
}
.box1-right-arrow {
position: absolute;
z-index: 9999;
right: 0px;
top: 135px;
width: 24px;
height: 48px;
background: #e7f1ff;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.icon {
width: 11px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
}
.box1-absolute {
position: absolute;
width: 240px;
height: 89px;
top: 12px;
right: -24px;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
padding-right: 50px;
box-sizing: border-box;
background: linear-gradient(to right, rgba(206, 79, 81, 0), rgba(206, 79, 81, 0.3));
cursor: pointer;
&-des {
display: flex;
gap: 5px;
align-items: center;
font-size: 18px;
font-weight: 700;
color: rgba(206, 79, 81, 1);
}
&-num {
font-size: 32px;
font-weight: 700;
color: rgba(206, 79, 81, 1);
}
}
.box1-top {
// display: flex;
// flex-direction: column;
// gap: 10px;
padding-left: 30px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
&-title {
font-size: 20px;
font-weight: 700;
color: $base-color;
margin-top: 10px;
margin-bottom: 15px;
max-width: 80%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&-content {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 20px;
&-item {
display: flex;
gap: 5px;
&-title {
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
}
}
}
}
.box1-bottom {
padding-left: 30px;
height: 172px;
padding-top: 16px;
box-sizing: border-box;
padding-right: 24px;
&-title {
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
margin-bottom: 15px;
}
&-content__wx {
display: flex;
flex-direction: column;
gap: 10px;
justify-content: flex-start;
padding-left: 10px;
height: 156px;
overflow: auto;
&-item {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 10px;
cursor: pointer;
&-id {
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 700;
color: rgb(95, 101, 108);
}
&-txt {
font-size: 16px;
font-weight: 400;
color: rgba(95, 101, 108, 1);
line-height: 24px;
}
}
}
&-content {
display: flex;
gap: 15px;
flex-wrap: wrap;
justify-content: space-between;
padding-left: 10px;
height: 156px;
overflow: auto;
&-item {
display: flex;
align-items: center;
justify-content: flex-start;
width: 48%;
/* 留出2%的间距 */
// margin-bottom: 6px;
box-sizing: border-box;
gap: 10px;
cursor: pointer;
&-img {
width: 24px;
height: 24px;
flex-shrink: 0;
}
&-imgUndefined {
width: 24px;
height: 24px;
font-size: 14px;
font-weight: 700;
flex-shrink: 0;
color: rgba(5, 95, 194, 1);
background-color: rgb(236, 245, 255);
line-height: 24px;
text-align: center;
border-radius: 12px;
}
&-txt {
font-size: 16px;
font-weight: 400;
color: rgba(95, 101, 108, 1);
}
}
}
}
.box1-right {
display: flex;
flex-direction: column;
gap: 10px;
.box1-right-title {
font-size: 20px;
font-weight: 700;
color: $base-color;
}
.box1-right-tags {
display: flex;
gap: 10px;
}
.box1-right-content {
color: rgba(59, 65, 75, 1);
font-size: 16px;
font-weight: 400;
line-height: 28px;
}
.box1-right-footer {
margin-top: auto;
display: flex;
justify-content: space-between;
align-items: center;
.box1-right-footer-time {
color: rgba(95, 101, 108, 1);
font-size: 14px;
font-weight: 400;
}
}
}
}
.box2-main {
height: 320px;
overflow-y: auto;
.box2-main-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
box-sizing: border-box;
padding-right: 3px;
cursor: pointer;
&:hover {
background: var(--color-bg-hover);
}
.itemLeftStatus1 {
color: rgba(82, 196, 26, 1) !important;
background: rgba(246, 255, 237, 1) !important;
}
.itemLeftStatus2 {
color: rgba(250, 140, 22, 1) !important;
background: rgba(255, 247, 230, 1) !important;
}
.item-left {
display: flex;
align-items: center;
width: 40px;
height: 40px;
padding: 5px;
border-radius: 100%;
background: rgba(255, 241, 240);
color: rgba(245, 34, 45, 1);
font-family: Microsoft YaHei;
font-size: 12px;
font-weight: 400;
line-height: 14px;
box-sizing: border-box;
text-align: center;
flex-shrink: 0;
}
.item-right {
margin-left: 13px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
height: 47px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
.text {
font-family: Microsoft YaHei;
line-height: 47px;
width: 260px;
font-size: 16px;
font-weight: 400;
color: rgba(59, 65, 75, 1);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.time {
margin-left: 10px;
line-height: 47px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
}
}
}
}
.box2-footer {
position: absolute;
left: 0;
right: 0;
bottom: 20px;
width: 461px;
height: 42px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
border-radius: 6px;
background: var(--color-main-active);
margin: 0 auto;
cursor: pointer;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 8px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
}
}
.box3 {
display: flex;
justify-content: center;
// align-items: flex-start;
gap: 100px;
flex: 1;
.box3-content {
display: flex;
flex-direction: column;
// gap: 20px;
flex: 1;
position: relative;
}
.box3-content-title {
font-size: 18px;
font-weight: 700;
font-family: Microsoft YaHei;
// width: 640px;
width: 100%;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(247, 248, 249, 1);
color: $base-color;
margin-bottom: 15px;
}
.box3-content {
// flex: 1;
.el-progress--line {
width: 82px;
}
}
}
.box4 {
height: 786px;
overflow: auto;
display: flex;
flex-direction: column;
// justify-content: space-between;
padding-top: 16px;
// padding-bottom: 50px;
position: relative;
.box4-item {
display: flex;
gap: 10px;
align-items: flex-start;
padding-bottom: 35px;
position: relative;
.box4-item-left {
display: flex;
flex-direction: column;
align-items: center;
.box4-item-left-icon {
width: 10px;
height: 10px;
}
.box4-item-left-line {
width: 1px;
height: 100%;
position: absolute;
border-left: 1px solid rgba(10, 87, 166, 0.3);
}
}
.box4-item-right {
display: flex;
flex-direction: column;
.box4-item-right-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(234, 236, 238, 1);
position: relative;
top: -7.5px;
padding-bottom: 8px;
cursor: pointer;
&-title {
font-size: 18px;
color: $base-color;
font-weight: 700;
}
&-desc {
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
}
}
.box4-item-right-content {
font-size: 16px;
font-weight: 400;
color: rgba(95, 101, 108, 1);
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
line-height: 25px;
}
}
}
.box4-footer {
position: absolute;
// margin-top: auto;
display: flex;
justify-content: center;
align-items: center;
bottom: 30px;
left: 50%;
margin-left: -30px;
// margin-bottom: 30px;
}
}
.box5 {
height: 115%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
:deep(.table-header) {
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
}
:deep(.table-row) {
height: 64px;
}
.domain-tags {
display: flex;
gap: 8px;
}
.box5-header-right {
font-size: 16px;
font-weight: 700;
color: $base-color;
}
.table-footer {
margin-top: 20px;
}
.home-wrapper {
width: 100%;
height: 100%;
position: relative;
overflow-y: hidden;
.home-main {
position: relative;
width: 100%;
height: 100%;
overflow-y: auto;
.home-top-bg {
background:
url("./assets/images/background.png"),
linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%);
background-size: 100% 100%;
position: absolute;
width: 100%;
height: 100%;
z-index: -100;
top: -64px;
}
.home-main-header {
display: flex;
flex-direction: column;
align-items: center;
.home-main-header-center {
margin-top: 51px;
width: 960px;
height: 48px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
padding: 1px;
position: relative;
border: 1px solid transparent;
&:hover {
border: 1px solid var(--color-main-active);
}
.search {
position: absolute;
right: -1px;
top: 0px;
width: 120px;
height: 46px;
border-radius: 10px;
background: var(--color-main-active);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.search-icon {
width: 18px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
.search-text {
margin-left: 8px;
height: 22px;
color: #fff;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
}
}
}
.home-main-header-footer {
margin-top: 64px;
width: 700px;
height: 64px;
box-sizing: border-box;
padding: 0 108px;
display: flex;
justify-content: space-between;
.home-main-header-footer-item {
padding: 0 10px;
text-align: center;
.item-top {
height: 22px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 36px;
font-weight: 700;
line-height: 22px;
}
.item-footer {
margin-top: 10px;
height: 30px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
}
}
}
.home-main-header-footer-link,
.home-main-header-footer-info {
// width: 100%;
max-width: 1600px;
display: flex;
justify-content: center;
gap: 16px;
// padding: 30px 0;
}
.home-main-header-footer-info {
margin-top: 36px;
}
.home-main-header-btn-box {
width: 688px;
margin: 0 auto;
margin-top: 39px;
display: flex;
justify-content: space-between;
.btn {
display: flex;
align-items: center;
gap: 9px;
width: 160px;
height: 48px;
border: 1px solid #aed6ff;
box-sizing: border-box;
border-radius: 24px;
background: #e7f3ff;
cursor: pointer;
position: relative;
&:hover {
background: #cae3fc;
}
.btn-text {
width: 80px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 400;
line-height: 48px;
margin-left: 36px;
text-align: center;
}
.btn-icon {
position: absolute;
top: 16px;
right: 19px;
width: 6px;
height: 12px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
.home-main-center {
margin-top: 64px;
.center-top {
height: 450px;
display: flex;
gap: 20px;
.box1 {
display: flex;
gap: 10px;
position: relative;
.box1-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
justify-content: space-between;
.box1-header-left {
display: flex;
.icon {
width: 18px;
height: 18px;
margin-top: 19px;
img {
width: 100%;
height: 100%;
}
}
.title {
height: 22px;
margin-left: 18px;
margin-top: 16px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 22px;
}
}
.box1-header-right {
margin-top: 19px;
height: 16px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 16px;
cursor: pointer;
}
}
.box1-main {
display: flex;
height: 354px;
margin-top: 22px;
.box1-main-top {
height: 68px;
display: flex;
justify-content: space-between;
.box1-main-top-left {
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
.box1-main-top-right {
margin-left: 20px;
display: flex;
.num {
padding: 1px 8px;
height: 24px;
box-sizing: border-box;
border: 1px solid rgba(145, 202, 255, 1);
border-radius: 4px;
background: rgba(230, 244, 255, 1);
}
.tag {
box-sizing: border-box;
border: 1px solid rgba(135, 232, 222, 1);
border-radius: 4px;
background: rgba(230, 255, 251, 1);
}
}
}
}
}
.box2 {
flex: 1;
padding-right: 20px;
height: 450px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.box2-header {
height: 54px;
display: flex;
.icon {
width: 24px;
height: 22px;
margin-left: 33px;
margin-top: 18px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 22px;
margin-top: 16px;
height: 22px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 22px;
}
.num {
width: 24px;
height: 16px;
text-align: center;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 12px;
margin-left: 6px;
margin-top: 17px;
border: 1px solid rgba(255, 255, 255, 1);
border-radius: 100px;
background: rgba(255, 77, 79, 1);
}
.more {
margin-top: 19px;
margin-left: 256px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 16px;
}
}
.box2-main {
height: 282px;
overflow-y: auto;
.box2-main-item {
margin-left: 23px;
height: 47px;
width: 464px;
display: flex;
.itemLeftStatus1 {
color: rgba(82, 196, 26, 1) !important;
background: rgba(246, 255, 237, 1) !important;
}
.itemLeftStatus2 {
color: rgba(250, 140, 22, 1) !important;
background: rgba(255, 247, 230, 1) !important;
}
.item-left {
margin-top: 4px;
margin-left: 2px;
width: 40px;
height: 40px;
border-radius: 20px;
background: rgba(255, 241, 240);
color: rgba(245, 34, 45, 1);
font-family: Microsoft YaHei;
font-size: 12px;
font-weight: 400;
line-height: 14px;
box-sizing: border-box;
padding: 6px 4px;
text-align: center;
}
.item-right {
margin-left: 13px;
width: 408px;
height: 47px;
border-top: 1px solid rgba(240, 242, 244, 1);
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
.text {
width: 348px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 47px;
}
.time {
margin-left: 10px;
line-height: 47px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
}
}
}
}
.box2-footer {
position: absolute;
left: 26px;
bottom: 20px;
width: 430px;
height: 42px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
border-radius: 6px;
background: rgba(22, 119, 255, 1);
cursor: pointer;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 8px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
}
}
.center-footer {
margin-top: 21px;
height: 450px;
display: flex;
.box3 {
width: 900px;
height: 450px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
.box3-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 0 20px;
.box3-header-left {
display: flex;
.box3-header-icon {
margin-top: 15px;
width: 13px;
height: 13px;
img {
width: 100%;
height: 100%;
}
}
.box3-header-title {
margin-top: 16px;
margin-left: 22px;
height: 22px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 22px;
}
}
.box3-header-right {
display: flex;
justify-content: flex-end;
width: 178px;
height: 22px;
.right-box {
display: flex;
margin-top: 16px;
width: 89px;
height: 22px;
justify-content: flex-end;
.icon1 {
margin-top: 5px;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgba(20, 89, 187, 1);
}
.icon2 {
margin-top: 5px;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgba(250, 140, 22, 1);
}
.text {
margin-left: 5px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
}
}
.box3-main {
height: 397px;
}
}
.box4 {
margin-left: 20px;
width: 521px;
height: 450px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
.box4-header {
width: 452px;
margin: 0 auto;
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
.header-icon {
margin-top: 18px;
width: 18px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
.header-title {
margin-top: 16px;
margin-left: 26px;
height: 22px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 22px;
}
}
.box4-main {
width: 452px;
margin: 0 auto;
margin-top: 8px;
height: 360px;
overflow-y: auto;
.box4-main-item {
margin-top: 6px;
height: 30px;
display: flex;
.leftStatus3 {
color: rgba(255, 197, 61, 1) !important;
}
.leftStatus2 {
color: rgba(255, 169, 64, 1) !important;
}
.left {
width: 44px;
text-align: left;
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
line-height: 30px;
color: rgba(206, 79, 81, 1);
}
.center {
width: 300px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
}
.right {
width: 108px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 30px;
text-align: right;
}
}
}
}
}
}
.home-main-footer {
// width: 100%;
// height: 911px;
background: rgba(248, 249, 250, 1);
.home-main-footer-header {
margin-top: 37px;
margin-bottom: 36px;
// width: 1600px;
height: 42px;
// background: orange;
display: flex;
justify-content: space-between;
.btn-box {
width: 1300px;
display: flex;
justify-content: space-between;
.btn {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 42px;
padding: 0 24px;
border-radius: 21px;
background: rgba(20, 89, 187, 0);
cursor: pointer;
&:hover {
background: rgba(20, 89, 187, 0.1);
}
}
.btnActive {
padding: 0 24px;
border-radius: 21px;
background: rgba(20, 89, 187, 1);
color: #fff;
&:hover {
color: #fff;
background: rgba(20, 89, 187, 1);
}
}
}
.select-box {
height: 42px;
box-sizing: border-box;
padding: 5px 0;
}
}
.home-main-footer-main {
width: 100%;
// background: orange;
display: flex;
flex-wrap: wrap;
// justify-content: space-between;
// justify-content: center;
.main-item {
width: 240px;
height: 320px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: #fff;
margin-bottom: 24px;
margin-right: 25px;
.main-item-box1 {
margin-top: 20px;
margin-left: 45px;
width: 150px;
height: 200px;
box-sizing: border-box;
border: 1px solid rgba(240, 242, 244, 1);
img {
width: 100%;
height: 100%;
}
}
.main-item-box2 {
margin-top: 26px;
text-align: center;
height: 30px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
.main-item-box3 {
text-align: center;
height: 30px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
}
}
}
}
}
}
.tableName {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
.box1-bottom-content-item-imgUndefined {
width: 24px;
height: 24px;
font-size: 14px;
font-weight: 700;
flex-shrink: 0;
color: rgb(5, 95, 194);
background-color: rgb(236, 245, 255);
line-height: 24px;
text-align: center;
border-radius: 12px;
}
}
.num-item {
width: 280px;
display: flex;
.name-item {
width: 215px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.dialog-title {
text-align: center;
font-size: 24px;
font-weight: 700;
font-family: $base-font-family;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.dialog-ett-wrpper {
display: flex;
flex-wrap: wrap;
gap: 10px;
height: 500px;
.box1-bottom-content {
display: flex;
gap: 15px;
flex-wrap: wrap;
justify-content: space-between;
padding-left: 10px;
height: 156px;
overflow: auto;
&-item {
display: flex;
// align-items: center;
justify-content: flex-start;
width: 48%;
/* 留出2%的间距 */
// margin-bottom: 6px;
box-sizing: border-box;
gap: 10px;
cursor: pointer;
&-img {
width: 24px;
height: 24px;
flex-shrink: 0;
}
&-imgUndefined {
width: 24px;
height: 24px;
font-size: 14px;
font-weight: 700;
flex-shrink: 0;
color: rgba(5, 95, 194, 1);
background-color: rgb(236, 245, 255);
line-height: 24px;
text-align: center;
border-radius: 12px;
}
&-txt {
font-size: 16px;
font-weight: 400;
color: rgba(95, 101, 108, 1);
}
}
}
}
:deep(.el-input__wrapper) {
box-shadow: none;
// border-radius: 10px;
}
:deep(.el-input__wrapper:hover) {
// box-shadow: none !important;
}
:deep(.el-input__wrapper.is-focus) {
// box-shadow: none !important;
}
:deep(.el-table thead) {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
:deep(.el-table tr) {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.resource-tabs {
width: 100%;
display: flex;
align-items: center;
margin-top: 6px;
margin-bottom: 36px;
// padding-left: 10px;
.resource-tab-item {
margin-right: 12px;
cursor: pointer;
font-size: 20px;
color: rgb(59, 65, 75);
font-weight: 400;
font-family: "Microsoft YaHei";
padding: 8px 24px;
border-radius: 21px;
transition: all 0.3s;
&:last-child {
margin-right: 0;
}
&.active {
background: rgb(5, 95, 194);
color: #ffffff;
font-weight: 700;
}
&.disabled {
cursor: not-allowed;
color: #999999;
background: transparent;
}
&:hover:not(.active):not(.disabled) {
color: #0a57a6;
}
}
}
.all-content {
width: 100%;
height: auto;
padding-bottom: 30px;
display: flex;
justify-content: space-between;
// align-items: center;
gap: 16px;
.left {
width: 360px;
height: auto;
align-self: flex-start;
background: #fff;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.title {
width: 100%;
height: 56px;
display: flex;
align-items: center;
padding: 14px 12px 16px 0;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 16px;
font-weight: 700;
font-family: "Source Han Sans CN";
line-height: 24px;
color: rgb(5, 95, 194);
}
}
.left-main {
width: 100%;
height: auto;
padding-left: 24px;
.checkbox-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
row-gap: 16px;
padding-bottom: 16px;
}
:deep(.el-checkbox) {
margin-right: 0;
height: auto;
}
:deep(.el-checkbox__label) {
font-size: 16px;
color: #666666;
font-weight: 400;
}
}
}
.right {
width: 1224px;
height: auto;
background: #fff;
border-radius: 4px;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.right-title {
width: 100%;
height: 48px;
border-bottom: 1px solid rgb(234, 236, 238);
display: flex;
align-items: center;
img {
width: 22px;
height: 18px;
margin-left: 19px;
}
div {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
margin-left: 19px;
}
}
.right-main {
width: 100%;
height: auto;
padding: 24px 35px 0 20px;
.sanction-list {
width: 1169px;
padding: 0px 0 12px 0;
display: flex;
position: relative;
&:not(:last-child)::after {
content: "";
position: absolute;
left: 111px; // 80px(time width) + 16px(margin) + 15px(30px img half)
top: 44px; // 14px(img margin-top) + 30px(img height)
bottom: -14px; // 延伸到下一个图标的顶部
width: 2px;
background-color: rgb(234, 236, 238);
z-index: 1;
}
// justify-content: flex-start;
.time {
width: 80px;
// height: 50px;
// font-size: 16px;
// font-weight: 700;
// line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(5, 95, 194);
margin-right: 16px;
display: flex;
flex-direction: column;
align-items: flex-end;
.year {
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
.date {
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
}
img {
width: 30px;
height: 30px;
border-radius: 50%;
margin-top: 14px;
margin-right: 16px;
}
.main {
width: 1027px;
padding-top: 14px;
position: relative;
.main-title {
width: 800px;
font-size: 20px;
font-weight: 700;
line-height: 26px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
margin-bottom: 11px;
cursor: pointer;
}
.main-desc {
font-size: 16px;
font-weight: 400;
line-height: 30px;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
margin-bottom: 9px;
}
.tag-box {
display: flex;
.tag-item {
padding: 1px 8px;
margin-right: 8px;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
line-height: 22px;
font-family: "Microsoft YaHei";
color: rgb(5, 95, 194);
background-color: rgba(231, 243, 255, 1);
}
}
.count-tag {
position: absolute;
padding: 2px 8px;
top: 0;
right: 0;
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(206, 79, 81);
border-radius: 20px;
background-color: rgba(206, 79, 81, 0.1);
}
}
}
}
.right-footer {
width: 100%;
height: 73px;
border-top: 1px solid rgb(234, 236, 238);
padding: 0 31px;
display: flex;
justify-content: space-between;
align-items: center;
.total-count {
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
}
}
}
}
.search-header {
width: 100%;
height: 144px;
background: #fff;
overflow: hidden;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.3);
.home-main-header-center {
margin-top: 20px;
margin-left: 200px;
width: 800px;
height: 48px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
padding: 1px;
position: relative;
border: 1px solid transparent;
&:hover {
border: 1px solid var(--color-main-active);
}
.search {
position: absolute;
right: -1px;
top: 0px;
width: 120px;
height: 46px;
border-radius: 10px;
background: var(--color-main-active);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.search-icon {
width: 18px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
.search-text {
margin-left: 8px;
height: 22px;
color: #fff;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
}
}
}
.home-main-header-btn-box {
margin-top: 20px;
margin-left: 200px;
display: flex;
gap: 16px;
.btn {
display: flex;
align-items: center;
gap: 9px;
width: 160px;
height: 48px;
border: 1px solid #aed6ff;
box-sizing: border-box;
border-radius: 24px;
background: #e7f3ff;
cursor: pointer;
position: relative;
&:hover {
background: #cae3fc;
}
.btn-text {
width: 80px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 400;
line-height: 48px;
margin-left: 36px;
text-align: center;
}
.btn-icon {
position: absolute;
top: 16px;
right: 19px;
width: 6px;
height: 12px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
.scroll-main {
// height: calc(100% - 144px) !important;
}
.center-center {
width: 1600px;
margin: 0 auto;
margin-top: 21px;
height: 450px;
display: flex;
gap: 16px;
.center-center-news {
flex-shrink: 0;
}
.boxs4 {
margin-left: 20px;
width: 792px;
height: 450px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(25, 69, 130, 0.2);
background: rgba(255, 255, 255, 1);
}
}
.data-origin-box {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 22px 0;
.data-origin-icon {
width: 16px;
height: 16px;
font-size: 0px;
margin-right: 8px;
img {
width: 100%;
height: 100%;
}
}
.data-origin-text {
font-family: Source Han Sans CN;
font-size: 14px;
color: var(--text-primary-50-color);
}
}
.ai-pane {
position: absolute;
right: 0px;
bottom: 15px;
z-index: 999;
:deep(.ai-pane-wrapper) {
display: none;
}
:deep(.ai-button-wrapper) {
display: flex;
}
&:hover {
width: 100%;
bottom: 0px;
:deep(.ai-pane-wrapper) {
display: block;
}
:deep(.ai-button-wrapper) {
display: none;
}
}
}
</style>
......@@ -688,6 +688,7 @@
import NewsList from "@/components/base/newsList/index.vue";
import RiskSignal from "@/components/base/riskSignal/index.vue";
import SimplePagination from "@/components/SimplePagination.vue";
import { ElMessage } from "element-plus";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/index.vue";
import { onMounted, ref, computed, reactive, shallowRef, watch, nextTick } from "vue";
......@@ -702,7 +703,7 @@ import tipsIcon from "./assets/icons/info-icon.png";
import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
import AreaTag from "@/components/base/AreaTag/index.vue";
import { useChartInterpretation } from "@/views/exportControl/utils/common";
// import { useChartInterpretation } from "@/views/exportControl/utils/common";
import { TAGTYPE } from "@/public/constant";
import { useGotoCompanyPages } from "@/router/modules/company";
import { useGotoNewsDetail } from "@/router/modules/news";
......@@ -846,9 +847,12 @@ const handleTitleClick = item => {
const handleCompClick = item => {
// console.log("item", item);
if (!item.id) return;
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
window.sessionStorage.setItem("curTabName", item.name);
gotoCompanyPages(item.entityId);
gotoCompanyPages(item.id);
};
const tagsType = ["primary", "success", "warning", "danger"];
......@@ -1186,6 +1190,10 @@ const processYearDomainCountData = yearDomainCountData => {
const handleEntityClick = item => {
console.log("item", item);
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
window.sessionStorage.setItem("curTabName", item.name || item.entityNameZh);
gotoCompanyPages(item.id);
};
......@@ -2596,14 +2604,14 @@ const handleMediaClick = item => {
}
.box4-footer {
// position: absolute;
position: absolute;
// margin-top: auto;
display: flex;
justify-content: center;
align-items: center;
bottom: 30px;
bottom: 0px;
left: 50%;
margin-left: -30px;
margin-left: -40px;
// margin-bottom: 30px;
}
}
......
......@@ -270,6 +270,7 @@ import {
getTechDomainCount,
getEntityTypeCount
} from "@/api/exportControlV2.0";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import EChart from "@/components/Chart/index.vue";
import { useRoute } from "vue-router";
import { useRouter } from "vue-router";
......
......@@ -80,6 +80,7 @@
<script setup>
import { ref, computed, watch } from "vue";
import router from "@/router";
import { ElMessage } from "element-plus";
import { Close } from "@element-plus/icons-vue";
import defaultIcon from "@/assets/icons/default-icon1.png";
import { useGotoCompanyPages } from "@/router/modules/company";
......@@ -155,6 +156,10 @@ const getTagStyle = tag => {
// 跳转公司详情页
const handleCompClick = item => {
console.log("item", item);
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
window.sessionStorage.setItem("curTabName", item.name || item.orgName);
gotoCompanyPages(item.id);
};
......
......@@ -184,6 +184,7 @@
<script setup>
import { ref, computed, onMounted, watch } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { Search } from "@element-plus/icons-vue";
import defaultIcon from "../../../../../assets/icons/default-avatar.png";
import RuleSubsidiaryDialog from "./RuleSubsidiaryDialog.vue";
......@@ -203,6 +204,10 @@ const orderOptions = ref([
// 跳转公司详情页
const handleCompClick = item => {
console.log("item", item);
if (!item.entityId) {
ElMessage.warning("暂无对应数据");
return;
}
window.sessionStorage.setItem("curTabName", item.entityNameZh || item.entityName);
// const route = router.resolve({
// name: "companyPages",
......
......@@ -124,7 +124,7 @@ const headerNavList = ref([
height: 148px;
background-color: #fff;
padding-top: 16px;
position: sticky;
// position: sticky;
top: 0;
z-index: 1000;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.05);
......
......@@ -150,25 +150,28 @@
<div class="map-wrapper">
<div class="map-chart" ref="mapChartRef"></div>
<div class="rank-list">
<div
class="rank-item"
v-for="(item, index) in regionDistribution"
:key="index"
@click="handleToDataLibrary6(item)"
>
<div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="rank-name">{{ item.name }}</div>
<div class="rank-bar-bg">
<div
class="rank-bar-fill"
:style="{
width: (maxRegionCount > 0 ? (item.count / maxRegionCount) * 100 : 0) + '%',
background: getBarColor(index)
}"
></div>
<div class="rank-list-if" v-if="regionDistribution.length > 0">
<div
class="rank-item"
v-for="(item, index) in regionDistribution"
:key="index"
@click="handleToDataLibrary6(item)"
>
<div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="rank-name">{{ item.name }}</div>
<div class="rank-bar-bg">
<div
class="rank-bar-fill"
:style="{
width: (maxRegionCount > 0 ? (item.count / maxRegionCount) * 100 : 0) + '%',
background: getBarColor(index)
}"
></div>
</div>
<div class="rank-value">{{ item.count }}</div>
</div>
<div class="rank-value">{{ item.count }}</div>
</div>
<el-empty v-else :style="{ height: '90%' }" />
</div>
</div>
<div class="data-origin-box">
......@@ -215,10 +218,10 @@ import {
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import { useChartInterpretation } from "@/views/exportControl/utils/common";
const domainChart = useChartInterpretation();
const typeChart = useChartInterpretation();
const countryDistributionChart = useChartInterpretation();
const regionDistributionChart = useChartInterpretation();
// const domainChart = useChartInterpretation();
// const typeChart = useChartInterpretation();
// const countryDistributionChart = useChartInterpretation();
// const regionDistributionChart = useChartInterpretation();
const route = useRoute();
// 单次制裁-数据统计-制裁实体地域分布情况
......
......@@ -61,12 +61,6 @@
</div>
</template>
<div class="right-main" v-loading="scaleLoading">
<!-- <div class="echarts" ref="chartRef"></div> -->
<!-- <div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业营收初期下降,后基本趋于稳定。</span>
<img :src="right" class="right-icon" alt="" />
</div> -->
<EChart :option="revenueChartOption" autoresize :style="{ height: '300px' }" />
<div class="data-origin-box">
<div class="data-origin-icon">
......@@ -75,32 +69,19 @@
<div class="data-origin-text">数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="revenueChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="revenueChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('revenueChart')" />
<AiPane
v-if="aiPaneVisible?.revenueChart"
:aiContent="overviewAiContent.revenueChart"
@mouseleave="handleHideAiPane('revenueChart')"
/>
</div>
</div>
</AnalysisBox>
</div>
<div class="right-item">
<!-- <div class="title-com">
<div class="box"></div>
<div class="text">企业市值变化</div>
<div class="right-group">
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="marketChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业营收初期下降,后基本趋于稳定。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div> -->
<AnalysisBox title="企业市值变化">
<div class="right-main">
<!-- <div class="echarts" ref="marketChartRef"></div>
......@@ -117,38 +98,19 @@
<div class="data-origin-text">数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="marketChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="marketChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('marketChart')" />
<AiPane
v-if="aiPaneVisible?.marketChart"
:aiContent="overviewAiContent.marketChart"
@mouseleave="handleHideAiPane('marketChart')"
/>
</div>
</div>
</AnalysisBox>
</div>
<div class="right-item">
<!-- <div class="title-com">
<div class="box"></div>
<div class="text">企业研发投入</div>
<div class="right-group">
<div class="toggle-btns">
<div class="t-btn" :class="{ active: activeRD === item }" v-for="item in rdOptions" :key="item"
@click="activeRD = item">
{{ item }}
</div>
</div>
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="rdChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业研发资金投入逐渐提高。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div> -->
<AnalysisBox title="企业研发投入">
<template #header-btn>
<div class="toggle-btns">
......@@ -178,38 +140,19 @@
<div class="data-origin-text">数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="rdChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="rdChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('rdChart')" />
<AiPane
v-if="aiPaneVisible?.rdChart"
:aiContent="overviewAiContent.rdChart"
@mouseleave="handleHideAiPane('rdChart')"
/>
</div>
</div>
</AnalysisBox>
</div>
<div class="right-item">
<!-- <div class="title-com">
<div class="box"></div>
<div class="text">企业市场占比</div>
<div class="right-group">
<div class="toggle-btns">
<div class="t-btn" :class="{ active: activeMarketShare === item }" v-for="item in marketShareOptions"
:key="item" @click="activeMarketShare = item">
{{ item }}
</div>
</div>
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="shareChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业营收初期下降,后基本趋于稳定。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div> -->
<AnalysisBox title="企业市场占比">
<template #header-btn>
<div class="toggle-btns">
......@@ -239,8 +182,14 @@
<div class="data-origin-text">数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="shareChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="shareChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('shareChart')" />
<AiPane
v-if="aiPaneVisible?.shareChart"
:aiContent="overviewAiContent.shareChart"
@mouseleave="handleHideAiPane('shareChart')"
/>
</div>
</div>
</AnalysisBox>
......@@ -269,6 +218,7 @@ import {
getSingleSanctionEntityRDInvestment,
getSingleSanctionEntityMarketShare
} from "@/api/exportControlV2.0";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
import EChart from "@/components/Chart/index.vue";
......@@ -303,7 +253,8 @@ const getMarketShare = async () => {
nextTick(() => {
initShareChart();
});
shareChart.interpret({ type: "柱状图", name: "企业市场占比", data: sortedData });
// shareChart.interpret({ type: "柱状图", name: "企业市场占比", data: sortedData });
shareChartData1.value = sortedData;
}
}
} catch (error) {
......@@ -337,7 +288,8 @@ const getRDInvestment = async () => {
nextTick(() => {
initRDChart();
});
rdChart.interpret({ type: "折线图", name: "企业研发投入", data: sortedData });
// rdChart.interpret({ type: "折线图", name: "企业研发投入", data: sortedData });
rdChartData1.value = sortedData;
}
}
} catch (error) {
......@@ -371,7 +323,8 @@ const getMarketValue = async () => {
nextTick(() => {
initMarketChart();
});
marketChart.interpret({ type: "折线图", name: "企业市值变化", data: sortedData });
// marketChart.interpret({ type: "折线图", name: "企业市值变化", data: sortedData });
marketChartData1.value = sortedData;
}
}
} catch (error) {
......@@ -408,7 +361,8 @@ const getPersonnel = async () => {
nextTick(() => {
initRevenueChart();
});
revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
// revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
revenueChartData.value = sortedData;
}
} catch (error) {
console.log(error);
......@@ -445,7 +399,8 @@ const getNetProfitData = async () => {
nextTick(() => {
initRevenueChart();
});
revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
// revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
revenueChartData.value = sortedData;
}
} catch (error) {
console.log(error);
......@@ -487,7 +442,8 @@ const getRevenueData = async () => {
nextTick(() => {
initRevenueChart();
});
revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
// revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
revenueChartData.value = sortedData;
}
} catch (error) {
console.log(error);
......@@ -923,6 +879,128 @@ const initShareChart = () => {
shareChartOption.value = option;
};
const requestAiPaneContent = async key => {
if (!key || aiPaneLoading.value[key] || aiPaneFetched.value[key]) return;
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: true };
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成中..." };
try {
const payload = buildAiChartPayload(key);
const res = await getChartAnalysis(
{ text: JSON.stringify(payload) },
{
onChunk: chunk => {
const current = overviewAiContent.value[key];
const base = current === "智能总结生成中..." ? "" : current;
overviewAiContent.value = {
...overviewAiContent.value,
[key]: base + chunk
};
}
}
);
const list = res?.data;
const first = Array.isArray(list) ? list[0] : null;
const interpretation = first?.解读 || first?.["解读"];
// 流式已渲染过内容,最终用解析出的解读覆盖(保证显示格式统一)
if (interpretation) {
overviewAiContent.value = {
...overviewAiContent.value,
[key]: interpretation
};
}
aiPaneFetched.value = { ...aiPaneFetched.value, [key]: true };
} catch (error) {
console.error("获取图表解读失败", error);
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成失败" };
} finally {
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: false };
}
};
const revenueChartData = ref([]);
const marketChartData1 = ref([]);
const rdChartData1 = ref([]);
const shareChartData1 = ref([]);
const aiPaneVisible = ref({
revenueChart: false,
marketChart: false,
rdChart: false,
shareChart: false
});
const overviewAiContent = ref({
revenueChart: "智能总结生成中...",
marketChart: "智能总结生成中...",
rdChart: "智能总结生成中...",
shareChart: "智能总结生成中..."
});
const aiPaneFetched = ref({
revenueChart: false,
marketChart: false,
rdChart: false,
shareChart: false
});
const aiPaneLoading = ref({
revenueChart: false,
marketChart: false,
rdChart: false,
shareChart: false
});
const chartLoading = ref({
revenueChart: false,
marketChart: false,
rdChart: false,
shareChart: false
});
const buildAiChartPayload = key => {
if (key === "revenueChart") {
return { type: "折线图", name: "企业规模", data: revenueChartData.value };
}
if (key === "marketChart") {
return { type: "折线图", name: "企业市场占比", data: marketChartData1.value };
}
if (key === "rdChart") {
return {
type: "折线图",
name: "企业研发投入",
data: rdChartData1.value
};
}
if (key === "shareChart") {
return {
type: "折线图",
name: "企业市场份额",
data: shareChartData1.value
};
}
return { type: "", name: "", data: [] };
};
const handleShowAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: true
};
requestAiPaneContent(key);
};
const handleHideAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: false
};
};
onMounted(async () => {
getUrlParams();
// 单次制裁-影响分析-制裁企业列表-查询 (获取到默认的 selectedCompanyId 后会触发 watch 加载其他数据)
......
......@@ -80,11 +80,19 @@
<div class="content">{{ item.content }}</div>
</div>
</div>
<div class="view-more" v-if="hasMore" @click="loadMore">
<!-- <div class="view-more" v-if="hasMore" @click="loadMore">
查看更多
<el-icon class="icon-more">
<DArrowRight />
</el-icon>
</div> -->
<div class="left-bottom-footer">
<simple-pagination
v-model:current-page="timelinePage"
:page-size="timelinePageSize"
:total="totalNum"
@page-change="handleListPageChange"
/>
</div>
</div>
</AnalysisBox>
......@@ -361,8 +369,16 @@ const getSanctionOverviewList = async () => {
// 单次制裁-制裁概况-制裁背景
const timelinePage = ref(1);
const timelinePageSize = ref(5);
const totalNum = ref(0);
const hasMore = ref(true);
const handleListPageChange = pageNum => {
console.log("页面修改 =>", pageNum);
timelinePage.value = pageNum;
getSanctionBackground();
};
const getSanctionBackground = async (isLoadMore = false) => {
try {
const res = await getSingleSanctionBackground({
......@@ -386,6 +402,7 @@ const getSanctionBackground = async (isLoadMore = false) => {
// 判断是否还有更多数据
const totalElements = res.data.totalElements || 0;
totalNum.value = totalElements;
hasMore.value = timelineData.value.length < totalElements;
}
} catch (error) {
......@@ -783,7 +800,7 @@ onMounted(() => {
.left-bottom {
width: 100%;
height: 521px;
height: auto;
.left-bottom-content {
padding: 20px 25px 0 25px;
......
......@@ -248,7 +248,7 @@ const handlePageChange = async newPage => {
// ========== 选择某项 ==========
const selectSanction = async item => {
// console.log('item', item);
selectedSanctionId.value = item.id;
router.replace({
path: window.location.pathname,
......@@ -292,7 +292,7 @@ onMounted(() => {
height: 148px;
background-color: #fff;
padding-top: 16px;
position: sticky;
// position: sticky;
top: 0;
z-index: 1000;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.05);
......
......@@ -83,6 +83,7 @@
<script setup>
import { ref, onMounted, shallowReactive, shallowRef, watch } from "vue";
import { ElMessage } from "element-plus";
import CardCustom from "../../components/CardCustom.vue";
import Echarts from "@/components/Chart/index.vue";
import Hint from "./hint.vue";
......@@ -164,6 +165,10 @@ const handleEttClick = item => {
// }
// });
// window.open(route.href, "_blank");
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
gotoCompanyPages(item.id);
};
// 处理点击事件
......
......@@ -87,6 +87,7 @@
<script setup>
import { ref, shallowRef, onMounted, watch } from "vue";
import { ElMessage } from "element-plus";
import CardCustom from "../../components/CardCustom.vue";
import { Search } from "@element-plus/icons-vue";
import Echarts from "@/components/Chart/index.vue";
......@@ -350,6 +351,10 @@ const handleOrgClick = item => {
// }
// });
// window.open(route.href, "_blank");
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
gotoCompanyPages(item.id);
};
</script>
......
......@@ -66,6 +66,7 @@
<script setup>
import { ref, shallowRef, onMounted, watch } from "vue";
import { ElMessage } from "element-plus";
import CardCustom from "../../components/CardCustom.vue";
import { Search } from "@element-plus/icons-vue";
import Echarts from "@/components/Chart/index.vue";
......@@ -313,6 +314,10 @@ const handleOrgClick = item => {
// }
// });
// window.open(route.href, "_blank");
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
gotoCompanyPages(item.id);
};
</script>
......
......@@ -157,7 +157,7 @@
<CardCustom title="美国前序相关制裁、前序相关事件列表" :style="{ width: '600px', height: '678px' }">
<div class="panel6">
<div class="panel6-list">
<div class="item" v-for="(item, idx) in panel6" :key="item.title">
<div class="item" v-for="item in panel6" :key="item.title">
<div class="left">
<div class="icon"></div>
<div class="line"></div>
......@@ -182,6 +182,7 @@
<script setup>
import CardCustom from "../../components/CardCustom.vue";
import PieCharts from "../components/pieCharts.vue";
import { ElMessage } from "element-plus";
import MapCharts from "../components/mapCharts.vue";
import ButtonList from "@/components/buttonList/buttonList.vue";
import Hint from "../components/hint.vue";
......@@ -415,6 +416,10 @@ const handleOrgClick = item => {
// }
// });
// window.open(route.href, "_blank");
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
gotoCompanyPages(item.id);
};
......
......@@ -104,60 +104,31 @@
</div>
</div>
</template>
<EChart :option="sanctionCountChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" @chart-click="handleToDataLibrary4" />
<EChart
:option="sanctionCountChartOption"
autoresize
:style="{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary4"
/>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">
进入SDN清单的中国实体数量变化趋势,数据来源:美国财政部海外资产管理办公室官网
</div>
<div class="data-origin-text">数据来源:美国财政部海外资产管理办公室官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="sanctionCountChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="sanctionCountChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('sanctionCountChart')" />
<AiPane
v-if="aiPaneVisible?.sanctionCountChart"
:aiContent="overviewAiContent.sanctionCountChart"
@mouseleave="handleHideAiPane('sanctionCountChart')"
/>
</div>
</AnalysisBox>
</div>
<!-- <div class="main-item">
<AnalysisBox title="制裁实体各省分布情况">
<template #header-btn>
<el-select v-model="regionTime" class="time-select" placeholder="请选择" @change="getRegionCountData">
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<div class="map-wrapper">
<div class="map-chart" ref="mapChartRef"></div>
<div class="rank-list">
<div class="rank-item" v-for="(item, index) in rankData" :key="index">
<div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="rank-name">{{ item.name }}</div>
<div class="rank-bar-bg">
<div
class="rank-bar-fill"
:style="{
width: (item.value / maxRankValue) * 100 + '%',
background: getBarColor(index)
}"
></div>
</div>
<div class="rank-value">{{ item.value }}家</div>
</div>
</div>
</div>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">进入实体清单的中国实体各省分布情况,数据来源:美国商务部官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="rankChart.interpretation" />
</div>
</AnalysisBox>
</div> -->
<div class="main-item">
<AnalysisBox title="制裁实体领域分布情况">
<template #header-btn>
......@@ -165,19 +136,28 @@
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<EChart :option="domainChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" @chart-click="handleToDataLibrary6" />
<EChart
:option="domainChartOption"
autoresize
:style="{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary6"
/>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">
进入SDN清单的中国实体领域分布情况,数据来源:美国财政部海外资产管理办公室官网
</div>
<div class="data-origin-text">数据来源:美国财政部海外资产管理办公室官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="domainChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="domainChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('domainChart')" />
<AiPane
v-if="aiPaneVisible?.domainChart"
:aiContent="overviewAiContent.domainChart"
@mouseleave="handleHideAiPane('domainChart')"
/>
</div>
</AnalysisBox>
</div>
......@@ -190,28 +170,44 @@
</template> -->
<template #header-btn>
<div class="toggle-btns">
<div class="t-btn" :class="{ active: activeDomainTab === 'year' }" @click="handleDomainTabChange('year')">
<div
class="t-btn"
:class="{ active: activeDomainTab === 'year' }"
@click="handleDomainTabChange('year')"
>
按年度
</div>
<div class="t-btn" :class="{ active: activeDomainTab === 'sanction' }"
@click="handleDomainTabChange('sanction')">
<div
class="t-btn"
:class="{ active: activeDomainTab === 'sanction' }"
@click="handleDomainTabChange('sanction')"
>
按制裁
</div>
</div>
</template>
<EChart :option="domainNumChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" @chart-click="handleToDataLibrary5" />
<EChart
:option="domainNumChartOption"
autoresize
:style="{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary5"
/>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">
进入SDN清单的中国实体领域分布情况,数据来源:美国财政部海外资产管理办公室官网
</div>
<div class="data-origin-text">数据来源:美国财政部海外资产管理办公室官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="domainNumChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="domainNumChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('domainNumChart')" />
<AiPane
v-if="aiPaneVisible?.domainNumChart"
:aiContent="overviewAiContent.domainNumChart"
@mouseleave="handleHideAiPane('domainNumChart')"
/>
</div>
</AnalysisBox>
</div>
......@@ -223,7 +219,12 @@
</el-select>
</template>
<!-- <div class="echarts" ref="typeChartRef"></div> -->
<EChart :option="typeChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" @chart-click="handleToDataLibrary7" />
<EChart
:option="typeChartOption"
autoresize
:style="{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary7"
/>
<!-- <div class="bottom">
<div class="ai">
<div class="left">
......@@ -239,13 +240,17 @@
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">
进入SDN清单的中国实体类型分布情况,数据来源:美国财政部海外资产管理办公室官网
</div>
<div class="data-origin-text">数据来源:美国财政部海外资产管理办公室官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="typeChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="typeChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('typeChart')" />
<AiPane
v-if="aiPaneVisible?.typeChart"
:aiContent="overviewAiContent.typeChart"
@mouseleave="handleHideAiPane('typeChart')"
/>
</div>
</AnalysisBox>
</div>
......@@ -257,8 +262,6 @@
import { ref, onMounted, computed } from "vue";
import * as echarts from "echarts";
import chinaJson from "../../../utils/China.json";
import ai from "./assets/ai.png";
import right from "./assets/right.png";
import {
getTotalCount,
getSanctionCountChange,
......@@ -266,6 +269,7 @@ import {
getTechDomainCount,
getEntityTypeCount
} from "@/api/exportControlV2.0";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import { getDomainNum } from "@/api/finance";
import getMultiLineChart from "@/views/ZMOverView/components/fourSuppress/components/addDomain/multiLineChart";
import EChart from "@/components/Chart/index.vue";
......@@ -281,7 +285,7 @@ const domainNumChart = useChartInterpretation();
const typeChart = useChartInterpretation();
const rankChart = useChartInterpretation();
const router = useRouter()
const router = useRouter();
const route = useRoute();
// 实体清单-数据统计-制裁实体类型分布情况
const typeData = ref([]);
......@@ -305,7 +309,8 @@ const getTypeCountData = async () => {
value: item.count || item.value
}));
updateTypeChart();
typeChart.interpret({ type: "饼图", name: "制裁实体类型分布情况", data: data });
// typeChart.interpret({ type: "饼图", name: "制裁实体类型分布情况", data: data });
typeChartData.value = data;
}
} catch (error) {
console.error("获取实体清单-数据统计-制裁实体类型分布情况失败:", error);
......@@ -333,7 +338,8 @@ const getDomainCountData = async () => {
value: item.count || item.value
}));
updateDomainChart();
domainChart.interpret({ type: "饼图", name: "制裁实体领域分布情况", data: data });
// domainChart.interpret({ type: "饼图", name: "制裁实体领域分布情况", data: data });
domainChartData.value = data;
}
} catch (error) {
console.error("获取实体清单-数据统计-制裁实体领域分布情况失败:", error);
......@@ -370,7 +376,7 @@ const getRegionCountData = async () => {
}));
// Sort by value descending
rankData.value.sort((a, b) => b.value - a.value);
rankChart.interpret({ type: "柱状图", name: "制裁实体各省分布情况", data: data });
// rankChart.interpret({ type: "柱状图", name: "制裁实体各省分布情况", data: data });
updateMapChart();
}
} catch (error) {
......@@ -530,7 +536,8 @@ const getDomainNumData = async () => {
domainNumChartOption.value = getMultiLineChart(processedData);
console.log("获取实体清单-数据统计-processedData:", processedData);
console.log("获取实体清单-数据统计-domainNumChartOption:", res);
domainNumChart.interpret({ type: "折线图", name: "制裁实体领域数量变化情况", data: res });
// domainNumChart.interpret({ type: "折线图", name: "制裁实体领域数量变化情况", data: res });
domainNumChartData.value = res;
}
} catch (error) {
console.error("获取实体清单-数据统计-制裁实体领域数量变化趋势失败:", error);
......@@ -549,7 +556,8 @@ const getSanctionCountChangeData = async () => {
const res = await getSanctionCountChange(param);
sanctionCountChange.value = res.data || [];
updateSanctionCountChart();
sanctionCountChart.interpret({ type: "饼图", name: "制裁实体数量变化情况", data: res.data });
// sanctionCountChart.interpret({ type: "饼图", name: "制裁实体数量变化情况", data: res.data });
sanctionCountChartData.value = res.data;
} catch (error) {
console.error("获取实体清单-数据统计-制裁实体数量变化情况失败:", error);
}
......@@ -1141,10 +1149,10 @@ const updateTypeChart = () => {
let data = typeData.value.length
? [...typeData.value]
: [
{ value: 50, name: "企业" },
{ value: 32, name: "高校" },
{ value: 32, name: "科研院所" }
];
{ value: 50, name: "企业" },
{ value: 32, name: "高校" },
{ value: 32, name: "科研院所" }
];
// 2. 聚合逻辑:保留前5项,其余合并为“其他”
data.sort((a, b) => b.value - a.value);
......@@ -1275,7 +1283,7 @@ const handleToDataLibrary = () => {
}
});
window.open(route.href, "_blank");
}
};
const handleToDataLibrary1 = () => {
const route = router.resolve({
......@@ -1286,19 +1294,19 @@ const handleToDataLibrary1 = () => {
}
});
window.open(route.href, "_blank");
}
};
const handleToDataLibrary2 = () => {
const route = router.resolve({
path: "/dataLibrary/sDNList",
query: {
selectedDate: JSON.stringify([currentYear + '01-01', currentYear + '12-31'])
selectedDate: JSON.stringify([currentYear + "01-01", currentYear + "12-31"])
}
});
window.open(route.href, "_blank");
}
};
const handleToDataLibrary3 = (time) => {
const handleToDataLibrary3 = time => {
const route = router.resolve({
path: "/dataLibrary/sDNList",
query: {
......@@ -1306,8 +1314,7 @@ const handleToDataLibrary3 = (time) => {
}
});
window.open(route.href, "_blank");
}
};
// 点击制裁实体数量变化情况
const handleToDataLibrary4 = val => {
......@@ -1321,20 +1328,19 @@ const handleToDataLibrary4 = val => {
};
const route = router.resolve({
path: "/dataLibrary/sDNList",
query: params,
query: params
});
window.open(route.href, "_blank");
};
// 制裁实体各省分布情况
const handleToDataLibrary5 = item => {
console.log('item', item);
console.log("item", item);
const params = {
domains: item.seriesName,
isCnEntityOnly: true,
selectedDate:
JSON.stringify([item.name + "-01-01", item.name + "-12-31"])
selectedDate: JSON.stringify([item.name + "-01-01", item.name + "-12-31"])
};
const route = router.resolve({
path: "/dataLibrary/sDNList",
......@@ -1372,13 +1378,140 @@ const handleToDataLibrary7 = val => {
query: params
});
window.open(route.href, "_blank");
}
};
const requestAiPaneContent = async key => {
if (!key || aiPaneLoading.value[key] || aiPaneFetched.value[key]) return;
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: true };
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成中..." };
try {
const payload = buildAiChartPayload(key);
const res = await getChartAnalysis(
{ text: JSON.stringify(payload) },
{
onChunk: chunk => {
const current = overviewAiContent.value[key];
const base = current === "智能总结生成中..." ? "" : current;
overviewAiContent.value = {
...overviewAiContent.value,
[key]: base + chunk
};
}
}
);
const list = res?.data;
const first = Array.isArray(list) ? list[0] : null;
const interpretation = first?.解读 || first?.["解读"];
// 流式已渲染过内容,最终用解析出的解读覆盖(保证显示格式统一)
if (interpretation) {
overviewAiContent.value = {
...overviewAiContent.value,
[key]: interpretation
};
}
aiPaneFetched.value = { ...aiPaneFetched.value, [key]: true };
} catch (error) {
console.error("获取图表解读失败", error);
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成失败" };
} finally {
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: false };
}
};
// const sanctionCountChart = useChartInterpretation();
// const domainChart = useChartInterpretation();
// const domainNumChart = useChartInterpretation();
// const typeChart = useChartInterpretation();
const sanctionCountChartData = ref([]);
const domainChartData = ref([]);
const domainNumChartData = ref([]);
const typeChartData = ref([]);
const aiPaneVisible = ref({
sanctionCountChart: false,
domainChart: false,
domainNumChart: false,
typeChart: false
});
const overviewAiContent = ref({
sanctionCountChart: "智能总结生成中...",
domainChart: "智能总结生成中...",
domainNumChart: "智能总结生成中...",
typeChart: "智能总结生成中..."
});
const aiPaneFetched = ref({
sanctionCountChart: false,
domainChart: false,
domainNumChart: false,
typeChart: false
});
const aiPaneLoading = ref({
sanctionCountChart: false,
domainChart: false,
domainNumChart: false,
typeChart: false
});
const chartLoading = ref({
sanctionCountChart: false,
domainChart: false,
domainNumChart: false,
typeChart: false
});
const buildAiChartPayload = key => {
if (key === "sanctionCountChart") {
return { type: "饼图", name: "制裁实体数量变化情况", data: sanctionCountChartData.value };
}
if (key === "domainChart") {
return { type: "饼图", name: "制裁实体领域分布情况", data: domainChartData.value };
}
if (key === "domainNumChart") {
return {
type: "折线图",
name: "制裁实体领域数量变化情况",
data: domainNumChartData.value
};
}
if (key === "typeChart") {
return {
type: "饼图",
name: "制裁实体类型分布情况",
data: typeChartData.value
};
}
return { type: "", name: "", data: [] };
};
const handleShowAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: true
};
requestAiPaneContent(key);
};
const handleHideAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: false
};
};
onMounted(() => {
sanTypeId.value = route.query.sanTypeId || "";
console.log("数据统计页面接收到的 sanTypeId:", sanTypeId.value);
// initSanctionCountChart();
initMapChart();
// initMapChart();
initDomainChart();
initTypeChart();
// 获取实体清单-数据统计-总量统计
......@@ -1386,7 +1519,7 @@ onMounted(() => {
// 获取实体清单-数据统计-制裁实体数量变化情况
getSanctionCountChangeData();
// 获取实体清单-数据统计-制裁实体地域分布情况
getRegionCountData();
// getRegionCountData();
// 获取实体清单-数据统计-制裁实体领域分布情况
getDomainCountData();
// 获取实体清单-数据统计-制裁实体类型分布情况
......
......@@ -114,7 +114,7 @@
</div>
</div>
</div>
<div class="content-item">
<div class="content-item content-item-table">
<div class="item-label">制裁对象:</div>
<div class="item-desc item-desc-table">
<span class="item-table-desc" v-if="vertexInfo.addObjectList?.length || vertexInfo.delObjectList?.length">
......@@ -1040,10 +1040,11 @@ onMounted(() => {
top: 20px;
right: 20px;
}
.content-item {
display: flex;
justify-content: flex-start;
align-items: flex-start;
align-items: center;
gap: 8px;
.item-label {
min-width: 75px;
......@@ -1109,5 +1110,9 @@ onMounted(() => {
}
}
}
.content-item-table {
align-items: flex-start;
}
}
</style>
......@@ -29,50 +29,7 @@
</AnalysisBox>
</div>
<div class="left-bottom">
<!-- <div class="title">
<div class="box"></div>
<div class="text">实体清单更新历史</div>
<div class="filters">
<el-select v-model="selectedDomain" placeholder="Select"
style="width: 150px; height: 32px; margin-right: 16px">
<el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-checkbox v-model="onlyChina">只看涉华动态</el-checkbox>
</div>
<div class="btn">
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
<div class="left-bottom-main">
<div class="sanction-list" v-for="item in sanctionList" :key="item.id">
<div class="time">
<div class="year">{{ item.year }}</div>
<div class="date">{{ item.date }}</div>
</div>
<img :src="item.icon || title" alt="" />
<div class="main">
<div class="main-title" @click="handleClick(item)">{{ item.name }}</div>
<el-tooltip effect="dark" :content="item.summary" popper-class="common-prompt-popper" placement="top"
:show-after="500">
<div class="main-desc">{{ item.summary }}</div>
</el-tooltip>
<div class="tag-box">
<div v-for="tag in item.techDomainList" :key="tag" class="tag-item">{{ tag }}</div>
</div>
<div :class="{ 'count-tag': item.cnEntityCount }">
{{ item.cnEntityCount ? `${item.cnEntityCount}家中国实体` : "" }}
</div>
</div>
</div>
</div>
<div class="left-footer">
<div class="total-count"> {{ totalAll }} </div>
<el-pagination v-model:current-page="currentPageAll" :page-size="pageSizeAll" :total="totalAll"
layout="prev, pager, next" background @current-change="handlePageChangeAll" />
</div> -->
<AnalysisBox title="实体清单更新历史" :showAllBtn="false">
<AnalysisBox :title="route.query.sanTypeId == 3 ? 'CMC清单更新历史' : 'SDN清单更新历史'" :showAllBtn="false">
<template #header-btn>
<div class="filters">
<el-select
......@@ -532,7 +489,12 @@ onMounted(() => {
.left-bottom {
width: 100%;
min-height: 1000px;
.filters {
margin-left: auto;
display: flex;
align-items: center;
margin-right: 20px;
}
.title {
border-bottom: 1px solid rgb(234, 236, 238);
......@@ -557,7 +519,17 @@ onMounted(() => {
width: 1169px;
padding: 0px 0 12px 0;
display: flex;
position: relative;
&:not(:last-child)::after {
content: "";
position: absolute;
left: 130px;
top: 44px;
bottom: -14px;
width: 2px;
background-color: rgb(234, 236, 238);
z-index: 1;
}
// justify-content: flex-start;
.time {
width: 100px;
......@@ -697,6 +669,7 @@ onMounted(() => {
display: flex;
margin-bottom: 20px;
padding: 16px;
align-items: center;
img {
width: 64px;
......@@ -724,7 +697,7 @@ onMounted(() => {
.right-main-key-person {
width: 100%;
padding-bottom: 20px;
// padding-bottom: 20px;
border-bottom: 1px solid rgb(234, 236, 238);
margin-bottom: 18px;
......
......@@ -79,6 +79,7 @@
<script setup>
import { ref, computed, watch } from "vue";
import { ElMessage } from "element-plus";
import router from "@/router";
import { Close } from "@element-plus/icons-vue";
import defaultIcon from "@/assets/icons/default-icon1.png";
......@@ -156,6 +157,10 @@ const getTagStyle = tag => {
const handleCompClick = item => {
console.log("item", item);
window.sessionStorage.setItem("curTabName", item.name || item.orgName);
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
gotoCompanyPages(item.id);
};
</script>
......
......@@ -63,7 +63,7 @@
</div>
</div>
<div class="right">
<AnalysisBox title="实体清单" :showAllBtn="false">
<AnalysisBox title="SDN清单列表" :showAllBtn="false">
<template #header-btn>
<div class="stats">
<div class="dot"></div>
......@@ -178,12 +178,14 @@
<script setup>
import { ref, computed, onMounted, watch } from "vue";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
import { Search } from "@element-plus/icons-vue";
import TimeSortSelectBox from "@/components/base/TimeSortSelectBox/index.vue";
import defaultIcon from "../../../../../assets/icons/default-avatar.png";
import RuleSubsidiaryDialog from "./RuleSubsidiaryDialog.vue";
import { getExportControlList, get50PercentEntityCount } from "@/api/exportControlV2.0.js";
import { getEntityTypeList } from "@/api/finance/index.js";
import CommonPrompt from "@/views/exportControl/commonPrompt/index.vue";
import { useGotoCompanyPages } from "@/router/modules/company";
const gotoCompanyPages = useGotoCompanyPages();
......@@ -205,7 +207,10 @@ const handleCompClick = item => {
// }
// });
// window.open(route.href, "_blank");
if (!item.entityId) return;
if (!item.entityId) {
ElMessage.warning("暂无对应数据");
return;
}
gotoCompanyPages(item.entityId);
};
......@@ -470,6 +475,22 @@ watch(searchKeyword, () => {
const sanTypeId = ref("");
onMounted(() => {
sanTypeId.value = router.currentRoute.value.query.sanTypeId || "1";
console.log("sanTypeId.value", sanTypeId.value);
if (sanTypeId.value == "3") {
getEntityTypeList().then(res => {
console.log("实体类型列表", res);
if (res.length > 0) {
entityTypes.value = [
{ label: "全部类型", value: "all", checked: true },
...res.map(item => ({
label: item.name,
value: item.id,
checked: false
}))
];
}
});
}
getExportControlListApi();
});
......
......@@ -14,10 +14,10 @@
</div>
<div class="content-box">
<introductionPage
v-show="activeIndex == 1"
v-show="activeIndex == 0"
@update-entity-info="data => $emit('update-entity-info', data)"
></introductionPage>
<listPage v-show="activeIndex == 0"></listPage>
<listPage v-show="activeIndex == 1"></listPage>
</div>
</div>
</template>
......@@ -26,10 +26,13 @@
import { ref, defineEmits } from "vue";
import introductionPage from "./components/introductionPage/index.vue";
import listPage from "./components/listPage/index.vue";
import { useRoute } from "vue-router";
const route = useRoute();
console.log("路由参数", route.query.sanTypeId);
const emit = defineEmits(["update-entity-info"]);
const activeTab = ref(["SDN清单列表", "SDN清单简介"]);
const activeTab = ref(route.query.sanTypeId == 3 ? ["CMC清单简介", "CMC清单列表"] : ["SDN清单简介", "SDN清单列表"]);
const activeIndex = ref(0);
const handleClickTab = index => {
......@@ -39,10 +42,6 @@ const handleClickTab = index => {
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.sanctions-overview {
width: 1601px;
margin: 0 auto;
......
......@@ -124,7 +124,7 @@ const headerNavList = ref([
height: 148px;
background-color: #fff;
padding-top: 16px;
position: sticky;
// position: sticky;
top: 0;
z-index: 1000;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.05);
......
......@@ -76,13 +76,7 @@
</div>
<div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 涉及领域:</span>
<!-- <div
class="box1-top-content-item-tags"
v-for="(domainItem, index) in item.domains"
:key="index"
>
<el-tag :type="getTagType(domainItem)">{{ domainItem }}</el-tag>
</div> -->
<AreaTag
v-for="(domainItem, index) in item.domains"
:key="index"
......@@ -150,21 +144,7 @@
<el-row :gutter="16" style="width: 1600px; margin: 0 auto; height: 50px; margin-top: 64px">
<CustomTitle id="position2" title="资讯要闻" />
</el-row>
<!-- <el-col :span="12">
<custom-container title="新闻资讯" :titleIcon="newsIcon" height="450px">
<template #header-right>
<el-button type="primary" link @click="handleToMoreNews">
{{ "更多 +" }}
</el-button>
</template>
<template #default>
<div class="news-list">
<NewsList :list-data="newsList" @item-click="item => handleNewsInfoClick(item)" />
</div>
</template>
</custom-container>
</el-col> -->
<div class="center-center">
<NewsList
:newsList="newsList"
......@@ -756,6 +736,7 @@
import NewsList from "@/components/base/newsList/index.vue";
import RiskSignal from "@/components/base/riskSignal/index.vue";
import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/index.vue";
import { ElMessage } from "element-plus";
import { onMounted, ref, computed, reactive, shallowRef, watch, nextTick } from "vue";
import { useContainerScroll } from "@/hooks/useScrollShow";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
......@@ -930,7 +911,10 @@ const handleTitleClick = item => {
const handleCompClick = item => {
// console.log("item", item);
if (!item.entityId) return;
if (!item.entityId) {
ElMessage.warning("暂无对应数据");
return;
}
window.sessionStorage.setItem("curTabName", item.name);
gotoCompanyPages(item.entityId);
// const route = router.resolve({
......@@ -1071,7 +1055,10 @@ const processYearDomainCountData = yearDomainCountData => {
const handleEntityClick = item => {
console.log("item", item);
if (!item.id) return;
if (!item.id) {
ElMessage.warning("暂无对应数据");
return;
}
window.sessionStorage.setItem("curTabName", item.orgName || item.orgNameZh);
gotoCompanyPages(item.id);
// const route = router.resolve({
......@@ -1139,6 +1126,8 @@ const handleToEntityList = item => {
// 跳转到V2.0实体清单无ID
const handleToEntityListNoId = item => {
console.log("item", item);
window.sessionStorage.setItem("curTabName", item.id == 2 ? "SDN制裁清单概览" : "CMC清单概览");
const routeData = router.resolve({
path: "/finance/sdnlistoverview",
query: {
......
......@@ -54,18 +54,27 @@
</div>
</div>
</div> -->
<EChart :option="domainChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" @chart-click="handleToDataLibrary3" />
<EChart
:option="domainChartOption"
autoresize
:style="{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary3"
/>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">
进入本次SDN清单的中国实体领域分布情况,数据来源:美国财政部海外资产管理办公室官网
</div>
<div class="data-origin-text">数据来源:美国财政部海外资产管理办公室官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="domainChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="domainChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('domainChart')" />
<AiPane
v-if="aiPaneVisible?.domainChart"
:aiContent="overviewAiContent.domainChart"
@mouseleave="handleHideAiPane('domainChart')"
/>
</div>
</AnalysisBox>
</div>
......@@ -106,32 +115,49 @@
</div>
</div>
</div> -->
<EChart :option="typeChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" @chart-click="handleToDataLibrary4" />
<EChart
:option="typeChartOption"
autoresize
:style="{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary4"
/>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">
进入本次SDN清单的中国实体类型分布情况,数据来源:美国财政部海外资产管理办公室官网
</div>
<div class="data-origin-text">数据来源:美国财政部海外资产管理办公室官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="typeChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="typeChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('typeChart')" />
<AiPane
v-if="aiPaneVisible?.typeChart"
:aiContent="overviewAiContent.typeChart"
@mouseleave="handleHideAiPane('typeChart')"
/>
</div>
</AnalysisBox>
</div>
<div class="main-item">
<AnalysisBox title="制裁实体国家地区分布情况">
<div class="country-list">
<div class="list-item" v-for="(item, index) in countryDistribution" :key="index" @click="handleToDataLibrary5(item)">
<div
class="list-item"
v-for="(item, index) in countryDistribution"
:key="index"
@click="handleToDataLibrary5(item)"
>
<img :src="flag" alt="" class="flag" />
<div class="country-name">{{ item.name }}</div>
<div class="progress-bar-container">
<div class="progress-bar" :style="{
width: item.width,
background: item.gradient
}"></div>
<div
class="progress-bar"
:style="{
width: item.width,
background: item.gradient
}"
></div>
</div>
<div class="count" :class="{ highlight: index === 0 }">{{ item.count }}</div>
</div>
......@@ -151,13 +177,17 @@
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">
进入本次SDN清单的实体国家地区分布情况,数据来源:美国财政部海外资产管理办公室官网
</div>
<div class="data-origin-text">数据来源:美国财政部海外资产管理办公室官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="countryDistributionChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="countryDistributionChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('countryDistributionChart')" />
<AiPane
v-if="aiPaneVisible?.countryDistributionChart"
:aiContent="overviewAiContent.countryDistributionChart"
@mouseleave="handleHideAiPane('countryDistributionChart')"
/>
</div>
</AnalysisBox>
</div>
......@@ -166,41 +196,45 @@
<div class="map-wrapper">
<div class="map-chart" ref="mapChartRef"></div>
<div class="rank-list">
<div class="rank-item" v-for="(item, index) in regionDistribution" :key="index" @click="handleToDataLibrary6(item)">
<div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="rank-name">{{ item.name }}</div>
<div class="rank-bar-bg">
<div class="rank-bar-fill" :style="{
width: (maxRegionCount > 0 ? (item.count / maxRegionCount) * 100 : 0) + '%',
background: getBarColor(index)
}"></div>
<div class="rank-list-if" v-if="regionDistribution.length > 0">
<div
class="rank-item"
v-for="(item, index) in regionDistribution"
:key="index"
@click="handleToDataLibrary6(item)"
>
<div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="rank-name">{{ item.name }}</div>
<div class="rank-bar-bg">
<div
class="rank-bar-fill"
:style="{
width: (maxRegionCount > 0 ? (item.count / maxRegionCount) * 100 : 0) + '%',
background: getBarColor(index)
}"
></div>
</div>
<div class="rank-value">{{ item.count }}</div>
</div>
<div class="rank-value">{{ item.count }}</div>
</div>
<el-empty v-else :style="{ height: '90%' }" />
</div>
</div>
<!-- <div class="bottom">
<div class="ai">
<div class="left">
<img :src="ai" alt="" class="icon1" />
<div class="text">我国被制裁实体多分布于沿海经济活跃省份。</div>
</div>
<div class="right">
<img :src="right" alt="" class="icon2" />
</div>
</div>
</div> -->
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">
进入本次SDN清单的中国实体各省分布情况,数据来源:美国财政部海外资产管理办公室官网
</div>
<div class="data-origin-text">数据来源:美国财政部海外资产管理办公室官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="regionDistributionChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="regionDistributionChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('regionDistributionChart')" />
<AiPane
v-if="aiPaneVisible?.regionDistributionChart"
:aiContent="overviewAiContent.regionDistributionChart"
@mouseleave="handleHideAiPane('regionDistributionChart')"
/>
</div>
</AnalysisBox>
</div>
......@@ -227,13 +261,13 @@ import {
getSingleSanctionEntityCountryCount,
getSingleSanctionEntityRegionCount
} from "@/api/exportControlV2.0";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import { useChartInterpretation } from "@/views/exportControl/utils/common";
const sanctionCountChart = useChartInterpretation();
const domainChart = useChartInterpretation();
const typeChart = useChartInterpretation();
const countryDistributionChart = useChartInterpretation();
const regionDistributionChart = useChartInterpretation();
// const domainChart = useChartInterpretation();
// const typeChart = useChartInterpretation();
// const countryDistributionChart = useChartInterpretation();
// const regionDistributionChart = useChartInterpretation();
const route = useRoute();
// 单次制裁-数据统计-制裁实体地域分布情况
......@@ -252,8 +286,9 @@ const getRegionData = async () => {
if (res.code === 200) {
regionDistribution.value = res.data || [];
maxRegionCount.value = Math.max(...regionDistribution.value.map(item => item.count), 0);
regionDistributionChartData.value = res.data || [];
initMapChart();
regionDistributionChart.interpret({ type: "柱状图", name: "进入本次实体清单的中国实体各省分布情况", data: res.data });
// regionDistributionChart.interpret({ type: "柱状图", name: "进入本次实体清单的中国实体各省分布情况", data: res.data });
}
} catch (error) {
console.log(error);
......@@ -293,11 +328,12 @@ const getCountryCount = async () => {
gradient
};
});
countryDistributionChart.interpret({
type: "柱状图",
name: "进入本次实体清单的实体国家地区分布情况",
data: res.data
});
countryDistributionChartData.value = res.data || [];
// countryDistributionChart.interpret({
// type: "柱状图",
// name: "进入本次实体清单的实体国家地区分布情况",
// data: res.data
// });
}
} catch (error) {
console.log(error);
......@@ -318,7 +354,7 @@ const getEntityTypeCount = async () => {
const res = await getSingleSanctionEntityTypeCount(params);
if (res.code === 200) {
entityTypeCount.value = res.data || [];
typeChart.interpret({ type: "饼图", name: "进入本次实体清单的中国实体类型分布情况", data: entityTypeCount.value });
// typeChart.interpret({ type: "饼图", name: "进入本次实体清单的中国实体类型分布情况", data: entityTypeCount.value });
initTypeChart();
}
} catch (error) {
......@@ -341,7 +377,7 @@ const getDomainCount = async () => {
if (res.code === 200) {
domainCount.value = res.data || [];
initDomainChart();
domainChart.interpret({ type: "饼图", name: "进入本次实体清单的中国实体领域分布情况", data: domainCount.value });
// domainChart.interpret({ type: "饼图", name: "进入本次实体清单的中国实体领域分布情况", data: domainCount.value });
}
} catch (error) {
console.log(error);
......@@ -391,14 +427,6 @@ for (let i = 2025; i >= 2000; i--) {
timeOptions.push({ label: `${i}年`, value: `${i}` });
}
// const countryDistribution = [
// { name: "中国", count: 24, width: "80%", gradient: "linear-gradient(90deg, rgba(205, 66, 70, 0) 0%, rgba(205, 66, 70, 1) 100%)" },
// { name: "沙特阿拉伯", count: 2, width: "60%", gradient: "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)" },
// { name: "伊朗", count: 2, width: "60%", gradient: "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)" },
// { name: "俄罗斯", count: 2, width: "55%", gradient: "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)" },
// { name: "中国香港", count: 1, width: "40%", gradient: "linear-gradient(90deg, rgba(5, 95, 194, 0) 0%, rgba(5, 95, 194, 1) 100%)" }
// ];
const mapChartRef = ref(null);
const domainChartRef = ref(null);
const typeChartRef = ref(null);
......@@ -826,7 +854,7 @@ const sanTypeId = ref("");
// 跳转到数据资源库
const handleToDataLibrary = () => {
const dateStr = route.query.date ? route.query.date : ''
const dateStr = route.query.date ? route.query.date : "";
const curRoute = router.resolve({
path: "/dataLibrary/sDNList",
query: {
......@@ -835,10 +863,10 @@ const handleToDataLibrary = () => {
}
});
window.open(curRoute.href, "_blank");
}
};
const handleToDataLibrary1 = () => {
const dateStr = route.query.date ? route.query.date : ''
const dateStr = route.query.date ? route.query.date : "";
const curRoute = router.resolve({
path: "/dataLibrary/sDNList",
query: {
......@@ -848,78 +876,200 @@ const handleToDataLibrary1 = () => {
}
});
window.open(curRoute.href, "_blank");
}
};
const handleToDataLibrary2 = () => {
const dateStr = route.query.date ? route.query.date : ''
const dateStr = route.query.date ? route.query.date : "";
const curRoute = router.resolve({
path: "/dataLibrary/sDNList",
query: {
selectedDate: JSON.stringify([dateStr, dateStr]),
isCnEntityOnly: true,
isCnEntityOnly: true
}
});
window.open(curRoute.href, "_blank");
}
};
// 制裁实体领域分布情况
const handleToDataLibrary3 = (val) => {
const handleToDataLibrary3 = val => {
// console.log('val', val);
const params = {
domains: val.name,
isCnEntityOnly: true,
selectedDate: JSON.stringify([route.query.date, route.query.date])
}
};
const curRoute = router.resolve({
path: '/dataLibrary/sDNList',
path: "/dataLibrary/sDNList",
query: params
});
window.open(curRoute.href, "_blank");
}
};
// 制裁实体类型分布情况
const handleToDataLibrary4 = (val) => {
const handleToDataLibrary4 = val => {
// console.log('val', val);
const params = {
selectedEntityType: val.name,
isCnEntityOnly: true,
selectedDate: JSON.stringify([route.query.date, route.query.date])
}
};
const curRoute = router.resolve({
path: '/dataLibrary/sDNList',
path: "/dataLibrary/sDNList",
query: params
});
window.open(curRoute.href, "_blank");
}
};
// 制裁实体国家地区分布情况
const handleToDataLibrary5 = (item) => {
const handleToDataLibrary5 = item => {
const params = {
selectedCountryId: item.id,
isCnEntityOnly: true,
selectedDate: JSON.stringify([route.query.date, route.query.date])
}
};
const curRoute = router.resolve({
path: '/dataLibrary/sDNList',
path: "/dataLibrary/sDNList",
query: params
});
window.open(curRoute.href, "_blank");
}
};
// 制裁实体各省分布情况
const handleToDataLibrary6 = (item) => {
console.log('item', item);
const handleToDataLibrary6 = item => {
console.log("item", item);
const params = {
selectedProvince: item.name,
isCnEntityOnly: true,
selectedDate: JSON.stringify([route.query.date, route.query.date])
}
};
const curRoute = router.resolve({
path: '/dataLibrary/sDNList',
path: "/dataLibrary/sDNList",
query: params
});
window.open(curRoute.href, "_blank");
}
};
const requestAiPaneContent = async key => {
if (!key || aiPaneLoading.value[key] || aiPaneFetched.value[key]) return;
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: true };
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成中..." };
try {
const payload = buildAiChartPayload(key);
const res = await getChartAnalysis(
{ text: JSON.stringify(payload) },
{
onChunk: chunk => {
const current = overviewAiContent.value[key];
const base = current === "智能总结生成中..." ? "" : current;
overviewAiContent.value = {
...overviewAiContent.value,
[key]: base + chunk
};
}
}
);
const list = res?.data;
const first = Array.isArray(list) ? list[0] : null;
const interpretation = first?.解读 || first?.["解读"];
// 流式已渲染过内容,最终用解析出的解读覆盖(保证显示格式统一)
if (interpretation) {
overviewAiContent.value = {
...overviewAiContent.value,
[key]: interpretation
};
}
aiPaneFetched.value = { ...aiPaneFetched.value, [key]: true };
} catch (error) {
console.error("获取图表解读失败", error);
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成失败" };
} finally {
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: false };
}
};
const domainChartData = ref([]);
const typeChartData = ref([]);
const countryDistributionChartData = ref([]);
const regionDistributionChartData = ref([]);
const aiPaneVisible = ref({
domainChart: false,
typeChart: false,
countryDistributionChart: false,
regionDistributionChart: false
});
const overviewAiContent = ref({
domainChart: "智能总结生成中...",
typeChart: "智能总结生成中...",
countryDistributionChart: "智能总结生成中...",
regionDistributionChart: "智能总结生成中..."
});
const aiPaneFetched = ref({
domainChart: false,
typeChart: false,
countryDistributionChart: false,
regionDistributionChart: false
});
const aiPaneLoading = ref({
domainChart: false,
typeChart: false,
countryDistributionChart: false,
regionDistributionChart: false
});
const chartLoading = ref({
domainChart: false,
typeChart: false,
countryDistributionChart: false,
regionDistributionChart: false
});
const buildAiChartPayload = key => {
if (key === "domainChart") {
return { type: "饼图", name: "进入本次实体清单的中国实体领域分布情况", data: domainCount.value };
}
if (key === "typeChart") {
return { type: "饼图", name: "进入本次实体清单的中国实体类型分布情况", data: entityTypeCount.value };
}
if (key === "countryDistributionChart") {
return {
type: "柱状图",
name: "进入本次实体清单的实体国家地区分布情况",
data: countryDistributionChartData.value
};
}
if (key === "regionDistributionChart") {
return {
type: "柱状图",
name: "进入本次实体清单的中国实体各省分布情况",
data: regionDistributionChartData.value
};
}
return { type: "", name: "", data: [] };
};
const handleShowAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: true
};
requestAiPaneContent(key);
};
const handleHideAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: false
};
};
onMounted(() => {
// 获取路由参数id
......
......@@ -2,51 +2,6 @@
<div class="industrial-impact">
<div class="main">
<div class="left">
<!-- <div class="title-com">
<div class="box"></div>
<div class="text">制裁企业列表</div>
<div class="right-group">
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="left-main">
<div class="top-bar">
<el-select v-model="searchDomain" placeholder="全部领域" class="domain-select">
<el-option label="全部领域" value="" />
<el-option label="人工智能" value="1" />
<el-option label="生物科技" value="2" />
<el-option label="新一代信息技术" value="3" />
<el-option label="量子科技" value="4" />
<el-option label="新能源" value="5" />
<el-option label="集成电路" value="6" />
<el-option label="海洋" value="7" />
<el-option label="先进制造" value="8" />
<el-option label="新材料" value="9" />
<el-option label="航空航天" value="10" />
<el-option label="深海" value="11" />
<el-option label="极地" value="12" />
<el-option label="太空" value="13" />
<el-option label="核" value="14" />
</el-select>
<el-input v-model="searchKeyword" class="search-input" placeholder="搜索实体" :suffix-icon="Search" />
</div>
<div class="company-list-container">
<div class="list-header">企业名称</div>
<div class="company-list">
<div class="company-item" :class="{ active: selectedCompanyId === item.id }" v-for="item in entityList"
:key="item.id" @click="selectedCompanyId = item.id">
<div class="icon-wrapper">
<img :src="defaultTitle" alt="" />
</div>
<div class="company-name">{{ item.name }}</div>
</div>
</div>
</div>
</div> -->
<AnalysisBox title="制裁企业列表">
<div class="left-main">
<div class="top-bar">
......@@ -91,31 +46,6 @@
</div>
<div class="right">
<div class="right-item">
<!-- <div class="title-com">
<div class="box"></div>
<div class="text">企业规模</div>
<div class="right-group">
<div class="toggle-btns">
<div class="t-btn" :class="{ active: activeScale === item }" v-for="item in scaleOptions" :key="item"
@click="handleScaleClick(item)">
{{ item }}
</div>
</div>
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="chartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业营收初期下降,后基本趋于稳定。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div> -->
<AnalysisBox title="企业规模">
<template #header-btn>
<div class="toggle-btns">
......@@ -145,32 +75,19 @@
<div class="data-origin-text">企业规模情况,数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="revenueChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="revenueChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('revenueChart')" />
<AiPane
v-if="aiPaneVisible?.revenueChart"
:aiContent="overviewAiContent.revenueChart"
@mouseleave="handleHideAiPane('revenueChart')"
/>
</div>
</div>
</AnalysisBox>
</div>
<div class="right-item">
<!-- <div class="title-com">
<div class="box"></div>
<div class="text">企业市值变化</div>
<div class="right-group">
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="marketChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业营收初期下降,后基本趋于稳定。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div> -->
<AnalysisBox title="企业市值变化">
<div class="right-main">
<!-- <div class="echarts" ref="marketChartRef"></div>
......@@ -187,38 +104,19 @@
<div class="data-origin-text">企业市值变化情况,数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="marketChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="marketChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('marketChart')" />
<AiPane
v-if="aiPaneVisible?.marketChart"
:aiContent="overviewAiContent.marketChart"
@mouseleave="handleHideAiPane('marketChart')"
/>
</div>
</div>
</AnalysisBox>
</div>
<div class="right-item">
<!-- <div class="title-com">
<div class="box"></div>
<div class="text">企业研发投入</div>
<div class="right-group">
<div class="toggle-btns">
<div class="t-btn" :class="{ active: activeRD === item }" v-for="item in rdOptions" :key="item"
@click="activeRD = item">
{{ item }}
</div>
</div>
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="rdChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业研发资金投入逐渐提高。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div> -->
<AnalysisBox title="企业研发投入">
<template #header-btn>
<div class="toggle-btns">
......@@ -248,38 +146,19 @@
<div class="data-origin-text">企业研发投入情况,数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="rdChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="rdChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('rdChart')" />
<AiPane
v-if="aiPaneVisible?.rdChart"
:aiContent="overviewAiContent.rdChart"
@mouseleave="handleHideAiPane('rdChart')"
/>
</div>
</div>
</AnalysisBox>
</div>
<div class="right-item">
<!-- <div class="title-com">
<div class="box"></div>
<div class="text">企业市场占比</div>
<div class="right-group">
<div class="toggle-btns">
<div class="t-btn" :class="{ active: activeMarketShare === item }" v-for="item in marketShareOptions"
:key="item" @click="activeMarketShare = item">
{{ item }}
</div>
</div>
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="shareChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业营收初期下降,后基本趋于稳定。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div> -->
<AnalysisBox title="企业市场占比">
<template #header-btn>
<div class="toggle-btns">
......@@ -309,8 +188,14 @@
<div class="data-origin-text">企业市场占比情况,数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="shareChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="shareChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('shareChart')" />
<AiPane
v-if="aiPaneVisible?.shareChart"
:aiContent="overviewAiContent.shareChart"
@mouseleave="handleHideAiPane('shareChart')"
/>
</div>
</div>
</AnalysisBox>
......@@ -326,8 +211,6 @@ import { debounce } from "lodash";
import * as echarts from "echarts";
import { Search } from "@element-plus/icons-vue";
import defaultTitle from "../../../../assets/default-icon2.png";
import ai from "../../assets/ai.png";
import right from "../../assets/right.png";
import tipsIcon from "../../../../../assets/icons/info-icon.png";
import {
......@@ -339,6 +222,7 @@ import {
getSingleSanctionEntityRDInvestment,
getSingleSanctionEntityMarketShare
} from "@/api/exportControlV2.0";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
import EChart from "@/components/Chart/index.vue";
......@@ -373,7 +257,8 @@ const getMarketShare = async () => {
nextTick(() => {
initShareChart();
});
shareChart.interpret({ type: "柱状图", name: "企业市场占比", data: sortedData });
// shareChart.interpret({ type: "柱状图", name: "企业市场占比", data: sortedData });
shareChartData1.value = sortedData;
}
}
} catch (error) {
......@@ -407,7 +292,8 @@ const getRDInvestment = async () => {
nextTick(() => {
initRDChart();
});
rdChart.interpret({ type: "折线图", name: "企业研发投入", data: sortedData });
// rdChart.interpret({ type: "折线图", name: "企业研发投入", data: sortedData });
rdChartData1.value = sortedData;
}
}
} catch (error) {
......@@ -441,7 +327,8 @@ const getMarketValue = async () => {
nextTick(() => {
initMarketChart();
});
marketChart.interpret({ type: "折线图", name: "企业市值变化", data: sortedData });
// marketChart.interpret({ type: "折线图", name: "企业市值变化", data: sortedData });
marketChartData1.value = sortedData;
}
}
} catch (error) {
......@@ -474,7 +361,8 @@ const getPersonnel = async () => {
nextTick(() => {
initRevenueChart();
});
revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
// revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
revenueChartData.value = sortedData;
}
} catch (error) {
console.log(error);
......@@ -506,7 +394,8 @@ const getNetProfitData = async () => {
nextTick(() => {
initRevenueChart();
});
revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
// revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
revenueChartData.value = sortedData;
}
} catch (error) {
console.log(error);
......@@ -543,7 +432,8 @@ const getRevenueData = async () => {
nextTick(() => {
initRevenueChart();
});
revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
// revenueChart.interpret({ type: "折线图", name: "企业规模", data: sortedData });
revenueChartData.value = sortedData;
}
} catch (error) {
console.log(error);
......@@ -700,6 +590,12 @@ const getBaseOption = data => {
yAxis: {
type: "value",
min: 0,
name: "数量",
nameTextStyle: {
padding: [-10, 0, 0, -30], // [上, 右, 下, 左],负数向左移,正数向右移
align: "left", // 可选: 'left', 'center', 'right'
verticalAlign: "top" // 可选: 'top', 'middle', 'bottom'
},
splitNumber: 5,
axisLabel: {
color: "#606266",
......@@ -933,6 +829,11 @@ const initShareChart = () => {
option.xAxis.axisLabel.interval = "auto"; // 自动计算间隔,避免标签重叠导致柱子变细
option.yAxis.name = "百分比";
option.yAxis.nameTextStyle = {
padding: [-10, 0, 0, -45], // [上, 右, 下, 左],负数向左移,正数向右移
align: "left", // 可选: 'left', 'center', 'right'
verticalAlign: "top" // 可选: 'top', 'middle', 'bottom'
};
// 动态计算 Y 轴最大值,避免数据太小时展示不明显
const maxVal = Math.max(...data.values);
const maxY = maxVal === 0 ? 100 : maxVal * 1.5;
......@@ -977,6 +878,128 @@ const initShareChart = () => {
shareChartOption.value = option;
};
const requestAiPaneContent = async key => {
if (!key || aiPaneLoading.value[key] || aiPaneFetched.value[key]) return;
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: true };
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成中..." };
try {
const payload = buildAiChartPayload(key);
const res = await getChartAnalysis(
{ text: JSON.stringify(payload) },
{
onChunk: chunk => {
const current = overviewAiContent.value[key];
const base = current === "智能总结生成中..." ? "" : current;
overviewAiContent.value = {
...overviewAiContent.value,
[key]: base + chunk
};
}
}
);
const list = res?.data;
const first = Array.isArray(list) ? list[0] : null;
const interpretation = first?.解读 || first?.["解读"];
// 流式已渲染过内容,最终用解析出的解读覆盖(保证显示格式统一)
if (interpretation) {
overviewAiContent.value = {
...overviewAiContent.value,
[key]: interpretation
};
}
aiPaneFetched.value = { ...aiPaneFetched.value, [key]: true };
} catch (error) {
console.error("获取图表解读失败", error);
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成失败" };
} finally {
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: false };
}
};
const revenueChartData = ref([]);
const marketChartData1 = ref([]);
const rdChartData1 = ref([]);
const shareChartData1 = ref([]);
const aiPaneVisible = ref({
revenueChart: false,
marketChart: false,
rdChart: false,
shareChart: false
});
const overviewAiContent = ref({
revenueChart: "智能总结生成中...",
marketChart: "智能总结生成中...",
rdChart: "智能总结生成中...",
shareChart: "智能总结生成中..."
});
const aiPaneFetched = ref({
revenueChart: false,
marketChart: false,
rdChart: false,
shareChart: false
});
const aiPaneLoading = ref({
revenueChart: false,
marketChart: false,
rdChart: false,
shareChart: false
});
const chartLoading = ref({
revenueChart: false,
marketChart: false,
rdChart: false,
shareChart: false
});
const buildAiChartPayload = key => {
if (key === "revenueChart") {
return { type: "折线图", name: "企业规模", data: revenueChartData.value };
}
if (key === "marketChart") {
return { type: "折线图", name: "企业市场占比", data: marketChartData1.value };
}
if (key === "rdChart") {
return {
type: "折线图",
name: "企业研发投入",
data: rdChartData1.value
};
}
if (key === "shareChart") {
return {
type: "折线图",
name: "企业市场份额",
data: shareChartData1.value
};
}
return { type: "", name: "", data: [] };
};
const handleShowAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: true
};
requestAiPaneContent(key);
};
const handleHideAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: false
};
};
onMounted(async () => {
getUrlParams();
// 单次制裁-影响分析-制裁企业列表-查询 (获取到默认的 selectedCompanyId 后会触发 watch 加载其他数据)
......
......@@ -37,7 +37,12 @@
<div class="left-top-content">
<div class="content-title">新闻链接:</div>
<div class="distribution-list">
<div class="list-item news-item" v-for="item in newsList" :key="item.newsId">
<div
class="list-item news-item"
v-for="item in newsList"
:key="item.newsId"
@click="handleNewsClick(item.newsId)"
>
<div class="item-title">{{ item.newsTitle }}</div>
<img :src="openIcon" class="flag" />
</div>
......@@ -81,11 +86,19 @@
<div class="content">{{ item.content }}</div>
</div>
</div>
<div class="view-more" v-if="hasMore" @click="loadMore">
<!-- <div class="view-more" v-if="hasMore" @click="loadMore">
查看更多
<el-icon class="icon-more">
<DArrowRight />
</el-icon>
</div> -->
<div class="left-bottom-footer">
<simple-pagination
v-model:current-page="timelinePage"
:page-size="timelinePageSize"
:total="totalNum"
@page-change="handleListPageChange"
/>
</div>
</div>
</AnalysisBox>
......@@ -165,7 +178,7 @@
<template #default="scope">
<div class="name-cell">
<!-- <div class="dot"></div> -->
<img :src="defaultTitle" class="company-icon" />
<img :src="defaultOrgTitle" class="company-icon" />
<span class="company-name" @click="handleCompClick(scope.row)">{{
scope.row.name
}}</span>
......@@ -274,8 +287,10 @@ import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import AreaTag from "@/components/base/AreaTag/index.vue";
import { debounce } from "lodash";
import title from "../../assets/title.png";
import defaultTitle from "../../assets/default-icon1.png";
import defaultOrgTitle from "../../../assets/images/icon-entity.png";
import flag from "../../assets/default-icon2.png";
import openIcon from "../../../assets/icons/icon-open.png";
import {
......@@ -289,6 +304,8 @@ import { getReasonAndSan } from "@/api/finance";
import { useRoute } from "vue-router";
import { useGotoCompanyPages } from "@/router/modules/company";
import { useGotoNewsDetail } from "@/router/modules/news";
const gotoNewsDetail = useGotoNewsDetail();
const gotoCompanyPages = useGotoCompanyPages();
const route = useRoute();
......@@ -387,8 +404,16 @@ const getSanctionOverviewList = async () => {
// 单次制裁-制裁概况-制裁背景
const timelinePage = ref(1);
const timelinePageSize = ref(5);
const totalNum = ref(0);
const hasMore = ref(true);
const handleListPageChange = pageNum => {
console.log("页面修改 =>", pageNum);
timelinePage.value = pageNum;
getSanctionBackground();
};
const getSanctionBackground = async (isLoadMore = false) => {
try {
const res = await getSingleSanctionBackground({
......@@ -412,6 +437,7 @@ const getSanctionBackground = async (isLoadMore = false) => {
// 判断是否还有更多数据
const totalElements = res.data.totalElements || 0;
totalNum.value = totalElements || 0;
hasMore.value = timelineData.value.length < totalElements;
}
} catch (error) {
......@@ -501,6 +527,10 @@ const handleClick = () => {
window.open(route.href, "_blank");
};
const handleNewsClick = id => {
gotoNewsDetail(id);
};
// 计算属性处理数据
const formattedData = computed(() => {
const info = props.data || {};
......@@ -886,7 +916,7 @@ onMounted(() => {
.left-bottom {
width: 100%;
height: 521px;
height: auto;
.left-bottom-content {
padding: 20px 25px 0 25px;
......@@ -1139,6 +1169,7 @@ onMounted(() => {
.domain-box {
display: flex;
justify-content: center;
gap: 8px;
}
......
......@@ -2,14 +2,14 @@
<div class="entity-list">
<div class="header">
<div class="header-title">
<img :src="headerTitle.img" alt="" />
<img :src="headerTitle.postOrgLogoUrl" alt="" />
<div>
<div class="title">
{{ headerTitle.title }}
<!-- <span>{{ headerTitle.titleEn }}</span> -->
</div>
<div class="department">
{{ headerTitle.department }}
{{ headerTitle.postOrgName + " · " + "特别指定国民清单(SDN)" }}
</div>
</div>
<div class="btn" @click="openSanctionModal"><img :src="icon01" alt="" />切换</div>
......@@ -144,10 +144,12 @@ const getSingleSanctionOverviewData = async () => {
// 更新头部信息
headerTitle.value = {
...headerTitle.value,
...singleSanctionOverview.value,
title: `${dateStr}${singleSanctionOverview.value.sanTitleZh || singleSanctionOverview.value.sanTitle}》`,
titleEn: singleSanctionOverview.value.sanTitle || "",
department: singleSanctionOverview.value.fileCode || ""
};
console.log("headerTitle.value", headerTitle.value);
}
} catch (error) {
console.error("获取制裁概况失败:", error);
......@@ -284,7 +286,7 @@ onMounted(() => {
height: 148px;
background-color: #fff;
padding-top: 16px;
position: sticky;
// position: sticky;
top: 0;
z-index: 1000;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.05);
......
......@@ -3,7 +3,7 @@
<div class="header">
<div class="header-top">
<div class="header-top-left">
<img :src="headerTitle.img" alt="" />
<img :src="headerTitle.postOrgLogoUrl" alt="" />
<div>
<div class="title">{{ headerTitle.title }}</div>
<div class="en-title">
......@@ -22,7 +22,7 @@
</div>
<div class="main">
<div class="main-header">
<div>实体清单制裁文件</div>
<div>SDN清单制裁文件</div>
<div class="btn-box">
<div class="translate">
<div class="search-input-wrap" v-if="showSearchInput">
......@@ -78,7 +78,7 @@
</div>
</div>
</div>
<div class="report-box">
<div class="report-box" v-if="!reportUrlWithPage || !reportUrlEnWithPage">
<div class="pdf-pane-wrap" :class="{ 'is-full': !valueSwitch }" v-if="reportUrlWithPage">
<pdf
:key="`right-pdf-${valueSwitch ? 'split' : 'full'}`"
......@@ -91,6 +91,7 @@
<pdf ref="leftPdfRef" :pdfUrl="reportUrlEnWithPage" class="pdf-pane-inner" />
</div>
</div>
<el-empty v-else />
</div>
</div>
</template>
......@@ -366,8 +367,8 @@ const switchTab = name => {
};
onMounted(async () => {
window.sessionStorage.setItem("curTabName", "实体清单原文");
handleGetThinkTankReportSummary();
handleGetThinkTankReportcontentUrl();
// handleGetThinkTankReportSummary();
// handleGetThinkTankReportcontentUrl();
console.log("原文展示");
getUrlParams();
getSingleSanctionOverviewData();
......@@ -376,19 +377,20 @@ onMounted(async () => {
<style lang="scss" scoped>
.wrap {
overflow-y: hidden;
overflow-y: auto;
height: 100vh;
display: flex;
flex-direction: column;
.header {
width: 100%;
min-height: 88px;
height: 88px;
box-sizing: border-box;
border-bottom: 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);
position: sticky;
// position: sticky;
top: 0;
z-index: 99999;
overflow: hidden;
......@@ -404,7 +406,7 @@ onMounted(async () => {
display: flex;
img {
width: 44px;
width: 54px;
height: 54px;
}
......@@ -751,7 +753,7 @@ onMounted(async () => {
.report-box {
margin-left: 70px;
width: 1456px;
height: 881px;
// height: 881px;
display: flex;
overflow-y: auto;
/* 右侧统一滚动条,控制两侧原文+译文一起滚动 */
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论