提交 5fc86ad6 authored 作者: yanpeng's avatar yanpeng

exportControl api

上级 a3bb0583
......@@ -4,6 +4,7 @@ import { ElMessage } from "element-plus";
const request200 = requestP => {
return requestP.then(data => {
if (data.code === 200) {
console.log('返回的数据结构 =>', data.data)
return data.data;
}
ElMessage({
......@@ -122,13 +123,14 @@ export function getSanctionsInfoCount() {
* sanReason: string
* }[]>}
*/
export function getSanctionProcess(typeName = "实体清单", pageNum = 1, pageSize = 10, isCn = false) {
export function getSanctionProcess(sanTypeIds = "1", pageNum = 1, pageSize = 10, isCn = false) {
return request200(
request({
method: "POST",
url: "/api/entitiesDataCount/getSanctionProcess",
data: {
typeName,
sanTypeIds,
// typeName: tabMap[sanTypeId],
pageNum,
pageSize,
isCn
......
import request from "@/api/request.js";
// 实体清单-制裁概况-获取实体清单基本信息
export function getEntityInfo(sanType) {
export function getEntityInfo(id) {
return request({
method: "GET",
url: `/api/sanctionList/baseInfo/${sanType}`
url: `/api/sanctionList/baseInfoById/${id}`
});
}
......@@ -98,10 +98,10 @@ export function get50PercentEntityCount(data) {
}
// 实体清单-数据统计-总量统计
export function getTotalCount() {
export function getTotalCount(id) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/total`
url: `/api/sanctionList/statistics/total?sanTypeId=${id}`
});
}
......@@ -113,7 +113,7 @@ export function getTotalCount() {
export function getSanctionCountChange(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/num`,
url: `/api/sanctionList/statistics/num`,
params
});
}
......@@ -128,7 +128,7 @@ export function getSanctionCountChange(params) {
export function getRegionCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/region`,
url: `/api/sanctionList/statistics/region`,
params
});
}
......@@ -143,7 +143,7 @@ export function getRegionCount(params) {
export function getTechDomainCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/domain`,
url: `/api/sanctionList/statistics/domain`,
params
});
}
......@@ -158,7 +158,7 @@ export function getTechDomainCount(params) {
export function getEntityTypeCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/entityType`,
url: `/api/sanctionList/statistics/entityType`,
params
});
}
......@@ -247,7 +247,7 @@ export function getSingleSanctionOverview(params) {
export function getSingleSanctionEntityCountry(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/countryRegion`,
url: `/api/sanctionList/statistics/countryRegion`,
params
});
}
......@@ -292,11 +292,10 @@ export function getSingleSanctionOverviewList(data) {
* @param {string} params.sanRecordId - 制裁记录ID
* @header token
*/
export function getSingleSanctionTotalCount(params) {
export function getSingleSanctionTotalCount(id) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/total`,
params
url: `/api/sanctionList/statistics/total?sanTypeId=${id}`,
});
}
......@@ -311,7 +310,7 @@ export function getSingleSanctionTotalCount(params) {
export function getSingleSanctionDomainCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/domain`,
url: `/api/sanctionList/statistics/domain`,
params
});
}
......@@ -327,7 +326,7 @@ export function getSingleSanctionDomainCount(params) {
export function getSingleSanctionEntityTypeCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/entityType`,
url: `/api/sanctionList/statistics/entityType`,
params
});
}
......@@ -341,7 +340,7 @@ export function getSingleSanctionEntityTypeCount(params) {
export function getSingleSanctionEntityCountryCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/countryRegion`,
url: `/api/sanctionList/statistics/countryRegion`,
params
});
}
......@@ -357,7 +356,7 @@ export function getSingleSanctionEntityCountryCount(params) {
export function getSingleSanctionEntityRegionCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/region`,
url: `/api/sanctionList/statistics/region`,
params
});
}
......
......@@ -135,8 +135,8 @@ const headerTitleClasses = computed(() => [
.header-icon {
width: 22px;
height: 18px;
margin-left: 5px;
margin-right: 14px;
margin-left: 0px;
margin-right: 10px;
}
.blue-title-block {
......@@ -155,6 +155,7 @@ const headerTitleClasses = computed(() => [
/* color: var(--base-color); */
color: $base-color;
line-height: 48px;
padding: 0 5px;
// padding: 0 12px;
}
......
......@@ -403,7 +403,7 @@
v-for="tab in resourceTabs"
:key="tab.value"
class="resource-tab-item"
:class="{ active: activeResourceTab === tab.value, disabled: tab.disabled }"
:class="{ active: activeResourceTab == tab.value, disabled: tab.disabled }"
@click="handleResourceTabClick(tab)"
>
{{ tab.label }}
......@@ -856,7 +856,8 @@ const handleTitleClick = item => {
const route = router.resolve({
path: "/exportControl/singleSanction",
query: {
id: item.id
id: item.id,
sanTypeId: item.sanTypeId
}
});
window.open(route.href, "_blank");
......@@ -869,7 +870,8 @@ const handleCompClick = item => {
const route = router.resolve({
name: "companyPages",
params: {
id: item.id
id: item.id,
sanTypeId: item.sanTypeId
}
});
window.open(route.href, "_blank");
......@@ -938,7 +940,15 @@ onMounted(async () => {
// 交换第二个和第三个元素
[dataCount[1], dataCount[2]] = [dataCount[2], dataCount[1]];
infoList.value = dataCount.slice(0, 2);
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: item.id == "13" // 商业管制清单不展示
}));
resourceTabs.value.unshift({ label: "全部制裁", value: "all", id: "", disabled: false });
console.log("返回的数据结构 infoList =》", infoList.value);
const entityList = _.map(entitiesDataInfo?.sanEntities ?? [], ({ entityNameZh, entityName }) => {
return { name: entityNameZh, enName: entityName };
});
......@@ -1123,15 +1133,22 @@ const handleToEntityList = item => {
// 跳转到V2.0实体清单无ID
const handleToEntityListNoId = item => {
console.log("这是什么数据 =>", item);
if (item.nameZh == "实体清单") {
const routeData = router.resolve({
path: "/exportControl/entityList"
path: "/exportControl/entityList",
query: {
sanTypeId: item.id
}
});
// 打开一个新页面
window.open(routeData.href, "_blank");
} else if (item.nameZh == "商业管制清单") {
const routeData = router.resolve({
path: "/exportControl/commercialControlList"
path: "/exportControl/commercialControlList",
query: {
sanTypeId: item.id
}
});
// 打开一个新页面
window.open(routeData.href, "_blank");
......@@ -1146,6 +1163,7 @@ 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);
......@@ -1324,7 +1342,8 @@ const fetchSanctionList = async () => {
techDomainIds: techDomains,
years: years,
isCn: false,
typeName: "实体清单"
// typeName: "实体清单"
sanTypeIds: allSanTypeIds.value
};
const res = await getExportControlList(params);
......@@ -1454,7 +1473,8 @@ const fetchEntitiesList = async (page = 1, size = 10) => {
const handleGetMore = async () => {
sanctionPage.value++;
try {
const res = await getSanctionProcess("实体清单", sanctionPage.value, 10);
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 => ({
......@@ -1479,7 +1499,7 @@ const handleGetMore = async () => {
// 获取历次制裁过程数据
const fetchSanctionProcess = async (page = 1, size = 10) => {
try {
const res = await getSanctionProcess("实体清单", page, size);
const res = await getSanctionProcess(allSanTypeIds.value, page, size);
if (res) {
sanctionProcessList.value = res.content.map(item => ({
...item,
......@@ -1504,18 +1524,25 @@ const handlePageChange = page => {
const searchKeyword = ref("");
// 资源库 Tab 数据
const resourceTabs = [
{ label: "全部制裁", value: "all", disabled: false },
{ label: "实体清单", value: "entity", disabled: false },
{ label: "商业管制清单", value: "commerce", disabled: true }
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;
activeResourceTab.value = tab.value;
activeResourceTabItem.value = tab;
};
const strengthLabels = {
......
......@@ -25,8 +25,12 @@
<div class="left-center">
<AnalysisBox title="出口管制分类编码(ECCN)" :showAllBtn="false">
<div class="button-list">
<div :class="['button', { 'click': item.isClick }]" @click="changeECCN(item)" v-for="(item, i) in ECCNList"
:key="i">
<div
:class="['button', { click: item.isClick }]"
@click="changeECCN(item)"
v-for="(item, i) in ECCNList"
:key="i"
>
<span>{{ item.ranking }}{{ item.name }}</span>
</div>
</div>
......@@ -44,9 +48,17 @@
<AnalysisBox title="商业管制清单更新历史" :showAllBtn="false">
<template #header-btn>
<div class="filters">
<el-select v-model="selectedDomain" placeholder="Select"
style="width: 150px; height: 32px; margin-right: 16px">
<el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value" />
<el-select
v-model="selectedDomain"
placeholder="Select"
style="width: 150px; height: 32px; margin-right: 16px"
>
<el-option
v-for="item in domainOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<!-- <el-checkbox v-model="onlyChina">只看涉华动态</el-checkbox> -->
</div>
......@@ -59,30 +71,43 @@
</div>
<div class="img-zone">
<img :src="item.icon || title" alt />
<div v-if="i < sanctionList.length - 1"
:class="['img-line', { 'img-line-last': i === sanctionList.length - 1 }]">
</div>
<div
v-if="i < sanctionList.length - 1"
:class="['img-line', { 'img-line-last': i === sanctionList.length - 1 }]"
></div>
</div>
<div class="main">
<div class="main-title">{{ item.name }}</div>
<!-- <div class="main-title" @click="handleClick(item)">{{ item.name }}</div> -->
<el-tooltip effect="dark" :content="item.summary" popper-class="common-prompt-popper" placement="top"
:show-after="500">
<el-tooltip
effect="dark"
:content="item.summary"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="main-desc">{{ item.summary }}</div>
</el-tooltip>
<div class="tag-box">
<div v-for="tag in item.techDomainList" :key="tag" class="tag-item">{{ tag }}</div>
</div>
<div :class="{ 'count-tag': item.cnEntityCount }">{{ item.cnEntityCount ? `${item.cnEntityCount}家中国实体` :
"" }}</div>
<div :class="{ 'count-tag': item.cnEntityCount }">
{{ item.cnEntityCount ? `${item.cnEntityCount}家中国实体` : "" }}
</div>
</div>
</div>
</div>
<div class="left-footer">
<div class="total-count">共 {{ totalAll }} 项</div>
<el-pagination v-model:current-page="currentPageAll" :page-size="pageSizeAll" :total="totalAll"
layout="prev, pager, next" background @current-change="handlePageChangeAll" />
<el-pagination
v-model:current-page="currentPageAll"
:page-size="pageSizeAll"
:total="totalAll"
layout="prev, pager, next"
background
@current-change="handlePageChangeAll"
/>
</div>
</AnalysisBox>
</div>
......@@ -110,17 +135,31 @@
<span>关键人物</span>
</div>
<div class="key-person-list">
<div class="person-item" v-for="(item, index) in publishInfo.personList" :key="index"
@click="handlePerClick(item)">
<div
class="person-item"
v-for="(item, index) in publishInfo.personList"
:key="index"
@click="handlePerClick(item)"
>
<img :src="item.imageUrl" alt />
<div class="person-info">
<el-tooltip effect="dark" :content="item.name" popper-class="common-prompt-popper" placement="top"
:show-after="500">
<el-tooltip
effect="dark"
:content="item.name"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="name">{{ item.name }}</div>
</el-tooltip>
<el-tooltip effect="dark" :content="item.position" popper-class="common-prompt-popper" placement="top"
:show-after="500">
<el-tooltip
effect="dark"
:content="item.position"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="title1">{{ item.position }}</div>
</el-tooltip>
</div>
......@@ -166,9 +205,18 @@ import defaultIcon from "../../../../../assets/icons/default-avatar.png";
import icon01 from "../../assets/icon01.png";
import icon02 from "../../assets/icon02.png";
import { ArrowDown } from "@element-plus/icons-vue";
import { ElTable, ElTableColumn, ElButton, ElSelect, ElOption, ElInput } from 'element-plus'
import { getCCLInfo, getECCN, getECCNList, getPublishInfo, getPublishOrgInfo, getEntityUpdateInfo } from "@/api/exportControlV2.0.js";
import { ElTable, ElTableColumn, ElButton, ElSelect, ElOption, ElInput } from "element-plus";
import {
getCCLInfo,
getECCN,
getECCNList,
getPublishInfo,
getPublishOrgInfo,
getEntityUpdateInfo
} from "@/api/exportControlV2.0.js";
import { useRoute } from "vue-router";
const route = useRoute();
// 处理点击发布机构的方法
const handleClickOrg = item => {
// console.log("点击了发布机构:", item);
......@@ -238,13 +286,14 @@ const getSanctionUpdate = async () => {
isCn: onlyChina.value,
techDomainIds: selectedDomain.value ? [selectedDomain.value] : [],
// typeName: "实体清单",
typeName: "商业管制清单",
// typeName: "商业管制清单",
pageNum: currentPageAll.value,
pageSize: pageSizeAll.value
pageSize: pageSizeAll.value,
sanTypeIds: [Number(sanTypeId.value)] || 13 // 商业管制清单固定13
};
try {
const res = await getEntityUpdateInfo(data);
console.log('-----getSanctionUpdate', res)
console.log("-----getSanctionUpdate", res);
if (res && res.code === 200) {
console.log(res.data.content);
sanctionList.value = (res.data.content || []).map(item => ({
......@@ -288,13 +337,13 @@ const handlePageChangeAll = val => {
const publishInfo = ref({});
const getPublishInfoFn = async () => {
const params = {
sanTypeId: 13 // 商业管制清单固定13
sanTypeId: sanTypeId.value || 13 // 商业管制清单固定13
};
try {
const res = await getPublishInfo(params);
if (res && res.code === 200) {
publishInfo.value = res.data;
console.log('------getPublishInfoFn', res.data)
console.log("------getPublishInfoFn", res.data);
// 获取发布机构动态
getPublishOrgInfoFn();
}
......@@ -348,12 +397,12 @@ const CCLInfo = ref({
description: "xxxxx",
legalBasis: "法律依据",
controlledObject: "管制对象",
name: '',
orgId: '',
orgLogoUrl: '',
orgName: '',
originalName: '',
shortName: '',
name: "",
orgId: "",
orgLogoUrl: "",
orgName: "",
originalName: "",
shortName: "",
restrictiveMeasure: "核心限制措施"
});
const getCCLInfoFn = async () => {
......@@ -361,7 +410,7 @@ const getCCLInfoFn = async () => {
const res = await getCCLInfo();
if (res && res.code === 200) {
CCLInfo.value = res.data;
console.log('getCCLInfoFn', CCLInfo.value)
console.log("getCCLInfoFn", CCLInfo.value);
}
} catch (error) {
console.error("获取商业管制清单基本信息失败:", error);
......@@ -371,27 +420,28 @@ const getCCLInfoFn = async () => {
// 定义ECCN列表
const ECCNList = ref([
{
name: '第1位: 物项类别',
name: "第1位: 物项类别",
isClick: true,
description: 'ECCN的第一位是一个数字,它指明了受控物项所属的技术或行业类别,对于快速定位物项在《商业管制清单》中的位置至关重要',
key: 'wxlb' // 用于和后端对接的
description:
"ECCN的第一位是一个数字,它指明了受控物项所属的技术或行业类别,对于快速定位物项在《商业管制清单》中的位置至关重要",
key: "wxlb" // 用于和后端对接的
}
])
]);
const getCCL_ECCN_Type = async () => {
try {
const res = await getECCN();
if (res && res.code === 200) {
ECCNList.value = res.data;
console.log('getCCL_ECCN_Type', ECCNList.value)
console.log("getCCL_ECCN_Type", ECCNList.value);
// 为每个对象增加是否点击对象
ECCNList.value.forEach((item) => {
item.isClick = false
})
ECCNList.value.forEach(item => {
item.isClick = false;
});
// 默认点击第一个
ECCNList.value[0].isClick = true
ECCNList.value[0].isClick = true;
// 默认将第一个赋值给currentECCN
currentECCN.value = ECCNList.value[0]
currentECCN.value = ECCNList.value[0];
}
} catch (error) {
console.error("获取商业管制清单出口管制分类编码失败:", error);
......@@ -401,46 +451,47 @@ const getCCL_ECCN_Type = async () => {
// 表格数据
const tableData = ref([
{
categoryCode: '0',
categoryCode: "0",
categoryId: 1,
code: null,
description: null,
descriptionZh: null,
id: 1,
name: 'Nuclear Materials, Facilities and Equipment and Miscellaneous',
name: "Nuclear Materials, Facilities and Equipment and Miscellaneous",
nameZh: "核材料、设备设施及其他类似物项"
}
])
]);
const currentECCN = ref({
name: '第1位: 物项类别',
name: "第1位: 物项类别",
isClick: true,
description: 'ECCN的第一位是一个数字,它指明了受控物项所属的技术或行业类别,对于快速定位物项在《商业管制清单》中的位置至关重要',
description:
"ECCN的第一位是一个数字,它指明了受控物项所属的技术或行业类别,对于快速定位物项在《商业管制清单》中的位置至关重要",
id: 1,
ranking: 1
})
});
/**
* 修改ECCN
*/
function changeECCN(item) {
ECCNList.value.forEach((i) => i.isClick = false)
item.isClick = true
currentECCN.value = item
ECCNList.value.forEach(i => (i.isClick = false));
item.isClick = true;
currentECCN.value = item;
}
// 监听当前的ECCN,更新获取列表数据
watch(
() => currentECCN.value,
async (newValue) => {
async newValue => {
// 调用接口更新table数据
const params = {
id: currentECCN.value.id
}
};
try {
const res = await getECCNList(params);
if (res && res.code === 200) {
tableData.value = res.data;
console.log('getECCNList', tableData.value)
console.log("getECCNList", tableData.value);
}
} catch (error) {
console.error("获取商业管制清单出口管制分类编码信息列表列表数据:", error);
......@@ -448,18 +499,19 @@ watch(
},
{ deep: true }
);
const sanTypeId = ref("");
onMounted(() => {
// 获取路由参数sanTypeId
sanTypeId.value = route.query.sanTypeId || "13";
console.log("商业管制清单介绍页面接收到的 sanTypeId:", sanTypeId.value);
// 获取商业管制清单基本信息
getCCLInfoFn();
// 获取商业管制清单的分类编码
getCCL_ECCN_Type()
getCCL_ECCN_Type();
// 获取商业管制清单发布机构
getPublishInfoFn();
// 获取商业管制清单更新历史
getSanctionUpdate();
});
</script>
......
......@@ -10,9 +10,7 @@
</div>
<div class="department">{{ headerTitle.department }}</div>
</div>
<div class="btn">
<img :src="icon01" alt />切换
</div>
<div class="btn"><img :src="icon01" alt />切换</div>
</div>
<div class="header-nav">
<div
......@@ -38,15 +36,16 @@
</template>
<script setup>
import { ref } from 'vue'
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import sanctionsOverview from "./components/sanctionsOverview/index.vue"
import sanctionsOverview from "./components/sanctionsOverview/index.vue";
// import dataStatistics from "./components/dataStatistics/index.vue"
// import deepMining from "./components/deepMining/index.vue"
// import impactAnalysis from "./components/impactAnalysis/index.vue"
import title from "./assets/title.png"
import icon01 from "./assets/icon01.png"
import title from "./assets/title.png";
import icon01 from "./assets/icon01.png";
import icon1 from "../assets/icons/icon1.png";
import icon1Active from "../assets/icons/icon1_active.png";
import icon5 from "../assets/icons/icon5.png";
......@@ -56,41 +55,45 @@ import icon2Active from "../assets/icons/icon2_active.png";
import icon3 from "../assets/icons/icon3.png";
import icon3Active from "../assets/icons/icon3_active.png";
const route = useRoute();
const sanTypeId = ref("");
onMounted(() => {
// 获取路由参数sanTypeId
sanTypeId.value = route.query.sanTypeId;
console.log("CommercialControlList 页面接收到的 sanTypeId:", sanTypeId.value);
});
const headerTitle = ref({
img: title,
title: "商业管制清单(CCL)",
titleEn: "Commercial Control List",
department: "美国商务部工业与安全局"
})
img: title,
title: "商业管制清单(CCL)",
titleEn: "Commercial Control List",
department: "美国商务部工业与安全局"
});
const activeIndex = ref(0)
const activeIndex = ref(0);
const headerNavList = ref([
{
img: icon1,
imgActive: icon1Active,
title: "制裁概况"
},
{
img: icon5,
imgActive: icon5Active,
title: "数据统计"
},
{
img: icon2,
imgActive: icon2Active,
title: "深度挖掘"
},
{
img: icon3,
imgActive: icon3Active,
title: "影响分析"
}
])
{
img: icon1,
imgActive: icon1Active,
title: "制裁概况"
},
{
img: icon5,
imgActive: icon5Active,
title: "数据统计"
},
{
img: icon2,
imgActive: icon2Active,
title: "深度挖掘"
},
{
img: icon3,
imgActive: icon3Active,
title: "影响分析"
}
]);
</script>
<style scoped lang="scss">
......
......@@ -178,8 +178,13 @@
<div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="rank-name">{{ item.name }}</div>
<div class="rank-bar-bg">
<div class="rank-bar-fill"
:style="{ width: (item.value / maxRankValue) * 100 + '%', background: getBarColor(index) }"></div>
<div
class="rank-bar-fill"
:style="{
width: (item.value / maxRankValue) * 100 + '%',
background: getBarColor(index)
}"
></div>
</div>
<div class="rank-value">{{ item.value }}家</div>
</div>
......@@ -302,13 +307,22 @@ import * as echarts from "echarts";
import chinaJson from "../../../utils/China.json";
import ai from "./assets/ai.png";
import right from "./assets/right.png";
import { getTotalCount, getSanctionCountChange, getRegionCount, getTechDomainCount, getEntityTypeCount } from "@/api/exportControlV2.0";
import {
getTotalCount,
getSanctionCountChange,
getRegionCount,
getTechDomainCount,
getEntityTypeCount
} from "@/api/exportControlV2.0";
import { useRoute } from "vue-router";
const route = useRoute();
// 实体清单-数据统计-制裁实体类型分布情况
const typeData = ref([]);
const getTypeCountData = async () => {
// 参数
const param = {};
param.sanTypeId = sanTypeId.value;
if (typeTime.value !== "all") {
param.startDate = `${typeTime.value}-01-01`;
......@@ -330,14 +344,12 @@ const getTypeCountData = async () => {
}
};
// 实体清单-数据统计-制裁实体领域分布情况
const domainData = ref([]);
const getDomainCountData = async () => {
// 参数
const param = {};
param.sanTypeId = sanTypeId.value;
if (domainTime.value !== "all") {
param.startDate = `${domainTime.value}-01-01`;
......@@ -371,7 +383,7 @@ const maxRankValue = computed(() => {
const getRegionCountData = async () => {
// 参数
const param = {};
param.sanTypeId = sanTypeId.value;
if (regionTime.value !== "all") {
param.startDate = `${regionTime.value}-01-01`;
param.endDate = `${regionTime.value}-12-31`;
......@@ -396,14 +408,14 @@ const getRegionCountData = async () => {
}
};
// 实体清单-数据统计-制裁实体数量变化情况
const sanctionCountChange = ref([]);
// 获取实体清单-数据统计-制裁实体数量变化情况
const getSanctionCountChangeData = async () => {
// 参数
const param = {
countType: activeTab.value === "year" ? "year" : "record"
countType: activeTab.value === "year" ? "year" : "record",
sanTypeId: sanTypeId.value
};
try {
const res = await getSanctionCountChange(param);
......@@ -419,7 +431,7 @@ const totalCount = ref(0);
// 获取实体清单-数据统计-总量统计
const getTotalCountData = async () => {
try {
const res = await getTotalCount();
const res = await getTotalCount(sanTypeId.value);
totalCount.value = res.data || 0;
} catch (error) {
console.error("获取实体清单-数据统计-总量统计失败:", error);
......@@ -438,9 +450,7 @@ const typeTime = ref("all");
const currentYear = new Date().getFullYear();
const timeOptions = [
{ label: "全部时间", value: "all" },
];
const timeOptions = [{ label: "全部时间", value: "all" }];
for (let i = 0; i <= 10; i++) {
const year = currentYear - i;
......@@ -749,11 +759,13 @@ const updateTypeChart = () => {
chart = echarts.init(typeChartRef.value);
}
let data = typeData.value.length ? [...typeData.value] : [
{ value: 50, name: "企业" },
{ value: 32, name: "高校" },
{ value: 32, name: "科研院所" }
];
let data = typeData.value.length
? [...typeData.value]
: [
{ value: 50, name: "企业" },
{ value: 32, name: "高校" },
{ value: 32, name: "科研院所" }
];
// 2. 聚合逻辑:保留前5项,其余合并为“其他”
data.sort((a, b) => b.value - a.value);
......@@ -873,7 +885,10 @@ const initTypeChart = () => {
updateTypeChart();
};
const sanTypeId = ref("");
onMounted(() => {
sanTypeId.value = route.query.sanTypeId || "";
console.log("数据统计页面接收到的 sanTypeId:", sanTypeId.value);
// initSanctionCountChart();
initMapChart();
initDomainChart();
......
<template>
<div class="deep-mining">
<div class="side-nav">
<div v-for="(item, index) in activeTab" :key="index" class="tab-item" :class="{ active: index === activeIndex }"
@click="activeIndex = index">
<div
v-for="(item, index) in activeTab"
:key="index"
class="tab-item"
:class="{ active: index === activeIndex }"
@click="activeIndex = index"
>
{{ item }}
<span v-if="index === activeIndex" class="arrow"></span>
</div>
......@@ -12,9 +17,17 @@
<AnalysisBox title="选择制裁">
<div class="left-main">
<div class="date-picker-box">
<el-date-picker v-model="dateRange" type="daterange" range-separator="--" start-placeholder="开始日期"
end-placeholder="结束日期" value-format="YYYY-MM-DD" style="width: 100%" :clearable="false"
@change="handleDateChange" />
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="--"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 100%"
:clearable="false"
@change="handleDateChange"
/>
</div>
<div class="list-header">
<div class="count">{{ sanctionList.length }}次制裁</div>
......@@ -29,8 +42,13 @@
</div> -->
</div>
<div class="list-content" v-loading="loading">
<div class="list-item" v-for="item in sanctionList" :key="item.id"
:class="{ active: currentSanctionId === item.id }" @click="handleSanctionSelect(item.id)">
<div
class="list-item"
v-for="item in sanctionList"
:key="item.id"
:class="{ active: currentSanctionId === item.id }"
@click="handleSanctionSelect(item.id)"
>
<div class="item-left">{{ item.date }}-{{ item.title }}</div>
<div class="item-right">{{ item.count }}{{ item.unit }}</div>
</div>
......@@ -41,8 +59,17 @@
<div class="right">
<AnalysisBox title="制裁产业链时序图">
<template #header-btn>
<el-select v-model="selectedIndustryId" placeholder="请选择" class="industry-select"
@change="() => { getFishboneData(); getCnEntityOnChainData(); }">
<el-select
v-model="selectedIndustryId"
placeholder="请选择"
class="industry-select"
@change="
() => {
getFishboneData();
getCnEntityOnChainData();
}
"
>
<el-option v-for="item in industryList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</template>
......@@ -63,29 +90,44 @@
<div class="fishbone" ref="fishboneRef" v-if="fishboneDataList.length > 0">
<div class="main-line" :style="{ width: fishboneDataList.length * 200 + 300 + 'px' }">
<!-- 主轴上的标签 -->
<div class="main-line-text" v-for="(item, index) in mainLineLabels" :key="'label-' + index"
<div
class="main-line-text"
v-for="(item, index) in mainLineLabels"
:key="'label-' + index"
:class="{
'blue-theme': index < 2,
'green-theme': index >= 2 && index < 4,
'purple-theme': index >= 4
}" :style="{ left: index * 200 + 220 + 'px' }">
}"
:style="{ left: index * 200 + 220 + 'px' }"
>
{{ item }}
</div>
</div>
<!-- 奇数索引的数据组放在上方 -->
<div v-for="(causeGroup, groupIndex) in getOddGroups(fishboneDataList)" :key="'top-' + groupIndex"
:class="getTopBoneClass(groupIndex)" :style="{ left: groupIndex * 400 + 420 + 'px' }">
<div
v-for="(causeGroup, groupIndex) in getOddGroups(fishboneDataList)"
:key="'top-' + groupIndex"
:class="getTopBoneClass(groupIndex)"
:style="{ left: groupIndex * 400 + 420 + 'px' }"
>
<div class="left-bone">
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-' + index">
<div
class="left-bone-item"
v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-' + index"
>
<img :src="defaultTitle || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div class="right-bone-item" v-for="(item, index) in getRightItems(causeGroup.causes)"
:key="'right-' + index">
<div
class="right-bone-item"
v-for="(item, index) in getRightItems(causeGroup.causes)"
:key="'right-' + index"
>
<div class="line"></div>
<img :src="defaultTitle || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
......@@ -94,20 +136,29 @@
</div>
<!-- 偶数索引的数据组放在下方 -->
<div v-for="(causeGroup, groupIndex) in getEvenGroups(fishboneDataList)"
:key="'bottom-' + groupIndex" :class="getBottomBoneClass(groupIndex)"
:style="{ left: groupIndex * 400 + 220 + 'px' }">
<div
v-for="(causeGroup, groupIndex) in getEvenGroups(fishboneDataList)"
:key="'bottom-' + groupIndex"
:class="getBottomBoneClass(groupIndex)"
:style="{ left: groupIndex * 400 + 220 + 'px' }"
>
<div class="left-bone">
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-bottom-' + index">
<div
class="left-bone-item"
v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-bottom-' + index"
>
<img :src="defaultTitle || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div class="right-bone-item" v-for="(item, index) in getRightItems(causeGroup.causes)"
:key="'right-bottom-' + index">
<div
class="right-bone-item"
v-for="(item, index) in getRightItems(causeGroup.causes)"
:key="'right-bottom-' + index"
>
<div class="line"></div>
<img :src="defaultTitle || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
......@@ -115,8 +166,16 @@
</div>
</div>
</div>
<div v-else
style="display: flex; justify-content: center; align-items: center; height: 200px; width: 100%">
<div
v-else
style="
display: flex;
justify-content: center;
align-items: center;
height: 200px;
width: 100%;
"
>
<el-empty description="暂无相关数据" />
</div>
</div>
......@@ -130,9 +189,11 @@
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.upstreamInternalCount ||
0}家(${formatRate(cnEntityOnChainData.upstreamInternalRate)}%),受制裁${cnEntityOnChainData.upstreamEntityCount
|| 0}家(${formatRate(cnEntityOnChainData.upstreamEntityRate)}%)`
`中国企业${cnEntityOnChainData.upstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.upstreamInternalRate
)}%),受制裁${cnEntityOnChainData.upstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.upstreamEntityRate
)}%)`
}}
</div>
</div>
......@@ -145,9 +206,11 @@
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.midstreamInternalCount ||
0}家(${formatRate(cnEntityOnChainData.midstreamInternalRate)}%),受制裁${cnEntityOnChainData.midstreamEntityCount
|| 0}家(${formatRate(cnEntityOnChainData.midstreamEntityRate)}%)`
`中国企业${cnEntityOnChainData.midstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.midstreamInternalRate
)}%),受制裁${cnEntityOnChainData.midstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.midstreamEntityRate
)}%)`
}}
</div>
</div>
......@@ -160,9 +223,11 @@
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.downstreamInternalCount ||
0}家(${formatRate(cnEntityOnChainData.downstreamInternalRate)}%),受制裁${cnEntityOnChainData.downstreamEntityCount
|| 0}家(${formatRate(cnEntityOnChainData.downstreamEntityRate)}%)`
`中国企业${cnEntityOnChainData.downstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.downstreamInternalRate
)}%),受制裁${cnEntityOnChainData.downstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.downstreamEntityRate
)}%)`
}}
</div>
</div>
......@@ -181,14 +246,22 @@
import { ref, onMounted, nextTick } from "vue";
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
import defaultTitle from "../../assets/default-icon2.png";
import { getDeepMiningSelect, getDeepMiningIndustry, getDeepMiningIndustryFishbone, getDeepMiningIndustryEntity } from "@/api/exportControlV2.0";
import {
getDeepMiningSelect,
getDeepMiningIndustry,
getDeepMiningIndustryFishbone,
getDeepMiningIndustryEntity
} from "@/api/exportControlV2.0";
import { useRoute } from "vue-router";
const route = useRoute();
// 实体清单-深度挖掘-产业链中国企业实体信息查询
const getCnEntityOnChainData = async () => {
const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value);
const date = currentSanction ? currentSanction.date : '';
const date = currentSanction ? currentSanction.date : "";
// 确保 date 格式正确
const formattedDate = date && date.includes('年') ? date.replace('年', '-').replace('月', '-').replace('日', '') : date;
const formattedDate = date && date.includes("年") ? date.replace("年", "-").replace("月", "-").replace("日", "") : date;
const params = {
date: formattedDate
......@@ -207,17 +280,15 @@ const getCnEntityOnChainData = async () => {
console.error("获取产业链中国企业实体信息失败:", error);
cnEntityOnChainData.value = {};
}
}
};
// 实体清单-深度挖掘-产业链鱼骨图信息
const fishboneDataList = ref([]);
const getFishboneData = async () => {
const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value);
const date = currentSanction ? currentSanction.date : '';
const date = currentSanction ? currentSanction.date : "";
// 确保 date 格式正确
const formattedDate = date && date.includes('年') ? date.replace('年', '-').replace('月', '-').replace('日', '') : date;
const formattedDate = date && date.includes("年") ? date.replace("年", "-").replace("月", "-").replace("日", "") : date;
const params = {
date: formattedDate
......@@ -230,14 +301,13 @@ const getFishboneData = async () => {
if (res.code === 200 && res.data && res.data.causes && res.data.causes.length > 0) {
const rootCauses = res.data.causes;
if (rootCauses.length > 0 && rootCauses[0].causes) {
fishboneDataList.value = rootCauses.map(group => {
return {
causes: group.causes || []
};
});
mainLineLabels.value = rootCauses.map(group => group.text || '');
mainLineLabels.value = rootCauses.map(group => group.text || "");
} else {
fishboneDataList.value = [];
mainLineLabels.value = [];
......@@ -250,9 +320,7 @@ const getFishboneData = async () => {
console.error("获取产业链鱼骨图数据失败:", error);
fishboneDataList.value = [];
}
}
};
// 实体清单-深度挖掘-产业链列表信息
const industryList = ref([]);
......@@ -274,30 +342,25 @@ const getIndustryList = async () => {
industryList.value = [];
selectedIndustryId.value = null;
}
}
};
// 获取选择制裁
const loading = ref(false);
const currentPage = ref(1);
const pageSize = ref(10000);
const pageSize = ref(100);
const total = ref(0);
const totalPage = ref(0);
const getDeepMiningSelectData = async () => {
loading.value = true;
const params = {
startDate: dateRange.value && dateRange.value[0] ? dateRange.value[0] : '',
endDate: dateRange.value && dateRange.value[1] ? dateRange.value[1] : '',
typeName: "实体清单",
startDate: dateRange.value && dateRange.value[0] ? dateRange.value[0] : "",
endDate: dateRange.value && dateRange.value[1] ? dateRange.value[1] : "",
// typeName: "实体清单",
isCn: false,
pageNum: currentPage.value,
pageSize: pageSize.value
pageSize: pageSize.value,
sanTypeIds: [Number(sanTypeId.value)] || 1 // 实体清单固定1
};
try {
const res = await getDeepMiningSelect(params);
......@@ -307,7 +370,7 @@ const getDeepMiningSelectData = async () => {
date: item.postDate,
title: item.name,
count: item.cnEntityCount,
unit: '家中国实体', // 接口未返回单位,暂时固定
unit: "家中国实体", // 接口未返回单位,暂时固定
summary: item.summary, // 保留额外信息备用
techDomainList: item.techDomainList // 保留额外信息备用
}));
......@@ -326,7 +389,7 @@ const getDeepMiningSelectData = async () => {
} finally {
loading.value = false;
}
}
};
// 日期选择变化
const handleDateChange = () => {
......@@ -335,14 +398,14 @@ const handleDateChange = () => {
};
// 翻页
const handlePageChange = (page) => {
const handlePageChange = page => {
if (page < 1 || page > totalPage.value) return;
currentPage.value = page;
getDeepMiningSelectData();
};
// 列表项点击事件
const handleSanctionSelect = (id) => {
const handleSanctionSelect = id => {
currentSanctionId.value = id;
getFishboneData();
getCnEntityOnChainData();
......@@ -366,13 +429,7 @@ const currentSanctionId = ref(5);
const cnEntityOnChainData = ref({});
const mainLineLabels = ref([
"关键原材料", "电池材料", "电子元器件", "动力电池", "电子控制系统", "动力电池"
]);
const mainLineLabels = ref(["关键原材料", "电池材料", "电子元器件", "动力电池", "电子控制系统", "动力电池"]);
// 获取奇数索引的数据组(放在上方)
const getOddGroups = data => {
......@@ -409,12 +466,15 @@ const getRightItems = items => {
};
// 格式化比率
const formatRate = (rate) => {
if (rate === undefined || rate === null) return '0.00';
const formatRate = rate => {
if (rate === undefined || rate === null) return "0.00";
return (rate * 100).toFixed(2);
};
const sanTypeId = ref("");
onMounted(() => {
// 获取路由参数中的sanTypeId
sanTypeId.value = route.query.sanTypeId || "";
// 获取选择制裁
getDeepMiningSelectData();
// 获取产业链信息
......@@ -730,7 +790,7 @@ onMounted(() => {
// 虚线
&::after {
content: '';
content: "";
position: absolute;
top: 0;
left: 0;
......@@ -744,7 +804,7 @@ onMounted(() => {
position: absolute;
// top: -14px;
font-size: 16px;
color: #055FC2;
color: #055fc2;
font-weight: bold;
background-color: #f7f8f9;
padding: 0 10px;
......@@ -1235,7 +1295,6 @@ onMounted(() => {
}
}
.industry-select {
width: 160px;
height: 28px;
......
......@@ -75,9 +75,17 @@
<AnalysisBox title="实体清单更新历史" :showAllBtn="false">
<template #header-btn>
<div class="filters">
<el-select v-model="selectedDomain" placeholder="Select"
style="width: 150px; height: 32px; margin-right: 16px">
<el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value" />
<el-select
v-model="selectedDomain"
placeholder="Select"
style="width: 150px; height: 32px; margin-right: 16px"
>
<el-option
v-for="item in domainOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-checkbox v-model="onlyChina">只看涉华动态</el-checkbox>
</div>
......@@ -91,8 +99,13 @@
<img :src="item.icon || title" alt="" />
<div class="main">
<div class="main-title" @click="handleClick(item)">{{ item.name }}</div>
<el-tooltip effect="dark" :content="item.summary" popper-class="common-prompt-popper" placement="top"
:show-after="500">
<el-tooltip
effect="dark"
:content="item.summary"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="main-desc">{{ item.summary }}</div>
</el-tooltip>
<div class="tag-box">
......@@ -107,8 +120,14 @@
</div>
<div class="left-footer">
<div class="total-count"> {{ totalAll }} </div>
<el-pagination v-model:current-page="currentPageAll" :page-size="pageSizeAll" :total="totalAll"
layout="prev, pager, next" background @current-change="handlePageChangeAll" />
<el-pagination
v-model:current-page="currentPageAll"
:page-size="pageSizeAll"
:total="totalAll"
layout="prev, pager, next"
background
@current-change="handlePageChangeAll"
/>
</div>
</AnalysisBox>
</div>
......@@ -130,8 +149,12 @@
<span>关键人物</span>
</div>
<div class="key-person-list">
<div class="person-item" v-for="(item, index) in publishInfo.personList" :key="index"
@click="handlePerClick(item)">
<div
class="person-item"
v-for="(item, index) in publishInfo.personList"
:key="index"
@click="handlePerClick(item)"
>
<img :src="item.imageUrl" alt="" />
<div class="person-info">
<CommonPrompt :content="item.name">
......@@ -183,7 +206,9 @@ import icon02 from "../../assets/icon02.png";
import { ArrowDown } from "@element-plus/icons-vue";
import CommonPrompt from "../../../../../commonPrompt/index.vue";
import { getEntityInfo, getPublishInfo, getPublishOrgInfo, getEntityUpdateInfo } from "@/api/exportControlV2.0.js";
import { useRoute } from "vue-router";
const route = useRoute();
// 处理点击发布机构的方法
const handleClickOrg = item => {
// console.log("点击了发布机构:", item);
......@@ -275,9 +300,10 @@ const getSanctionUpdate = async () => {
const data = {
isCn: onlyChina.value,
techDomainIds: selectedDomain.value ? [selectedDomain.value] : [],
typeName: "实体清单",
// typeName: "实体清单",
pageNum: currentPageAll.value,
pageSize: pageSizeAll.value
pageSize: pageSizeAll.value,
sanTypeIds: [sanTypeId.value] || ["1"] // 实体清单固定1
};
try {
const res = await getEntityUpdateInfo(data);
......@@ -324,7 +350,7 @@ const handlePageChangeAll = val => {
const publishInfo = ref({});
const getPublishInfoFn = async () => {
const params = {
sanTypeId: 1 // 实体清单固定1
sanTypeId: sanTypeId.value || 1 // 实体清单固定1
};
try {
const res = await getPublishInfo(params);
......@@ -379,22 +405,25 @@ const handleLoadMoreDynamic = () => {
// 获取实体清单基本信息
const entityInfo = ref({});
const emit = defineEmits(['update-entity-info']);
const getEntityInfoFn = async () => {
const emit = defineEmits(["update-entity-info"]);
const getEntityInfoFn = async id => {
try {
const res = await getEntityInfo("el");
const res = await getEntityInfo(id);
if (res && res.code === 200) {
entityInfo.value = res.data;
emit('update-entity-info', res.data);
console.log("获取实体清单基本信息成功:", res.data);
emit("update-entity-info", res.data);
}
} catch (error) {
console.error("获取实体清单基本信息失败:", error);
}
};
const sanTypeId = ref("");
onMounted(() => {
sanTypeId.value = route.query.sanTypeId;
// 获取实体清单基本信息
getEntityInfoFn();
getEntityInfoFn(sanTypeId.value);
// 获取实体清单发布机构
getPublishInfoFn();
// 获取实体清单更新历史
......
......@@ -13,27 +13,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>
......@@ -106,33 +126,47 @@
<template #header-btn>
<div class="stats">
<div class="dot"></div>
<div class="count-text"><span class="highlight">{{ ruleCount.totalCount }}</span></div>
<div class="rule-text">(50%规则涉及<span class="highlight">{{ ruleCount.ruleCount }}</span>家)</div>
<div class="count-text">
<span class="highlight">{{ ruleCount.totalCount }}</span>
</div>
<div class="rule-text">
(50%规则涉及<span class="highlight">{{ 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)">
<el-image v-if="row.img" class="avatar" :src="row.img" alt=""></el-image>
<div v-else class="avatar-undefined">
{{
(row.entityNameZh || row.entityName)?.match(
/[\u4e00-\u9fa5a-zA-Z0-9]/
)?.[0]
}}
{{ (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>
......@@ -143,10 +177,11 @@
<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 || '' }}...等
{{ 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>
......@@ -154,15 +189,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"
/>
</template>
<script setup>
......@@ -171,15 +217,15 @@ import { useRouter } from "vue-router";
import { Search } from "@element-plus/icons-vue";
import defaultIcon from "../../../../../assets/icons/default-avatar.png";
import RuleSubsidiaryDialog from "./RuleSubsidiaryDialog.vue";
import { getExportControlList, get50PercentEntityCount } from "@/api/exportControlV2.0.js"
import CommonPrompt from '@/views/exportControl/commonPrompt/index.vue'
import { getExportControlList, get50PercentEntityCount } from "@/api/exportControlV2.0.js";
import CommonPrompt from "@/views/exportControl/commonPrompt/index.vue";
const router = useRouter();
// 跳转公司详情页
const handleCompClick = item => {
console.log("item", item);
window.sessionStorage.setItem('curTabName', item.entityNameZh || item.entityName)
window.sessionStorage.setItem("curTabName", item.entityNameZh || item.entityName);
const route = router.resolve({
name: "companyPages",
params: {
......@@ -199,7 +245,7 @@ const total = ref(0);
const entityRows = computed(() => mainList.value);
const handleCurrentChange = (val) => {
const handleCurrentChange = val => {
if (val === currentPage.value) return;
currentPage.value = val;
getExportControlListApi();
......@@ -275,7 +321,7 @@ const currentRuleCompany = ref("");
const currentRuleCount = ref(0);
const currentRuleList = ref([]);
const handleRuleClick = (row) => {
const handleRuleClick = row => {
currentRuleCompany.value = row.entityNameZh || row.entityName;
currentRuleCount.value = row.ruleOrgCount;
currentRuleList.value = row.ruleOrgList || [];
......@@ -285,11 +331,11 @@ const handleRuleClick = (row) => {
// 筛选逻辑处理
const handleFilterChange = (item, list, type) => {
// 如果点击的是"全部"
if (item.value === 'all') {
if (item.value === "all") {
if (item.checked) {
// 选中全部,取消其他所有
list.forEach(i => {
if (i.value !== 'all') i.checked = false;
if (i.value !== "all") i.checked = false;
});
} else {
// 取消全部(通常不允许全部取消,至少得选一个,这里如果取消全部,就默认为全部选中)
......@@ -299,17 +345,17 @@ const handleFilterChange = (item, list, type) => {
// 点击的是具体项
if (item.checked) {
// 选中具体项,取消"全部"
const allItem = list.find(i => i.value === 'all');
const allItem = list.find(i => i.value === "all");
if (allItem) allItem.checked = false;
// 特殊处理制裁时间的自定义和其他年份互斥
if (type === 'time') {
if (item.value === 'custom') {
if (type === "time") {
if (item.value === "custom") {
list.forEach(i => {
if (i.value !== 'custom' && i.value !== 'all') i.checked = false;
if (i.value !== "custom" && i.value !== "all") i.checked = false;
});
} else {
const customItem = list.find(i => i.value === 'custom');
const customItem = list.find(i => i.value === "custom");
if (customItem) customItem.checked = false;
}
}
......@@ -317,7 +363,7 @@ const handleFilterChange = (item, list, type) => {
// 取消具体项,检查是否还有选中的
const anyChecked = list.some(i => i.checked);
if (!anyChecked) {
const allItem = list.find(i => i.value === 'all');
const allItem = list.find(i => i.value === "all");
if (allItem) allItem.checked = true;
}
}
......@@ -337,50 +383,54 @@ let abortController = null;
const getExportControlListApi = async () => {
// 取消上一轮未完成的请求
if (abortController) {
try { abortController.abort(); } catch { }
try {
abortController.abort();
} catch {}
}
abortController = new AbortController();
isFetching.value = true;
// 处理科技领域筛选
let techDomains = [];
const allTech = techFields.value.find(item => item.value === 'all');
const allTech = techFields.value.find(item => item.value === "all");
if (!allTech || !allTech.checked) {
techDomains = techFields.value
.filter(item => item.checked && item.value !== 'all')
.map(item => item.value);
techDomains = techFields.value.filter(item => item.checked && item.value !== "all").map(item => item.value);
}
// 处理实体类型筛选
let typeIds = [];
const allType = entityTypes.value.find(item => item.value === 'all');
const allType = entityTypes.value.find(item => item.value === "all");
if (!allType || !allType.checked) {
typeIds = entityTypes.value
.filter(item => item.checked && item.value !== 'all')
.map(item => Number(item.value));
typeIds = entityTypes.value.filter(item => item.checked && item.value !== "all").map(item => Number(item.value));
}
// 处理制裁时间筛选
let years = [];
let startDate = undefined;
let endDate = undefined;
const allTime = sanctionTimes.value.find(item => item.value === 'all');
const allTime = sanctionTimes.value.find(item => item.value === "all");
if (!allTime || !allTime.checked) {
years = sanctionTimes.value
.filter(item => item.checked && item.value !== 'all' && item.value !== 'custom')
.filter(item => item.checked && item.value !== "all" && item.value !== "custom")
.map(item => Number(item.value));
const customTime = sanctionTimes.value.find(item => item.value === 'custom');
const customTime = sanctionTimes.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')}`;
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 data = {
typeName: "实体清单",
// typeName: "实体清单",
sanTypeId: sanTypeId.value || 1,
isCn: onlyChina.value,
techDomains: techDomains.length > 0 ? techDomains : undefined,
entityTypes: typeIds.length > 0 ? typeIds : undefined,
......@@ -404,14 +454,14 @@ const getExportControlListApi = async () => {
total.value = res.data.totalElements;
}
} catch (error) {
if (!error || (error.code !== 'ERR_CANCELED' && error.name !== 'CanceledError' && error.name !== 'AbortError')) {
if (!error || (error.code !== "ERR_CANCELED" && error.name !== "CanceledError" && error.name !== "AbortError")) {
console.error(error);
}
} finally {
isFetching.value = false;
abortController = null;
}
}
};
watch(onlyChina, () => {
currentPage.value = 1;
......@@ -428,13 +478,14 @@ watch(searchKeyword, () => {
getExportControlListApi();
}, 300);
});
const sanTypeId = ref("");
onMounted(() => {
sanTypeId.value = router.currentRoute.value.query.sanTypeId || "1";
getExportControlListApi();
});
watch(customDateRange, () => {
if (sanctionTimes.value.find(item => item.value === 'custom' && item.checked)) {
if (sanctionTimes.value.find(item => item.value === "custom" && item.checked)) {
currentPage.value = 1;
getExportControlListApi();
}
......@@ -795,40 +846,40 @@ watch(customDateRange, () => {
.total-text {
font-size: 14px;
font-weight: 400;
color: #5F656C;
color: #5f656c;
font-family: "Microsoft YaHei";
}
:deep(.el-pagination) {
--el-pagination-button-bg-color: #fff;
--el-pagination-hover-color: #0E78F1;
--el-pagination-hover-color: #0e78f1;
--el-pagination-font-size: 14px;
.el-pager li {
border: 1px solid #DCDFE6;
border: 1px solid #dcdfe6;
border-radius: 4px;
margin: 0 4px;
font-weight: 400;
color: #5F656C;
color: #5f656c;
min-width: 32px;
height: 32px;
line-height: 30px;
&.is-active {
background-color: #0E78F1;
background-color: #0e78f1;
color: #fff;
border-color: #0E78F1;
border-color: #0e78f1;
}
&:hover:not(.is-active) {
color: #0E78F1;
border-color: #0E78F1;
color: #0e78f1;
border-color: #0e78f1;
}
}
.btn-prev,
.btn-next {
border: 1px solid #DCDFE6;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0;
margin: 0 4px;
......@@ -838,13 +889,13 @@ watch(customDateRange, () => {
text-align: center;
&:hover {
color: #0E78F1;
border-color: #0E78F1;
color: #0e78f1;
border-color: #0e78f1;
}
&[disabled] {
border-color: #E4E7ED;
color: #C0C4CC;
border-color: #e4e7ed;
color: #c0c4cc;
}
}
}
......
<template>
<div class="sanctions-overview">
<div class="side-nav">
<div v-for="(item, index) in activeTab" :key="index" class="tab-item" :class="{'active': index === activeIndex}" @click="activeIndex = index">
{{item}}
<span v-if="index === activeIndex" class="arrow"></span>
</div>
</div>
<div class="content-box">
<introductionPage v-if="activeIndex === 0" @update-entity-info="(data) => $emit('update-entity-info', data)"></introductionPage>
<listPage v-if="activeIndex === 1"></listPage>
</div>
</div>
<div class="sanctions-overview">
<div class="side-nav">
<div
v-for="(item, index) in activeTab"
:key="index"
class="tab-item"
:class="{ active: index === activeIndex }"
@click="activeIndex = index"
>
{{ item }}
<span v-if="index === activeIndex" class="arrow"></span>
</div>
</div>
<div class="content-box">
<introductionPage
v-show="activeIndex === 1"
@update-entity-info="data => $emit('update-entity-info', data)"
></introductionPage>
<listPage v-show="activeIndex === 0"></listPage>
</div>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
import introductionPage from "./components/introductionPage/index.vue"
import listPage from "./components/listPage/index.vue"
const emit = defineEmits(['update-entity-info'])
const activeTab = ref(["实体清单简介", "实体清单列表"])
const activeIndex = ref(0)
import { ref, defineEmits } from "vue";
import introductionPage from "./components/introductionPage/index.vue";
import listPage from "./components/listPage/index.vue";
const emit = defineEmits(["update-entity-info"]);
const activeTab = ref(["实体清单列表", "实体清单简介"]);
const activeIndex = ref(0);
</script>
<style scoped lang="scss">
*{
margin: 0;
padding: 0;
* {
margin: 0;
padding: 0;
}
.sanctions-overview{
width: 1601px;
margin: 0 auto;
position: relative;
// min-height: 800px;
.side-nav {
position: absolute;
top: 27px;
right: 100%;
margin-right: 12px;
display: flex;
flex-direction: column;
gap: 16px;
.tab-item {
cursor: pointer;
padding: 4px 20px;
border-radius: 22px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
white-space: nowrap;
display: flex;
align-items: center;
&.active {
background-color: rgb(5, 95, 194);
color: #fff;
.arrow {
display: inline-block;
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 6px solid #fff;
margin-left: 8px;
}
}
}
}
.content-box {
width: 100%;
}
.sanctions-overview {
width: 1601px;
margin: 0 auto;
position: relative;
// min-height: 800px;
.side-nav {
position: absolute;
top: 27px;
right: 100%;
margin-right: 12px;
display: flex;
flex-direction: column;
gap: 16px;
.tab-item {
cursor: pointer;
padding: 4px 20px;
border-radius: 22px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
white-space: nowrap;
display: flex;
align-items: center;
&.active {
background-color: rgb(5, 95, 194);
color: #fff;
.arrow {
display: inline-block;
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 6px solid #fff;
margin-left: 8px;
}
}
}
}
.content-box {
width: 100%;
}
}
</style>
<template>
<div class="entity-list">
<div class="header">
<div class="header-title">
<img :src="headerTitle.img" alt="">
<div>
<div class="title">
{{ headerTitle.title }}
<span>{{ headerTitle.titleEn }}</span>
</div>
<div class="department">
{{ headerTitle.department }}
</div>
</div>
<div class="btn">
<img :src="icon01" alt="">切换
</div>
</div>
<div class="header-nav">
<div
class="nav-item"
v-for="(item, index) in headerNavList"
:key="index"
:class="{ active: activeIndex === index }"
@click="activeIndex = index"
>
<img :src="activeIndex === index ? item.imgActive : item.img" alt="">
<span>{{ item.title }}</span>
<div class="active-line" v-if="activeIndex === index"></div>
</div>
</div>
</div>
<div class="main">
<sanctions-overview v-if="activeIndex === 0" @update-entity-info="handleUpdateEntityInfo"></sanctions-overview>
<data-statistics v-if="activeIndex === 1"></data-statistics>
<deep-mining v-if="activeIndex === 2"></deep-mining>
<impact-analysis v-if="activeIndex === 3"></impact-analysis>
</div>
</div>
<div class="entity-list">
<div class="header">
<div class="header-title">
<img :src="headerTitle.img" alt="" />
<div>
<div class="title">
{{ headerTitle.title }}
<span>{{ headerTitle.titleEn }}</span>
</div>
<div class="department">
{{ headerTitle.department }}
</div>
</div>
<div class="btn"><img :src="icon01" alt="" />切换</div>
</div>
<div class="header-nav">
<div
class="nav-item"
v-for="(item, index) in headerNavList"
:key="index"
:class="{ active: activeIndex === index }"
@click="activeIndex = index"
>
<img :src="activeIndex === index ? item.imgActive : item.img" alt="" />
<span>{{ item.title }}</span>
<div class="active-line" v-if="activeIndex === index"></div>
</div>
</div>
</div>
<div class="main">
<sanctions-overview v-if="activeIndex === 0" @update-entity-info="handleUpdateEntityInfo"></sanctions-overview>
<data-statistics v-if="activeIndex === 1"></data-statistics>
<deep-mining v-if="activeIndex === 2"></deep-mining>
<impact-analysis v-if="activeIndex === 3"></impact-analysis>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import sanctionsOverview from "./components/sanctionsOverview/index.vue";
import dataStatistics from "./components/dataStatistics/index.vue";
......@@ -58,27 +57,34 @@ import icon2Active from "../assets/icons/icon2_active.png";
import icon3 from "../assets/icons/icon3.png";
import icon3Active from "../assets/icons/icon3_active.png";
const route = useRoute();
const headerTitle = ref({
// img: title,
// title: "实体清单",
// titleEn: "Entity List",
// department: "美国商务部工业与安全局"
})
// img: title,
// title: "实体清单",
// titleEn: "Entity List",
// department: "美国商务部工业与安全局"
});
const handleUpdateEntityInfo = (data) => {
if (data) {
headerTitle.value = {
...headerTitle.value,
title: data.name,
titleEn: data.originalName,
department: data.orgName,
departId: data.orgId,
img: data.orgLogoUrl || title
}
}
}
onMounted(() => {
// 获取路由参数id
const id = route.query.id;
console.log("EntityList 页面接收到的 id:", id);
});
const handleUpdateEntityInfo = data => {
console.log("更新实体清单基本信息:", data);
if (data) {
headerTitle.value = {
...headerTitle.value,
title: data.name,
titleEn: data.originalName,
department: data.orgName,
departId: data.orgId,
img: data.orgLogoUrl || title
};
}
};
const activeIndex = ref(0)
const activeIndex = ref(0);
const headerNavList = ref([
{
......
......@@ -137,15 +137,18 @@
<AnalysisBox title="制裁实体国家分布情况">
<div class="country-list">
<div class="list-item" v-for="(item, index) in countryDistribution" :key="index">
<img :src="flag" alt="" class="flag">
<img :src="flag" alt="" class="flag" />
<div class="country-name">{{ item.name }}</div>
<div class="progress-bar-container">
<div class="progress-bar" :style="{
width: item.width,
background: item.gradient
}"></div>
<div
class="progress-bar"
:style="{
width: item.width,
background: item.gradient
}"
></div>
</div>
<div class="count" :class="{ 'highlight': index === 0 }">{{ item.count }}</div>
<div class="count" :class="{ highlight: index === 0 }">{{ item.count }}</div>
</div>
</div>
<div class="bottom">
......@@ -170,9 +173,13 @@
<div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="rank-name">{{ item.name }}</div>
<div class="rank-bar-bg">
<div class="rank-bar-fill"
:style="{ width: (maxRegionCount > 0 ? (item.count / maxRegionCount) * 100 : 0) + '%', background: getBarColor(index) }">
</div>
<div
class="rank-bar-fill"
:style="{
width: (maxRegionCount > 0 ? (item.count / maxRegionCount) * 100 : 0) + '%',
background: getBarColor(index)
}"
></div>
</div>
<div class="rank-value">{{ item.count }}</div>
</div>
......@@ -201,10 +208,18 @@ import * as echarts from "echarts";
import chinaJson from "../../../utils/China.json";
import ai from "./assets/ai.png";
import right from "./assets/right.png";
import flag from "../../assets/default-icon2.png"
import flag from "../../assets/default-icon2.png";
import { useRouter } from "vue-router";
import { getSingleSanctionTotalCount, getSingleSanctionDomainCount, getSingleSanctionEntityTypeCount, getSingleSanctionEntityCountryCount, getSingleSanctionEntityRegionCount } from "@/api/exportControlV2.0";
import {
getSingleSanctionTotalCount,
getSingleSanctionDomainCount,
getSingleSanctionEntityTypeCount,
getSingleSanctionEntityCountryCount,
getSingleSanctionEntityRegionCount
} from "@/api/exportControlV2.0";
import { useRoute } from "vue-router";
const route = useRoute();
// 单次制裁-数据统计-制裁实体地域分布情况
const regionDistribution = ref([]);
const maxRegionCount = ref(0);
......@@ -212,8 +227,8 @@ const maxRegionCount = ref(0);
const getRegionData = async () => {
if (!sanRecordId.value) return;
try {
const params = { sanRecordId: sanRecordId.value };
if (regionTime.value && regionTime.value !== 'all') {
const params = { sanRecordId: sanRecordId.value, sanTypeId: route.query.sanTypeId || "1" };
if (regionTime.value && regionTime.value !== "all") {
params.startDate = `${regionTime.value}-01-01`;
params.endDate = `${regionTime.value}-12-31`;
}
......@@ -226,9 +241,7 @@ const getRegionData = async () => {
} catch (error) {
console.log(error);
}
}
};
// 单次制裁-数据统计-制裁实体国家分布情况
const countryDistribution = ref([]);
......@@ -236,7 +249,7 @@ const countryDistribution = ref([]);
const getCountryCount = async () => {
if (!sanRecordId.value) return;
try {
const params = { sanRecordId: sanRecordId.value };
const params = { sanRecordId: sanRecordId.value, sanTypeId: sanTypeId.value };
const res = await getSingleSanctionEntityCountryCount(params);
if (res.code === 200) {
const rawData = res.data || [];
......@@ -245,7 +258,7 @@ const getCountryCount = async () => {
countryDistribution.value = rawData.map((item, index) => {
// 计算宽度,最大值对应 80%
const width = maxCount > 0 ? (item.count / maxCount) * 80 + '%' : '0%';
const width = maxCount > 0 ? (item.count / maxCount) * 80 + "%" : "0%";
// 根据索引分配渐变色
let gradient = "";
......@@ -267,9 +280,7 @@ const getCountryCount = async () => {
} catch (error) {
console.log(error);
}
}
};
// 单次制裁-数据统计-制裁实体类型分布情况
const entityTypeCount = ref([]);
......@@ -277,8 +288,8 @@ const entityTypeCount = ref([]);
const getEntityTypeCount = async () => {
if (!sanRecordId.value) return;
try {
const params = { sanRecordId: sanRecordId.value };
if (typeTime.value && typeTime.value !== 'all') {
const params = { sanRecordId: sanRecordId.value, sanTypeId: sanTypeId.value };
if (typeTime.value && typeTime.value !== "all") {
params.startDate = `${typeTime.value}-01-01`;
params.endDate = `${typeTime.value}-12-31`;
}
......@@ -290,10 +301,7 @@ const getEntityTypeCount = async () => {
} catch (error) {
console.log(error);
}
}
};
// 单次制裁-数据统计-制裁实体领域分布情况
const domainCount = ref([]);
......@@ -301,8 +309,8 @@ const domainCount = ref([]);
const getDomainCount = async () => {
if (!sanRecordId.value) return;
try {
const params = { sanRecordId: sanRecordId.value };
if (domainTime.value && domainTime.value !== 'all') {
const params = { sanRecordId: sanRecordId.value, sanTypeId: sanTypeId.value };
if (domainTime.value && domainTime.value !== "all") {
params.startDate = `${domainTime.value}-01-01`;
params.endDate = `${domainTime.value}-12-31`;
}
......@@ -314,8 +322,7 @@ const getDomainCount = async () => {
} catch (error) {
console.log(error);
}
}
};
// 单次制裁-数据统计-总量统计
const totalCount = ref({});
......@@ -323,22 +330,21 @@ const totalCount = ref({});
const getTotalCount = async () => {
if (!sanRecordId.value) return;
try {
const res = await getSingleSanctionTotalCount({ sanRecordId: sanRecordId.value });
const res = await getSingleSanctionTotalCount(route.query.sanTypeId);
if (res.code === 200) {
totalCount.value = res.data || {};
}
} catch (error) {
console.log(error);
}
}
};
const router = useRouter();
const sanRecordId = ref("")
const sanRecordId = ref("");
const getUrlParams = () => {
const urlParams = new URLSearchParams(window.location.search);
sanRecordId.value = urlParams.get("id") || ""
}
sanRecordId.value = urlParams.get("id") || "";
};
const regionTime = ref("all");
const domainTime = ref("all");
......@@ -355,11 +361,7 @@ watch(regionTime, () => {
getRegionData();
});
const timeOptions = [
{ label: "全部时间", value: "all" }
];
const timeOptions = [{ label: "全部时间", value: "all" }];
// 生成2000-2025年的选项
for (let i = 2025; i >= 2000; i--) {
timeOptions.push({ label: `${i}年`, value: `${i}` });
......@@ -640,8 +642,11 @@ const initTypeChart = () => {
chart.resize();
});
};
const sanTypeId = ref("");
onMounted(() => {
// 获取路由参数id
sanTypeId.value = route.query.sanTypeId;
console.log("页面接收到的 sanTypeId:", sanTypeId.value);
// 获取url参数
getUrlParams();
// 单次制裁-数据统计-总量统计-请求
......
<template>
<div class="sanctions-overview">
<div class="main">
<div class="left">
<div class="left-top">
<AnalysisBox title="基本信息" :showAllBtn="false">
<div class="left-top-title">
<div class="info-row">
<div class="label">发布机构:</div>
<div class="value link">
<img :src="title" alt="" class="icon">
<span @click="handleClickDp">{{ formattedData.postOrgName }} ></span>
</div>
</div>
<div class="info-row">
<div class="label">发布时间:</div>
<div class="value">{{ formattedData.postDate }}</div>
</div>
<div class="info-row">
<div class="label">生效时间:</div>
<div class="value">{{ formattedData.effectiveDate }}</div>
</div>
<div class="info-row">
<div class="label">发布文件:</div>
<div class="value">{{ formattedData.fileCode }}</div>
</div>
<div class="info-row">
<div class="label">案卷号:</div>
<div class="value">{{ formattedData.administrativeOrderId }}</div>
</div>
<div class="info-row">
<div class="label">发布人:</div>
<div class="value link">
<img :src="defaultTitle" alt="" class="icon avatar">
<span @click="handleClick">{{ formattedData.postPersonName }} ></span>
</div>
</div>
<div class="info-row">
<div class="label">制裁领域:</div>
<div class="value tags">
<span class="tag" v-for="(domain, index) in formattedData.domains" :key="index">{{ domain }}</span>
</div>
</div>
</div>
<div class="left-top-content">
<div class="content-title">制裁实体分布:</div>
<div class="distribution-list">
<div class="list-item" v-for="(item, index) in entityDistribution" :key="index">
<img :src="flag" alt="" class="flag">
<div class="country-name">{{ item.name }}</div>
<div class="progress-bar-container">
<div class="progress-bar" :style="{
width: item.width,
background: item.gradient
}"></div>
</div>
<div class="count" :class="{ 'highlight': index === 0 }">{{ item.count }}</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
<div class="left-bottom">
<AnalysisBox title="制裁背景" :showAllBtn="false">
<div class="left-bottom-content">
<div class="timeline-list">
<div class="timeline-item" v-for="(item, index) in timelineData" :key="index">
<div class="date-row">
<div class="dot"></div>
<div class="date">{{ item.date }}</div>
</div>
<div class="content">{{ item.content }}</div>
</div>
</div>
<div class="view-more" v-if="hasMore" @click="loadMore">
查看更多 <el-icon class="icon-more">
<DArrowRight />
</el-icon>
</div>
</div>
</AnalysisBox>
</div>
</div>
<div class="right" v-loading="isLoading">
<AnalysisBox title="制裁清单" :showAllBtn="false">
<div class="right-title">
<div class="stats-row">
<div class="tabs">
<div class="tab-btn" :class="{ active: activeTab === 'add' }" @click="activeTab = 'add'">新增实体</div>
<div class="tab-btn" :class="{ active: activeTab === 'remove' }" @click="activeTab = 'remove'">移除实体
</div>
</div>
<div class="stats-info">
<div class="stat-item">
<span class="dot red"></span>
<span class="text">新增 <span class="num red">{{ addCount }}</span> 家 (50%规则涉及<span class="num red">{{
addRuleCount }}</span>家)</span>
</div>
<div class="stat-item">
<span class="dot green"></span>
<span class="text">移除 <span class="num green">{{ removeCount }}</span> 家 (50%规则涉及<span
class="num green">{{ removeRuleCount }}</span>家)</span>
</div>
</div>
</div>
<div class="filter-row">
<div class="filter-left">
<!-- <el-select v-model="filterEntity" placeholder="受制裁实体" style="width: 184px">
<div class="sanctions-overview">
<div class="main">
<div class="left">
<div class="left-top">
<AnalysisBox title="基本信息" :showAllBtn="false">
<div class="left-top-title">
<div class="info-row">
<div class="label">发布机构:</div>
<div class="value link">
<img :src="title" alt="" class="icon" />
<span @click="handleClickDp">{{ formattedData.postOrgName }} ></span>
</div>
</div>
<div class="info-row">
<div class="label">发布时间:</div>
<div class="value">{{ formattedData.postDate }}</div>
</div>
<div class="info-row">
<div class="label">生效时间:</div>
<div class="value">{{ formattedData.effectiveDate }}</div>
</div>
<div class="info-row">
<div class="label">发布文件:</div>
<div class="value">{{ formattedData.fileCode }}</div>
</div>
<div class="info-row">
<div class="label">案卷号:</div>
<div class="value">{{ formattedData.administrativeOrderId }}</div>
</div>
<div class="info-row">
<div class="label">发布人:</div>
<div class="value link">
<img :src="defaultTitle" alt="" class="icon avatar" />
<span @click="handleClick">{{ formattedData.postPersonName }} ></span>
</div>
</div>
<div class="info-row">
<div class="label">制裁领域:</div>
<div class="value tags">
<span class="tag" v-for="(domain, index) in formattedData.domains" :key="index">{{
domain
}}</span>
</div>
</div>
</div>
<div class="left-top-content">
<div class="content-title">制裁实体分布:</div>
<div class="distribution-list">
<div class="list-item" v-for="(item, index) in entityDistribution" :key="index">
<img :src="flag" alt="" class="flag" />
<div class="country-name">{{ item.name }}</div>
<div class="progress-bar-container">
<div
class="progress-bar"
:style="{
width: item.width,
background: item.gradient
}"
></div>
</div>
<div class="count" :class="{ highlight: index === 0 }">{{ item.count }}</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
<div class="left-bottom">
<AnalysisBox title="制裁背景" :showAllBtn="false">
<div class="left-bottom-content">
<div class="timeline-list">
<div class="timeline-item" v-for="(item, index) in timelineData" :key="index">
<div class="date-row">
<div class="dot"></div>
<div class="date">{{ item.date }}</div>
</div>
<div class="content">{{ item.content }}</div>
</div>
</div>
<div class="view-more" v-if="hasMore" @click="loadMore">
查看更多
<el-icon class="icon-more">
<DArrowRight />
</el-icon>
</div>
</div>
</AnalysisBox>
</div>
</div>
<div class="right" v-loading="isLoading">
<AnalysisBox title="制裁清单" :showAllBtn="false">
<div class="right-title">
<div class="stats-row">
<div class="tabs">
<div class="tab-btn" :class="{ active: activeTab === 'add' }" @click="activeTab = 'add'">
新增实体
</div>
<div class="tab-btn" :class="{ active: activeTab === 'remove' }" @click="activeTab = 'remove'">
移除实体
</div>
</div>
<div class="stats-info">
<div class="stat-item">
<span class="dot red"></span>
<span class="text"
>新增 <span class="num red">{{ addCount }}</span> 家 (50%规则涉及<span class="num red">{{
addRuleCount
}}</span
>家)</span
>
</div>
<div class="stat-item">
<span class="dot green"></span>
<span class="text"
>移除 <span class="num green">{{ removeCount }}</span> 家 (50%规则涉及<span
class="num green"
>{{ removeRuleCount }}</span
>家)</span
>
</div>
</div>
</div>
<div class="filter-row">
<div class="filter-left">
<!-- <el-select v-model="filterEntity" placeholder="受制裁实体" style="width: 184px">
<el-option label="受制裁实体" value="1" />
</el-select> -->
</div>
<div class="filter-right">
<el-checkbox v-model="onlyChina" label="只看中国实体" />
<el-select v-model="filterField" placeholder="全部领域" style="width: 150px; margin: 0 12px 0 16px">
<el-option label="全部领域" value="" />
<el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-input v-model="searchKeyword" placeholder="搜索实体" style="width: 150px" :suffix-icon="Search" />
</div>
</div>
</div>
<div class="right-content">
<div class="sanction-group-list">
<div class="sanction-group" v-for="(group, index) in sanctionList" :key="index">
<el-table :data="group.entities" style="width: 100%">
<el-table-column label="实体名称" min-width="280">
<template #default="scope">
<div class="name-cell">
<div class="dot"></div>
<img :src="defaultTitle" class="company-icon" />
<span class="company-name" @click="handleCompClick(scope.row)">{{ scope.row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="涉及领域" width="180" align="center">
<template #default="scope">
<span v-for="(item, index) in scope.row.fields" :key="index" class="tag"
:style="getTagStyle(item)" style="margin: 0 2px;">{{ item }}</span>
</template>
</el-table-column>
<el-table-column prop="location" label="上市地点" width="90" align="center" />
<el-table-column prop="date" label="制裁时间" width="150" align="center" />
<el-table-column prop="revenue" label="营收(亿元)" width="110" align="center" />
<el-table-column label="50%规则子企业" width="180" align="center">
<template #default="scope">
<span v-if="scope.row.subsidiaryCount" class="subsidiary-link"
@click="handleSubsidiaryClick(scope.row)">
{{ scope.row.subsidiaryText }} <span class="blue-text">{{ scope.row.subsidiaryCount }}家 ></span>
</span>
<span v-else>--</span>
</template>
</el-table-column>
</el-table>
<div class="reason-box">
{{ group.reason }}
</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
<!-- 50%规则子企业弹框 -->
<RuleSubsidiaryDialog v-model="subsidiaryDialogVisible" :company-name="currentSubsidiaryCompanyName"
:total-count="currentSubsidiaryCount" :data-list="currentSubsidiaryList" />
</div>
</div>
<div class="filter-right">
<el-checkbox v-model="onlyChina" label="只看中国实体" />
<el-select
v-model="filterField"
placeholder="全部领域"
style="width: 150px; margin: 0 12px 0 16px"
>
<el-option label="全部领域" value="" />
<el-option
v-for="item in domainOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-model="searchKeyword"
placeholder="搜索实体"
style="width: 150px"
:suffix-icon="Search"
/>
</div>
</div>
</div>
<div class="right-content">
<div class="sanction-group-list">
<div class="sanction-group" v-for="(group, index) in sanctionList" :key="index">
<el-table :data="group.entities" style="width: 100%">
<el-table-column label="实体名称" min-width="280">
<template #default="scope">
<div class="name-cell">
<div class="dot"></div>
<img :src="defaultTitle" class="company-icon" />
<span class="company-name" @click="handleCompClick(scope.row)">{{
scope.row.name
}}</span>
</div>
</template>
</el-table-column>
<el-table-column label="涉及领域" width="180" align="center">
<template #default="scope">
<span
v-for="(item, index) in scope.row.fields"
:key="index"
class="tag"
:style="getTagStyle(item)"
style="margin: 0 2px"
>{{ item }}</span
>
</template>
</el-table-column>
<el-table-column prop="location" label="上市地点" width="90" align="center" />
<el-table-column prop="date" label="制裁时间" width="150" align="center" />
<el-table-column prop="revenue" label="营收(亿元)" width="110" align="center" />
<el-table-column label="50%规则子企业" width="180" align="center">
<template #default="scope">
<span
v-if="scope.row.subsidiaryCount"
class="subsidiary-link"
@click="handleSubsidiaryClick(scope.row)"
>
{{ scope.row.subsidiaryText }}
<span class="blue-text">{{ scope.row.subsidiaryCount }}家 ></span>
</span>
<span v-else>--</span>
</template>
</el-table-column>
</el-table>
<div class="reason-box">
{{ group.reason }}
</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
<!-- 50%规则子企业弹框 -->
<RuleSubsidiaryDialog
v-model="subsidiaryDialogVisible"
:company-name="currentSubsidiaryCompanyName"
:total-count="currentSubsidiaryCount"
:data-list="currentSubsidiaryList"
/>
</div>
</template>
<script setup>
......@@ -172,34 +219,40 @@ import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { DArrowRight, Search } from "@element-plus/icons-vue";
import { debounce } from "lodash";
import title from "../../assets/title.png"
import defaultTitle from "../../assets/default-icon1.png"
import flag from "../../assets/default-icon2.png"
import { getSingleSanctionEntityCountry, getSingleSanctionBackground, getSingleSanctionOverviewList } from "@/api/exportControlV2.0";
import title from "../../assets/title.png";
import defaultTitle from "../../assets/default-icon1.png";
import flag from "../../assets/default-icon2.png";
import {
getSingleSanctionEntityCountry,
getSingleSanctionBackground,
getSingleSanctionOverviewList
} from "@/api/exportControlV2.0";
import RuleSubsidiaryDialog from "../../../v2.0EntityList/components/sanctionsOverview/components/listPage/RuleSubsidiaryDialog.vue";
import { useRoute } from "vue-router";
const route = useRoute();
// 跳转公司详情页
const handleCompClick = item => {
if (!item.entityId) {
ElMessage.warning("暂无数据");
return;
}
window.sessionStorage.setItem('curTabName', item.name)
const curRoute = router.resolve({ name: "companyPages", params: { id: item.entityId } });
window.open(curRoute.href, "_blank");
if (!item.entityId) {
ElMessage.warning("暂无数据");
return;
}
window.sessionStorage.setItem("curTabName", item.name);
const curRoute = router.resolve({ name: "companyPages", params: { id: item.entityId } });
window.open(curRoute.href, "_blank");
};
// 跳转发布机构详情页
const handleClickDp = () => {
// console.log("点击了发布机构:", props.data);
window.sessionStorage.setItem('curTabName', props.data.postOrgName)
const route = router.resolve({
path: "/institution",
query: {
id: props.data.postOrgId
}
});
window.open(route.href, "_blank");
// console.log("点击了发布机构:", props.data);
window.sessionStorage.setItem("curTabName", props.data.postOrgName);
const route = router.resolve({
path: "/institution",
query: {
id: props.data.postOrgId
}
});
window.open(route.href, "_blank");
};
// 50%规则子企业弹框逻辑
......@@ -208,63 +261,65 @@ const currentSubsidiaryCompanyName = ref("");
const currentSubsidiaryCount = ref(0);
const currentSubsidiaryList = ref([]);
const handleSubsidiaryClick = (row) => {
currentSubsidiaryCompanyName.value = row.name;
currentSubsidiaryCount.value = row.subsidiaryCount;
currentSubsidiaryList.value = row.ruleOrgList || [];
subsidiaryDialogVisible.value = true;
const handleSubsidiaryClick = row => {
currentSubsidiaryCompanyName.value = row.name;
currentSubsidiaryCount.value = row.subsidiaryCount;
currentSubsidiaryList.value = row.ruleOrgList || [];
subsidiaryDialogVisible.value = true;
};
// 单次制裁-制裁概况-制裁清单
const sanctionList = ref([])
const addCount = ref(0)
const addRuleCount = ref(0)
const removeCount = ref(0)
const removeRuleCount = ref(0)
const sanctionList = ref([]);
const addCount = ref(0);
const addRuleCount = ref(0);
const removeCount = ref(0);
const removeRuleCount = ref(0);
const isLoading = ref(false)
const isLoading = ref(false);
// 调用单次制裁-制裁概况-制裁清单接口
const getSanctionOverviewList = async () => {
isLoading.value = true
try {
const res = await getSingleSanctionOverviewList({
sanRecordId: sanRecordId.value,
isOnlyCn: onlyChina.value,
domainId: filterField.value || undefined,
searchText: searchKeyword.value || undefined,
searchType: searchType.value
})
isLoading.value = false
if (res.code === 200) {
const data = res.data || {}
addCount.value = data.addCount || 0
addRuleCount.value = data.addRuleCount || 0
removeCount.value = data.removeCount || 0
removeRuleCount.value = data.removeRuleCount || 0
const list = data.sanList || []
sanctionList.value = list.map(item => ({
reason: item.sanReason,
entities: (item.orgList || []).map(org => ({
...org,
name: org.entityNameZh || org.entityName,
fields: org.techDomains || [],
location: "--", // 接口暂无数据
date: org.startTime ? org.startTime.replace(/^(\d{4})-(\d{2})-(\d{2}).*$/, '$1年$2月$3日') : '--', // 格式化日期
revenue: "--", // 接口暂无数据
subsidiaryCount: org.ruleOrgCount || 0,
subsidiaryText: org.ruleOrgList && org.ruleOrgList.length > 0
? (org.ruleOrgList[0].orgName.length > 10 ? org.ruleOrgList[0].orgName.slice(0, 10) + '...' : org.ruleOrgList[0].orgName) + '...等'
: ''
}))
}))
}
} catch (error) {
console.log(error)
}
}
isLoading.value = true;
try {
const res = await getSingleSanctionOverviewList({
sanRecordId: sanRecordId.value,
isOnlyCn: onlyChina.value,
domainId: filterField.value || undefined,
searchText: searchKeyword.value || undefined,
searchType: searchType.value
});
isLoading.value = false;
if (res.code === 200) {
const data = res.data || {};
addCount.value = data.addCount || 0;
addRuleCount.value = data.addRuleCount || 0;
removeCount.value = data.removeCount || 0;
removeRuleCount.value = data.removeRuleCount || 0;
const list = data.sanList || [];
sanctionList.value = list.map(item => ({
reason: item.sanReason,
entities: (item.orgList || []).map(org => ({
...org,
name: org.entityNameZh || org.entityName,
fields: org.techDomains || [],
location: "--", // 接口暂无数据
date: org.startTime ? org.startTime.replace(/^(\d{4})-(\d{2})-(\d{2}).*$/, "$1年$2月$3日") : "--", // 格式化日期
revenue: "--", // 接口暂无数据
subsidiaryCount: org.ruleOrgCount || 0,
subsidiaryText:
org.ruleOrgList && org.ruleOrgList.length > 0
? (org.ruleOrgList[0].orgName.length > 10
? org.ruleOrgList[0].orgName.slice(0, 10) + "..."
: org.ruleOrgList[0].orgName) + "...等"
: ""
}))
}));
}
} catch (error) {
console.log(error);
}
};
// 单次制裁-制裁概况-制裁背景
const timelinePage = ref(1);
......@@ -272,811 +327,814 @@ const timelinePageSize = ref(3);
const hasMore = ref(true);
const getSanctionBackground = async (isLoadMore = false) => {
try {
const res = await getSingleSanctionBackground({
sanRecordId: sanRecordId.value,
pageNum: timelinePage.value,
pageSize: timelinePageSize.value,
})
if (res.code === 200) {
const list = res.data.content || [];
const formattedList = list.map(item => ({
...item,
date: item.createTime ? item.createTime.split(' ')[0] : '', // 提取日期部分
content: item.summaryZh
}));
if (isLoadMore) {
timelineData.value = [...timelineData.value, ...formattedList];
} else {
timelineData.value = formattedList;
}
// 判断是否还有更多数据
const totalElements = res.data.totalElements || 0;
hasMore.value = timelineData.value.length < totalElements;
}
} catch (error) {
console.log(error)
}
}
try {
const res = await getSingleSanctionBackground({
sanRecordId: sanRecordId.value,
pageNum: timelinePage.value,
pageSize: timelinePageSize.value
});
if (res.code === 200) {
const list = res.data.content || [];
const formattedList = list.map(item => ({
...item,
date: item.createTime ? item.createTime.split(" ")[0] : "", // 提取日期部分
content: item.summaryZh
}));
if (isLoadMore) {
timelineData.value = [...timelineData.value, ...formattedList];
} else {
timelineData.value = formattedList;
}
// 判断是否还有更多数据
const totalElements = res.data.totalElements || 0;
hasMore.value = timelineData.value.length < totalElements;
}
} catch (error) {
console.log(error);
}
};
const loadMore = () => {
timelinePage.value++;
getSanctionBackground(true);
timelinePage.value++;
getSanctionBackground(true);
};
const router = useRouter();
const sanRecordId = ref("")
const sanRecordId = ref("");
const getUrlParams = () => {
const urlParams = new URLSearchParams(window.location.search);
sanRecordId.value = urlParams.get("id") || ""
}
const urlParams = new URLSearchParams(window.location.search);
sanRecordId.value = urlParams.get("id") || "";
};
// 单次制裁-制裁概况-制裁实体国家分布
const getEntityCountry = async () => {
try {
const res = await getSingleSanctionEntityCountry({
sanRecordId: sanRecordId.value
})
if (res.code === 200) {
const rawData = res.data || [];
const maxCount = Math.max(...rawData.map(item => item.count || 0), 1);
// 颜色池 - 用于循环分配
const gradientPool = [
"linear-gradient(270deg, rgba(255,170,0,1) 0%, rgba(255,255,255,0) 100%)", // 橙
"linear-gradient(270deg, rgba(5,95,194,1) 0%, rgba(255,255,255,0) 100%)", // 蓝
"linear-gradient(270deg, rgba(114,46,209,1) 0%, rgba(255,255,255,0) 100%)", // 紫
"linear-gradient(270deg, rgba(16,178,166,1) 0%, rgba(255,255,255,0) 100%)" // 青
];
// 固定颜色映射 - 特定国家使用固定颜色
const fixedColorMap = {
"中国": "linear-gradient(270deg, rgba(206,79,81,1) 0%, rgba(255,255,255,0) 100%)" // 红
};
entityDistribution.value = rawData.map((item, index) => ({
...item,
width: `${((item.count || 0) / maxCount) * 100}%`,
// 优先使用固定颜色,否则循环使用颜色池中的颜色
gradient: fixedColorMap[item.name] || gradientPool[index % gradientPool.length]
}));
}
} catch (error) {
console.log(error)
}
}
try {
const res = await getSingleSanctionEntityCountry({
sanRecordId: sanRecordId.value,
sanTypeId: sanTypeId.value || 1 // 实体清单固定1
});
if (res.code === 200) {
const rawData = res.data || [];
const maxCount = Math.max(...rawData.map(item => item.count || 0), 1);
// 颜色池 - 用于循环分配
const gradientPool = [
"linear-gradient(270deg, rgba(255,170,0,1) 0%, rgba(255,255,255,0) 100%)", // 橙
"linear-gradient(270deg, rgba(5,95,194,1) 0%, rgba(255,255,255,0) 100%)", // 蓝
"linear-gradient(270deg, rgba(114,46,209,1) 0%, rgba(255,255,255,0) 100%)", // 紫
"linear-gradient(270deg, rgba(16,178,166,1) 0%, rgba(255,255,255,0) 100%)" // 青
];
// 固定颜色映射 - 特定国家使用固定颜色
const fixedColorMap = {
中国: "linear-gradient(270deg, rgba(206,79,81,1) 0%, rgba(255,255,255,0) 100%)" // 红
};
entityDistribution.value = rawData.map((item, index) => ({
...item,
width: `${((item.count || 0) / maxCount) * 100}%`,
// 优先使用固定颜色,否则循环使用颜色池中的颜色
gradient: fixedColorMap[item.name] || gradientPool[index % gradientPool.length]
}));
}
} catch (error) {
console.log(error);
}
};
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
data: {
type: Object,
default: () => ({})
}
});
// 跳转到人物页
const handleClick = () => {
window.sessionStorage.setItem("curTabName", props.data.postPersonName)
const route = router.resolve({
path: "/characterPage",
query: {
// type: props.data.type,
personId: props.data.postPersonId
}
});
window.open(route.href, "_blank");
window.sessionStorage.setItem("curTabName", props.data.postPersonName);
const route = router.resolve({
path: "/characterPage",
query: {
// type: props.data.type,
personId: props.data.postPersonId
}
});
window.open(route.href, "_blank");
};
// 计算属性处理数据
const formattedData = computed(() => {
const info = props.data || {};
// 日期格式化
const formatDate = (dateStr) => {
if (!dateStr) return '';
const date = new Date(dateStr);
if (isNaN(date.getTime())) return dateStr;
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日`;
};
return {
postOrgName: info.postOrgName,
postDate: formatDate(info.postDate),
effectiveDate: formatDate(info.effectiveDate),
fileCode: info.fileCode ? `${info.fileCode} ` : '',
administrativeOrderId: info.administrativeOrderId ? `No. ${info.administrativeOrderId}` : '',
postPersonName: info.postPersonName,
domains: info.domainNames
};
const info = props.data || {};
// 日期格式化
const formatDate = dateStr => {
if (!dateStr) return "";
const date = new Date(dateStr);
if (isNaN(date.getTime())) return dateStr;
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日`;
};
return {
postOrgName: info.postOrgName,
postDate: formatDate(info.postDate),
effectiveDate: formatDate(info.effectiveDate),
fileCode: info.fileCode ? `${info.fileCode} ` : "",
administrativeOrderId: info.administrativeOrderId ? `No. ${info.administrativeOrderId}` : "",
postPersonName: info.postPersonName,
domains: info.domainNames
};
});
const filterEntity = ref('')
const onlyChina = ref(false)
const filterField = ref('')
const searchKeyword = ref('')
const activeTab = ref('add')
const searchType = computed(() => activeTab.value)
const filterEntity = ref("");
const onlyChina = ref(false);
const filterField = ref("");
const searchKeyword = ref("");
const activeTab = ref("add");
const searchType = computed(() => activeTab.value);
// 监听筛选条件变化
watch([onlyChina, filterField, activeTab], () => {
getSanctionOverviewList()
})
getSanctionOverviewList();
});
// 搜索框防抖
const debouncedSearch = debounce(() => {
getSanctionOverviewList()
}, 500)
getSanctionOverviewList();
}, 500);
watch(searchKeyword, () => {
debouncedSearch()
})
debouncedSearch();
});
const domainOptions = ref([
{ 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" }
{ 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 tagColors = [
{ bg: 'rgb(242, 235, 255)', border: 'rgb(211, 190, 255)', text: 'rgb(114, 46, 209)' }, // Purple
{ bg: 'rgb(225, 250, 248)', border: 'rgb(178, 242, 238)', text: 'rgb(16, 178, 166)' }, // Cyan
{ bg: 'rgb(231, 243, 255)', border: 'rgb(186, 224, 255)', text: 'rgb(5, 95, 194)' }, // Blue
{ bg: 'rgb(255, 247, 230)', border: 'rgb(255, 213, 145)', text: 'rgb(250, 140, 22)' }, // Orange
{ bg: 'rgb(255, 241, 240)', border: 'rgb(255, 204, 199)', text: 'rgb(245, 34, 45)' }, // Red
{ bg: 'rgb(246, 255, 237)', border: 'rgb(183, 235, 143)', text: 'rgb(82, 196, 26)' }, // Green
{ bg: "rgb(242, 235, 255)", border: "rgb(211, 190, 255)", text: "rgb(114, 46, 209)" }, // Purple
{ bg: "rgb(225, 250, 248)", border: "rgb(178, 242, 238)", text: "rgb(16, 178, 166)" }, // Cyan
{ bg: "rgb(231, 243, 255)", border: "rgb(186, 224, 255)", text: "rgb(5, 95, 194)" }, // Blue
{ bg: "rgb(255, 247, 230)", border: "rgb(255, 213, 145)", text: "rgb(250, 140, 22)" }, // Orange
{ bg: "rgb(255, 241, 240)", border: "rgb(255, 204, 199)", text: "rgb(245, 34, 45)" }, // Red
{ bg: "rgb(246, 255, 237)", border: "rgb(183, 235, 143)", text: "rgb(82, 196, 26)" } // Green
];
const getTagStyle = (tagName) => {
if (!tagName) return {};
let hash = 0;
for (let i = 0; i < tagName.length; i++) {
hash = tagName.charCodeAt(i) + ((hash << 5) - hash);
}
const index = Math.abs(hash) % tagColors.length;
const color = tagColors[index];
return {
backgroundColor: color.bg,
borderColor: color.border,
color: color.text
};
}
const getTagStyle = tagName => {
if (!tagName) return {};
let hash = 0;
for (let i = 0; i < tagName.length; i++) {
hash = tagName.charCodeAt(i) + ((hash << 5) - hash);
}
const index = Math.abs(hash) % tagColors.length;
const color = tagColors[index];
return {
backgroundColor: color.bg,
borderColor: color.border,
color: color.text
};
};
const timelineData = ref([]);
const entityDistribution = ref([
{
name: "中国",
count: 24,
width: "100%",
gradient: "linear-gradient(270deg, rgba(206,79,81,1) 0%, rgba(255,255,255,0) 100%)"
},
{
name: "沙特阿拉伯",
count: 2,
width: "60%",
gradient: "linear-gradient(270deg, rgba(255,170,0,1) 0%, rgba(255,255,255,0) 100%)"
},
{
name: "俄罗斯",
count: 2,
width: "60%",
gradient: "linear-gradient(270deg, rgba(255,170,0,1) 0%, rgba(255,255,255,0) 100%)"
},
{
name: "中国香港",
count: 1,
width: "50%",
gradient: "linear-gradient(270deg, rgba(5,95,194,1) 0%, rgba(255,255,255,0) 100%)"
}
])
{
name: "中国",
count: 24,
width: "100%",
gradient: "linear-gradient(270deg, rgba(206,79,81,1) 0%, rgba(255,255,255,0) 100%)"
},
{
name: "沙特阿拉伯",
count: 2,
width: "60%",
gradient: "linear-gradient(270deg, rgba(255,170,0,1) 0%, rgba(255,255,255,0) 100%)"
},
{
name: "俄罗斯",
count: 2,
width: "60%",
gradient: "linear-gradient(270deg, rgba(255,170,0,1) 0%, rgba(255,255,255,0) 100%)"
},
{
name: "中国香港",
count: 1,
width: "50%",
gradient: "linear-gradient(270deg, rgba(5,95,194,1) 0%, rgba(255,255,255,0) 100%)"
}
]);
const sanTypeId = ref("");
onMounted(() => {
// 获取URL参数
getUrlParams()
// 单次制裁-制裁概况-制裁实体国家分布
getEntityCountry()
// 单次制裁-制裁概况-制裁背景
getSanctionBackground()
// 单次制裁-制裁概况-制裁清单
getSanctionOverviewList()
})
// 获取路由参数中的sanTypeId
sanTypeId.value = route.query.sanTypeId || "1";
// 获取URL参数
getUrlParams();
// 单次制裁-制裁概况-制裁实体国家分布
getEntityCountry();
// 单次制裁-制裁概况-制裁背景
getSanctionBackground();
// 单次制裁-制裁概况-制裁清单
getSanctionOverviewList();
});
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
margin: 0;
padding: 0;
}
.sanctions-overview {
width: 1601px;
margin: 0 auto;
padding-top: 16px;
padding-bottom: 16px;
.main {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
.left {
width: 520px;
// height: 1119px;
.left-top {
width: 100%;
height: auto;
// padding-bottom: 20px;
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background-color: #fff;
margin-bottom: 16px;
.left-top-title {
padding: 22px 20px 22px 27px;
width: 100%;
height: auto;
border-bottom: 1px solid rgb(234, 236, 238);
display: flex;
flex-direction: column;
// justify-content: space-between;
gap: 12px;
.info-row {
display: flex;
align-items: center;
font-size: 16px;
font-family: "Microsoft YaHei";
line-height: 24px;
.label {
min-width: 92px;
color: rgb(59, 65, 75);
font-weight: 700;
font-size: 16px;
font-family: "Microsoft YaHei";
line-height: 24px;
text-align: left;
}
.value {
display: flex;
align-items: center;
color: rgb(59, 65, 75);
font-weight: 400;
font-size: 16px;
font-family: "Microsoft YaHei";
line-height: 24px;
overflow: hidden;
&.link {
color: rgb(5, 95, 194);
cursor: pointer;
}
.icon {
width: 20px;
height: 20px;
margin-right: 8px;
object-fit: contain;
&.avatar {
border-radius: 50%;
width: 24px;
height: 24px;
}
}
&.tags {
gap: 8px;
flex-wrap: wrap;
overflow: visible;
.tag {
padding: 1px 8px;
background: rgba(246, 250, 255, 1);
border-radius: 4px;
border: 1px solid rgba(231, 243, 255, 1);
color: rgb(5, 95, 194);
font-size: 14px;
line-height: 20px;
}
}
}
}
}
.left-top-content {
width: 100%;
height: 234px;
padding: 19px 29px 22px 27px;
overflow: auto;
.content-title {
font-size: 16px;
font-weight: 700;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
margin-bottom: 12px;
text-align: left;
}
.distribution-list {
display: flex;
flex-direction: column;
gap: 16px;
overflow: auto;
.list-item {
display: flex;
align-items: center;
height: 24px;
.flag {
width: 24px;
height: 24px;
margin-right: 16px;
border-radius: 50%;
object-fit: cover;
}
.country-name {
width: 96px;
font-size: 16px;
font-weight: 400;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
margin-right: 12px;
text-align: left;
}
.progress-bar-container {
flex: 1;
height: 8px;
// background: #f0f2f5;
// border-radius: 4px;
// overflow: hidden;
margin-right: 24px;
display: flex;
align-items: center;
.progress-bar {
height: 100%;
border-radius: 4px;
}
}
.count {
// width: 40px;
text-align: right;
font-size: 16px;
font-weight: 400;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
&.highlight {
color: rgb(206, 79, 81);
font-weight: 700;
}
}
}
}
}
}
.left-bottom {
width: 100%;
height: 521px;
.left-bottom-content {
padding: 20px 25px 0 25px;
height: calc(100% - 45px); // 减去标题高度
display: flex;
flex-direction: column;
.timeline-list {
display: flex;
flex-direction: column;
gap: 24px;
margin-bottom: 24px;
overflow-y: auto; // 允许垂直滚动
flex: 1; // 占据剩余空间
padding-right: 10px; // 防止滚动条遮挡内容
/* Custom Scrollbar */
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.timeline-item {
display: flex;
flex-direction: column;
// margin-left: 12px;
position: relative;
&::before {
content: "";
position: absolute;
left: 6px;
top: 12px;
height: calc(100% + 24px);
width: 1px;
background-color: rgb(234, 236, 238);
transform: translateX(-50%);
}
&:last-child::before {
display: none;
}
}
.date-row {
display: flex;
align-items: center;
margin-bottom: 3px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #fff;
border: 3px solid rgb(5, 95, 194);
box-sizing: border-box;
margin-right: 13px;
position: relative;
z-index: 1;
}
.date {
font-size: 16px;
font-weight: 700;
color: rgb(5, 95, 194);
font-family: "Microsoft YaHei";
line-height: 24px;
// margin-bottom: 3px;
}
.content {
font-size: 16px;
font-weight: 400;
color: rgb(59, 65, 75);
line-height: 24px;
margin-left: 25px;
text-align: left;
font-family: "Microsoft YaHei";
}
.view-more {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 400;
color: rgb(5, 95, 194);
cursor: pointer;
margin-top: 24px;
font-family: "Microsoft YaHei";
.icon-more {
transform: rotate(90deg);
margin-left: 8px;
}
}
}
}
}
.right {
width: 1064px;
// height: auto;
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background-color: #fff;
.right-title {
width: 100%;
// height: 107px;
padding: 4px 20px 20px 20px;
box-sizing: border-box;
.filter-row {
display: flex;
justify-content: right;
align-items: center;
// margin-bottom: 20px;
:deep(.el-input__inner) {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
&::placeholder {
color: rgb(95, 101, 108);
}
}
:deep(.el-checkbox__label) {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
}
.filter-right {
display: flex;
align-items: center;
}
}
.stats-row {
display: flex;
margin-bottom: 20px;
justify-content: space-between;
align-items: center;
.tabs {
display: flex;
gap: 8px;
.tab-btn {
padding: 4px 8px;
border: 1px solid rgb(230, 231, 232);
border-radius: 4px;
cursor: pointer;
font-size: 18px;
color: rgb(59, 65, 75);
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
&.active {
color: rgb(5, 95, 194);
border-color: rgb(5, 95, 194);
font-weight: 700;
}
}
}
.stats-info {
display: flex;
gap: 24px;
align-items: center;
.stat-item {
display: flex;
align-items: center;
font-size: 18px;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
margin-right: 34px;
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
&.red {
background-color: rgb(206, 79, 81);
}
&.green {
background-color: rgb(33, 129, 57);
}
}
.text {
font-size: 18px;
font-weight: 700;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
.num {
font-weight: 700;
&.red {
color: rgb(206, 79, 81);
}
&.green {
color: rgb(33, 129, 57);
}
}
}
}
}
}
}
.right-content {
padding: 0 20px 17px 20px;
.sanction-group-list {
display: flex;
flex-direction: column;
gap: 16px;
.sanction-group {
border: 1px solid rgb(234, 236, 238);
border-radius: 8px;
overflow: hidden;
:deep(.el-table__header-wrapper) {
th {
background-color: var(--color-primary-100) !important;
height: 48px;
padding: 0;
color: #fff;
font-weight: 700;
font-size: 16px;
font-family: "Microsoft YaHei";
border-bottom: none;
}
th:first-child {
border-top-left-radius: 8px;
}
th:last-child {
border-top-right-radius: 8px;
}
}
:deep(.el-table__body-wrapper) {
tr:nth-child(odd) td {
background-color: rgba(248, 249, 250, 1);
}
tr:nth-child(even) td {
background-color: #fff;
}
td {
height: 48px;
padding: 0;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
line-height: 24px;
}
}
.name-cell {
display: flex;
align-items: center;
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgb(206, 79, 81);
margin-right: 16px;
flex-shrink: 0;
}
.company-icon {
width: 24px;
height: 24px;
margin-right: 12px;
border-radius: 50%;
flex-shrink: 0;
}
.company-name {
font-size: 16px;
font-weight: 700;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgba(59, 65, 75, 1);
cursor: pointer;
}
}
.tag {
padding: 1px 8px;
border-radius: 4px;
font-size: 14px;
display: inline-block;
border: 1px solid transparent;
}
.subsidiary-link {
cursor: pointer;
font-size: 14px;
color: rgb(59, 65, 75);
.blue-text {
color: rgb(5, 95, 194);
}
}
.reason-box {
padding: 12px 19px 14px 39px;
background-color: #fff;
border-top: 1px solid rgb(234, 236, 238);
font-size: 16px;
color: rgb(59, 65, 75);
line-height: 28px;
font-family: "Microsoft YaHei";
text-align: left;
}
}
}
}
}
}
width: 1601px;
margin: 0 auto;
padding-top: 16px;
padding-bottom: 16px;
.main {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
.left {
width: 520px;
// height: 1119px;
.left-top {
width: 100%;
height: auto;
// padding-bottom: 20px;
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background-color: #fff;
margin-bottom: 16px;
.left-top-title {
padding: 22px 20px 22px 27px;
width: 100%;
height: auto;
border-bottom: 1px solid rgb(234, 236, 238);
display: flex;
flex-direction: column;
// justify-content: space-between;
gap: 12px;
.info-row {
display: flex;
align-items: center;
font-size: 16px;
font-family: "Microsoft YaHei";
line-height: 24px;
.label {
min-width: 92px;
color: rgb(59, 65, 75);
font-weight: 700;
font-size: 16px;
font-family: "Microsoft YaHei";
line-height: 24px;
text-align: left;
}
.value {
display: flex;
align-items: center;
color: rgb(59, 65, 75);
font-weight: 400;
font-size: 16px;
font-family: "Microsoft YaHei";
line-height: 24px;
overflow: hidden;
&.link {
color: rgb(5, 95, 194);
cursor: pointer;
}
.icon {
width: 20px;
height: 20px;
margin-right: 8px;
object-fit: contain;
&.avatar {
border-radius: 50%;
width: 24px;
height: 24px;
}
}
&.tags {
gap: 8px;
flex-wrap: wrap;
overflow: visible;
.tag {
padding: 1px 8px;
background: rgba(246, 250, 255, 1);
border-radius: 4px;
border: 1px solid rgba(231, 243, 255, 1);
color: rgb(5, 95, 194);
font-size: 14px;
line-height: 20px;
}
}
}
}
}
.left-top-content {
width: 100%;
height: 234px;
padding: 19px 29px 22px 27px;
overflow: auto;
.content-title {
font-size: 16px;
font-weight: 700;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
margin-bottom: 12px;
text-align: left;
}
.distribution-list {
display: flex;
flex-direction: column;
gap: 16px;
overflow: auto;
.list-item {
display: flex;
align-items: center;
height: 24px;
.flag {
width: 24px;
height: 24px;
margin-right: 16px;
border-radius: 50%;
object-fit: cover;
}
.country-name {
width: 96px;
font-size: 16px;
font-weight: 400;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
margin-right: 12px;
text-align: left;
}
.progress-bar-container {
flex: 1;
height: 8px;
// background: #f0f2f5;
// border-radius: 4px;
// overflow: hidden;
margin-right: 24px;
display: flex;
align-items: center;
.progress-bar {
height: 100%;
border-radius: 4px;
}
}
.count {
// width: 40px;
text-align: right;
font-size: 16px;
font-weight: 400;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
&.highlight {
color: rgb(206, 79, 81);
font-weight: 700;
}
}
}
}
}
}
.left-bottom {
width: 100%;
height: 521px;
.left-bottom-content {
padding: 20px 25px 0 25px;
height: calc(100% - 45px); // 减去标题高度
display: flex;
flex-direction: column;
.timeline-list {
display: flex;
flex-direction: column;
gap: 24px;
margin-bottom: 24px;
overflow-y: auto; // 允许垂直滚动
flex: 1; // 占据剩余空间
padding-right: 10px; // 防止滚动条遮挡内容
/* Custom Scrollbar */
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.timeline-item {
display: flex;
flex-direction: column;
// margin-left: 12px;
position: relative;
&::before {
content: "";
position: absolute;
left: 6px;
top: 12px;
height: calc(100% + 24px);
width: 1px;
background-color: rgb(234, 236, 238);
transform: translateX(-50%);
}
&:last-child::before {
display: none;
}
}
.date-row {
display: flex;
align-items: center;
margin-bottom: 3px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #fff;
border: 3px solid rgb(5, 95, 194);
box-sizing: border-box;
margin-right: 13px;
position: relative;
z-index: 1;
}
.date {
font-size: 16px;
font-weight: 700;
color: rgb(5, 95, 194);
font-family: "Microsoft YaHei";
line-height: 24px;
// margin-bottom: 3px;
}
.content {
font-size: 16px;
font-weight: 400;
color: rgb(59, 65, 75);
line-height: 24px;
margin-left: 25px;
text-align: left;
font-family: "Microsoft YaHei";
}
.view-more {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 400;
color: rgb(5, 95, 194);
cursor: pointer;
margin-top: 24px;
font-family: "Microsoft YaHei";
.icon-more {
transform: rotate(90deg);
margin-left: 8px;
}
}
}
}
}
.right {
width: 1064px;
// height: auto;
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background-color: #fff;
.right-title {
width: 100%;
// height: 107px;
padding: 4px 20px 20px 20px;
box-sizing: border-box;
.filter-row {
display: flex;
justify-content: right;
align-items: center;
// margin-bottom: 20px;
:deep(.el-input__inner) {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
&::placeholder {
color: rgb(95, 101, 108);
}
}
:deep(.el-checkbox__label) {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
}
.filter-right {
display: flex;
align-items: center;
}
}
.stats-row {
display: flex;
margin-bottom: 20px;
justify-content: space-between;
align-items: center;
.tabs {
display: flex;
gap: 8px;
.tab-btn {
padding: 4px 8px;
border: 1px solid rgb(230, 231, 232);
border-radius: 4px;
cursor: pointer;
font-size: 18px;
color: rgb(59, 65, 75);
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
&.active {
color: rgb(5, 95, 194);
border-color: rgb(5, 95, 194);
font-weight: 700;
}
}
}
.stats-info {
display: flex;
gap: 24px;
align-items: center;
.stat-item {
display: flex;
align-items: center;
font-size: 18px;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
margin-right: 34px;
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
&.red {
background-color: rgb(206, 79, 81);
}
&.green {
background-color: rgb(33, 129, 57);
}
}
.text {
font-size: 18px;
font-weight: 700;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
.num {
font-weight: 700;
&.red {
color: rgb(206, 79, 81);
}
&.green {
color: rgb(33, 129, 57);
}
}
}
}
}
}
}
.right-content {
padding: 0 20px 17px 20px;
.sanction-group-list {
display: flex;
flex-direction: column;
gap: 16px;
.sanction-group {
border: 1px solid rgb(234, 236, 238);
border-radius: 8px;
overflow: hidden;
:deep(.el-table__header-wrapper) {
th {
background-color: var(--color-primary-100) !important;
height: 48px;
padding: 0;
color: #fff;
font-weight: 700;
font-size: 16px;
font-family: "Microsoft YaHei";
border-bottom: none;
}
th:first-child {
border-top-left-radius: 8px;
}
th:last-child {
border-top-right-radius: 8px;
}
}
:deep(.el-table__body-wrapper) {
tr:nth-child(odd) td {
background-color: rgba(248, 249, 250, 1);
}
tr:nth-child(even) td {
background-color: #fff;
}
td {
height: 48px;
padding: 0;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
line-height: 24px;
}
}
.name-cell {
display: flex;
align-items: center;
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgb(206, 79, 81);
margin-right: 16px;
flex-shrink: 0;
}
.company-icon {
width: 24px;
height: 24px;
margin-right: 12px;
border-radius: 50%;
flex-shrink: 0;
}
.company-name {
font-size: 16px;
font-weight: 700;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgba(59, 65, 75, 1);
cursor: pointer;
}
}
.tag {
padding: 1px 8px;
border-radius: 4px;
font-size: 14px;
display: inline-block;
border: 1px solid transparent;
}
.subsidiary-link {
cursor: pointer;
font-size: 14px;
color: rgb(59, 65, 75);
.blue-text {
color: rgb(5, 95, 194);
}
}
.reason-box {
padding: 12px 19px 14px 39px;
background-color: #fff;
border-top: 1px solid rgb(234, 236, 238);
font-size: 16px;
color: rgb(59, 65, 75);
line-height: 28px;
font-family: "Microsoft YaHei";
text-align: left;
}
}
}
}
}
}
}
.title-com {
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: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.btn {
width: 60px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
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: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.btn {
width: 60px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
}
</style>
\ No newline at end of file
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论