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

bugfix-3

上级 02ee47e1
......@@ -8,6 +8,28 @@ import {
// Token管理
const TOKEN_KEY = 'auth_token'
// 定义全局控制器,以便在取消后重新赋值
let currentMainAbortController = new AbortController()
/**
* 获取当前有效的 AbortSignal
* 供 axios 拦截器和 fetch 请求共同使用
*/
export const getMainAbortSignal = () => {
return currentMainAbortController.signal
}
/**
* 取消所有正在进行的请求
* 路由守卫中调用此方法
*/
export const cancelAllMainRequests = () => {
// 1. 终止当前控制器的所有请求
currentMainAbortController.abort()
// 2. 创建一个新的控制器,供后续新请求使用
currentMainAbortController = new AbortController()
}
// ===== 兼容导出(勿删):历史代码仍会 import formatBearerAuthorization =====
// 说明:当前线上版本后端用 `token` 头,不用 Authorization;但为了不影响其它模块编译/运行,这里保留该方法导出。
const formatBearerAuthorization = (raw) => {
......@@ -70,6 +92,10 @@ service.interceptors.request.use(config => {
config.headers['X-API-Key'] = aiApiKey
}
}
if (!config.signal) {
config.signal = getMainAbortSignal()
}
return config
}, error => {
console.log(error)
......
......@@ -3,6 +3,7 @@ import { setToken, removeToken, getToken } from "@/api/request.js";
import { AUTH_LOGOUT_CHANNEL } from "@/utils/authCrossTabLogout.js";
import { cancelAllRequests } from "@/api/finance/service.js"
import { cancelAllMainRequests } from "@/api/request.js"
/** localStorage:跨标签页记录当前前端的 bootId(与 vite define 的 __APP_BOOT_ID__ 对齐) */
const VITE_BOOT_STORAGE_KEY = "app_vite_boot_id";
......@@ -149,6 +150,7 @@ const router = createRouter({
router.beforeEach((to, from, next) => {
// 在每次路由跳转开始前,取消上一个页面所有未完成的请求
// 这能防止旧页面的数据回来覆盖新页面,也能减少服务器压力
cancelAllMainRequests();
cancelAllRequests();
if (import.meta.env.DEV) {
clearTokenIfNewDevBoot();
......
<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>
......@@ -197,7 +197,7 @@
<el-table-column prop="year" label="年份" width="200" />
<el-table-column label="发布次数" width="300">
<template #default="scope">
<div style="display: flex; align-items: center">
<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"
......@@ -243,7 +243,7 @@
<el-table-column prop="year" label="年份" width="200" />
<el-table-column label="发布次数" width="300">
<template #default="scope">
<div style="display: flex; align-items: center">
<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"
......@@ -379,7 +379,7 @@
</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" />
<CustomTitle id="position4" title="出口管制数据库" style="margin-top: 0px" />
<div class="resource-tabs">
<div
v-for="tab in resourceTabs"
......@@ -393,7 +393,7 @@
</div>
<template v-if="activeResourceTab === 'entity'">
<el-col :span="8" style="padding-left: 0">
<custom-container title="历次制裁过程" :titleIcon="listIcon" height="845px">
<custom-container title="历次制裁过程" :titleIcon="listIcon" height="auto">
<template #default>
<div class="box4">
<div style="height: 90%; overflow-y: auto; padding-top: 10px">
......@@ -407,12 +407,15 @@
</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 class="box4-item-right-header-top">
<span class="box4-item-right-header-title">{{ item.postDate }}</span>
<span class="box4-item-right-header-desc">{{ item.desc }}</span>
</div>
<div class="box4-item-right-header-bottom">
<span class="box4-item-right-header-title">{{ item.title }}</span>
</div>
</div>
<el-tooltip
<!-- <el-tooltip
effect="dark"
:content="item.content"
popper-class="common-prompt-popper"
......@@ -422,7 +425,10 @@
<div class="box4-item-right-content">
{{ item.content }}
</div>
</el-tooltip>
</el-tooltip> -->
<div class="box4-item-right-content">
{{ item.content }}
</div>
</div>
</div>
</div>
......@@ -430,12 +436,18 @@
class="box4-footer"
:style="{ marginTop: sanctionProcessList.length > 0 ? '0px' : 'auto' }"
>
<el-button type="primary" link @click="handleGetMore"
<!-- <el-button type="primary" link @click="handleGetMore"
>查看更多
<el-icon>
<DArrowRight />
</el-icon>
</el-button>
</el-button> -->
<simple-pagination
v-model:current-page="sanctionPage"
:page-size="listPageSize"
:total="totalNum"
@page-change="handleListPageChange"
/>
</div>
</div>
</template>
......@@ -564,13 +576,34 @@
<div class="text">制裁时间</div>
</div>
<div class="left-main">
<el-checkbox-group v-model="checkedTime">
<!-- <el-checkbox-group v-model="checkedTime">
<div class="checkbox-grid">
<el-checkbox v-for="item in timeOptions" :key="item" :label="item">{{
item
<el-checkbox v-for="item in timeOptions" :key="item.value" :label="item.label">{{
item.label
}}</el-checkbox>
</div>
</el-checkbox-group>
</el-checkbox-group> -->
<div class="checkbox-grid">
<el-checkbox
v-for="(item, index) in timeOptions"
:key="index"
v-model="item.checked"
:label="item.label"
@change="handleFilterChange(item, timeOptions, 'time')"
/>
<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>
<div class="right">
......@@ -578,34 +611,39 @@
<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="right-main-box" v-loading="sancLoading">
<div class="right-main-wrapper" v-if="sanctionList.length > 0">
<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 :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>
<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"
/>
<el-empty v-else />
</div>
</div>
</div>
......@@ -649,6 +687,7 @@
//这是一个备注
import NewsList from "@/components/base/newsList/index.vue";
import RiskSignal from "@/components/base/riskSignal/index.vue";
import SimplePagination from "@/components/SimplePagination.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";
......@@ -754,7 +793,7 @@ const handleToRiskSignalDetail = item => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true;
};
const sancLoading = ref(false);
const sanctionList = ref([]);
const techOptions = [
......@@ -774,36 +813,18 @@ const techOptions = [
{ label: "太空", value: 13 },
{ label: "核", value: 14 }
];
const timeOptions = [
"全部时间",
"2025年",
"2024年",
"2023年",
"2022年",
"2021年",
"2020年",
"2019年",
"2018年",
"2017年",
"2016年",
"2015年",
"2014年",
"2013年",
"2012年",
"2011年",
"2010年",
"2009年",
"2008年",
"2007年",
"2006年",
"2005年",
"2004年",
"2003年",
"2002年",
"2001年"
];
const customDateRange = ref("");
const timeOptions = ref([
{ label: "全部时间", value: "all", checked: true },
{ label: "2026年", value: "2026", checked: false },
{ 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: "自定义", value: "custom", checked: false }
]);
const checkedTech = ref([0]);
const checkedTime = ref(["全部时间"]);
const checkedTime = ref(["all"]);
// 跳转到单条制裁页面,单独打开一个新页面
const handleTitleClick = item => {
......@@ -825,17 +846,9 @@ const handleTitleClick = item => {
const handleCompClick = item => {
// console.log("item", item);
// if (item.entityType != 2) return;
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"];
......@@ -880,6 +893,7 @@ const commerceControlListReleaseFreq = ref([]);
// 历次制裁过程
const sanctionProcessList = ref([]);
const sanctionPage = ref(1);
const totalNum = ref(0);
// 制裁实体清单
const entitiesList = ref([]);
// 风险信号
......@@ -964,7 +978,7 @@ onMounted(async () => {
// 获取新闻资讯
await fetchNewsInfo();
await fetchEntitiesList(currentPage.value, pageSize.value);
await fetchSanctionProcess(sanctionPage.value, 10);
await fetchSanctionProcess(sanctionPage.value, listPageSize.value);
// 获取雷达图数据
await fetchRadarData(domainChecked.value);
// 获取出口管制制裁措施
......@@ -1426,18 +1440,86 @@ const currentPageAll = ref(1);
const pageSizeAll = ref(10);
const totalAll = ref(0);
// 筛选逻辑处理
const handleFilterChange = (item, list, type) => {
debugger;
// 如果点击的是"全部"
if (item.value === "all") {
if (item.checked) {
// 选中全部,取消其他所有
list.forEach(i => {
if (i.value !== "all") i.checked = false;
});
} else {
// 取消全部(通常不允许全部取消,至少得选一个,这里如果取消全部,就默认为全部选中)
item.checked = true;
}
} else {
// 点击的是具体项
if (item.checked) {
// 选中具体项,取消"全部"
const allItem = list.find(i => i.value === "all");
if (allItem) allItem.checked = false;
// 特殊处理制裁时间的自定义和其他年份互斥
if (type === "time") {
if (item.value === "custom") {
list.forEach(i => {
if (i.value !== "custom" && i.value !== "all") i.checked = false;
});
} else {
const customItem = list.find(i => i.value === "custom");
if (customItem) customItem.checked = false;
}
}
} else {
// 取消具体项,检查是否还有选中的
const anyChecked = list.some(i => i.checked);
if (!anyChecked) {
const allItem = list.find(i => i.value === "all");
if (allItem) allItem.checked = true;
}
}
}
// 重置页码并查询
currentPageAll.value = 1;
fetchSanctionList();
};
watch(customDateRange, () => {
if (timeOptions.value.find(item => item.value === "custom" && item.checked)) {
currentPageAll.value = 1;
fetchSanctionList();
}
});
const fetchSanctionList = async () => {
try {
sancLoading.value = true;
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;
let startDate = undefined;
let endDate = undefined;
const allTime = timeOptions.value.find(item => item.value === "all");
console.log("allTime", allTime);
if (!allTime || !allTime.checked) {
years = timeOptions.value
.filter(item => item.checked && item.value !== "all" && item.value !== "custom")
.map(item => Number(item.value));
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 = {
......@@ -1447,10 +1529,13 @@ const fetchSanctionList = async () => {
years: years,
isCn: false,
// typeName: "实体清单"
sanTypeIds: allSanTypeIds.value
sanTypeIds: allSanTypeIds.value,
startDate: startDate,
endDate: endDate
};
const res = await getExportControlList(params);
sancLoading.value = false;
if (res && res.content) {
sanctionList.value = res.content.map(item => {
const tags = Array.isArray(item.techDomains)
......@@ -1493,7 +1578,10 @@ const fetchSanctionList = async () => {
});
totalAll.value = res.totalElements;
}
} catch (error) {}
} catch (error) {
console.error("错误信息", error);
sancLoading.value = false;
}
};
const handlePageChangeAll = val => {
......@@ -1533,16 +1621,16 @@ watch(
(newVal, oldVal) => {
let isModified = false;
if (newVal.includes("全部时间")) {
if (!oldVal.includes("全部时间")) {
checkedTime.value = ["全部时间"];
if (newVal.includes("all")) {
if (!oldVal.includes("all")) {
checkedTime.value = ["all"];
isModified = true;
} else if (newVal.length > 1) {
checkedTime.value = newVal.filter(v => v !== "全部时间");
checkedTime.value = newVal.filter(v => v !== "all");
isModified = true;
}
} else if (newVal.length === 0) {
checkedTime.value = ["全部时间"];
checkedTime.value = ["all"];
isModified = true;
}
......@@ -1576,6 +1664,26 @@ const fetchEntitiesList = async (page = 1, size = 10) => {
}
};
const listPageSize = ref(5);
const handleListPageChange = async page => {
console.log("页面修改 =>", page);
sanctionPage.value = page;
fetchSanctionProcess(page, listPageSize.value);
// 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 = [...newData];
// }
};
const handleGetMore = async () => {
sanctionPage.value++;
try {
......@@ -1603,7 +1711,7 @@ const handleGetMore = async () => {
};
// 获取历次制裁过程数据
const fetchSanctionProcess = async (page = 1, size = 10) => {
const fetchSanctionProcess = async (page = 1, size = listPageSize.value) => {
try {
const res = await getSanctionProcess(
activeResourceTabItem.value.id ? [activeResourceTabItem.value.id] : allSanTypeIds.value,
......@@ -1620,6 +1728,8 @@ const fetchSanctionProcess = async (page = 1, size = 10) => {
item.summary ||
"2025年3月25日,美国商务部工业与安全局以从事有悖于美国国家安全和外交政策利益的活动为由,宣布将来自中国的54家实体新增至“实体清单”。"
}));
totalNum.value = res.totalElements;
// currentPage.value = res.number + 1; // API返回的页码从0开始,前端从1开始
}
} catch (err) {
console.error(err);
......@@ -2401,7 +2511,7 @@ const handleMediaClick = item => {
}
.box4 {
height: 786px;
min-height: 980px;
overflow: auto;
display: flex;
flex-direction: column;
......@@ -2441,18 +2551,27 @@ const handleMediaClick = item => {
.box4-item-right-header {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
// align-items: center;
border-bottom: 1px solid rgba(234, 236, 238, 1);
position: relative;
top: -7.5px;
padding-bottom: 8px;
cursor: pointer;
&-top {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
&-title {
font-size: 18px;
color: $base-color;
font-weight: 700;
&:hover {
text-decoration: underline;
background: var(--color-primary-2);
}
}
&-desc {
......@@ -2477,7 +2596,7 @@ const handleMediaClick = item => {
}
.box4-footer {
position: absolute;
// position: absolute;
// margin-top: auto;
display: flex;
justify-content: center;
......@@ -3498,6 +3617,47 @@ const handleMediaClick = item => {
color: #666666;
font-weight: 400;
}
.custom-date-picker {
width: 100%;
margin-top: 8px;
padding-right: 24px;
box-sizing: border-box;
grid-column: 1 / -1;
:deep(.el-date-editor) {
width: 100%;
height: 32px;
box-shadow: none;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 10px;
&:hover {
border-color: #c0c4cc;
}
&.is-active {
border-color: #409eff;
}
.el-range-input {
font-size: 14px;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
}
.el-range-separator {
color: rgb(95, 101, 108);
line-height: 30px;
}
.el-input__icon {
line-height: 32px;
color: rgb(95, 101, 108);
}
}
}
}
}
......
......@@ -129,8 +129,14 @@
<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>
......@@ -165,17 +171,6 @@
</div>
</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="" />
......@@ -183,8 +178,14 @@
<div class="data-origin-text">进入实体清单的中国实体各省分布情况,数据来源:美国商务部官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="rankChart.interpretation" />
<!-- <AiButton />
<AiPane :aiContent="rankChart.interpretation" /> -->
<AiButton @mouseenter="handleShowAiPane('rankChart')" />
<AiPane
v-if="aiPaneVisible?.rankChart"
:aiContent="overviewAiContent.rankChart"
@mouseleave="handleHideAiPane('rankChart')"
/>
</div>
</AnalysisBox>
</div>
......@@ -202,17 +203,6 @@
:style="{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary6"
/>
<!-- <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="" />
......@@ -220,8 +210,14 @@
<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>
......@@ -239,17 +235,6 @@
:style="{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary7"
/>
<!-- <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="" />
......@@ -257,8 +242,14 @@
<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>
......@@ -286,7 +277,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 { useChartInterpretation } from "@/views/exportControl/utils/common";
const sanctionCountChart = useChartInterpretation();
// const sanctionCountChart = useChartInterpretation();
const domainChart = useChartInterpretation();
const typeChart = useChartInterpretation();
const rankChart = useChartInterpretation();
......@@ -313,7 +304,7 @@ const getTypeCountData = async () => {
name: item.name,
value: item.count || item.value
}));
typeChart.interpret({ type: "饼图", name: "制裁实体类型分布情况", data: data });
// typeChart.interpret({ type: "饼图", name: "制裁实体类型分布情况", data: data });
updateTypeChart();
}
} catch (error) {
......@@ -342,7 +333,7 @@ const getDomainCountData = async () => {
value: item.count || item.value
}));
updateDomainChart();
domainChart.interpret({ type: "饼图", name: "制裁实体领域分布情况", data: data });
// domainChart.interpret({ type: "饼图", name: "制裁实体领域分布情况", data: data });
}
} catch (error) {
console.error("获取实体清单-数据统计-制裁实体领域分布情况失败:", error);
......@@ -379,7 +370,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) {
......@@ -400,7 +391,7 @@ 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 });
} catch (error) {
console.error("获取实体清单-数据统计-制裁实体数量变化情况失败:", error);
}
......@@ -1039,96 +1030,6 @@ const updateTypeChart = () => {
// }
typeChartOption.value.series[0].data = topData;
}
// const option = {
// tooltip: {
// trigger: "item",
// formatter: params => {
// if (params.name === "其他" && params.data.extra) {
// let listStr = params.data.extra
// .map(item => {
// const percent = totalValue ? ((item.value / totalValue) * 100).toFixed(2) : 0;
// return `<div style="display:flex;justify-content:space-between;gap:10px;"><span>${item.name}</span><span style="font-weight:bold">${item.value}家 (${percent}%)</span></div>`;
// })
// .join("");
// return `<div style="text-align:left;">
// <div style="font-weight:bold;margin-bottom:5px;">其他 (${params.value} ${params.percent}%)</div>
// ${listStr}
// </div>`;
// }
// return `${params.name}: ${params.value} (${params.percent}%)`;
// }
// },
// color: [
// "#3B82F6", // 企业 - 蓝色
// "#feb64d", // 高校 - 橙色
// "#ff9f9f" // 科研院所 - 粉红
// ],
// series: [
// {
// name: "制裁实体类型分布",
// type: "pie",
// radius: [73.5, 89.5],
// center: ["50%", "50%"],
// startAngle: -90,
// data: data,
// label: {
// show: true,
// alignTo: "edge",
// minMargin: 5,
// edgeDistance: 10,
// formatter: params => {
// return "{name|" + params.name + "}\n{value|" + params.value + "家 " + params.percent + "%}";
// },
// rich: {
// name: {
// fontSize: 18,
// fontWeight: 700,
// color: "rgb(59, 65, 75)",
// padding: [0, 0, 5, 0],
// fontFamily: "Microsoft YaHei",
// lineHeight: 26
// },
// value: {
// fontSize: 16,
// fontWeight: 400,
// color: "rgb(95, 101, 108)",
// fontFamily: "Microsoft YaHei",
// lineHeight: 24,
// padding: [5, 0, 0, 0]
// }
// }
// },
// labelLine: {
// show: true,
// length: 15,
// length2: 0,
// maxSurfaceAngle: 80,
// lineStyle: {
// width: 1
// }
// },
// labelLayout: function (params) {
// const isLeft = params.labelRect.x < chart.getWidth() / 2;
// const points = params.labelLinePoints;
// // Update the end point.
// points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width;
// return {
// labelLinePoints: points
// };
// },
// itemStyle: {
// borderWidth: 0
// }
// }
// ]
// };
// chart.setOption(option);
// window.addEventListener("resize", () => {
// chart.resize();
// });
};
const initTypeChart = () => {
......@@ -1239,6 +1140,133 @@ const handleToDataLibrary3 = time => {
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 typeChart = useChartInterpretation();
// const rankChart = useChartInterpretation();
const sanctionCountChartData = ref([]);
const domainChartData = ref([]);
const typeChartData = ref([]);
const rankChartData = ref([]);
const aiPaneVisible = ref({
sanctionCountChart: false,
domainChart: false,
typeChart: false,
rankChart: false
});
const overviewAiContent = ref({
sanctionCountChart: "智能总结生成中...",
domainChart: "智能总结生成中...",
typeChart: "智能总结生成中...",
rankChart: "智能总结生成中..."
});
const aiPaneFetched = ref({
sanctionCountChart: false,
domainChart: false,
typeChart: false,
rankChart: false
});
const aiPaneLoading = ref({
sanctionCountChart: false,
domainChart: false,
typeChart: false,
rankChart: false
});
const chartLoading = ref({
sanctionCountChart: false,
domainChart: false,
typeChart: false,
rankChart: false
});
const buildAiChartPayload = key => {
if (key === "sanctionCountChart") {
return { type: "饼图", name: "制裁实体数量变化情况", data: sanctionCountChange.value };
}
if (key === "domainChart") {
return { type: "饼图", name: "制裁实体领域分布情况", data: domainData.value };
}
if (key === "typeChart") {
return {
type: "饼图",
name: "制裁实体类型分布情况",
data: typeData.value
};
}
if (key === "rankChart") {
return {
type: "柱状图",
name: "制裁实体各省分布情况",
data: rankData.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);
......
......@@ -17,27 +17,47 @@
<div class="text">科技领域</div>
</div>
<div class="checkbox-group">
<el-checkbox v-for="(item, index) in techFields" :key="index" v-model="item.checked" :label="item.label"
@change="handleFilterChange(item, techFields, 'tech')" />
<el-checkbox
v-for="(item, index) in techFields"
:key="index"
v-model="item.checked"
:label="item.label"
@change="handleFilterChange(item, techFields, 'tech')"
/>
</div>
<div class="title">
<div class="box"></div>
<div class="text">实体类型</div>
</div>
<div class="checkbox-group">
<el-checkbox v-for="(item, index) in entityTypes" :key="index" v-model="item.checked" :label="item.label"
@change="handleFilterChange(item, entityTypes, 'type')" />
<el-checkbox
v-for="(item, index) in entityTypes"
:key="index"
v-model="item.checked"
:label="item.label"
@change="handleFilterChange(item, entityTypes, 'type')"
/>
</div>
<div class="title">
<div class="box"></div>
<div class="text">制裁时间</div>
</div>
<div class="checkbox-group">
<el-checkbox v-for="(item, index) in sanctionTimes" :key="index" v-model="item.checked" :label="item.label"
@change="handleFilterChange(item, sanctionTimes, 'time')" />
<el-checkbox
v-for="(item, index) in sanctionTimes"
:key="index"
v-model="item.checked"
:label="item.label"
@change="handleFilterChange(item, sanctionTimes, 'time')"
/>
<div v-if="sanctionTimes.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="结束日期" />
<el-date-picker
v-model="customDateRange"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</div>
</div>
</div>
......@@ -114,13 +134,20 @@
<span class="highlight" @click="handlToDataLibrary">{{ ruleCount.totalCount }}</span>
</div>
<div class="rule-text">
(50%规则涉及<span class="highlight" @click="handlToDataLibrary1">{{ ruleCount.ruleCount }}</span>家)
(50%规则涉及<span class="highlight" @click="handlToDataLibrary1">{{
ruleCount.ruleCount
}}</span
>家)
</div>
</div>
</template>
<div class="right-table">
<el-table :data="entityRows" table-layout="fixed" :row-class-name="tableRowClassName"
:header-cell-style="{ background: '#fff' }">
<el-table
:data="entityRows"
table-layout="fixed"
:row-class-name="tableRowClassName"
:header-cell-style="{ background: '#fff' }"
>
<el-table-column label="实体名称" min-width="200">
<template #default="{ row }">
<div class="entity-name-cell" @click="handleCompClick(row)">
......@@ -128,30 +155,56 @@
<div v-else class="avatar-undefined">
{{ (row.entityNameZh || row.entityName)?.match(/[\u4e00-\u9fa5a-zA-Z0-9]/)?.[0] }}
</div>
<CommonPrompt :content="row.entityNameZh || row.entityName" style="flex: 1; overflow: hidden" />
<CommonPrompt
:content="row.entityNameZh || row.entityName"
style="flex: 1; overflow: hidden"
/>
</div>
</template>
</el-table-column>
<el-table-column label="涉及领域" min-width="150">
<template #default="{ row }">
<div class="domain-cell">
<el-tag v-for="tag in row.techDomains" :key="tag" class="domain-tag" effect="plain"
:disable-transitions="true" :style="getTagStyle(tag)">
<el-tag
v-for="tag in row.techDomains"
:key="tag"
class="domain-tag"
effect="plain"
:disable-transitions="true"
:style="getTagStyle(tag)"
>
{{ tag }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="listingLocation" label="上市地点" width="140" show-overflow-tooltip align="center" />
<el-table-column prop="startTime" label="制裁时间" width="140" show-overflow-tooltip align="center" />
<el-table-column
prop="listingLocation"
label="上市地点"
width="140"
show-overflow-tooltip
align="center"
/>
<el-table-column
prop="startTime"
label="制裁时间"
width="140"
show-overflow-tooltip
align="center"
/>
<el-table-column label="50%规则子企业" min-width="280" show-overflow-tooltip align="right">
<template #default="{ row }">
<div class="rule-cell" v-if="row.ruleOrgCount > 0">
<div class="rule-text" :title="row.ruleOrgList?.[0]?.orgName || ''">
{{ row.ruleOrgList?.[0]?.orgName || "" }}...等
</div>
<el-link class="rule-link" type="primary" :underline="false" @click="handleRuleClick(row)">{{
row.ruleOrgCount }}家 ></el-link>
<el-link
class="rule-link"
type="primary"
:underline="false"
@click="handleRuleClick(row)"
>{{ row.ruleOrgCount }}家 ></el-link
>
</div>
</template>
</el-table-column>
......@@ -159,15 +212,26 @@
</div>
<div class="tight-footer">
<div class="total-text">共 {{ total }} 项</div>
<el-pagination :current-page="currentPage" v-model:page-size="pageSize" :total="total"
layout="prev, pager, next" prev-text="<" next-text=">" @current-change="handleCurrentChange" />
<el-pagination
:current-page="currentPage"
v-model:page-size="pageSize"
:total="total"
layout="prev, pager, next"
prev-text="<"
next-text=">"
@current-change="handleCurrentChange"
/>
</div>
</AnalysisBox>
</div>
</div>
</div>
<RuleSubsidiaryDialog v-model="ruleDialogVisible" :company-name="currentRuleCompany" :total-count="currentRuleCount"
:data-list="currentRuleList" />
<RuleSubsidiaryDialog
v-model="ruleDialogVisible"
:company-name="currentRuleCompany"
:total-count="currentRuleCount"
:data-list="currentRuleList"
/>
</div>
</template>
......@@ -273,6 +337,7 @@ const entityTypes = ref([
const sanctionTimes = ref([
{ label: "全部时间", value: "all", checked: true },
{ label: "2026年", value: "2026", checked: false },
{ label: "2025年", value: "2025", checked: false },
{ label: "2024年", value: "2024", checked: false },
{ label: "2023年", value: "2023", checked: false },
......@@ -352,7 +417,7 @@ const getExportControlListApi = async () => {
if (abortController) {
try {
abortController.abort();
} catch { }
} catch {}
}
abortController = new AbortController();
isFetching.value = true;
......@@ -468,26 +533,25 @@ watch(customDateRange, () => {
// 跳转到数据资源库
const handlToDataLibrary = () => {
const params = {
isCnEntityOnly: true,
isCnEntityOnly: true
};
const route = router.resolve({
path: "/dataLibrary/dataEntityList",
query: params
});
window.open(route.href, "_blank");
}
};
const handlToDataLibrary1 = () => {
const params = {
isCnEntityOnly: true,
isHalfRule: true,
isHalfRule: true
};
const route = router.resolve({
path: "/dataLibrary/dataEntityList",
query: params
});
window.open(route.href, "_blank");
}
};
</script>
<style scoped lang="scss">
......@@ -692,7 +756,6 @@ const handlToDataLibrary1 = () => {
.highlight {
color: #cd4246;
margin: 0 4px;
}
}
......
......@@ -1080,11 +1080,6 @@ onMounted(() => {
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.data-statistics {
width: 1601px;
margin: 0 auto;
......@@ -1156,6 +1151,11 @@ onMounted(() => {
.number {
display: flex;
align-items: baseline;
&:hover {
text-decoration: underline;
text-decoration-color: #cd4246;
background: var(--color-primary-2);
}
.num {
font-size: 32px;
......
......@@ -34,7 +34,12 @@
<el-option label="太空" value="13" />
<el-option label="核" value="14" />
</el-select>
<el-input v-model="searchText" placeholder="搜索实体" class="search-input">
<el-input
v-model="searchText"
placeholder="搜索实体"
class="search-input"
@keyup.enter="getSingleSanctionEntityListRequest"
>
<template #suffix>
<el-icon class="el-input__icon">
<Search />
......
......@@ -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">
......@@ -130,7 +60,7 @@
</div>
</div>
</template>
<div class="right-main">
<div class="right-main" v-loading="scaleLoading">
<!-- <div class="echarts" ref="chartRef"></div> -->
<!-- <div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
......@@ -142,7 +72,7 @@
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">企业规模情况,数据来源:美国各行政机构官网</div>
<div class="data-origin-text">数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
......@@ -184,7 +114,7 @@
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">企业市值变化情况,数据来源:美国各行政机构官网</div>
<div class="data-origin-text">数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
......@@ -245,7 +175,7 @@
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">企业研发投入情况,数据来源:美国各行政机构官网</div>
<div class="data-origin-text">数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
......@@ -306,7 +236,7 @@
<div class="data-origin-icon">
<img :src="tipsIcon" alt="" />
</div>
<div class="data-origin-text">企业市场占比情况,数据来源:美国各行政机构官网</div>
<div class="data-origin-text">数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
......@@ -453,11 +383,15 @@ const getMarketValue = async () => {
const personnelData = ref([]);
// 单次制裁-影响分析-企业规模-人员-调查
const getPersonnel = async () => {
if (!selectedCompanyId.value) return;
if (!selectedCompanyId.value) {
scaleLoading.value = false;
return;
}
try {
const res = await getSingleSanctionEntityPersonnel({
id: selectedCompanyId.value
});
scaleLoading.value = false;
if (res.code === 200) {
personnelData.value = res.data || [];
......@@ -478,6 +412,7 @@ const getPersonnel = async () => {
}
} catch (error) {
console.log(error);
scaleLoading.value = false;
}
};
......@@ -485,11 +420,15 @@ const getPersonnel = async () => {
const netProfitData = ref([]);
// 单次制裁-影响分析-企业规模-净利润-调查
const getNetProfitData = async () => {
if (!selectedCompanyId.value) return;
if (!selectedCompanyId.value) {
scaleLoading.value = false;
return;
}
try {
const res = await getSingleSanctionEntityNetProfit({
id: selectedCompanyId.value
});
scaleLoading.value = false;
if (res.code === 200) {
netProfitData.value = res.data || [];
......@@ -510,6 +449,7 @@ const getNetProfitData = async () => {
}
} catch (error) {
console.log(error);
scaleLoading.value = false;
}
};
......@@ -524,11 +464,15 @@ const sanRecordId = ref("");
const revenueData = ref([]);
// 单次制裁-影响分析-企业规模-营收-查询
const getRevenueData = async () => {
if (!selectedCompanyId.value) return;
if (!selectedCompanyId.value) {
scaleLoading.value = false;
return;
}
try {
const res = await getSingleSanctionEntityRevenue({
id: selectedCompanyId.value
});
scaleLoading.value = false;
if (res.code === 200) {
revenueData.value = res.data || [];
// 将数据格式化为图表所需格式
......@@ -547,11 +491,13 @@ const getRevenueData = async () => {
}
} catch (error) {
console.log(error);
scaleLoading.value = false;
}
};
const scaleLoading = ref(false);
const handleScaleClick = item => {
activeScale.value = item;
scaleLoading.value = true;
if (item === "营收") {
getRevenueData();
} else if (item === "净利润") {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论