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

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

Zy dev 查看合并请求 !342
流水线 #487 已通过 于阶段
in 4 分 50 秒
......@@ -11,10 +11,11 @@ export function getStatCount(params) {
}
// 分类接口
export function getStatSort() {
export function getStatSort(params) {
return request({
method: 'GET',
url: `/api/marketsearchHome/statSort`
url: `/api/marketsearchHome/statSort`,
params
})
}
......@@ -39,6 +40,18 @@ export function getStatNum(params) {
})
}
// 查询关税税率
/**
* @param {byYorM}
*/
export function getSearchTariff(params) {
return request({
method: 'GET',
url: `/api/marketsearchDetails/getSearchTariff`,
params
})
}
// 制裁领域分布
/**
* @param {years}
......@@ -178,6 +191,14 @@ export function getSearchBlurb(params) {
})
}
// 获取原文
export function getOriginalUrl(params) {
return request({
method: 'GET',
url: `/api/marketsearchDetails/getOriginalUrl/${params.id}`,
})
}
// 获取相关事件
export function getRelatedEvents(params) {
return request({
......
......@@ -3,7 +3,10 @@
<div class="icon">
<img src="./tip-icon.svg" alt="">
</div>
<div class="text text-tip-2 text-primary-50-clor">{{ tipText }}</div>
<div class="text text-tip-2 text-primary-50-clor">
<div v-if="ellipsis" :title="tipText" class="one-line-ellipsis">{{ tipText }}</div>
<div v-else>{{ tipText }}</div>
</div>
</div>
</template>
......@@ -23,7 +26,10 @@ const props = defineProps({
type: String,
default: '2023.1至2025.12'
},
ellipsis: {
type: Boolean,
default: false
}
})
const tipText = computed(() => props.text || `数据来源:${props.dataSource},数据时间:${props.dataTime}`)
......@@ -48,5 +54,9 @@ const tipText = computed(() => props.text || `数据来源:${props.dataSource}
height: 100%;
}
}
.text {
width: 20px;
flex: auto;
}
}
</style>
\ No newline at end of file
......@@ -6,6 +6,7 @@ const MarketAccessCase = () => import('@/views/marketAccessRestrictions/marketAc
const MarketSingleCaseLayout = () => import('@/views/marketAccessRestrictions/singleCaseLayout/index.vue')
const MarketSingleCaseOverview = () => import('@/views/marketAccessRestrictions/singleCaseLayout/overview/index.vue')
const MarketSingleCaseDeepdig = () => import('@/views/marketAccessRestrictions/singleCaseLayout/deepdig/index.vue')
const MarketSingleReportOriginal = () => import('@/views/marketAccessRestrictions/pages/reportOriginal.vue')
const marketAccessRestrictionsRoutes = [
// 市场准入限制首页
......@@ -42,7 +43,6 @@ const marketAccessRestrictionsRoutes = [
}
]
},
{
path: "/marketSingleCaseLayout",
name: "MarketSingleCaseLayout",
......@@ -67,7 +67,12 @@ const marketAccessRestrictionsRoutes = [
}
]
},
{
path: "reportOriginal",
name: "MarketSingleReportOriginal",
component: MarketSingleReportOriginal,
meta: { noTitle: true }
}
]
export default marketAccessRestrictionsRoutes
\ No newline at end of file
......@@ -152,7 +152,7 @@
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="summarize1" />
<AiPane :aiContent="aiContent.content1" />
</div>
</div>
......@@ -188,7 +188,7 @@
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="summarize2" />
<AiPane :aiContent="aiContent.content2" />
</div>
</div>
</div>
......@@ -442,6 +442,7 @@ import setChart from "@/utils/setChart";
import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import tipsTcon from "./assets/images/tips-icon.png";
import { ElMessage } from "element-plus";
import { getAIReport, getNearYearList } from "@/views/marketAccessRestrictions/utils/index.ts"
import { useGotoNewsDetail } from '@/router/modules/news';
......@@ -722,17 +723,16 @@ const handleClickPerson = async item => {
} catch (error) { }
};
// 获取最近年份列表
const currentYear = new Date().getFullYear();
const getYearList = (count = 6) => {
const yearOptions = [];
for (let i = 0; i < count; i++) {
const year = currentYear - i;
yearOptions.push({ label: year.toString(), value: year.toString() });
}
return yearOptions;
};
const yearList = getYearList();
const yearList = getNearYearList();
// 获取AI智能报告
const aiContent = reactive({
content1: "正在生成...",
content2: "正在生成...",
})
const onAIReport = (data, key) => {
getAIReport(data).then(res => { aiContent[key] = res })
}
// 行政令发布频度
const chart1Data = ref({
......@@ -745,7 +745,6 @@ const box5Params = reactive({
proposeName: '',
loading: false,
})
const summarize1 = ref()
const handleGetDecreeYearOrder = async () => {
box5Params.loading = true
try {
......@@ -763,61 +762,17 @@ const handleGetDecreeYearOrder = async () => {
chart1Data.value.dataY = res.data.map(item => {
return item.count;
});
onChartInterpretation({ type: "柱状图", name: "数量变化趋势", data: res.data }, summarize1)
onAIReport({ type: "柱状图", name: "数量变化趋势", data: res.data }, "content1")
} else {
chart1Data.value.dataX = [];
chart1Data.value.dataY = [];
aiContent.content1 = ""
}
} catch (error) {
console.error("行政令发布频度error", error);
}
box5Params.loading = false
};
// AI智能总结
const onChartInterpretation = async (text, param) => {
param.value = "正在生成..."
// 👇 新增:超时 + 终止请求(只加这一段)
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000); // 10秒超时
try {
const response = await fetch('/aiAnalysis/chart_interpretation', {
method: 'POST',
headers: {
"X-API-Key": "aircasKEY19491001",
'Content-Type': 'application/json',
},
body: JSON.stringify({ text }),
signal: controller.signal // 👇 新增:绑定中断信号
});
clearTimeout(timeout); // 👇 新增:请求成功清除定时器
if (!response.ok) throw new Error(`HTTP 错误 ${response.status}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let summarize = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const content = line.substring(6);
const textMatch = content.match(/"解读":\s*"([^"]*)"/);
if (textMatch && textMatch[1]) summarize = textMatch[1];
}
}
}
param.value = summarize
} catch (err) {
param.value = "系统异常,生成失败";
}
}
const handleBox5 = async () => {
await handleGetDecreeYearOrder();
......@@ -863,7 +818,6 @@ const box6Params = reactive({
proposeName: '',
loading: false,
});
const summarize2 = ref()
const handleGetDecreeArea = async () => {
box6Params.loading = true
try {
......@@ -880,7 +834,10 @@ const handleGetDecreeArea = async () => {
value: item.count
};
});
onChartInterpretation({ type: "环形图", name: "领域分布情况", data: res.data }, summarize2)
onAIReport({ type: "环形图", name: "领域分布情况", data: res.data }, "content2")
} else {
chart2Data.value = []
aiContent.content2 = ""
}
} catch (error) {
console.error("政令科技领域error", error);
......
......@@ -53,7 +53,7 @@
<div v-for="(subSubItem, subSubIndex) in subItem.slaver" :key="subSubIndex"
class="sub-sub-item">
<div class="sub-sub-item-dot">{{ ALPHABET[subSubIndex % 26] }}.</div>
<div class="sub-sub-item-word" v-html="subItem.content"></div>
<div class="sub-sub-item-word" v-html="subSubItem.content"></div>
</div>
</div>
</div>
......@@ -157,6 +157,7 @@ import ActionButton from '@/components/base/ActionButton/index.vue'
import DefaultIcon1 from "@/assets/icons/default-icon1.png";
import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import defaultCom from "@/views/coopRestriction/assets/images/default-icon2.png"
import { onNumToChinese } from "@/views/marketAccessRestrictions/utils/index"
const route = useRoute();
......@@ -185,70 +186,7 @@ const handleGetAreaList = async () => {
// 主要指令
const isHighlight = ref(false);
const commandWord = ref("");
const contentList = ref([
// {
// content: "建立美国人工智能出口计划建立美国人工智能出口计划建立美国人工智能出口计划建立美国人工智能出口计划建立美国人工智能出口计划",
// slaver: [
// {
// content: '在本命令发布之日起 90 天内,商务部长应与国务卿及科学技术政策办公室(OSTP)主任协商,建立并实施美国人工智能出口计划(计划),以支持美国全栈人工智能出口软件包的开发和部署。'
// },
// {
// content: '商务部长应公开征集由行业主导的联盟提案,以纳入该计划。公开征集要求每项提案必须:',
// slaver: [
// {
// content: '包含一套全栈人工智能技术包,涵盖:',
// slaver: [
// {
// content: 'AI 优化的计算机硬件(如芯片、服务器和加速器)、数据中心存储、云服务和网络,以及这些设备是否以及在多大程度上在美国制造的描述;'
// },
// {
// content: '数据管道和标签系统;'
// },
// {
// content: '人工智能模型与系统;'
// },
// {
// content: '采取措施保障人工智能模型和系统的安全性和网络安全;'
// },
// {
// content: '针对特定用例的人工智能应用(如软件工程、教育、医疗保健、农业或交通运输);'
// }
// ]
// },
// {
// content: '确定出口参与者的具体目标国家或区域集团;'
// },
// {
// content: '描述一个业务和运营模型,以在高层次上说明哪些实体将建设、拥有和运营数据中心及相关基础设施;'
// },
// {
// content: '联邦激励和支持机制请求的细节;'
// },
// {
// content: '遵守所有相关的美国出口管制制度、出境投资法规和终端用户政策,包括美国法典第 50 编第 58 章及商务部工业与安全局的相关指导。'
// }
// ]
// },
// {
// content: '商务部要求提案须在公开征集后不超过 90 天内提交,并应滚动考虑提案纳入项目。'
// },
// {
// content: '商务部长应与国务卿、国防部长、能源部长及 OSTP 主任协商,评估提交的纳入计划提案。商务部长与国务卿、国防部长、能源部长及 OSTP 主任协商后选定的提案,将被指定为优先 AI 出口包,并通过优先访问本命令第 4 节指定的工具予以支持,符合适用法律。'
// }
// ]
// },
// {
// content: "动员联邦融资工具",
// slaver: [
// {
// content: '经济外交行动小组(EDAG),于 2024 年 6 月 21 日总统备忘录中成立,由国务卿主持,并与商务部长和美国贸易代表协商,并根据 2019 年《通过外交倡导美国企业法案》(公共法 116-94 J 部分第七章)第 708 条(CABDA)所述,应协调联邦融资工具的动员,以支持优先的人工智能出口方案。'
// },
// {
// content: '我将根据 CABDA α 第 708 (c) (3) 条授权小企业管理局局长和 OSTP 主任任命各自执行部门和机构的高级官员担任 EDAG 。'
// }
// ]
// }
]);
const contentList = ref([]);
const ALPHABET = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
const onMainContentData = async () => {
try {
......@@ -257,7 +195,7 @@ const onMainContentData = async () => {
console.log("主要指令", res);
if (res && res.code === 200) {
contentList.value = res.data || [];
contentList.value.forEach((item, index) => { item.content = `(${simpleNumToChinese(index + 1)}) ${item.content}` })
contentList.value.forEach((item, index) => { item.content = `(${onNumToChinese(index + 1)}) ${item.content}` })
if (keyword) {
let word = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
contentList.value.forEach(item => { onHighlight(word, item) })
......@@ -279,30 +217,6 @@ const onHighlight = (word, row) => {
row.slaver.forEach(item => { onHighlight(word, item) })
}
}
// 数字转中文(支持 0-99 整数)
const simpleNumToChinese = (num) => {
// 1. 基础校验:只处理 0-99 的整数
if (!Number.isInteger(num) || num < 0 || num > 99) return '100';
// 2. 定义基础字符
const singleChars = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
const tenChar = '十';
// 3. 核心转换逻辑
if (num < 10) {
// 0-9 直接返回对应字符
return singleChars[num];
} else if (num === 10) {
// 10 特殊处理
return tenChar;
} else if (num < 20) {
// 11-19:十 + 个位(如十一、十九)
return tenChar + singleChars[num - 10];
} else {
// 20-99:十位 + 十 + 个位(个位为0则省略,如二十、二十九)
const ten = Math.floor(num / 10); // 十位数字
const unit = num % 10; // 个位数字
return singleChars[ten] + tenChar + (unit === 0 ? '' : singleChars[unit]);
}
}
// 思维导图
const isTreeDialog = ref(false);
......
<template>
<div class="data-list">
<div class="data-item" v-for="(item, index) in props.list" :key="index">
<div class="item-head">
<div class="item-name">{{ `(${onNumToChinese(Number(index)+1)}). ${item.title}` }}</div>
<div class="button-box" @click="onNavigateTo()">
<div class="button-icon">
<img src="../assets/icons/open.png" alt="" />
</div>
<div class="button-text">跳转原文</div>
</div>
</div>
<Level2List :list="item.data"></Level2List>
</div>
</div>
</template>
<script setup lang="ts" name="Level2List">
import router from "@/router";
import { useRoute } from "vue-router";
import { onNumToChinese } from "@/views/marketAccessRestrictions/utils/index"
import Level2List from "@/views/marketAccessRestrictions/com/Level2List.vue";
const route = useRoute();
const props = defineProps({
list: {
type: Array as any,
default: () => ([])
},
})
const onNavigateTo = () => {
const page = router.resolve({
name: "MarketSingleReportOriginal",
query: { ...route.query }
});
window.open(page.href, "_blank");
}
</script>
<style scoped lang="scss">
.data-list {
margin-bottom: 16px;
border-top: 1px solid rgba(234, 236, 238, 1);
.data-item {
.item-head {
padding: 0 20px;
height: 48px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
background: rgba(247, 248, 249, 1);
display: flex;
align-items: center;
.item-name {
width: 20px;
flex: auto;
font-family: Source Han Sans CN;
font-size: 18px;
font-weight: bold;
line-height: 30px;
color: var(--text-primary-80-color);
}
.button-box {
display: flex;
align-items: center;
margin-left: 50px;
cursor: pointer;
.button-icon {
width: 16px;
height: 16px;
font-size: 0;
margin-right: 4px;
img {
width: 100%;
height: 100%;
}
}
.button-text {
color: var(--color-primary-100);
font-family: Microsoft YaHei;
font-size: 12px;
font-weight: 400;
line-height: 12px;
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="view-box">
<div class="item-info" v-for="(text, num) in props.list" :key="num">
<div class="item-num">{{ Number(num) + 1 }}.</div>
<div class="text-align-justify">{{ text }}</div>
</div>
</div>
</template>
<script setup lang="ts" name="Level2List">
const props = defineProps({
list: {
type: Array as any,
default: () => ([])
}
})
</script>
<style scoped lang="scss">
.view-box {
.item-info {
padding: 12px 20px 12px 50px;
color: rgba(59, 65, 75, 1);
font-family: Source Han Sans CN;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
position: relative;
.item-num {
position: absolute;
left: 26px;
top: 12px;
}
}
}
</style>
\ No newline at end of file
......@@ -2,29 +2,15 @@
<AnalysisBox :title="title" :showAllBtn="false" height="auto">
<el-empty v-if="!props.listData?.length" description="暂无数据" :image-size="200" />
<div v-else class="box-main">
<div class="data-list">
<div class="data-item" v-for="(item, index) in props.listData" :key="index">
<div class="item-head">
<div class="item-name">{{ item.title }}</div>
<div class="button-box">
<div class="button-icon">
<img src="../assets/icons/open.png" alt="" />
</div>
<div class="button-text">跳转原文</div>
</div>
</div>
<div class="item-down">
<div class="item-text" v-for="(text, num) in item.data" :key="num">{{ text }}</div>
</div>
</div>
</div>
<AiTips :tips="tips"></AiTips>
<Level1List :list="props.listData"></Level1List>
<!-- <AiTips :tips="tips"></AiTips> -->
</div>
</AnalysisBox>
</template>
<script setup lang="ts" name="SurveyConclusion">
import AiTips from "@/views/marketAccessRestrictions/com/AiTips.vue";
import Level1List from "@/views/marketAccessRestrictions/com/Level1List.vue";
const props = defineProps({
listData: {
......@@ -46,62 +32,5 @@ const props = defineProps({
<style scoped lang="scss">
.box-main {
padding: 0 22px 20px;
.data-list {
margin-bottom: 16px;
border-top: 1px solid rgba(234, 236, 238, 1);
.data-item {
.item-head {
padding: 0 20px;
height: 48px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
background: rgba(247, 248, 249, 1);
display: flex;
align-items: center;
.item-name {
width: 20px;
flex: auto;
font-family: Source Han Sans CN;
font-size: 18px;
font-weight: bold;
line-height: 30px;
color: var(--text-primary-80-color);
}
.button-box {
display: flex;
align-items: center;
margin-left: 50px;
.button-icon {
width: 16px;
height: 16px;
font-size: 0;
margin-right: 4px;
img {
width: 100%;
height: 100%;
}
}
.button-text {
color: var(--color-primary-100);
font-family: Microsoft YaHei;
font-size: 12px;
font-weight: 400;
line-height: 12px;
}
}
}
.item-text {
letter-spacing: 1px;
padding: 12px 20px 12px 40px;
color: rgba(59, 65, 75, 1);
font-family: Source Han Sans CN;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
border-bottom: 1px solid rgba(234, 236, 238, 1);
}
}
}
}
</style>
\ No newline at end of file
......@@ -2,19 +2,19 @@
<div class="box-text-box">
<div class="box-text-item">
<div class="box-text-left">{{ "启动时间:" }}</div>
<div class="box-text-right one-line-ellipsis">{{ props.baseInfo.searchnum }}</div>
<div class="box-text-right one-line-ellipsis">{{ props.baseInfo.progressdate }}</div>
</div>
<div class="box-text-item">
<div class="box-text-left">{{ "调查概括:" }}</div>
<div class="box-text-right two-line-ellipsis">{{ props.baseInfo.product }}</div>
<div class="box-text-right two-line-ellipsis">{{ props.baseInfo.investSummary }}</div>
</div>
<div class="box-text-item">
<div class="box-text-left">{{ "调查阶段:" }}</div>
<div class="box-text-right one-line-ellipsis">{{ props.baseInfo.plaintiff }}</div>
<div class="box-text-right one-line-ellipsis">{{ props.baseInfo.investStage }}</div>
</div>
<div class="box-text-item">
<div class="box-text-left">{{ "调查范围:" }}</div>
<div class="box-text-right five-line-ellipsis">{{ props.baseInfo.defendant }}</div>
<div class="box-text-right five-line-ellipsis">{{ props.baseInfo.investScope }}</div>
</div>
</div>
</template>
......
......@@ -2,26 +2,25 @@
<div class="box-blue-box">
<div class="box-blue-name">
<div class="box-blue-time">{{ props.baseInfo.progressdate }}</div>
<div class="box-blue-time">{{ props.baseInfo.progressresult }}</div>
</div>
<div class="box-blue-text one-line-ellipsis">{{ props.baseInfo.progressdetails }}</div>
</div>
<div class="box-text-box">
<div class="box-text-item">
<div class="box-text-left">{{ "启动时间:" }}</div>
<div class="box-text-right one-line-ellipsis">{{ props.baseInfo.searchnum }}</div>
<div class="box-text-right one-line-ellipsis">{{ props.baseInfo.investDate }}</div>
</div>
<div class="box-text-item">
<div class="box-text-left">{{ "调查对象:" }}</div>
<div class="box-text-right one-line-ellipsis">{{ props.baseInfo.product }}</div>
<div class="box-text-right one-line-ellipsis">{{ props.baseInfo.investSubject }}</div>
</div>
<div class="box-text-item">
<div class="box-text-left">{{ "调查状态:" }}</div>
<div class="box-text-right one-line-ellipsis">{{ props.baseInfo.plaintiff }}</div>
<div class="box-text-right one-line-ellipsis">{{ props.baseInfo.investStatus }}</div>
</div>
<div class="box-text-item">
<div class="box-text-left">{{ "请愿方:" }}</div>
<div class="box-text-right three-line-ellipsis">{{ props.baseInfo.product }}</div>
<div class="box-text-right three-line-ellipsis">{{ props.baseInfo.petitioner }}</div>
</div>
</div>
</template>
......
......@@ -11,7 +11,7 @@
<div class="date-icon">
<img :src="tipsTcon" alt="">
</div>
<div class="date-text">近期美国各联邦政府机构市场准入调查数量汇总</div>
<div class="date-text">近期美国各联邦政府机构市场准入限制调查数量汇总</div>
<TimeTabPane @time-click="handleGetStatSort" activeTime="近一年" />
</div>
<div class="home-main-header-card-box">
......@@ -26,7 +26,7 @@
{{ item.sortDescription }}
</div>
</div>
<div class="item-dot">+{{ "999" }}</div>
<div class="item-dot" v-if="item.addInvestCount">+{{ item.addInvestCount }}</div>
</div>
</div>
</div>
......@@ -74,7 +74,7 @@
<DivideHeader id="position3" class="divide-header" :titleText="'数据总览'"></DivideHeader>
<div class="center-footer">
<div class="box5">
<OverviewNormalBox title="调查数量">
<OverviewNormalBox title="数量变化趋势">
<template #header-icon>
<img style="width: 100%; height: 100%;" src="./assets/icons/icon2.svg" alt="" />
</template>
......@@ -89,14 +89,18 @@
<div class="box-echart-main">
<div class="box-echart-content">
<el-empty v-if="!box5ChartData.title.length" description="暂无数据" style="padding: 100px 0 0;" :image-size="100" />
<div v-if="box5ChartData.title.length" style="width: 100%; height: 100%;" id="box5Chart"></div>
<div v-if="box5ChartData.title.length" style="width: 100%; height: 100%;" ref="box5Ref"></div>
</div>
<TipTab text="美对华发起调查案件数量变化趋势,数据来源:美国国际贸易委员会、商务部、贸易代表办公室官网" style="margin-top: 16px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content5" />
</div>
<TipTab style="margin-top: 6px;" />
</div>
</OverviewNormalBox>
</div>
<div class="box6">
<OverviewNormalBox title="制裁领域分布" width="521px">
<OverviewNormalBox title="领域分布情况" width="521px">
<template #header-icon>
<img style="width: 100%; height: 100%;" src="./assets/icons/icon3.svg" alt="" />
</template>
......@@ -112,14 +116,18 @@
<el-empty v-if="!box6Data.title.length" description="暂无数据" style="padding: 100px 0 0;" :image-size="100" />
<div v-if="box6Data.title.length" style="width: 100%; height: 100%;" id="box6Chart"></div>
</div>
<TipTab style="margin-top: 6px;" />
<TipTab text="美对华发起调查案件领域分布情况,数据来源:美国国际贸易委员会、商务部、贸易代表办公室官网" ellipsis style="padding-right: 50px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content6" />
</div>
</div>
</OverviewNormalBox>
</div>
</div>
<div class="center-footer1">
<div class="box7">
<OverviewNormalBox title="受调查国家分布" width="1064px">
<OverviewNormalBox title="国家分布情况" width="1064px">
<template #header-icon>
<img style="width: 100%; height: 100%;" src="./assets/icons/icon4.svg" alt="" />
</template>
......@@ -140,12 +148,16 @@
<el-empty v-if="!box7Data.data.length" description="暂无数据" style="padding: 100px 0 0;" :image-size="100" />
<div v-if="box7Data.data.length" style="width: 100%; height: 100%;" id="box7Chart"></div>
</div>
<TipTab style="margin-top: 6px;" />
<TipTab :text="`美发起调查案件的被调查国家分布情况,数据来源:${box7TipText}`" style="margin-top: 10px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content7" />
</div>
</div>
</OverviewNormalBox>
</div>
<div class="box8">
<OverviewNormalBox title="调查结果分布" width="521px">
<OverviewNormalBox title="结果分布情况" width="521px">
<template #header-icon>
<img style="width: 100%; height: 100%" src="./assets/icons/icon5.svg" alt="" />
</template>
......@@ -159,9 +171,13 @@
<div class="box-echart-main">
<div class="box-echart-content">
<el-empty v-if="!box8Data.length" description="暂无数据" style="padding: 100px 0 0;" :image-size="100" />
<div v-if="box8Data.length" style="width: 100%; height: 100%;" id="box8Chart"></div>
<div v-if="box8Data.length" style="width: 100%; height: 100%;" ref="box8Ref"></div>
</div>
<TipTab :text="`美发起调查案件的被调查国家分布情况,数据来源:${box8TipText}`" ellipsis style="padding-right: 50px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content8" />
</div>
<TipTab style="margin-top: 6px;" />
</div>
</OverviewNormalBox>
</div>
......@@ -181,7 +197,6 @@
</template>
<el-option v-for="item in releaseTimeList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
<div class="home-main-footer-main">
......@@ -262,7 +277,7 @@
</template>
<script setup>
import { onMounted, ref, nextTick, reactive } from "vue";
import { onMounted, ref, nextTick, reactive, computed } from "vue";
import LeftBtn from "@/components/base/pageBtn/LeftBtn.vue";
import RightBtn from "@/components/base/pageBtn/RightBtn.vue";
......@@ -279,13 +294,16 @@ import CarouselItem301 from '@/views/marketAccessRestrictions/marketAccessHome/c
import CarouselItem232 from '@/views/marketAccessRestrictions/marketAccessHome/com/CarouselItem232.vue';
import setChart from "@/utils/setChart";
import { getDateBefore, getAIReport, getNearYearList } from "@/views/marketAccessRestrictions/utils/index.ts";
import router from "@/router";
import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate";
import getMultiLineChart from "./utils/multiLineChart";
import getPieChart from "./utils/piechart";
import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChart";
import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js";
import getRadarChart from "./utils/radarChart";
import getBarChart from "./utils/barChart1";
import AiButton from '@/components/base/Ai/AiButton/index.vue';
import AiPane from '@/components/base/Ai/AiPane/index.vue';
import { getPersonSummaryInfo } from "@/api/common/index";
import {
......@@ -307,6 +325,17 @@ import tipsTcon from "./assets/icons/tips-icon.png";
const getCardClass = (code) => ['theme-card', `theme-${code}`]
// 获取AI智能报告
const aiContent = reactive({
content5: "正在生成...",
content6: "正在生成...",
content7: "正在生成...",
content8: "正在生成...",
})
const onAIReport = (data, key) => {
getAIReport(data).then(res => { aiContent[key] = res })
}
const handleToPosi = id => {
const element = document.getElementById(id);
if (element && containerRef.value) {
......@@ -334,9 +363,12 @@ let containerRef = ref(null);
// 首页分类
const sortInfo = ref([]);
const handleGetStatSort = async (event) => {
console.log('周期筛选', event)
let day = 365
if (event?.time === '近一周') day = 7
if (event?.time === '近一月') day = 30
if (event?.time === '近一年') day = 365
try {
const res = await getStatSort();
const res = await getStatSort({startDate: getDateBefore(day)});
console.log("首页分类", res);
// sortInfo.value = res.data.sort((a, b) => a.sortCode - b.sortCode);
sortInfo.value = res.data;
......@@ -543,7 +575,7 @@ function transformAllData(originalData) {
// 4. 构建最终结果
return {
title: allYears,
data: categoryData
list: categoryData
};
}
......@@ -599,38 +631,55 @@ function transformAllData1(originalData) {
// 4. 构建最终结果
return {
title: allDates,
data: categoryData
list: categoryData
};
}
const box5ChartData = ref({
title: [],
data: [
list: [
{ name: "337调查", value: [] },
{ name: "301调查", value: [] },
{ name: "232调查", value: [] }
]
});
const box5Ref = ref(null)
const box5Active = ref(1);
const hadleGetStatNum = async (event) => {
if (event) box5Active.value = event;
try {
let byYorM = box5Active.value
const res = await getStatNum({byYorM});
console.log("调查数量", res);
console.log("数量变化趋势", res);
if (res.code === 200 && res.data) {
if (byYorM === 1) {
box5ChartData.value = transformAllData1(res.data);
} else {
box5ChartData.value = transformAllData(res.data);
}
onAIReport({ type: "折线图", name: "数量变化趋势", data: res.data }, "content5")
} else {
box5ChartData.value.title = [];
box5ChartData.value.list = [
{ name: "337调查", value: [] },
{ name: "301调查", value: [] },
{ name: "232调查", value: [] }
]
aiContent.content5 = "";
}
} catch (error) {
box5ChartData.value.title = [];
box5ChartData.value.list = [
{ name: "337调查", value: [] },
{ name: "301调查", value: [] },
{ name: "232调查", value: [] }
]
aiContent.content5 = "";
}
} catch (error) {}
let box5Chart = getMultiLineChart(box5ChartData.value);
nextTick(() => { setChart(box5Chart, "box5Chart") })
nextTick(() => { createLineChart(box5Ref, box5ChartData.value) })
};
// 制裁领域分布
// 领域分布情况
const box6SelectedYear = ref("2025");
const handleChangeBox6Year = () => {
handleBox6();
......@@ -650,7 +699,7 @@ const handleGetStatArea = async () => {
};
try {
const res = await getStatArea(params);
console.log("制裁领域分布", res);
console.log("领域分布情况", res);
if (res.code === 200 && res.data) {
const arr = res.data.map(item => {
return item.areaname;
......@@ -707,6 +756,17 @@ const handleGetStatArea = async () => {
});
box6Data.value.maxNum = Math.max(...numArr);
onAIReport({ type: "雷达图", name: "领域分布情况", data: res.data }, "content6")
} else {
box6Data.value.title = [];
box6Data.value.data = [
{ name: "337调查", value: [] },
{ name: "232调查", value: [] },
{ name: "301调查", value: [] }
];
box6Data.value.maxNum = 0;
aiContent.content6 = "";
}
} catch (error) { }
};
......@@ -716,30 +776,28 @@ const handleBox6 = async () => {
setChart(box6Chart, "box6Chart");
};
// 受调查国家分布
// 国家分布情况
const box7SelectedSurvey = ref("337");
const box7YearList = ref([
{ label: "2025", value: "2025" },
{ label: "2024", value: "2024" },
{ label: "2023", value: "2023" },
{ label: "2022", value: "2022" },
{ label: "2021", value: "2021" },
{ label: "2020", value: "2020" },
]);
const box7YearList = getNearYearList();
const box7SelectedYear = ref("2025");
const box7Data = reactive({
title: [],
data: []
});
const box7TipText = computed(() => {
if (box7SelectedSurvey.value === "337") return "美国国际贸易委员会官网"
if (box7SelectedSurvey.value === "232") return "美国商务部官网"
if (box7SelectedSurvey.value === "301") return "美国贸易代表办公室官网"
return ""
})
const handleGetBox7Data = async () => {
const params = {
sortCode: box7SelectedSurvey.value,
year: box7SelectedYear.value
years: box7SelectedYear.value
};
try {
const res = await getSearchCountry(params);
console.log("受调查国家分布", res);
console.log("国家分布情况", res);
if (res.code === 200 && res.data) {
box7Data.title = res.data.map(item => {
return {
......@@ -750,9 +808,16 @@ const handleGetBox7Data = async () => {
box7Data.data = res.data.map(item => {
return item.NUM;
});
onAIReport({ type: "柱状图", name: "国家分布情况", data: res.data }, "content7")
} else {
box7Data.title = [];
box7Data.data = [];
aiContent.content7 = "";
}
} catch (error) {
console.error("受调查国家分布error", error);
box7Data.title = [];
box7Data.data = [];
aiContent.content7 = "";
}
};
......@@ -767,23 +832,32 @@ const box8SurveyList = ref([
{ label: "301调查", value: "301" },
{ label: "232调查", value: "232" },
]);
// 调查结果分布
// 结果分布情况
const box8SelectedSurvey = ref("337");
const box8Data = ref([]);
const box8Ref = ref(null);
const box8TipText = computed(() => {
if (box8SelectedSurvey.value === "337") return "美国国际贸易委员会官网"
if (box8SelectedSurvey.value === "232") return "美国商务部官网"
if (box8SelectedSurvey.value === "301") return "美国贸易代表办公室官网"
return ""
})
const handleGetBox8Data = async () => {
try {
const res = await getSearchResult({sortCode: box8SelectedSurvey.value});
console.log("调查结果分布", res);
console.log("结果分布情况", res);
if (res.code === 200 && res.data) {
box8Data.value = res.data.map(item => ({ name: item.RESULTNAME, value: item.RESULTNUM }))
onAIReport({ type: "环形图", name: "结果分布情况", data: res.data }, "content8")
} else {
box8Data.value = []
aiContent.content8 = "";
}
} catch (error) {
box8Data.value = []
aiContent.content8 = "";
}
const box8Chart = getPieChart(box8Data.value);
nextTick(() => { setChart(box8Chart, "box8Chart") })
nextTick(() => { createPieChart(box8Ref, box8Data.value, {labelType:1}) })
};
// 资源库
......@@ -975,12 +1049,40 @@ onMounted(async () => {
height: 100%;
display: flex;
flex-direction: column;
padding: 8px 22px 20px;
padding: 12px 30px 20px;
position: relative;
.box-echart-content {
width: 100%;
height: 20px;
flex: auto;
}
.ai-pane {
position: absolute;
right: 0px;
bottom: 15px;
z-index: 2;
:deep(.ai-pane-wrapper) {
display: none;
}
:deep(.ai-button-wrapper) {
display: flex;
}
&:hover {
width: 100%;
bottom: 0px;
:deep(.ai-pane-wrapper) {
display: block;
}
:deep(.ai-button-wrapper) {
display: none;
}
}
}
}
.home-wrapper {
......
import { symbolCircle } from "d3";
import * as echarts from "echarts";
const getBarChart = (nameList, valueList) => {
......@@ -15,27 +14,45 @@ const getBarChart = (nameList, valueList) => {
},
yAxis: {
type: 'value',
splitLine: {
show: false
name: "项",
nameLocation: 'end',
nameGap: 12,
nameTextStyle: {
color: '#666',
fontSize: 14,
fontWeight: 400,
padding: [0, 0, 6, -26]
},
show: false
axisLabel: {
formatter: '{value}',
color: '#666',
fontSize: 14,
fontWeight: 400
},
xAxis: {
type: 'category',
data: nameList.map(item => {
return item.name
}),
splitLine: {
show: false
show: true,
lineStyle: {
color: '#e7f3ff',
type: 'dashed',
}
},
axisTick: {
show: false
},
xAxis: {
type: 'category',
data: nameList.map(item => item.name),
axisLine: {
show: false
show: true,
lineStyle: {
color: '#e7f3ff',
},
},
axisLabel: {
show: true
show: true,
textStyle: {
color: 'rgba(95, 101, 108, 1)',
fontFamily: 'Microsoft YaHei',
fontsize: 14,
}
}
},
series: [{
......@@ -57,8 +74,7 @@ const getBarChart = (nameList, valueList) => {
barWidth: 20,
markPoint: {
symbol: 'circle',
symbolSize: 20,
symbolSize: 0,
data: (function () {
const data = [];
nameList.forEach((item, index) => {
......@@ -67,9 +83,7 @@ const getBarChart = (nameList, valueList) => {
xAxis: index,
yAxis: valueList[index],
symbol: `image://${item.img}`,
// symbolSize: [20, 20],
symbolSize: 20,
// symbolOffset: [0, 20],
symbolCircle: 20,
itemStyle: {
borderRadius: '50%',
......
......@@ -11,22 +11,24 @@
<el-option label="调查中" value="1" />
<el-option label="调查结束" value="0" />
</el-select>
<el-select v-model="filterParty" placeholder="全部原告/被告" class="filter-select" clearable>
<!-- <el-select v-model="filterParty" placeholder="全部原告/被告" class="filter-select" clearable>
<el-option label="全部原告/被告" value="" />
</el-select>
<el-select v-model="filterReason" placeholder="全部原因" class="filter-select" clearable>
<el-option label="全部原因" value="" />
</el-select>
</el-select> -->
</div>
</div>
<div class="select-box">
<div class="paixu-btn" @click="handleSwithSort">
<div class="text">{{ "发布时间" }}</div>
<div class="icon2">
<img v-if="isSort" src="@/assets/icons/shengxu2.png" alt="" />
<img v-else src="@/assets/icons/jiangxu2.png" alt="" />
</div>
<el-select v-model="isSort" placeholder="发布时间" style="width: 166px">
<template #prefix>
<div style="display: flex; align-items: center; height: 100%">
<img src="@/assets/icons/jiangxu1.png" style="width: 14px; height: 14px" />
</div>
</template>
<el-option label="按发布时间降序" value="desc" />
<el-option label="按发布时间升序" value="asc" />
</el-select>
</div>
</div>
<div class="wrapper-main">
......@@ -95,11 +97,7 @@ const searchText = ref("");
const filterStage = ref("");
const filterParty = ref("");
const filterReason = ref("");
const isSort = ref(false); // false 降序
const handleSwithSort = () => {
isSort.value = !isSort.value;
};
const isSort = ref('desc'); // 降序
// 科技领域过滤
const surveyAreaList = ref([]);
......@@ -166,20 +164,17 @@ const handleFetchSurveyList = async () => {
publishYear: checkedYearList.value.join(',') || null,
Area: checkedAreaList.value.join(',') || null,
caseStatus: filterStage.value,
keywords: searchText.value,
keywords: searchText.value || null,
sortField: "date",
sortOrder: isSort.value ? "asc" : "desc"
sortOrder: isSort.value
};
const res = await getSurveyList(params);
if (res.code === 200 && res.data) {
surveyInfoList.value = res.data.content;
totalDiscussNum.value = res.data.totalElements || 0;
}
} catch (error) {
console.error("获取调查列表失败", error);
} finally {
} catch (error) {}
listLoading.value = false;
}
};
const handleCurrentChange = val => {
......@@ -241,30 +236,6 @@ onMounted(async () => {
}
}
}
.select-box {
.paixu-btn {
display: flex;
align-items: center;
gap: 8px;
height: 32px;
border: 1px solid #e6e7e8;
border-radius: 4px;
background: #fff;
cursor: pointer;
padding: 0 12px;
.text {
font-size: 14px;
color: #5f656c;
}
.icon2 {
width: 10px;
img {
width: 100%;
}
}
}
}
}
.wrapper-main {
......@@ -335,18 +306,26 @@ onMounted(async () => {
height: 48px;
display: flex;
align-items: center;
padding: 0 20px;
.icon {
width: 22px;
height: 18px;
margin-left: 19px;
img {
width: 100%;
height: 100%;
}
}
.title {
height: 26px;
margin-left: 12px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 20px;
font-weight: 700;
color: var(--color-main-active);
line-height: 26px;
letter-spacing: 0px;
text-align: left;
}
}
......
<template>
<div class="case-wrapper">
<div class="wrapper-header">
<div class="header-filters">
<div class="search-box">
<el-input v-model="searchText" style="width: 360px; height: 32px" placeholder="搜索调查案件" @keyup.enter="handleSearch" :suffix-icon="Search"></el-input>
</div>
<div class="select-box">
<div class="paixu-btn" @click="handleSwithSort">
<div class="icon1">
<img v-if="isSort" src="@/assets/icons/shengxu1.png" alt="" />
<img v-else src="@/assets/icons/jiangxu1.png" alt="" />
</div>
<div class="text">{{ "发布时间" }}</div>
<div class="icon2">
<img v-if="isSort" src="@/assets/icons/shengxu2.png" alt="" />
<img v-else src="@/assets/icons/jiangxu2.png" alt="" />
</div>
<div class="select-box">
<el-select v-model="isSort" placeholder="发布时间" style="width: 166px">
<template #prefix>
<div style="display: flex; align-items: center; height: 100%">
<img src="@/assets/icons/jiangxu1.png" style="width: 14px; height: 14px" />
</div>
</template>
<el-option label="按发布时间降序" value="desc" />
<el-option label="按发布时间升序" value="asc" />
</el-select>
</div>
</div>
<div class="wrapper-main">
......@@ -54,7 +54,7 @@
</div>
<SurveyHistory v-loading="listLoading" :surveyList="surveyInfoList"></SurveyHistory>
<div class="right-footer">
<div class="footer-left">{{ `共 ${totalDiscussNum}` }}</div>
<div class="footer-left">{{ `共${totalDiscussNum}项调查` }}</div>
<div class="footer-right">
<el-pagination @current-change="handleCurrentChange" :pageSize="pageSize" :current-page="currentPage" background layout="prev, pager, next" :total="totalDiscussNum" />
</div>
......@@ -70,10 +70,8 @@ import { Search } from "@element-plus/icons-vue";
import { getSearchAllArea, getSearchAllYear, getSurveyList } from "@/api/marketAccessRestrictions";
import SurveyHistory from "@/views/marketAccessRestrictions/com/SurveyHistory.vue"
const isSort = ref(true); // true 升序 false 倒序
const handleSwithSort = () => {
isSort.value = !isSort.value;
};
const searchText = ref(''); // 搜索文本
const isSort = ref('desc'); // 降序
// 科技领域过滤
const surveyAreaList = ref([]);
......@@ -139,20 +137,17 @@ const handleFetchSurveyList = async () => {
sortCode: "301",
publishYear: checkedYearList.value.join(',') || null,
Area: checkedAreaList.value.join(',') || null,
// keywords: searchText.value,
keywords: searchText.value || null,
sortField: "date",
sortOrder: isSort.value ? "asc" : "desc"
sortOrder: isSort.value
};
const res = await getSurveyList(params);
if (res.code === 200 && res.data) {
surveyInfoList.value = res.data.content;
totalDiscussNum.value = res.data.totalElements || 0;
}
} catch (error) {
console.error("获取调查列表失败", error);
} finally {
} catch (error) {}
listLoading.value = false;
}
};
const handleCurrentChange = (val) => {
......@@ -190,56 +185,24 @@ onMounted(async () => {
display: flex;
margin-bottom: 16px;
justify-content: space-between;
.header-filters {
display: flex;
gap: 16px;
align-items: center;
.search-box {
background-color: #fff;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 4px;
}
.select-box {
height: 32px;
box-sizing: border-box;
.paixu-btn {
.dropdown-filters {
display: flex;
width: 120px;
height: 32px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
&:hover {
background: var(--color-bg-hover);
}
cursor: pointer;
.icon1 {
width: 11px;
height: 14px;
margin-top: 10px;
margin-left: 9px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 19px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0px;
text-align: left;
margin-top: 7px;
margin-left: 9px;
}
.icon2 {
width: 10px;
height: 5px;
margin-top: 5px;
margin-left: 13px;
img {
width: 100%;
height: 100%;
gap: 12px;
.filter-select {
width: 140px;
:deep(.el-input__wrapper) {
background-color: #fff;
}
}
}
......@@ -324,7 +287,7 @@ onMounted(async () => {
}
.title {
height: 26px;
margin-left: 19px;
margin-left: 12px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-style: Bold;
......
......@@ -11,12 +11,12 @@
<el-option label="调查中" value="1" />
<el-option label="调查结束" value="0" />
</el-select>
<el-select v-model="filterParty" placeholder="全部原告/被告" class="filter-select" clearable>
<!-- <el-select v-model="filterParty" placeholder="全部原告/被告" class="filter-select" clearable>
<el-option label="全部原告/被告" value="" />
</el-select>
<el-select v-model="filterReason" placeholder="全部原因" class="filter-select" clearable>
<el-option label="全部原因" value="" />
</el-select>
</el-select> -->
</div>
</div>
<div class="select-box">
......@@ -26,7 +26,8 @@
<img src="@/assets/icons/jiangxu1.png" style="width: 14px; height: 14px" />
</div>
</template>
<el-option v-for="item in releaseTimeList" :key="item.value" :label="item.label" :value="item.value" />
<el-option label="按发布时间降序" value="desc" />
<el-option label="按发布时间升序" value="asc" />
</el-select>
</div>
</div>
......@@ -99,19 +100,7 @@ const searchText = ref("");
const filterStage = ref("");
const filterParty = ref("");
const filterReason = ref("");
const isSort = ref(false); // false 降序
const releaseTimeList = ref([
{
label: "按发布时间倒序",
value: false
},
{
label: "按发布时间升序",
value: true
}
]);
const isSort = ref('desc'); // 降序
// 科技领域过滤
const surveyAreaList = ref([]);
......@@ -203,18 +192,15 @@ const handleFetchSurveyList = async () => {
caseStatus: filterStage.value || null,
keywords: searchText.value || null,
sortField: "date",
sortOrder: isSort.value ? "asc" : "desc"
sortOrder: isSort.value
};
const res = await getSurveyList(params);
if (res.code === 200) {
surveyInfoList.value = res.data?.content || [];
totalDiscussNum.value = res.data?.totalElements || 0;
}
} catch (error) {
console.error("获取调查列表失败", error);
} finally {
} catch (error) {}
listLoading.value = false;
}
};
const handleCurrentChange = val => {
......@@ -278,33 +264,6 @@ onMounted(async () => {
}
}
}
.select-box {
.paixu-btn {
display: flex;
align-items: center;
gap: 8px;
height: 32px;
border: 1px solid #e6e7e8;
border-radius: 4px;
background: #fff;
cursor: pointer;
padding: 0 12px;
.text {
font-size: 14px;
color: #5f656c;
}
.icon2 {
width: 10px;
img {
width: 100%;
}
}
}
}
}
.wrapper-main {
......
......@@ -19,8 +19,7 @@
<div class="page-tabs">
<div :class="['tab-item', {'tab-active': activeName==item.name}]" v-for="(item, index) in tabList" :key="index" @click="handleClickBtn(item)">
<div class="icon">
<img :src="item.activeIcon" alt="" v-if="activeName==item.name" />
<img :src="item.icon" alt="" v-else />
<img :src="activeName==item.name ? item.activeIcon : item.icon" alt="" />
</div>
<div class="text" :class="{ textActive: activeName==item.name }">
{{ item.name }}
......
<template>
<div class="wrap">
<div class="top">
<div class="content-top">
<div class="item">
<div class="item-left-box">
<div class="item-left1">{{ "总调查案件数" }}</div>
<div class="item-left2">{{ "1980-2025" }}</div>
</div>
<div class="item-right">{{ "452项" }}</div>
<div class="item-right">{{ `${totalCaseNum}项` }}</div>
</div>
<div class="item">
<div class="item-left">{{ "仍在调查中的案件" }}</div>
<div class="item-right">{{ "28项" }}</div>
<div class="item-right">{{ `${onCaseNum}项` }}</div>
</div>
<div class="item">
<div class="item-left">{{ "涉及中企数量" }}</div>
<div class="item-right">{{ "326家" }}</div>
<div class="item-left">{{ "已征收关税的案件" }}</div>
<div class="item-right">{{ `${isTariffNum}项` }}</div>
</div>
<div class="item">
<div class="item-left">{{ "胜诉/和解率" }}</div>
<div class="item-right">{{ "38%" }}</div>
<div class="item-left">{{ "撤销案件梳理" }}</div>
<div class="item-right">{{ `${cancelNum}` }}</div>
</div>
</div>
<div class="center">
<div class="box1">
<div class="box-header">
<div class="header-left"></div>
<div class="title">232调查数量年度变化趋势</div>
<div class="content-list">
<div class="content-item" v-loading="box1Loading">
<AnalysisBox title="数量变化趋势">
<template #header-btn>
<div class="header-btn-box">
<!-- <div
class="btn"
:class="{ btnActive: btnActiveName === '发起调查' }"
@click="handleClickBox1Btn('发起调查')"
>
{{ "发起调查" }}
</div>
<div
class="btn"
:class="{ btnActive: btnActiveName === '结束调查' }"
@click="handleClickBox1Btn('结束调查')"
>
{{ "结束调查" }}
</div> -->
<ActionButton :type="btnActiveName === '发起调查' ? 'active' : 'normal'" name="发起调查" @click="handleClickBox1Btn('发起调查')"></ActionButton>
<ActionButton :type="btnActiveName === '结束调查' ? 'active' : 'normal'" name="结束调查" @click="handleClickBox1Btn('结束调查')"></ActionButton>
</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box1-main" id="chart1"></div>
</div>
<div class="box2">
<div class="box-header">
<div class="header-left"></div>
<div class="title">调查案件领域分布</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box2-main" id="chart2"></div>
</div>
</div>
<div class="footer">
<div class="box3">
<div class="box-header">
<div class="header-left"></div>
<div class="title">关税税率</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box3-main" id="chart3"></div>
</div>
<div class="box4">
<div class="box-header">
<div class="header-left"></div>
<div class="title">被调查国家分布</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box4-main" id="chart4"></div>
<ActionButton :type="activeName === '1' ? 'active' : 'normal'" name="发起调查" @click="onStatNum('1')"></ActionButton>
<ActionButton :type="activeName === '0' ? 'active' : 'normal'" name="结束调查" @click="onStatNum('0')"></ActionButton>
</div>
</template>
<div class="box-main">
<div class="box-head" ref="chart1"></div>
<TipTab text="美对华232调查案件的数量变化趋势,数据来源:美国商务部官网" style="margin-top: 16px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content1" />
</div>
</div>
</AnalysisBox>
</div>
<div class="content-item" v-loading="box2Loading">
<AnalysisBox title="领域分布情况">
<template #header-btn>
<el-select v-model="box2Paarams.years" @change="handleGetStatArea()" placeholder="选择时间" style="width:120px; margin-right:12px;">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<div class="box-main">
<div class="box-head" ref="chart2"></div>
<TipTab text="美对华232调查案件的领域分布情况,数据来源:美国商务部官网" style="margin-top: -16px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content2" />
</div>
</div>
</AnalysisBox>
</div>
</div>
<div class="content-list">
<div class="content-item" v-loading="box3Loading">
<AnalysisBox title="关税变化分布">
<template #header-btn>
<el-select v-model="box3Paarams.years" @change="onSearchTariff()" placeholder="选择时间" style="width:120px; margin-right:12px;">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<div class="box-main">
<div class="box-head" ref="chart3"></div>
<TipTab text="美对华232调查案件导致的关税变化分布,数据来源:美国商务部官网" style="margin-top: -16px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content3" />
</div>
</div>
</AnalysisBox>
</div>
<div class="content-item" v-loading="box4Loading">
<AnalysisBox title="国家分布情况">
<template #header-btn>
<el-select v-model="box4Paarams.years" @change="handleGetSearchCountry()" placeholder="选择时间" style="width:120px; margin-right:12px;">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<div class="box-main">
<div class="box-head" ref="chart4"></div>
<TipTab text="美232调查所涉及的国家分布情况,数据来源:美国商务部官网" style="margin-top: -16px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content4" />
</div>
</div>
</AnalysisBox>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import * as echarts from "echarts";
import { ref, onMounted, nextTick, reactive } from "vue";
import {
getStatCount,
getStatArea,
getSearchCountry,
getStatNum,
getSearchTariff,
} from "@/api/marketAccessRestrictions";
import ChinaJson from "../../assets/json/China.json";
import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChart";
import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js";
import AiButton from '@/components/base/Ai/AiButton/index.vue';
import AiPane from '@/components/base/Ai/AiPane/index.vue';
import { getNearYearList, getAIReport } from "@/views/marketAccessRestrictions/utils/index.ts";
import getMultiLineChart from "./utils/multiLineChart";
import getBarChart from "./utils/barChart";
import getPieChart from "./utils/piechart";
import getMapChart from "./utils/mapChart";
// 绘制echarts图表
const setChart = (option, chartId) => {
let chartDom = document.getElementById(chartId);
chartDom.removeAttribute("_echarts_instance_");
let chart = echarts.init(chartDom);
chart.setOption(option);
return chart;
};
const btnActiveName = ref("发起调查");
const handleClickBox1Btn = name => {
btnActiveName.value = name;
};
const chart1Data = ref({
title: ["2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025"],
data: [
{
name: "提出法案",
value: [145, 52, 84, 99, 71, 96, 128, 144, 140, 168, 188, 172]
}
]
});
const yearList = getNearYearList();
// 获取AI智能报告
const aiContent = reactive({
content1: "正在生成...",
content2: "正在生成...",
content3: "正在生成...",
content4: "正在生成...",
})
const onAIReport = (data, key) => {
getAIReport(data).then(res => { aiContent[key] = res })
}
const chart2Data = ref([
{ name: "半导体", value: 50 },
{ name: "电子设备", value: 46 },
{ name: "显示技术", value: 40 },
{ name: "新能源", value: 32 },
{ name: "通信设备", value: 31 },
{ name: "汽车", value: 31 },
{ name: "轻工业制造", value: 30 },
{ name: "其他", value: 24 }
]);
const chart2ColorList = ref(["#69B1FF", "#FFC069", "#87E8DE", "#597EF7", "#D6E4FF", "#FF7875", "#B37FEB", "#FFA39E"]);
// 数量统计
const totalCaseNum = ref(0)
const onCaseNum = ref(0)
const isTariffNum = ref(0)
const cancelNum = ref('0%')
const handleGetStat = async () => {
try {
const res = await getStatCount({sortCode: '232'})
console.log('232数量统计', res);
if (res.code === 200 && res.data) {
totalCaseNum.value = res.data.allCaseNum
onCaseNum.value = res.data.underCaseNum
isTariffNum.value = res.data.isTariffNum
cancelNum.value = res.data.cancelNum
}
} catch (error) {}
}
const chart3Data = ref([
{ name: "税率25%+", value: 50 },
{ name: "税率11%-25%", value: 46 },
{ name: "税率1%-10%", value: 22 },
{ name: "税率0%", value: 10 },
]
const chart1 = ref(null);
const activeName = ref('1');
const box1Loading = ref(false);
const onStatNum = async (event) => {
if (event) activeName.value = event;
box1Loading.value = true;
let chartData = { title: [], list: [] };
try {
const res = await getStatNum({ sortCode: 232, searchStatus:activeName.value })
console.log('数量变化趋势', res);
if (res.code === 200 && res.data) {
chartData.title = res.data.map(item => item.searchYorM),
chartData.list = [{ name: "232调查", value: res.data.map(item => item.searchCount) }]
} else {
chartData.title = [];
chartData.list = [];
}
} catch (error) {
chartData.title = [];
chartData.list = [];
}
onAIReport({ type: "折线图", name: "数量变化趋势", data: chartData }, "content1")
nextTick(() => { createLineChart(chart1, chartData) });
box1Loading.value = false;
}
);
const chart3ColorList = ref(["#69B1FF", "#FFC069", "#87E8DE", "#FFA39E"]);
const chart2 = ref(null);
const box2Paarams = reactive({
sortCode: 232,
years: '2025'
})
const box2Loading = ref(false);
const handleGetStatArea = async () => {
box2Loading.value = true;
let chartData = []
try {
const res = await getStatArea(box2Paarams);
console.log('领域分布情况', res);
if (res.code === 200 && res.data) {
chartData = res.data.map(item => ({ name: item.areaname, value: item.areacount }) );
} else {
chartData = [];
}
} catch (error) {
chartData = [];
}
onAIReport({ type: "环形图", name: "领域分布情况", data: chartData }, "content2")
nextTick(() => { createPieChart(chart2, chartData) });
box2Loading.value = false;
};
const chart4Data = ref([
{ name: "加拿大", value: 50 },
{ name: "欧盟", value: 46 },
{ name: "韩国", value: 40 },
{ name: "日本", value: 31 },
{ name: "中国", value: 24 },
const chart3 = ref(null);
const box3Paarams = reactive({
sortCode: 232,
years: '2025'
})
const box3Loading = ref(false);
const onSearchTariff = async () => {
box3Loading.value = true;
let chartData = []
try {
const res = await getSearchTariff(box3Paarams);
console.log('关税变化分布', res);
if (res.code === 200 && res.data) {
chartData = [
{ name: "税率25%+", value: res.data.TARIFF25 },
{ name: "税率11%-25%", value: res.data.TARIFF11 },
{ name: "税率1%-10%", value: res.data.TARIFF1 },
{ name: "税率0%", value: res.data.TARIFF0 },
];
} else {
chartData = [];
}
}catch (error) {
chartData = [];
}
onAIReport({ type: "环形图", name: "关税变化分布", data: chartData }, "content3")
nextTick(() => { createPieChart(chart3, chartData) });
box3Loading.value = false;
}
]);
const chart4ColorList = ref(["#69B1FF", "#FFC069", "#87E8DE", "#D6E4FF", "#FFA39E"]);
const chart4 = ref(null);
const box4Paarams = reactive({
sortCode: 232,
years: '2025'
})
const box4Loading = ref(false);
const handleGetSearchCountry = async () => {
box4Loading.value = true;
let chartData = []
try {
const res = await getSearchCountry(box4Paarams);
console.log('国家分布情况', res);
if (res.code === 200 && res.data) {
chartData = res.data.map(item => ({ name: item.COUNTRY, value: item.NUM }) );
} else {
chartData = []
}
} catch (error) {
chartData = []
}
onAIReport({ type: "环形图", name: "国家分布情况", data: chartData }, "content4")
nextTick(() => { createPieChart(chart4, chartData) });
box4Loading.value = false;
};
onMounted(() => {
let chart1 = getMultiLineChart(chart1Data.value.title, chart1Data.value.data[0].value);
setChart(chart1, "chart1");
let chart2 = getPieChart(chart2Data.value, chart2ColorList.value);
setChart(chart2, "chart2");
let chart3 = getPieChart(chart3Data.value, chart3ColorList.value);
setChart(chart3, "chart3");
let chart4 = getPieChart(chart4Data.value, chart4ColorList.value);
setChart(chart4, "chart4");
handleGetStat()
onStatNum()
handleGetStatArea()
onSearchTariff()
handleGetSearchCountry()
});
</script>
......@@ -208,18 +266,22 @@ onMounted(() => {
flex-direction: column;
gap: 16px;
.top {
.content-top {
display: flex;
justify-content: space-between;
gap: 16px;
.item {
width: 388px;
width: 20px;
flex: auto;
height: 80px;
border-radius: 4px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
&::before {
position: absolute;
content: "";
......@@ -229,9 +291,9 @@ onMounted(() => {
left: 0;
top: 15px;
}
.item-left {
margin-left: 30px;
margin-top: 25px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
......@@ -239,8 +301,8 @@ onMounted(() => {
font-weight: 700;
line-height: 24px;
}
.item-right {
margin-top: 28px;
margin-right: 35px;
height: 24px;
color: var(--color-main-active);
......@@ -249,58 +311,76 @@ onMounted(() => {
font-weight: 700;
line-height: 24px;
}
.item-left-box {
margin-left: 30px;
font-size: 16px;
line-height: 24px;
.item-left1 {
margin-top: 13px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
.item-left2 {
height: 2px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
}
.box-header {
height: 56px;
.content-list {
height: 410px;
display: flex;
gap: 16px;
.content-item {
width: 20px;
flex: auto;
height: 100%;
.box-main {
width: 100%;
height: 100%;
padding: 12px 30px 20px;
display: flex;
flex-direction: column;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
.box-head {
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
flex: auto;
}
.title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
line-height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
.ai-pane {
position: absolute;
right: 0px;
bottom: 15px;
z-index: 2;
:deep(.ai-pane-wrapper) {
display: none;
}
:deep(.ai-button-wrapper) {
display: flex;
}
&:hover {
width: 100%;
bottom: 0px;
:deep(.ai-pane-wrapper) {
display: block;
}
:deep(.ai-button-wrapper) {
display: none;
}
}
}
}
}
}
.header-btn-box {
position: absolute;
top: 14px;
right: 120px;
display: flex;
gap: 8px;
margin-right: 12px;
.btn {
margin-left: 8px;
height: 28px;
padding: 0 8px;
box-sizing: border-box;
......@@ -321,117 +401,5 @@ onMounted(() => {
color: var(--color-main-active);
}
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
display: flex;
justify-content: flex-end;
gap: 4px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
}
}
.center {
display: flex;
justify-content: space-between;
.box1 {
width: 792px;
height: 360px;
border-radius: 4px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.box1-main {
height: 300px;
}
}
.box2 {
width: 792px;
height: 360px;
border-radius: 4px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.box2-main {
height: 300px;
// width: 752px;
box-sizing: border-box;
padding: 0 20px;
}
}
}
.footer {
display: flex;
justify-content: space-between;
.box3 {
width: 792px;
height: 360px;
border-radius: 4px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.box3-main {
height: 300px;
}
.box3-main1 {
height: 300px;
display: flex;
.box3-main1-left {
width: 380px;
height: 300px;
overflow-y: auto;
overflow-x: hidden;
.box3-main1-left-item {
display: flex;
height: 36px;
margin-left: 20px;
.box3-main1-left-item-left {
width: 24px;
height: 24px;
border-radius: 12px;
margin-left: 12px;
margin-top: 6px;
background: #e7f3ff;
color: #0a57a6;
text-align: center;
line-height: 24px;
}
.box3-main1-left-item-center {
height: 36px;
line-height: 36px;
width: 220px;
margin-left: 12px;
}
.box3-main1-left-item-right {
width: 80px;
box-sizing: border-box;
padding-right: 10px;
text-align: right;
}
}
}
.box3-main1-right {
width: 390px;
height: 300px;
}
}
}
.box4 {
width: 792px;
height: 360px;
border-radius: 4px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.box4-main {
height: 300px;
box-sizing: border-box;
padding: 0 20px;
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="wrap">
<div class="top">
<div class="box1 box" v-loading="box1Loading">
<div class="box-header">
<div class="header-left"></div>
<div class="title">对华301调查年度数量趋势</div>
<div class="warning-text">
{{ `${inProgressCount}项调查仍在进行中` }}
</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box1-main" id="box1Chart"></div>
<div class="box-footer">
<div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box-footer-center">
{{ "近年来美对华301调查呈现案件频发、聚焦专利壁垒、力图阻断产业链升级的特点。" }}
</div>
<div class="box-footer-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="" />
</div>
</div>
</div>
<div class="box2 box" v-loading="box2Loading">
<div class="box-header">
<div class="header-left"></div>
<div class="title">301调查国家分布</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box2-main" id="box2Chart"></div>
<div class="box-footer">
<div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box-footer-center">
{{ "现有调查以先进制造、半导体产业为主。" }}
</div>
<div class="box-footer-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="" />
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="box3 box" v-loading="box3Loading">
<div class="box-header">
<div class="header-left"></div>
<div class="title">301调查 direction 及结果分布</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box3-main" id="box3Chart"></div>
<div class="box-footer">
<div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box-footer-center">
{{ "近年来美对华301调查呈现案件频发、聚焦专利壁垒、力图阻断产业链升级的特点。" }}
</div>
<div class="box-footer-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="" />
</div>
</div>
</div>
<div class="box4 box" v-loading="box4Loading">
<div class="box-header">
<div class="header-left"></div>
<div class="title">301调查领域分布</div>
<div class="header-select-box">
<el-select v-model="selectYear" @change="handleSelectYear" placeholder="选择时间" style="width: 120px">
<div class="content-list">
<div class="content-item" v-loading="box1Loading">
<AnalysisBox title="数量变化趋势">
<template #header-btn>
<div class="warning-text">{{ `${inProgressCount}项调查仍在进行中` }}</div>
</template>
<div class="box-main">
<div class="box-head" ref="box1Chart"></div>
<TipTab text="美对华301调查案件的数量变化趋势,数据来源:美国贸易代表办公室官网" style="margin-top: 16px" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content1" />
</div>
</div>
</AnalysisBox>
</div>
<div class="content-item" v-loading="box2Loading">
<AnalysisBox title="国家分布情况">
<template #header-btn>
<el-select v-model="box2Paarams.years" @change="handleGetSearchCountry()" placeholder="选择时间" style="width:120px; margin-right:12px;">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<div class="box-main">
<div class="box-head" id="box2Chart"></div>
<TipTab text="美301调查所涉及的国家分布情况,数据来源:美国贸易代表办公室官网" style="margin-top: 16px" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content2" />
</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</AnalysisBox>
</div>
</div>
<div class="content-list">
<div class="content-item" v-loading="box3Loading">
<AnalysisBox title="调查方向及结果分布">
<div class="box-main">
<div class="box-head" id="box3Chart"></div>
<TipTab text="美301调查方向及结果分布情况,数据来源:美国贸易代表办公室官网" style="margin-top: 16px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content3" />
</div>
<div class="box4-main" id="box4Chart"></div>
<div class="box-footer">
<div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box-footer-center">
{{ "主要集中在存储、芯片设计及通信领域,如长江存储、长鑫存储、华为、中兴等" }}
</AnalysisBox>
</div>
<div class="box-footer-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="" />
<div class="content-item" v-loading="box4Loading">
<AnalysisBox title="领域分布情况">
<template #header-btn>
<el-select v-model="box4Paarams.years" @change="handleGetStatArea()" placeholder="选择时间" style="width:120px; margin-right:12px;">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<div class="box-main">
<div class="box-head" ref="box4Chart"></div>
<TipTab text="美对华301调查案件的领域分布情况,数据来源:美国贸易代表办公室官网" style="margin-top: -16px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content4" />
</div>
</div>
</AnalysisBox>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { ref, onMounted, nextTick, reactive } from "vue";
import setChart from "@/utils/setChart";
import getLineChart from "./utils/lineChart";
import getBarChart from "./utils/barChart";
import getSankeyChart from "./utils/sankey";
import getPieChart from "./utils/piechart";
import getbarChart from "@/views/bill/utils/barchart";
import { getSearchCountry, getStatArea, getStatNum, getSearchDirection } from "@/api/marketAccessRestrictions";
const selectYear = ref("2025");
const yearList = ref([
{
label: "2025",
value: "2025"
},
{
label: "2024",
value: "2024"
},
{
label: "2023",
value: "2023"
}
]);
import {
getSearchCountry,
getStatArea,
getStatNum,
getSearchDirection
} from "@/api/marketAccessRestrictions";
import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChart";
import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js";
import AiButton from '@/components/base/Ai/AiButton/index.vue';
import AiPane from '@/components/base/Ai/AiPane/index.vue';
import { getNearYearList, getAIReport } from "@/views/marketAccessRestrictions/utils/index.ts";
const yearList = getNearYearList();
// 获取AI智能报告
const aiContent = reactive({
content1: "正在生成...",
content2: "正在生成...",
content3: "正在生成...",
content4: "正在生成...",
})
const onAIReport = (data, key) => {
getAIReport(data).then(res => { aiContent[key] = res })
}
const inProgressCount = ref(0);
const box1Loading = ref(false);
const box2Loading = ref(false);
const box3Loading = ref(false);
const box4Loading = ref(false);
const box1ChartData = ref({
title: [],
data: []
});
const box1Chart = ref(null);
const box1Loading = ref(false);
const handleGetStatNum = async () => {
box1Loading.value = true;
let chartData = { title: [], list: [] }
try {
const res = await getStatNum({
byYorM: "12",
sortCode: "301"
});
const res = await getStatNum({ byYorM: "12", sortCode: "301" });
console.log('数量变化趋势', res)
if (res.code === 200 && res.data) {
const sortedData = res.data.sort((a, b) => parseInt(a.searchYorM) - parseInt(b.searchYorM));
box1ChartData.value.title = sortedData.map(item => item.searchYorM);
box1ChartData.value.data = sortedData.map(item => item.searchCount);
chartData.title = sortedData.map(item => item.searchYorM);
chartData.list = [{ name: "301调查", value: sortedData.map(item => item.searchCount) }]
inProgressCount.value = res.data.reduce((acc, cur) => acc + (cur.inSearchCount || 0), 0);
const box1Chart = getLineChart(box1ChartData.value.title, box1ChartData.value.data);
setChart(box1Chart, "box1Chart");
} else {
chartData.title = []
chartData.list = []
inProgressCount.value = 0
}
} catch (error) {
console.error("获取调查年度数量趋势失败", error);
} finally {
box1Loading.value = false;
chartData.title = []
chartData.list = []
inProgressCount.value = 0
}
onAIReport({ type: "折线图", name: "数量变化趋势", data: chartData }, "content1")
nextTick(() => { createLineChart(box1Chart, chartData) })
box1Loading.value = false;
};
const box2ChartData = ref({
title: [],
data: []
});
const box2Paarams = reactive({
sortCode: 301,
years: '2025'
})
const box2Loading = ref(false);
const handleGetSearchCountry = async () => {
box2Loading.value = true;
let chartData = { title: [], data: [] }
try {
const res = await getSearchCountry(box2Paarams);
console.log('国家分布情况', res)
if (res.code === 200 && res.data) {
chartData.title = res.data.map(item => ({
img: item.COUNTRYIMAGE ? (item.COUNTRYIMAGE.startsWith("http") ? item.COUNTRYIMAGE : `http://${item.COUNTRYIMAGE}`) : "",
name: item.COUNTRY
}));
chartData.data = res.data.map(item => item.NUM)
const box3ChartData = ref({
nodes: [],
links: []
});
} else {
chartData.title = []
chartData.data = []
}
} catch (error) {
chartData.title = []
chartData.data = []
}
onAIReport({ type: "柱状图", name: "国家分布情况", data: chartData }, "content2")
nextTick(() => {
const box2Chart = getBarChart(chartData.title, chartData.data);
setChart(box2Chart, "box2Chart");
})
box2Loading.value = false;
};
const box3Loading = ref(false);
const handleGetSearchDirection = async () => {
box3Loading.value = true;
let chartData = { nodes: [], links: [] }
try {
const res = await getSearchDirection({
sortCode: "301"
});
const res = await getSearchDirection({ sortCode: "301" });
console.log('调查方向及结果分布', res)
chartData.nodes = [];
chartData.links = [];
if (res.code === 200 && res.data) {
const nodes = [];
const links = [];
const nodeNames = new Set();
res.data.forEach(item => {
if (item.SEARCHDIRECTION) nodeNames.add(item.SEARCHDIRECTION);
if (item.SEARCHRESULT) nodeNames.add(item.SEARCHRESULT);
if (item.SEARCHDIRECTION && item.SEARCHRESULT) {
links.push({
chartData.links.push({
source: item.SEARCHDIRECTION,
target: item.SEARCHRESULT,
value: item.SEARCHCOUNT || 0
});
}
});
nodeNames.forEach(name => {
nodes.push({ name });
});
box3ChartData.value = { nodes, links };
const box3Chart = getSankeyChart(box3ChartData.value.nodes, box3ChartData.value.links);
setChart(box3Chart, "box3Chart");
nodeNames.forEach(name => { chartData.nodes.push({ name }) });
}
} catch (error) {
console.error("获取调查方向及结果分布失败", error);
} finally {
box3Loading.value = false;
chartData.nodes = [];
chartData.links = [];
}
onAIReport({ type: "桑基图", name: "调查方向及结果分布", data: chartData }, "content3")
nextTick(() => {
const box3Chart = getSankeyChart(chartData.nodes, chartData.links);
setChart(box3Chart, "box3Chart");
})
box3Loading.value = false;
};
const box4ChartData = ref([]);
// 切换年份
const handleSelectYear = val => {
selectYear.value = val;
handleGetStatArea();
};
const box4Chart = ref(null)
const box4Paarams = reactive({
sortCode: 301,
years: '2025'
})
const box4Loading = ref(false);
const handleGetStatArea = async () => {
box4Loading.value = true;
const yearMap = {
2023: 2023,
2024: 2024,
2025: 2025
};
const params = {
years: yearMap[selectYear.value] || 2025,
sortCode: 301
};
try {
const res = await getStatArea(params);
if (res.code === 200 && res.data) {
box4ChartData.value = res.data
.filter(item => item.SORTNAME === "301调查")
.map(item => ({
name: item.AREANAME,
value: item.AREACOUNT
}));
const box4Chart = getPieChart(box4ChartData.value);
setChart(box4Chart, "box4Chart");
}
} catch (error) {
console.error("获取制裁领域分布失败", error);
} finally {
box4Loading.value = false;
}
};
const handleGetSearchCountry = async () => {
box2Loading.value = true;
let chartData = []
try {
const res = await getSearchCountry({
sortCode: 301,
year: new Date().getFullYear() - 1
});
const res = await getStatArea(box4Paarams);
console.log('领域分布情况', res)
if (res.code === 200 && res.data) {
box2ChartData.value = {
title: res.data.map(item => ({
img: item.COUNTRYIMAGE ? (item.COUNTRYIMAGE.startsWith("http") ? item.COUNTRYIMAGE : `http://${item.COUNTRYIMAGE}`) : "",
name: item.COUNTRY
})),
data: res.data.map(item => item.NUM)
};
const box2Chart = getBarChart(box2ChartData.value.title, box2ChartData.value.data);
setChart(box2Chart, "box2Chart");
chartData = res.data.map(item => ({name: item.areaname, value: item.areacount}));
} else {
chartData = []
}
} catch (error) {
console.error("获取受调查国家分布失败", error);
} finally {
box2Loading.value = false;
chartData = []
}
onAIReport({ type: "环形图", name: "领域分布情况", data: chartData }, "content4")
nextTick(() => { createPieChart(box4Chart, chartData) })
box4Loading.value = false;
};
onMounted(() => {
......@@ -321,164 +240,62 @@ onMounted(() => {
flex-direction: column;
gap: 16px;
.box {
width: 792px;
.content-list {
height: 410px;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
}
display: flex;
gap: 16px;
.box-header {
height: 46px;
.content-item {
width: 20px;
flex: auto;
height: 100%;
.box-main {
width: 100%;
height: 100%;
padding: 12px 30px 20px;
display: flex;
flex-direction: column;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
.box-head {
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
flex: auto;
}
.warning-text {
.ai-pane {
position: absolute;
top: 16px;
right: 124px;
height: 24px;
color: rgba(206, 79, 81, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: center;
}
.header-select-box {
position: absolute;
top: 13px;
right: 121px;
right: 0px;
bottom: 15px;
z-index: 2;
:deep(.ai-pane-wrapper) {
display: none;
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
:deep(.ai-button-wrapper) {
display: flex;
justify-content: flex-end;
gap: 4px;
.icon {
width: 28px;
height: 28px;
img {
}
&:hover {
width: 100%;
height: 100%;
bottom: 0px;
:deep(.ai-pane-wrapper) {
display: block;
}
:deep(.ai-button-wrapper) {
display: none;
}
}
}
}
.box-footer {
margin: 5px auto;
width: 759px;
height: 40px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 10px;
padding: 6px 12px 6px 12px;
box-sizing: border-box;
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
background: rgba(246, 250, 255, 1);
.box-footer-left {
width: 19px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.box-footer-center {
width: 666px;
.warning-text {
margin-right: 12px;
height: 24px;
color: rgba(5, 95, 194, 1);
color: rgba(206, 79, 81, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
font-size: 18px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.box-footer-right {
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
}
}
.top {
display: flex;
justify-content: center;
gap: 16px;
.box1 {
.box1-main {
height: 304px;
// background: orange;
}
}
.box2 {
.box2-main {
height: 304px;
// background: orange;
}
}
}
.bottom {
display: flex;
justify-content: center;
gap: 16px;
.box3 {
.box3-main {
height: 304px;
// background: orange;
}
}
.box4 {
.box4-main {
height: 304px;
// background: orange;
}
}
text-align: center;
}
}
</style>
......@@ -3,48 +3,56 @@ import * as echarts from "echarts";
const getBarChart = (nameList, valueList) => {
const option = {
tooltip: {},
title: {
text: '单位:万人',
left: 660,
top: 10,
textStyle: {
color: 'rgba(95, 101, 108, 1)',
fontSize: 14,
fontFamily: 'Microsoft YaHei',
fontstyle: 'Regular',
fontWeight: 'normal'
}
},
grid: {
top: '5%',
right: '3%',
bottom: '1%',
width: '100%',
height: '83%',
top: '15%',
right: '5%',
bottom: '2%',
left: '1%',
containLabel: true
},
yAxis: {
type: 'value',
splitLine: {
show: false
name: "项",
nameLocation: 'end',
nameGap: 12,
nameTextStyle: {
color: '#666',
fontSize: 14,
fontWeight: 400,
padding: [0, 0, 6, -26]
},
show: false
axisLabel: {
formatter: '{value}',
color: '#666',
fontSize: 14,
fontWeight: 400
},
xAxis: {
type: 'category',
data: nameList.map(item => {
return item.name
}),
splitLine: {
show: false
show: true,
lineStyle: {
color: '#e7f3ff',
type: 'dashed',
}
},
axisTick: {
show: false
},
xAxis: {
type: 'category',
data: nameList.map(item => item.name),
axisLine: {
show: false
show: true,
lineStyle: {
color: '#e7f3ff',
},
},
axisLabel: {
show: true
show: true,
textStyle: {
color: 'rgba(95, 101, 108, 1)',
fontFamily: 'Microsoft YaHei',
fontsize: 14,
}
}
},
series: [{
......@@ -71,8 +79,7 @@ const getBarChart = (nameList, valueList) => {
const data = [];
nameList.forEach((item, index) => {
data.push({
name: 'icon',
// value: '',
name: `icon${index}`,
xAxis: index,
yAxis: valueList[index],
symbol: `image://${item.img}`,
......
<template>
<div class="wrap">
<div class="top">
<div class="content-top">
<div class="item">
<div class="item-left-box">
<div class="item-left1">{{ "总调查案件数" }}</div>
......@@ -18,63 +18,94 @@
</div>
<div class="item">
<div class="item-left">{{ "胜诉/和解率" }}</div>
<div class="item-right">{{ `${windRate}/${cancelRate}` }}</div>
<div class="item-right">{{ `${windRate}` }}</div>
</div>
</div>
<div class="center">
<div class="box1" v-loading="box1Loading">
<AnalysisBox title="美国对华337调查年度趋势">
<div class="box1-main" id="chart1"></div>
<div class="box1-footer">
<TipTab />
<div class="content-list">
<div class="content-item" v-loading="box1Loading">
<AnalysisBox title="数量变化趋势">
<template #header-btn>
<div class="header-btn-box">
<ActionButton :type="box1Paarams.byYorM === '12' ? 'active' : 'normal'" name="按年度" @click="handleGetStatNum('12')"></ActionButton>
<ActionButton :type="box1Paarams.byYorM === '0' ? 'active' : 'normal'" name="按调查" @click="handleGetStatNum('0')"></ActionButton>
</div>
</AnalysisBox>
</template>
<div class="box-main">
<div class="box-head" ref="chart1"></div>
<TipTab text="美对华337调查案件的数量变化趋势,数据来源:美国国际贸易委员会官网" style="margin-top: 16px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content1" />
</div>
<div class="box2" v-loading="box2Loading">
<AnalysisBox title="调查案件领域分布">
<div class="box2-main" id="chart2"></div>
<div class="box2-footer">
<TipTab />
</div>
</AnalysisBox>
</div>
</div>
<div class="footer">
<div class="box3" v-loading="box3Loading">
<AnalysisBox title="中国公司受调查情况">
<div class="content-item" v-loading="box2Loading">
<AnalysisBox title="领域分布情况">
<template #header-btn>
<div class="header-btn-box">
<div class="btn" :class="{ btnActive: btnActiveName === '调查次数' }" @click="handleClickBox3Btn('调查次数')">
调查次数
<el-select v-model="box2Paarams.years" @change="handleGetStatArea()" style="width:120px; margin-right:12px;">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<div class="box-main">
<div class="box-head" ref="chart2"></div>
<TipTab text="美对华337调查案件的领域分布情况,数据来源:美国国际贸易委员会官网" style="margin-top: -16px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content2" />
</div>
</div>
<div class="btn" :class="{ btnActive: btnActiveName === '注册地分布' }" @click="handleClickBox3Btn('注册地分布')">
注册地分布
</AnalysisBox>
</div>
</div>
<div class="content-list">
<div class="content-item" v-loading="box3Loading">
<AnalysisBox title="中国实体分布情况">
<template #header-btn>
<el-select v-model="box3Paarams.years" @change="handleGetStatcnOrgCount()" style="width:120px; margin-right:12px;">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="box3Paarams.type" @change="handleGetStatcnOrgCount()" style="width:120px; margin-right:12px;">
<el-option label="调查次数" value="01" />
<el-option label="注册地分布" value="02" />
</el-select>
</template>
<div v-show="btnActiveName === '调查次数'" class="box3-main" id="chart3"></div>
<div v-show="btnActiveName === '注册地分布'" class="box3-main1">
<div class="box3-main1-left">
<div class="box3-main1-left-item" v-for="(item, index) in mapData" :key="index">
<div class="box3-main1-left-item-left">{{ index + 1 }}</div>
<div class="box3-main1-left-item-center">{{ item.name }}</div>
<div class="box3-main1-left-item-right">
<div class="box-main">
<div v-show="box3Paarams.type === '01'" class="box-head" id="chart3"></div>
<div v-show="box3Paarams.type === '02'" class="box-head2">
<div class="map-box-left">
<div class="map-box-left-item" v-for="(item, index) in mapData" :key="index">
<div class="map-box-left-item-left">{{ index + 1 }}</div>
<div class="map-box-left-item-center">{{ item.name }}</div>
<div class="map-box-left-item-right">
{{ item.value + "次" }}
</div>
</div>
</div>
<div class="box3-main1-right" id="chartMap"></div>
<div class="map-box-right" id="chartMap"></div>
</div>
<TipTab text="美对华337调查案件的中国实体分布情况,数据来源:美国国际贸易委员会官网" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content3" />
</div>
<div class="box3-footer">
<TipTab />
</div>
</AnalysisBox>
</div>
<div class="box4" v-loading="box4Loading">
<div class="content-item" v-loading="box4Loading">
<AnalysisBox title="调查结果分布">
<div class="box4-main" id="chart4"></div>
<div class="box4-footer">
<TipTab />
<template #header-btn>
<el-select v-model="box4Paarams.years" @change="handleGetSearchResult()" style="width:120px; margin-right:12px;">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<div class="box-main">
<div class="box-head" ref="chart4"></div>
<TipTab text="美对华337调查案件的调查结果分布情况,数据来源:美国国际贸易委员会官网" style="margin-top: -16px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content4" />
</div>
</div>
</AnalysisBox>
</div>
......@@ -82,178 +113,37 @@
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import { ref, onMounted, nextTick, reactive } from "vue";
import setChart from "@/utils/setChart";
import TipTab from "@/components/base/TipTab/index.vue"
import AnalysisBox from "@/components/base/boxBackground/analysisBox.vue"
import { getStatCount, getStatcnOrgCount, getSearchResult, getStatArea, getStatNum } from "@/api/marketAccessRestrictions";
import {
getStatCount,
getStatcnOrgCount,
getSearchResult,
getStatArea,
getStatNum
} from "@/api/marketAccessRestrictions";
import getMultiLineChart from "./utils/multiLineChart";
import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChart";
import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js";
import getBarChart from "./utils/barChart";
import getPieChart from "./utils/piechart";
import getMapChart from "./utils/mapChart";
import CompanyImg from "./symbol.png"
import getGraphChart from "./utils/graphChart";
import getGraph from "./utils/graph";
const box1Loading = ref(false);
const box2Loading = ref(false);
const box3Loading = ref(false);
const box4Loading = ref(false);
const btnActiveName = ref("调查次数");
const nodes = ref([
{
id: 0,
name: "泰丰先行",
// category: 0,
symbolSize: 30,
value: 8,
symbol: `image://${CompanyImg}`,
},
{
id: 1,
name: "国轩高科",
// category: 0,
symbolSize: 30,
value: 9,
symbol: `image://${CompanyImg}`,
},
{
id: 2,
name: "智方纳米",
// category: 2,
symbolSize: 30,
value: 7,
symbol: `image://${CompanyImg}`,
},
{
id: 3,
name: "香百科技",
// category: 1,
symbolSize: 30,
value: 6,
symbol: `image://${CompanyImg}`,
},
{
id: 4,
name: "格林滨",
// category: 2,
symbolSize: 30,
value: 6,
symbol: `image://${CompanyImg}`,
},
{
id: 5,
name: "江西紫宸",
// category: 2,
symbolSize: 30,
value: 7,
symbol: `image://${CompanyImg}`,
},
{
id: 6,
name: "紫江企业",
// category: 4,
symbolSize: 30,
value: 6,
symbol: `image://${CompanyImg}`,
},
{
id: 7,
name: "大而美法案",
// category: 4,
symbolSize: 50,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 8,
name: "比亚迪",
// category: 0,
symbolSize: 30,
value: 10,
symbol: `image://${CompanyImg}`,
},
{
id: 9,
name: "铜陵有色",
// category: 3,
symbolSize: 30,
value: 8,
symbol: `image://${CompanyImg}`,
},
{
id: 10,
name: "长盛精密",
// category: 1,
symbolSize: 30,
value: 7,
symbol: `image://${CompanyImg}`,
},
{
id: 11,
name: "天合光能",
// category: 0,
symbolSize: 30,
value: 8,
symbol: `image://${CompanyImg}`,
},
{
id: 12,
name: "昆仑化学",
// category: 2,
symbolSize: 30,
value: 6,
symbol: `image://${CompanyImg}`,
},
{
id: 13,
name: "嘉源科技",
// category: 1,
symbolSize: 30,
value: 6,
symbol: `image://${CompanyImg}`,
},
{
id: 14,
name: "华阳集团",
// category: 4,
symbolSize: 30,
value: 7,
symbol: `image://${CompanyImg}`,
},
{
id: 15,
name: "海辰智能",
// category: 1,
symbolSize: 30,
value: 7,
symbol: `image://${CompanyImg}`,
},
]);
const links = ref([
{ source: 1, target: 7, label: { show: true, formatter: '合作' } },
{ source: 2, target: 7, label: { show: true, formatter: '持股' } },
{ source: 3, target: 7, label: { show: true, formatter: '合作' } },
{ source: 4, target: 7, lineStyle: { type: 'dashed', color: '#d32f2f' }, label: { show: true, formatter: '从属' } },
{ source: 5, target: 7, label: { show: true, formatter: '合作' } },
{ source: 6, target: 7, label: { show: true, formatter: '持股' } },
{ source: 0, target: 7, label: { show: true, formatter: '持股' } },
{ source: 8, target: 7, label: { show: true, formatter: '合作' } },
{ source: 9, target: 7, lineStyle: { type: 'dashed', color: '#d32f2f' }, label: { show: true, formatter: '从属' } },
{ source: 10, target: 7, lineStyle: { type: 'dashed', color: '#d32f2f' }, label: { show: true, formatter: '合作' } },
{ source: 11, target: 7, label: { show: true, formatter: '合作' } },
{ source: 12, target: 7, label: { show: true, formatter: '合作' } },
{ source: 13, target: 7, label: { show: true, formatter: '合作' } },
{ source: 14, target: 7, label: { show: true, formatter: '合作' } },
{ source: 15, target: 7, label: { show: true, formatter: '合作', color: 'red', borderColor: 'red' } },
]);
import AiButton from '@/components/base/Ai/AiButton/index.vue';
import AiPane from '@/components/base/Ai/AiPane/index.vue';
import { getNearYearList, getAIReport } from "@/views/marketAccessRestrictions/utils/index.ts";
const yearList = getNearYearList();
// 获取AI智能报告
const aiContent = reactive({
content1: "正在生成...",
content2: "正在生成...",
content3: "正在生成...",
content4: "正在生成...",
})
const onAIReport = (data, key) => {
getAIReport(data).then(res => { aiContent[key] = res })
}
const provinceCoords = {
"北京": [116.46, 39.92],
......@@ -296,185 +186,164 @@ const provinceCoords = {
const totalCaseNum = ref(0)
const onCaseNum = ref(0)
const relateCnNum = ref(0)
const cancelRate = ref('0%')
const windRate = ref('0%')
const handleGetStat = async () => {
const params = {
sortCode: '337'
}
try {
const res = await getStatCount(params)
console.log('337数量统计', res);
const res = await getStatCount({sortCode: '337'})
console.log('数量统计', res);
if (res.code === 200 && res.data) {
totalCaseNum.value = res.data.allCaseNum
onCaseNum.value = res.data.underCaseNum
relateCnNum.value = res.data.involvinChinese
cancelRate.value = res.data.cancelNum
windRate.value = res.data.winRate
}
} catch (error) {
console.error('337数量统计error', error);
}
} catch (error) {}
}
handleGetStat()
const handleGetStatcnOrgCount = async (type) => {
box3Loading.value = true;
const chart1 = ref(null);
const box1Paarams = reactive({
sortCode: 337,
byYorM: '12'
})
const box1Loading = ref(false);
const handleGetStatNum = async (type) => {
if (type) box1Paarams.byYorM = type
box1Loading.value = true;
let chartData = { title: [], list: [] }
try {
const res = await getStatcnOrgCount({
type,
sortCode: "337"
});
console.log('中国公司受调查情况', res);
const res = await getStatNum(box1Paarams);
console.log('数量变化趋势', res);
if (res.code === 200 && res.data) {
if (type === "01") {
chart3Data.value = {
name: res.data.map(item => item.ORGNAME),
value: res.data.map(item => item.ORGCOUNT)
};
nextTick(() => {
let chart3 = getBarChart(chart3Data.value.name, chart3Data.value.value);
setChart(chart3, "chart3");
});
} else if (type === "02") {
mapData.value = res.data.map(item => {
const name = item.ORGPROVINCE//.replace(/省|市|自治区|特别行政区/g, "");
return {
name: item.ORGPROVINCE,
value: item.PROVINCECOUNT,
coord: provinceCoords[name] || [0, 0]
};
});
nextTick(() => {
let chartMap = getMapChart(mapData.value);
setChart(chartMap, "chartMap");
});
}
const sortedData = res.data.sort((a, b) => parseInt(a.searchYorM) - parseInt(b.searchYorM));
chartData.title = sortedData.map(item => item.searchYorM);
chartData.list = [
{ name: "调查数量", value: sortedData.map(item => item.searchCount) }
];
} else {
chartData.title = [];
chartData.list = [];
}
} catch (error) {
console.error("获取中国公司受调查情况失败", error);
} finally {
box3Loading.value = false;
chartData.title = [];
chartData.list = [];
}
onAIReport({ type: "折线图", name: "数量变化趋势", data: chartData }, "content1")
nextTick(() => { createLineChart(chart1, chartData) });
box1Loading.value = false;
};
const handleClickBox3Btn = name => {
btnActiveName.value = name;
if (name === "调查次数") {
handleGetStatcnOrgCount("01");
const chart2 = ref(null);
const box2Paarams = reactive({
sortCode: 337,
years: '2025'
})
const box2Loading = ref(false);
const handleGetStatArea = async () => {
box2Loading.value = true;
let chartData = []
try {
const res = await getStatArea(box2Paarams);
console.log('领域分布情况', res);
if (res.code === 200 && res.data) {
chartData = res.data.map(item => ({ name: item.areaname, value: item.areacount }));
} else {
handleGetStatcnOrgCount("02");
chartData = [];
}
} catch (error) {
chartData = [];
}
onAIReport({ type: "环形图", name: "领域分布情况", data: chartData }, "content2")
nextTick(() => { createPieChart(chart2, chartData) });
box2Loading.value = false;
};
const chart1Data = ref({
title: [],
data: []
});
const handleGetStatNum = async () => {
box1Loading.value = true;
const mapData = ref([]);
const box3Paarams = reactive({
sortCode: 337,
years: '2025',
type: '01'
})
const box3Loading = ref(false);
const handleGetStatcnOrgCount = async () => {
box3Loading.value = true;
if (box3Paarams.type === "01") {
let chartData = { name: [], value: [] }
try {
const res = await getStatNum({
byYorM: "12",
sortCode: "337"
});
const res = await getStatcnOrgCount(box3Paarams);
console.log('中国实体分布情况', res);
if (res.code === 200 && res.data) {
const sortedData = res.data.sort((a, b) => parseInt(a.searchYorM) - parseInt(b.searchYorM));
chart1Data.value.title = sortedData.map(item => item.searchYorM);
chart1Data.value.data = [
{
name: "调查数量",
value: sortedData.map(item => item.searchCount)
}
];
nextTick(() => {
let chart1 = getMultiLineChart(chart1Data.value.title, chart1Data.value.data[0].value);
setChart(chart1, "chart1");
});
chartData.name = res.data.map(item => item.ORGNAME)
chartData.value = res.data.map(item => item.ORGCOUNT)
} else {
chartData.name = [];
chartData.value = [];
}
} catch (error) {
console.error("获取年度趋势数据失败", error);
} finally {
box1Loading.value = false;
chartData.name = [];
chartData.value = [];
}
};
const chart2Data = ref([]);
const handleGetStatArea = async () => {
box2Loading.value = true;
try {
const res = await getStatArea({
sortCode: "337"
onAIReport({ type: "柱状图", name: "中国实体分布情况", data: chartData }, "content3")
nextTick(() => {
let chart3 = getBarChart(chartData.name, chartData.value);
setChart(chart3, "chart3");
});
} else if (box3Paarams.type === "02") {
try {
const res = await getStatcnOrgCount(box3Paarams);
console.log('中国实体分布情况', res);
if (res.code === 200 && res.data) {
console.log('调查案件领域分布', res);
chart2Data.value = res.data
.filter(item => item.sortcode === "337" || item.sortname === "337调查")
.map(item => ({
name: item.areaname,
value: item.areacount
}));
nextTick(() => {
let chart2 = getPieChart(chart2Data.value);
setChart(chart2, "chart2");
mapData.value = res.data.map(item => {
return {
name: item.ORGPROVINCE,
value: item.PROVINCECOUNT,
coord: provinceCoords[item.ORGPROVINCE] || [0, 0]
};
});
} else {
mapData.value = [];
}
} catch (error) {
console.error("获取调查案件领域分布失败", error);
} finally {
box2Loading.value = false;
mapData.value = [];
}
onAIReport({ type: "地图", name: "中国实体分布情况", data: mapData.value }, "content3")
nextTick(() => {
let chartMap = getMapChart(mapData.value);
setChart(chartMap, "chartMap");
});
}
box3Loading.value = false;
};
const chart3Data = ref({
name: [],
value: []
});
const mapData = ref([]);
const chart4Data = ref([]);
const chart4 = ref(null)
const box4Paarams = reactive({
sortCode: 337,
years: '2025'
})
const box4Loading = ref(false);
const handleGetSearchResult = async () => {
box4Loading.value = true;
let chartData = []
try {
const res = await getSearchResult({
sortCode: "337"
});
const res = await getSearchResult(box4Paarams);
console.log('调查结果分布', res);
if (res.code === 200 && res.data) {
chart4Data.value = res.data.map(item => ({
name: item.RESULTNAME,
value: item.RESULTNUM
}));
nextTick(() => {
let chart4 = getPieChart(chart4Data.value);
setChart(chart4, "chart4");
});
chartData = res.data.map(item => ({ name: item.RESULTNAME, value: item.RESULTNUM }));
} else {
chartData = [];
}
} catch (error) {
console.error("获取调查结果分布失败", error);
} finally {
box4Loading.value = false;
chartData = [];
}
onAIReport({ type: "环形图", name: "调查结果分布", data: chartData }, "content4")
nextTick(() => { createPieChart(chart4, chartData) });
box4Loading.value = false;
};
onMounted(() => {
let graph = getGraphChart(nodes.value, links.value)
setChart(graph, 'graphChart')
handleGetStat()
handleGetStatNum();
handleGetStatArea();
handleGetStatcnOrgCount(btnActiveName.value === "调查次数" ? "01" : "02");
handleGetStatcnOrgCount();
handleGetSearchResult();
});
</script>
......@@ -488,18 +357,20 @@ onMounted(() => {
flex-direction: column;
gap: 16px;
.top {
.content-top {
display: flex;
justify-content: space-between;
gap: 16px;
.item {
width: 388px;
width: 20px;
flex: auto;
height: 80px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
&::before {
......@@ -514,7 +385,6 @@ onMounted(() => {
.item-left {
margin-left: 30px;
margin-top: 25px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
......@@ -524,7 +394,6 @@ onMounted(() => {
}
.item-right {
margin-top: 28px;
margin-right: 35px;
height: 24px;
color: var(--color-main-active);
......@@ -536,218 +405,120 @@ onMounted(() => {
.item-left-box {
margin-left: 30px;
font-size: 16px;
line-height: 24px;
.item-left1 {
margin-top: 13px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
.item-left2 {
height: 2px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
}
.box-header {
height: 56px;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
line-height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
.content-list {
height: 410px;
display: flex;
justify-content: flex-end;
gap: 4px;
.icon {
width: 28px;
height: 28px;
gap: 16px;
img {
.content-item {
width: 20px;
flex: auto;
height: 100%;
.box-main {
width: 100%;
height: 100%;
padding: 12px 30px 20px;
display: flex;
flex-direction: column;
position: relative;
.box-head {
height: 20px;
flex: auto;
}
.ai-pane {
position: absolute;
right: 0px;
bottom: 15px;
z-index: 2;
:deep(.ai-pane-wrapper) {
display: none;
}
}
}
.center {
:deep(.ai-button-wrapper) {
display: flex;
justify-content: space-between;
.box1 {
width: 792px;
height: 360px;
.box1-main {
height: 263px;
}
.box1-footer {
height: 40px;
&:hover {
width: 100%;
bottom: 0px;
:deep(.ai-pane-wrapper) {
display: block;
}
:deep(.ai-button-wrapper) {
display: none;
}
}
.box2 {
width: 792px;
height: 360px;
.box2-main {
width: 792px;
height: 263px;
box-sizing: border-box;
padding: 0 24px;
}
.box2-footer {
height: 40px;
}
}
}
.footer {
display: flex;
justify-content: space-between;
.box3 {
width: 792px;
height: 360px;
.header-btn-box {
display: flex;
.btn {
margin-left: 8px;
height: 28px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
text-align: center;
line-height: 28px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
cursor: pointer;
}
.btnActive {
border: 1px solid var(--color-main-active);
background: rgba(246, 250, 255, 1);
color: var(--color-main-active);
}
}
.box3-main {
height: 263px;
gap: 12px;
margin-right: 12px;
}
.box3-main1 {
height: 263px;
.box-head2 {
height: 100%;
width: 100%;
padding-top: 10px;
display: flex;
.box3-main1-left {
padding-top: 24px;
width: 380px;
height: 263px;
// overflow-y: auto;
overflow-x: hidden;
.map-box-left {
width: 20px;
flex: auto;
height: 100%;
.box3-main1-left-item {
.map-box-left-item {
display: flex;
align-items: center;
height: 36px;
margin-left: 20px;
.box3-main1-left-item-left {
.map-box-left-item-left {
width: 24px;
height: 24px;
border-radius: 12px;
margin-left: 12px;
margin-top: 6px;
background: #e7f3ff;
margin-right: 12px;
color: #0a57a6;
text-align: center;
line-height: 24px;
}
.box3-main1-left-item-center {
.map-box-left-item-center {
height: 36px;
line-height: 36px;
width: 220px;
margin-left: 12px;
width: 20px;
flex: auto;
}
.box3-main1-left-item-right {
width: 80px;
box-sizing: border-box;
.map-box-left-item-right {
min-width: 80px;
padding-right: 10px;
text-align: right;
}
}
}
.box3-main1-right {
width: 390px;
height: 263px;
box-sizing: border-box;
padding-top: 24px;
padding-right: 24px;
}
.box3-footer {
height: 40px;
}
}
}
.box4 {
width: 792px;
height: 360px;
.box4-main {
height: 263px;
box-sizing: border-box;
padding: 0 24px;
}
.box4-footer {
height: 40px;
}
.map-box-right {
width: 450px;
height: 100%;
}
}
}
......
......@@ -43,7 +43,7 @@ const getBarChart = (nameList, valueList) => {
data: valueList,
label: {
show: true,
position: [480, 0],
position: [434, 0],
formatter: function (params) {
return params.value + ' 次'
},
......
<template>
<div class="page-box">
<div class="page-top">
<div class="head-box">
<div class="head-icon">
<img :src="codeInfo.sortImageUrl || Img337" alt="" />
</div>
<div class="head-info">
<div class="head-name one-line-ellipsis">{{ baseInfo.SEARCHNAME }}</div>
<div class="head-text">{{ baseInfo.SEARCHDATE }}</div>
</div>
<div :class="`item-tag tag-${codeInfo.sortCode}`">{{ codeInfo.sortName }}</div>
</div>
</div>
<div class="main">
<div class="main-header">
<div>市场准入限制报告原文</div>
<div class="btn-box">
<div class="translate">
<div class="search-input-wrap" v-if="showSearchInput">
<input v-model="searchKeywordText" class="search-input" placeholder="回车查询"
@keyup.enter="handleSearchInPdf" />
<div class="search-match-count">{{ matchInfo.current }}/{{ matchInfo.total }}</div>
<button class="search-nav-btn" type="button" @click="handlePrevMatch"
:disabled="matchInfo.total === 0 || matchInfo.current <= 1">
上一个
</button>
<button class="search-nav-btn" type="button" @click="handleNextMatch"
:disabled="matchInfo.total === 0 || matchInfo.current >= matchInfo.total">
下一个
</button>
</div>
<div class="switch">
<el-switch v-model="valueSwitch" />
</div>
<div class="translate-image">
<img class="translate-icon" src="@/views/thinkTank/ReportDetail/images/image-translate.png" alt=""
style="width: 16px; height: 16px; max-width: 16px; max-height: 16px; display: block; object-fit: contain;" />
</div>
<div class="translate-text">{{ "显示原文" }}</div>
</div>
<div class="btn" @click="handleDownload">
<div class="icon">
<img src="@/views/thinkTank/ReportDetail/images/image-pdf.png" alt="" />
</div>
<div class="text">{{ "下载" }}</div>
</div>
</div>
</div>
<div class="report-box">
<div class="pdf-pane-wrap" v-if="valueSwitch && reportUrlEnWithPage">
<pdf ref="leftPdfRef" :pdfUrl="reportUrlEnWithPage" class="pdf-pane-inner" />
</div>
<div class="pdf-pane-wrap" :class="{ 'is-full': !valueSwitch }" v-if="reportUrlWithPage">
<pdf :key="`right-pdf-${valueSwitch ? 'split' : 'full'}`" ref="rightPdfRef" :pdfUrl="reportUrlWithPage"
class="pdf-pane-inner" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { computed, ref, onMounted, watch, reactive } from "vue";
import pdf from "@/views/thinkTank/reportOriginal/pdf.vue";
import { getSurvyInfo, getOriginalUrl } from "@/api/marketAccessRestrictions/index.js";
import { useRoute } from "vue-router";
const route = useRoute();
const reportUrl = ref('')
const reportUrlEn = ref('')
const thinkInfo = ref({})
const defaultPdfPage = ref(1)
const buildPdfPageUrl = url => {
if (!url) return ''
return `${url}#page=${defaultPdfPage.value}`
}
const reportUrlWithPage = computed(() => buildPdfPageUrl(reportUrl.value))
const reportUrlEnWithPage = computed(() => buildPdfPageUrl(reportUrlEn.value))
const valueSwitch = ref(true)
const showSearchInput = ref(true)
const searchKeywordText = ref('')
const leftPdfRef = ref(null)
const rightPdfRef = ref(null)
const matchInfo = ref({ current: 0, total: 0 })
const activePdfRef = ref(null)
const clearPdfSearchState = () => {
activePdfRef.value = null
matchInfo.value = { current: 0, total: 0 }
const leftPdf = leftPdfRef.value
const rightPdf = rightPdfRef.value
if (leftPdf && typeof leftPdf.clearSearch === 'function') {
leftPdf.clearSearch()
}
if (rightPdf && typeof rightPdf.clearSearch === 'function') {
rightPdf.clearSearch()
}
}
const updateMatchInfo = () => {
const pdf = activePdfRef.value
if (pdf && typeof pdf.getMatchInfo === 'function') {
matchInfo.value = pdf.getMatchInfo()
return
}
matchInfo.value = { current: 0, total: 0 }
}
watch(
() => searchKeywordText.value,
(val) => {
const keyword = String(val ?? '').trim()
if (!keyword) {
clearPdfSearchState()
}
}
)
watch(
() => valueSwitch.value,
() => {
// 切换「显示原文」会导致 PDF 重新挂载/布局变化:清空搜索与计数,回到初始状态
searchKeywordText.value = ''
clearPdfSearchState()
}
)
const handleSearchInPdf = async () => {
const keyword = searchKeywordText.value?.trim()
if (!keyword) return
activePdfRef.value = null
matchInfo.value = { current: 0, total: 0 }
const leftPdf = leftPdfRef.value
const rightPdf = rightPdfRef.value
let page = 0
let targetRef = null
if (leftPdf && typeof leftPdf.searchKeyword === 'function') {
page = await leftPdf.searchKeyword(keyword)
if (page) targetRef = leftPdf
}
if (!page && rightPdf && typeof rightPdf.searchKeyword === 'function') {
page = await rightPdf.searchKeyword(keyword)
if (page) targetRef = rightPdf
}
if (page && targetRef && typeof targetRef.goToPage === 'function') {
targetRef.goToPage(page)
activePdfRef.value = targetRef
updateMatchInfo()
} else {
try {
const { ElMessage } = await import('element-plus')
ElMessage.warning('未找到包含该关键词的页面')
} catch (_) { }
}
}
const handlePrevMatch = () => {
const pdf = activePdfRef.value
if (!pdf || typeof pdf.prevMatch !== 'function') return
pdf.prevMatch()
updateMatchInfo()
}
const handleNextMatch = () => {
const pdf = activePdfRef.value
if (!pdf || typeof pdf.nextMatch !== 'function') return
pdf.nextMatch()
updateMatchInfo()
}
// 下载:中英文都下载,与政令原文页相同的 fetch → blob → a 标签触发下载
const downloadOnePdf = async (url, filename) => {
const response = await fetch(url, {
method: 'GET',
headers: { 'Content-Type': 'application/pdf' },
})
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
const blob = await response.blob()
const blobUrl = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = blobUrl
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(blobUrl)
}
const handleDownload = async () => {
const urlZh = reportUrl.value ? String(reportUrl.value).split('#')[0] : ''
const urlEn = reportUrlEn.value ? String(reportUrlEn.value).split('#')[0] : ''
if (!urlZh && !urlEn) {
try {
const { ElMessage } = await import('element-plus')
ElMessage.warning('暂无下载链接')
} catch (_) { }
return
}
const baseName = (thinkInfo.value?.name || '报告原文').replace(/[/\\?%*:|"<>]/g, '-')
const { ElMessage } = await import('element-plus')
try {
if (urlZh) {
await downloadOnePdf(urlZh, `${baseName}_中文.pdf`)
}
if (urlEn) {
if (urlZh) await new Promise(r => setTimeout(r, 300))
await downloadOnePdf(urlEn, `${baseName}_英文.pdf`)
}
if (urlZh || urlEn) {
ElMessage.success(urlZh && urlEn ? '已下载中文、英文两份 PDF' : '下载成功')
}
} catch (error) {
console.error('下载失败:', error)
ElMessage.error('PDF 下载失败,请稍后重试')
}
}
// 获取调查信息
const codeInfo = reactive({
sortCode: route.query.id,
sortName: "",
sortImageUrl: "",
})
const onSurvyInfo = async () => {
try {
const res = await getSurvyInfo({sortCode: route.query.id});
console.log("获取调查信息", res);
if (res.code == 200) {
Object.assign(codeInfo, res.data)
}
} catch (error) {
console.error("获取调查信息error", error);
}
};
//获取原文
const baseInfo = reactive({
SEARCHNAME: "调查详情",
SEARCHDATE: "",
})
const onOriginalUrl = async () => {
try {
const res = await getOriginalUrl({id: route.query.searchId});
console.log("获取原文", res);
if (res.code === 200 && res.data) {
reportUrl.value = res.data.urlZh
reportUrlEn.value = res.data.url
baseInfo.SEARCHNAME = res.data.investTitleZh
baseInfo.SEARCHDATE = res.data.investDate
}
document.title = baseInfo.SEARCHNAME;
} catch (error) {
console.error("获取原文error", error);
}
};
onMounted(() => {
onSurvyInfo()
onOriginalUrl()
});
</script>
<style lang="scss" scoped>
.page-box {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: #f7f8f9;
.page-top {
width: 100%;
box-sizing: border-box;
background-color: white;
.head-box {
width: 1600px;
display: flex;
margin: 0 auto;
padding: 30px 0 20px;
.head-icon {
width: 54px;
height: 54px;
font-size: 0px;
margin-right: 16px;
img {
width: 100%;
height: 100%;
}
}
.head-info {
width: 20px;
flex: auto;
.head-name {
width: 100%;
height: 26px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: bold;
line-height: 26px;
}
.head-text {
margin-top: 4px;
width: 100%;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
.item-tag {
height: 24px;
line-height: 24px;
padding: 0 8px;
border-radius: 4px;
font-weight: bold;
font-size: 16px;
letter-spacing: 2px;
}
.tag-337 {
border: 1px solid #91caff;
background: #e6f4ff;
color: #055fc2;
}
.tag-232 {
border: 1px solid #b37feb;
background: #f9f0ff;
color: #722ed1;
}
.tag-301 {
border: 1px solid #ffd591;
background: #fff7e6;
color: #fa8c16;
}
.head-button {
margin-left: 100px;
width: 120px;
height: 36px;
border-radius: 6px;
background: var(--color-main-active);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.button-icon {
width: 16px;
height: 16px;
font-size: 0;
img {
width: 100%;
height: 100%;
}
}
.button-text {
margin-left: 8px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
}
}
}
}
.main {
margin: 0 auto;
background: rgb(255, 255, 255);
width: 1600px;
height: 20px;
flex: auto;
border: 1px, solid, rgb(234, 236, 238);
box-shadow: 0 0 20px 0 rgba(25, 69, 130, 0.1);
display: flex;
flex-direction: column;
.main-header {
height: 64px;
border-bottom: 1px solid rgb(234, 236, 238);
background: rgb(255, 255, 255);
margin: 0 70px;
color: rgba(59, 65, 75, 1);
font-family: "Source Han Sans CN";
font-style: Bold;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
width: 1456px;
text-align: left;
display: flex;
justify-content: space-between;
align-items: center;
overflow: visible;
.btn-box {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
.translate {
display: flex;
flex-wrap: nowrap;
align-items: center;
height: 24px;
margin-right: 16px;
flex-shrink: 0;
:deep(.el-switch) {
width: 22px !important;
height: 14px !important;
margin-bottom: 5px;
margin-right: 8px;
}
:deep(.el-switch__core) {
width: 22px !important;
height: 14px !important;
min-width: 22px !important;
}
:deep(.el-switch__button),
:deep(.el-switch__action) {
width: 10px !important;
height: 10px !important;
}
/* 打开时圆球从左边移到最右边:轨道 22px - 圆球 10px = 12px */
:deep(.el-switch.is-checked .el-switch__button),
:deep(.el-switch.is-checked .el-switch__action) {
transform: translateX(6px) !important;
}
.translate-image {
display: flex;
width: 16px;
height: 16px;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
.translate-text {
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
.btn {
width: 88px;
height: 32px;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 6px;
background: rgba(255, 255, 255, 1);
display: flex;
gap: 8px;
cursor: pointer;
;
.icon {
width: 16px;
height: 16px;
display: inline-flex;
margin-top: 8px;
margin-left: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-top: 4px;
width: 32px;
height: 24px;
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
.search-btn {
cursor: pointer;
}
.search-input-wrap {
display: inline-flex;
align-items: center;
gap: 8px;
margin-left: 4px;
flex-shrink: 0;
}
.search-input {
width: 160px;
height: 24px;
border: 1px solid rgba(231, 243, 255, 1);
background: rgba(246, 250, 255, 1);
border-radius: 4px;
padding: 0 10px;
font-family: "Source Han Sans CN";
font-size: 14px;
line-height: 22px;
outline: none;
}
.search-match-count {
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 14px;
line-height: 22px;
min-width: 48px;
text-align: center;
flex-shrink: 0;
}
.search-nav-btn {
width: 68px;
height: 24px;
border: 1px solid rgba(231, 243, 255, 1);
background: rgba(246, 250, 255, 1);
border-radius: 4px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 14px;
line-height: 22px;
cursor: pointer;
padding: 0;
flex-shrink: 0;
white-space: nowrap;
}
.search-nav-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
}
}
.report-box {
height: 20px;
flex: auto;
margin-left: 70px;
width: 1456px;
display: flex;
overflow-y: auto;
/* 右侧统一滚动条,控制两侧原文+译文一起滚动 */
overflow-x: hidden;
}
.pdf-pane-wrap {
flex: 0 0 50%;
max-width: 50%;
height: 100%;
min-width: 0;
}
.pdf-pane-wrap.is-full {
flex: 0 0 100%;
max-width: 100%;
}
.pdf-pane-inner {
width: 100%;
height: 100%;
}
}
}
</style>
\ No newline at end of file
......@@ -2,10 +2,8 @@
<div class="wrapper">
<AnalysisBox title="行业背景" :showAllBtn="false" height="auto">
<div class="box1-main">
<div class="data-list">
<div class="data-item" v-for="(bg, index) in bgList" :key="index">{{ bg.title }}</div>
</div>
<AiTips tips="钒作为被美国列为对经济与国家安全至关重要且供应链易受中断的关键矿产,广泛应用于国防、关键基础设施等多个领域,美国钒产业以二次生产为主、产业高度集中且依赖进口原料,同时通过征收反倾销税、加征关税及签订国际合作协议等方式管控钒进口。"></AiTips>
<Level2List :list="bgList"></Level2List>
<!-- <AiTips tips="钒作为被美国列为对经济与国家安全至关重要且供应链易受中断的关键矿产,广泛应用于国防、关键基础设施等多个领域,美国钒产业以二次生产为主、产业高度集中且依赖进口原料,同时通过征收反倾销税、加征关税及签订国际合作协议等方式管控钒进口。"></AiTips> -->
</div>
</AnalysisBox>
<SurveyConclusion title="市场需求" :listData="demandList" tips="美国钒市场年均表观消费量约 8590 吨且进口依赖度超 80%,需求主要集中在钢铁产业(占 90%),钛产业和非冶金领域各占 5%,2020 年受新冠疫情冲击需求下滑;全球钒需求以钢铁产业为主(90%-93%),中国是最大消费国,航空航天领域需求稳定、钒液流电池为新兴增长点,化工领域提供稳定补充。"></SurveyConclusion>
......@@ -17,7 +15,7 @@
<div class="data-item" v-for="(item, index) in suggestionList" :key="index">
<div class="item-head">
<div class="item-name">{{ item.title }}</div>
<div class="button-box">
<div class="button-box" @click="onNavigateTo()">
<div class="button-icon">
<img src="@/views/marketAccessRestrictions/assets/icons/open.png" alt="" />
</div>
......@@ -26,13 +24,12 @@
</div>
<div class="item-down">
<div class="content-item" v-for="(val, idx) in item.data" :key="idx">
<div class="content-item-title">{{val.title }}</div>
<div class="content-item-title">{{val.TITLE }}</div>
<div class="content-item-info">
<div class="desc">{{ val.info.desc }}</div>
<div class="info-bill" v-if="val.info.bill">
<div class="info-bill-box">
<Level2List :list="val.CONTENT"></Level2List>
<div class="info-bill-box" v-if="val.BILLNAME && val.BILLID" @click="onNavigateToBill(val)">
<div class="info-bill-left">{{ "法案" }}</div>
<div class="info-bill-center">{{ val.info.bill }}</div>
<div class="info-bill-center">{{ val.BILLNAME }}</div>
<div class="info-bill-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="" />
</div>
......@@ -42,8 +39,7 @@
</div>
</div>
</div>
</div>
<AiTips tips="美国为降低盟友及自身在钕铁硼磁体价值链对中国的依赖、保障国家安全,一方面推动关键矿物多边及双边合作,另一方面通过支持相关税收抵免法案、分配额外资金等立法与政策举措,强化稀土及非稀土磁体相关国内供应链建设。"></AiTips>
<!-- <AiTips tips="美国为降低盟友及自身在钕铁硼磁体价值链对中国的依赖、保障国家安全,一方面推动关键矿物多边及双边合作,另一方面通过支持相关税收抵免法案、分配额外资金等立法与政策举措,强化稀土及非稀土磁体相关国内供应链建设。"></AiTips> -->
</div>
</AnalysisBox>
</div>
......@@ -52,9 +48,11 @@
<script setup>
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import router from "@/router";
import { getReportAnalyze } from "@/api/marketAccessRestrictions/index.js";
import SurveyConclusion from "@/views/marketAccessRestrictions/com/SurveyConclusion.vue";
import AiTips from "@/views/marketAccessRestrictions/com/AiTips.vue";
import Level2List from "@/views/marketAccessRestrictions/com/Level2List.vue";
const route = useRoute();
const searchId = route.query.searchId || "232";
......@@ -65,14 +63,19 @@ const supplyList = ref([]);
const box4List = ref([]);
const suggestionList = ref([]);
const onNavigateTo = () => {
const page = router.resolve({
name: "MarketSingleReportOriginal",
query: { ...route.query }
});
window.open(page.href, "_blank");
}
const getData = async () => {
// 行业背景
getReportAnalyze({ searchId, type: "01" }).then((res) => {
if (res.data) {
bgList.value = res.data.map((item) => ({
title: item.CONTENT
}));
}
console.log('行业背景', res)
if (res.data) bgList.value = res.data.flatMap(item => item.CONTENT);
});
// 市场需求
......@@ -81,7 +84,7 @@ const getData = async () => {
demandList.value = res.data.map(item => {
return {
title: item.TITLE,
data: item.CONTENT?.split(/\r\n|\n/).filter(line => line.trim()) || [],
data: item.CONTENT || [],
};
});
}
......@@ -93,7 +96,7 @@ const getData = async () => {
supplyList.value = res.data.map(item => {
return {
title: item.TITLE,
data: item.CONTENT?.split(/\r\n|\n/).filter(line => line.trim()) || [],
data: item.CONTENT || [],
};
});
}
......@@ -105,7 +108,7 @@ const getData = async () => {
box4List.value = res.data.map(item => {
return {
title: item.TITLE,
data: item.CONTENT?.split(/\r\n|\n/).filter(line => line.trim()) || [],
data: item.CONTENT || [],
};
});
}
......@@ -113,27 +116,20 @@ const getData = async () => {
// 调查建议
getReportAnalyze({ searchId, type: "05" }).then((res) => {
if (res.data) {
console.log('调查建议', res)
if (res.code==200 && res.data) {
suggestionList.value = groupSuggestionData(res.data);
}
});
};
const groupData = (data) => {
const groups = {};
data.forEach((item) => {
const title = item.TITLE || "其他";
if (!groups[title]) {
groups[title] = [];
}
groups[title].push({
title: item.CONTENT
});
// 跳转科技法案详情页
const onNavigateToBill = (item) => {
window.sessionStorage.setItem("curTabName", item.BILLNAME);
const route = router.resolve({
path: "/billLayout",
query: { billId: item.BILLID }
});
return Object.keys(groups).map((title) => ({
title,
data: groups[title]
}));
window.open(route.href, "_blank");
};
const groupSuggestionData = (data) => {
......@@ -143,13 +139,7 @@ const groupSuggestionData = (data) => {
if (!groups[groupTitle]) {
groups[groupTitle] = [];
}
groups[groupTitle].push({
title: item.TITLE,
info: {
desc: item.CONTENT,
bill: item.BILLNAME
}
});
groups[groupTitle].push(item);
});
return Object.keys(groups).map((title) => ({
title,
......@@ -173,22 +163,6 @@ onMounted(() => {
.box1-main {
padding: 0 22px 20px;
.data-list {
margin-bottom: 16px;
border-top: 1px solid rgba(234, 236, 238, 1);
.data-item {
letter-spacing: 1px;
padding: 12px 20px 12px 40px;
color: rgba(59, 65, 75, 1);
font-family: Source Han Sans CN;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
border-bottom: 1px solid rgba(234, 236, 238, 1);
}
}
}
.box5-main {
......@@ -217,6 +191,7 @@ onMounted(() => {
display: flex;
align-items: center;
margin-left: 50px;
cursor: pointer;
.button-icon {
width: 16px;
height: 16px;
......@@ -240,7 +215,7 @@ onMounted(() => {
margin-bottom: 10px;
.content-item {
.content-item-title {
padding: 12px 23px 12px 44px;
padding: 12px 23px 12px 30px;
box-sizing: border-box;
border-bottom: 1px solid rgba(234, 236, 238, 1);
color: rgba(59, 65, 75, 1);
......@@ -252,26 +227,17 @@ onMounted(() => {
text-align: justify;
}
.content-item-info {
padding: 12px 23px 12px 44px;
box-sizing: border-box;
border-bottom: 1px solid rgba(234, 236, 238, 1);
.desc {
color: rgba(59, 65, 75, 1);
font-family: Source Han Sans CN;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
padding-left: 15px;
:deep(.item-info) {
padding-left: 70px;
.item-num {
left: 46px;
}
}
.info-bill {
border-radius: 4px;
display: inline-block;
background: rgba(246, 250, 255, 1);
.info-bill-box {
display: flex;
margin-top: 4px;
cursor: pointer;
margin: 10px 44px;
display: inline-flex;
height: 32px;
border-radius: 4px;
background: rgba(246, 250, 255, 1);
......@@ -304,7 +270,6 @@ onMounted(() => {
.info-bill-right {
width: 20px;
height: 20px;
cursor: pointer;
img {
width: 100%;
height: 100%;
......@@ -316,6 +281,5 @@ onMounted(() => {
}
}
}
}
}
</style>
\ No newline at end of file
......@@ -14,18 +14,11 @@
</div>
<div class="box-content">
<div class="filter-row">
<el-select v-model="selectedArea" placeholder="全部领域" class="area-select" clearable @change="fetchEnterpriseList">
<el-select v-model="selectedArea" placeholder="全部领域" class="area-select" clearable @change="onEnterpriseList()">
<el-option label="全部领域" value="" />
<el-option v-for="item in areaOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-input
v-model="searchText"
placeholder="搜索实体"
class="search-input"
:suffix-icon="Search"
clearable
@input="handleSearchInput"
/>
<el-input v-model="searchText" placeholder="搜索实体" class="search-input" :suffix-icon="Search" clearable @keyup.enter="onEnterpriseList()" />
</div>
<div class="enterprise-list" v-loading="listLoading">
<div class="list-label">企业名称</div>
......@@ -179,7 +172,7 @@ const businessLabels = ref([]);
const businessData = ref([]);
// 获取企业列表
const fetchEnterpriseList = async () => {
const onEnterpriseList = async () => {
listLoading.value = true;
try {
const params = {
......@@ -278,14 +271,6 @@ const updateBusinessChart = () => {
}
};
let searchTimer = null;
const handleSearchInput = () => {
if (searchTimer) clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
fetchEnterpriseList();
}, 500);
};
const handleEnterpriseClick = (item, index) => {
activeIndex.value = index;
if (item.ORGID) {
......@@ -432,7 +417,7 @@ const handleBusinessToggle = (type) => {
};
onMounted(() => {
fetchEnterpriseList();
onEnterpriseList();
nextTick(() => {
initCharts();
window.addEventListener('resize', () => {
......@@ -571,6 +556,9 @@ onMounted(() => {
.search-input {
flex: auto;
width: 20px;
background-color: #fff;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 4px;
}
}
......
......@@ -12,15 +12,19 @@
<div :class="`item-tag tag-${codeInfo.sortCode}`">{{ codeInfo.sortName }}</div>
</div>
<div class="page-tabs">
<div :class="['tab-item', {'tab-active': activeName==item.name}]" v-for="(item, index) in tabList" :key="index" @click="handleClickBtn(item)">
<div :class="['tab-item', {'tab-active': activeName==1}]" @click="handleClickBtn(1)">
<div class="icon">
<img :src="item.activeIcon" alt="" v-if="activeName==item.name" />
<img :src="item.icon" alt="" v-else />
<img :src="activeName==2 ? NavIcon1Active : NavIcon1" alt="" />
</div>
<div class="text" :class="{ textActive: activeName==item.name }">
{{ item.name }}
<div class="text" :class="{ textActive: activeName==1 }">调查概况</div>
</div>
<div :class="['tab-item', {'tab-active': activeName==2}]" @click="handleClickBtn(2)" v-if="codeInfo.sortCode==337 || codeInfo.sortCode==232">
<div class="icon">
<img :src="activeName==2 ? NavIcon3Active : NavIcon3" alt="" />
</div>
<div class="text" :class="{ textActive: activeName==2 }">{{ codeInfo.sortCode==337 ? '影响分析' : '报告解析' }}</div>
</div>
<div style="width:20px; flex:auto;"></div>
<div class="head-button">
<div class="button-icon">
......@@ -48,26 +52,12 @@ import { getSurvyInfo, getSearchBlurb } from "@/api/marketAccessRestrictions/ind
import { useRoute } from "vue-router";
const route = useRoute();
const tabList = ref([
{
name: "调查概况",
icon: NavIcon1,
activeIcon: NavIcon1Active,
path: "/marketSingleCaseLayout/overview"
},
{
name: "影响分析",
icon: NavIcon3,
activeIcon: NavIcon3Active,
path: "/marketSingleCaseLayout/deepdig"
}
]);
const activeName = ref("调查案件");
const handleClickBtn = item => {
activeName.value = item.name;
const activeName = ref(1);
const handleClickBtn = index => {
activeName.value = index;
router.push({
path: item.path,
path: ['', "/marketSingleCaseLayout/overview", "/marketSingleCaseLayout/deepdig"][index],
query: { ...route.query }
});
};
......@@ -102,10 +92,10 @@ const onSearchBlurb = async () => {
onMounted(() => {
onSurvyInfo()
onSearchBlurb()
if (route.path === "/marketSingleCaseLayout/deepdig") {
activeName.value = "影响分析";
if (route.path === "/marketSingleCaseLayout/overview") {
activeName.value = 1;
} else {
activeName.value = "调查概况";
activeName.value = 2;
}
});
</script>
......
......@@ -113,20 +113,16 @@ const tips = "美国 232 调查认定,钕铁硼永磁体(关键领域核心
const route = useRoute();
const loading = ref(false);
const box2Loading = ref(false);
const box3Loading = ref(false);
const baseInfo = ref({});
const reasonList = ref([]);
const timeLineList = ref([]);
const surveyResult = ref([]);
const handleGetSearchBlurb = async () => {
loading.value = true;
try {
const res = await getSearchBlurb({
searchId: route.query.searchId,
sortCode: "232"
});
const res = await getSearchBlurb({ searchId: route.query.searchId, sortCode: "232" });
console.log('获取基本信息', res)
if (res.code === 200 && res.data) {
const data = res.data;
baseInfo.value = data;
......@@ -139,42 +135,42 @@ const handleGetSearchBlurb = async () => {
}
}
} catch (error) {
console.error("获取调查详情失败", error);
} finally {
loading.value = false;
console.error("获取基本信息失败", error);
}
loading.value = false;
};
const handleGetSearchContext = async () => {
box2Loading.value = true;
try {
const res = await getSearchContext({
searchId: route.query.searchId
});
if (res.code === 200 && res.data) {
timeLineList.value = res.data.map(item => ({
time: item.CONTTIME,
content: item.CONTDESC
}));
}
} catch (error) {
console.error("获取事件脉络失败", error);
} finally {
box2Loading.value = false;
}
};
// const box2Loading = ref(false);
// const timeLineList = ref([]);
// const handleGetSearchContext = async () => {
// box2Loading.value = true;
// try {
// const res = await getSearchContext({
// searchId: route.query.searchId
// });
// if (res.code === 200 && res.data) {
// timeLineList.value = res.data.map(item => ({
// time: item.CONTTIME,
// content: item.CONTDESC
// }));
// }
// } catch (error) {
// console.error("获取事件脉络失败", error);
// } finally {
// box2Loading.value = false;
// }
// };
const handleGetSearchConclusion = async () => {
box3Loading.value = true;
try {
const res = await getSearchConclusion({
searchId: route.query.searchId
});
const res = await getSearchConclusion({ searchId: route.query.searchId });
console.log('获取调查结论', res)
if (res.code === 200) {
surveyResult.value = res.data.map(item => {
return {
title: item.TITLE,
data: item.CONTENT?.split(/\r\n|\n/).filter(line => line.trim()) || [],
data: item.CONTENT || [],
};
});
}
......@@ -189,9 +185,8 @@ const handleGetSearchConclusion = async () => {
const eventList = ref([])
const handleGetRelatedEvents = async () => {
try {
const res = await getRelatedEvents({
searchId: route.query.searchId
});
const res = await getRelatedEvents({ searchId: route.query.searchId });
console.log('获取相关行政举措', res)
if(res.code === 200) eventList.value = res.data || [];
} catch (error) {
console.error("获取相关行政举措失败", error);
......@@ -201,7 +196,7 @@ const handleGetRelatedEvents = async () => {
onMounted(() => {
handleGetSearchBlurb();
handleGetRelatedEvents();
handleGetSearchContext();
// handleGetSearchContext();
handleGetSearchConclusion();
});
</script>
......@@ -240,8 +235,6 @@ onMounted(() => {
font-size: 16px;
font-weight: 700;
line-height: 24px;
margin-top: 3px;
margin-bottom: 3px;
}
.box1-item-right {
width: 346px;
......
<template>
<div class="wrapper">
<div class="left">
<div class="page-left">
<AnalysisBox title="基本信息" :showAllBtn="false" height="auto">
<div class="box1-main">
<div class="box1-item">
......@@ -19,7 +19,7 @@
<div class="icon">
<img src="./assets/images/icon1.png" alt="" />
</div>
<div class="text">{{ (baseInfo.SEARCHORG || '-') + " >" }}</div>
<div class="text" style="cursor: pointer;" @click="onNavigateToORG()">{{ (baseInfo.SEARCHORG || '-') + " >" }}</div>
</div>
</div>
<div class="box1-item">
......@@ -46,7 +46,7 @@
<div class="box1-item-right3">
<template v-if="baseInfo.AdminstrativeData && baseInfo.AdminstrativeData.length">
<div v-for="(item, index) in baseInfo.AdminstrativeData" :key="index">
{{ item.ORDERNAME + " >" }}
<div style="cursor: pointer;" @click="onNavigateToDecree(item)">{{ item.ORDERNAME + " >" }}</div>
</div>
</template>
<template v-else>-</template>
......@@ -56,7 +56,7 @@
</AnalysisBox>
<SurveyAffiche title="调查公告" :listData="box2Data"></SurveyAffiche>
</div>
<div class="right">
<div class="page-right">
<AnalysisBox :showAllBtn="false" height="auto">
<template #custom-title>
<div class="btn-box">
......@@ -88,24 +88,7 @@
</div>
</div>
<div class="box3-main2" v-else>
<div class="box3-main2-item" v-for="(item, index) in box3Data2" :key="index">
<div class="box3-main2-item-header">
<div class="title">{{ item.title }}</div>
<div class="more">
<div class="icon">
<img src="./assets/images/open-active.png" alt="" />
</div>
<div class="text">
{{ "跳转原文" }}
</div>
</div>
</div>
<div class="box3-main2-item-content">
<div class="item" v-for="(val, idx) in item.data" :key="idx">
{{ idx+1 + "." + val.content }}
</div>
</div>
</div>
<Level1List :list="box3Data2"></Level1List>
</div>
</div>
</AnalysisBox>
......@@ -116,6 +99,7 @@
<script setup>
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import router from "@/router";
import {
getSearchBlurb,
getRelatedEvents,
......@@ -124,6 +108,7 @@ import {
} from "@/api/marketAccessRestrictions";
import RelatedEvent from "@/views/marketAccessRestrictions/com/RelatedEvent.vue";
import SurveyAffiche from "@/views/marketAccessRestrictions/com/SurveyAffiche.vue";
import Level1List from "@/views/marketAccessRestrictions/com/Level1List.vue";
const route = useRoute();
......@@ -137,6 +122,23 @@ const handleClickBox3Btn = btn => {
box3BtnActive.value = btn;
};
// 跳转到机构详情页
const onNavigateToORG = () => {
const page = router.resolve({
path: "/institution",
query: { id: baseInfo.value.SEARCHORGID }
});
window.open(page.href, "_blank");
}
// 跳转到政令详情页
const onNavigateToDecree = (item) => {
const page = router.resolve({
path: "/decreeLayout",
query: { id: item.ORDERID }
});
window.open(page.href, "_blank");
}
const box2Data = ref([]);
const handleGetSearchBlurb = async () => {
try {
......@@ -184,26 +186,23 @@ const box3Data1 = ref([]);
const handleGetSearchContext = async () => {
try {
const res = await getSearchContext({ searchId: route.query.searchId });
console.log('事件脉络', res.data)
console.log('事件脉络', res)
if(res.code === 200) box3Data1.value = res.data;
} catch (error) {
console.error("获取事件脉络失败", error);
}
} catch (error) {}
}
// 报复性措施
const box3Data2 = ref([]);
const handleGetSearchMeasures = async () => {
try {
const res = await getSearchMeasures({ searchId: route.query.searchId });
console.log('报复性措施', res)
if(res.code === 200 && res.data) {
box3Data2.value = res.data.map(item => ({
title: item.TITLE,
data: [{ content: item.CONTENT }]
data: item.CONTENT
}));
}
} catch (error) {
console.error("获取报复性措施失败", error);
}
} catch (error) {}
};
onMounted(() => {
handleGetSearchBlurb();
......@@ -218,13 +217,13 @@ onMounted(() => {
width: 1600px;
margin: 20px auto;
display: flex;
.left {
.page-left {
width: 520px;
display: flex;
flex-direction: column;
gap: 16px;
}
.right {
.page-right {
width: 20px;
flex: auto;
display: flex;
......@@ -247,8 +246,6 @@ onMounted(() => {
font-size: 16px;
font-weight: 700;
line-height: 24px;
margin-top: 3px;
margin-bottom: 3px;
}
.box1-item-right {
width: 346px;
......@@ -336,7 +333,7 @@ onMounted(() => {
padding: 15px 20px;
.box3-main1 {
.box3-main1-item {
margin-bottom: 20px;
margin-bottom: 30px;
display: flex;
.left {
width: 10px;
......@@ -358,7 +355,6 @@ onMounted(() => {
.right {
margin-left: 17px;
.header {
height: 24px;
display: flex;
gap: 16px;
color: var(--color-main-active);
......@@ -366,18 +362,15 @@ onMounted(() => {
font-style: Bold;
font-size: 18px;
font-weight: 700;
line-height: 24px;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
padding-bottom: 10px;
}
.content {
border-top: 1px solid #eaecee;
margin-top: 10px;
margin-bottom: 36px;
padding: 10px 0;
padding-top: 10px;
width: 971px;
max-height: 60px;
min-height: 0;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
......@@ -422,9 +415,11 @@ onMounted(() => {
justify-content: flex-end;
align-items: center;
gap: 7px;
cursor: pointer;
.icon {
width: 12px;
height: 12px;
font-size: 0;
img {
width: 100%;
height: 100%;
......@@ -432,13 +427,10 @@ onMounted(() => {
}
.text {
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 12px;
font-weight: 400;
line-height: 14px;
line-height: 12px;
letter-spacing: 0px;
text-align: justify;
}
}
}
......
......@@ -42,10 +42,10 @@
<AnalysisBox title="原告信息" :showAllBtn="false" height="auto">
<div class="box2-main">
<div class="data-head">
<div class="data-icon">{{ baseInfo.ORGNAME?.substring(0, 3) }}</div>
<div class="data-icon">{{ baseInfo.orgAbb }}</div>
<div class="data-right">
<div class="data-name">{{ baseInfo.ORGNAME }}</div>
<div class="data-desc">{{ "" }}</div>
<div class="data-name">{{ baseInfo.orgNameEn }}</div>
<div class="data-desc">{{ baseInfo.ORGNAME }}</div>
</div>
</div>
<div class="data-text">{{ baseInfo.ORGBLURB || '-' }}</div>
......@@ -110,7 +110,7 @@ const handleGetSearchBlurb = async () => {
if (data.defendantOrg) {
const groups = {};
data.defendantOrg.forEach(item => {
const companyName = item.COMPANYNAME || "其他相关企业";
const companyName = item.COMPANYNAM || "其他相关企业";
if (!groups[companyName]) {
groups[companyName] = {
title: companyName + (companyName !== "其他" ? "及相关企业" : ""),
......@@ -118,7 +118,7 @@ const handleGetSearchBlurb = async () => {
};
}
groups[companyName].companyList.push({
name: item.COMNAME,
name: item.COMNAM,
logo: "" // API 不提供 logo
});
});
......@@ -276,8 +276,6 @@ onMounted(() => {
font-size: 16px;
font-weight: 700;
line-height: 24px;
margin-top: 3px;
margin-bottom: 3px;
}
.box1-item-right {
width: 346px;
......
import * as echarts from 'echarts'
import { MUTICHARTCOLORS } from '@/common/constant'
const getMultiLineChart = (data) => {
// 十六进制颜色转 rgba 的工具函数
function hexToRgba(hex, alpha) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
const getSerie = (data, index) => {
return {
name: data.name,
type: 'line',
smooth: true,
symbol: 'emptyCircle',
symbolSize: 6,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: hexToRgba(MUTICHARTCOLORS[index], 0.7)
}, {
offset: 1,
color: hexToRgba(MUTICHARTCOLORS[index], 0)
}])
},
emphasis: { focus: 'series' },
data: data.value
}
}
const createLineChart = (chartDom, data, option={}) => {
if (!chartDom.value) return;
// 先销毁之前的实例
const existingChart = echarts.getInstanceByDom(chartDom.value);
if (existingChart) existingChart.dispose();
// 获取容器宽度/高度
const containerWidth = chartDom.value.clientWidth;
const containerHeight = chartDom.value.clientHeight;
const myChart = echarts.init(chartDom.value);
myChart.setOption({
color: MUTICHARTCOLORS,
tooltip: {
trigger: 'item',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
label: { backgroundColor: '#6a7985' }
}
},
grid: {
width: '95%',
height: '83%',
top: '15%',
left: '1%',
top: 40,
bottom: 0,
right: 26,
left: 12,
containLabel: true
},
legend: {
show: true,
show: data?.list?.length>1,
top: 10,
icon: 'circle',
textStyle: {
......@@ -28,7 +68,6 @@ const getMultiLineChart = (data) => {
fontSize: '14px',
}
},
color: ['#0A57A6', '#FA8C16', '#722ED1'],
xAxis: [
{
type: 'category',
......@@ -54,14 +93,14 @@ const getMultiLineChart = (data) => {
{
type: 'value',
position: 'left',
name: '数量',
name: "项",
nameLocation: 'end',
nameGap: 12,
nameTextStyle: {
color: '#666',
fontSize: 14,
fontWeight: 400,
padding: [0, 0, 6, -20]
padding: [0, 0, 6, -26]
},
axisLabel: {
formatter: '{value}',
......@@ -78,69 +117,9 @@ const getMultiLineChart = (data) => {
},
}
],
series: [
{
name: data.data[0]?.name,
type: 'line',
smooth: true,
symbol: 'emptyCircle',
symbolSize: 6,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(10, 87, 166, 0.7)' // 起始颜色
}, {
offset: 1,
color: 'rgba(10, 87, 166, 0)' // 结束颜色
}])
},
emphasis: {
focus: 'series'
},
data: data.data[0]?.value
},
{
name: data.data[1]?.name,
type: 'line',
smooth: true,
symbol: 'emptyCircle',
symbolSize: 6,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(255, 172, 77, 0.7)' // 起始颜色
}, {
offset: 1,
color: 'rgba(255, 172, 77, 0)' // 结束颜色
}])
},
emphasis: {
focus: 'series'
},
data: data.data[1]?.value,
},
{
name: data.data[2]?.name,
type: 'line',
smooth: true,
symbol: 'emptyCircle',
symbolSize: 6,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(114, 46, 209, 0.7)' // 起始颜色
}, {
offset: 1,
color: 'rgba(114, 46, 209, 0)' // 结束颜色
}])
},
emphasis: {
focus: 'series'
},
data: data.data[2]?.value,
}
]
}
series: data.list.map(getSerie)
})
return myChart
}
export default getMultiLineChart
\ No newline at end of file
export default createLineChart
\ No newline at end of file
const getPieChart = (data) => {
const colorList = ['#69B1FF','#FF7875','#B37FEB','#FFC069','#1677FF','#87E8DE','#ADC6FF','#FFBB96','#BAE0FF','#FFD591',]
let option = {
color: colorList,
import * as echarts from 'echarts'
import { MUTICHARTCOLORS } from '@/common/constant'
const truncateLabel = (value, maxLen = 6) => {
if (value === null || value === undefined) return ''
const str = String(value)
const chars = Array.from(str)
if (chars.length <= maxLen) return str
return `${chars.slice(0, maxLen).join('')}...`
}
const formatLabel = (node, type) => {
if (type==1) {
const name = truncateLabel(node.name, 6)
return `{name|${name}}\n{time|${ node.percent||0}%}`
}
return `{name|${node.name}} {time| ${node.value}${ node.percent||0}%}\n`
}
const createPieChart = (chartDom, data=[], option={}) => {
if (!chartDom.value) return;
// 先销毁之前的实例
const existingChart = echarts.getInstanceByDom(chartDom.value);
if (existingChart) existingChart.dispose();
// 获取容器宽度
const containerWidth = chartDom.value.clientWidth;
const myChart = echarts.init(chartDom.value);
myChart.setOption({
color: MUTICHARTCOLORS,
tooltip: {
formatter: node => `${node.name}: ${node.value}项 (${node.percent || 0}%)`,
},
series: [
{
type: 'pie',
radius: [75, 100],
radius: [70, 100],
height: '100%',
left: 'center',
width: '100%',
left: 'center',
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
label: {
alignTo: 'edge',
formatter: '{name|{b}}\n{time|{c} 条 {d}%}',
formatter: (node) => formatLabel(node, option.labelType),
minMargin: 5,
edgeDistance: 10,
lineHeight: 25,
lineHeight: 22,
rich: {
name: {
color: 'rgba(59, 65, 75, 1)',
fontFamily: 'Microsoft YaHei',
fontSize: 16,
fontWeight: 'bold',
padding: [10, 0, 10, 0]
},
time: {
fontSize: 16,
color: 'rgba(95, 101, 108, 1)',
fontFamily: 'Microsoft YaHei',
color: '#rgba(95, 101, 108, 1)'
fontSize: 16,
padding: [10, 0, 10, 0]
}
}
},
labelLine: {
length: 15,
length2: 0,
maxSurfaceAngle: 80
maxSurfaceAngle: 88
},
labelLayout: function (params) {
const isLeft = params.labelRect.x < 556 / 2;
const isLeft = params.labelRect.x < containerWidth / 2;
const points = params.labelLinePoints;
// Update the end point.
points[2][0] = isLeft
? params.labelRect.x
: params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
};
// 调整引导线终点
if (isLeft) {
points[2][0] = params.labelRect.x; // 左对齐
} else {
points[2][0] = params.labelRect.x + params.labelRect.width; // 右对齐
}
return { labelLinePoints: points };
},
data: data
}]
}
return option
]
});
return myChart
}
export default getPieChart;
\ No newline at end of file
export default createPieChart;
\ No newline at end of file
/**
* 数字转中文(支持 0-99 整数)
* @param num 需要转化的数字
*/
export const onNumToChinese = (num:number) => {
// 1. 基础校验:只处理 0-99 的整数
if (!Number.isInteger(num) || num < 0 || num > 99) return '100';
// 2. 定义基础字符
const singleChars = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
const tenChar = '十';
// 3. 核心转换逻辑
if (num < 10) {
// 0-9 直接返回对应字符
return singleChars[num];
} else if (num === 10) {
// 10 特殊处理
return tenChar;
} else if (num < 20) {
// 11-19:十 + 个位(如十一、十九)
return tenChar + singleChars[num - 10];
} else {
// 20-99:十位 + 十 + 个位(个位为0则省略,如二十、二十九)
const ten = Math.floor(num / 10); // 十位数字
const unit = num % 10; // 个位数字
return singleChars[ten] + tenChar + (unit === 0 ? '' : singleChars[unit]);
}
}
/**
* 获取 n 天前的日期,格式:YYYY-MM-DD
* @param num 往前推的天数
*/
export const getDateBefore = (num: number) => {
const now = new Date();
const targetTime = now.getTime() - num * 24 * 60 * 60 * 1000;
const targetDate = new Date(targetTime);
const year = targetDate.getFullYear();
const month = String(targetDate.getMonth() + 1).padStart(2, '0');
const day = String(targetDate.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* 获取最近年份列表
* @param num 需要几年前的列表
*/
export const getNearYearList = (num=6) => {
const currentYear = new Date().getFullYear();
const yearOptions = [];
for (let i = 0; i < num; i++) {
const year = currentYear - i;
yearOptions.push({ label: year.toString()+'年', value: year.toString() });
}
return yearOptions;
};
/**
* AI智能总结
* @param data 需要分析的数据
*/
export const getAIReport = async (data:any) => {
let word = ""
// 👇 新增:超时 + 终止请求(只加这一段)
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10*1000); // 10秒超时
try {
const res = await fetch('/aiAnalysis/chart_interpretation', {
method: 'POST',
headers: {
"X-API-Key": "aircasKEY19491001",
'Content-Type': 'application/json',
},
body: JSON.stringify({text: JSON.stringify(data)}),
signal: controller.signal // 👇 新增:绑定中断信号
});
clearTimeout(timeout); // 👇 新增:请求成功清除定时器
if (!res.ok) throw new Error(`HTTP 错误 ${res.status}`);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let summarize = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const content = line.substring(6);
const textMatch = content.match(/"解读":\s*"([^"]*)"/);
if (textMatch && textMatch[1]) summarize = textMatch[1];
}
}
}
word = summarize
} catch (err) {
word = "系统异常,生成失败";
}
return word
}
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论