提交 50739242 authored 作者: 张烨's avatar 张烨

feat:市场准入限制-概览页图表增加ai智能总结

上级 5d43cdf1
...@@ -3,7 +3,10 @@ ...@@ -3,7 +3,10 @@
<div class="icon"> <div class="icon">
<img src="./tip-icon.svg" alt=""> <img src="./tip-icon.svg" alt="">
</div> </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> </div>
</template> </template>
...@@ -23,7 +26,10 @@ const props = defineProps({ ...@@ -23,7 +26,10 @@ const props = defineProps({
type: String, type: String,
default: '2023.1至2025.12' default: '2023.1至2025.12'
}, },
ellipsis: {
type: Boolean,
default: false
}
}) })
const tipText = computed(() => props.text || `数据来源:${props.dataSource},数据时间:${props.dataTime}`) const tipText = computed(() => props.text || `数据来源:${props.dataSource},数据时间:${props.dataTime}`)
...@@ -48,5 +54,9 @@ const tipText = computed(() => props.text || `数据来源:${props.dataSource} ...@@ -48,5 +54,9 @@ const tipText = computed(() => props.text || `数据来源:${props.dataSource}
height: 100%; height: 100%;
} }
} }
.text {
width: 20px;
flex: auto;
}
} }
</style> </style>
\ No newline at end of file
...@@ -152,7 +152,7 @@ ...@@ -152,7 +152,7 @@
</div> </div>
<div class="ai-pane"> <div class="ai-pane">
<AiButton /> <AiButton />
<AiPane :aiContent="summarize1" /> <AiPane :aiContent="aiContent.content1" />
</div> </div>
</div> </div>
...@@ -188,7 +188,7 @@ ...@@ -188,7 +188,7 @@
</div> </div>
<div class="ai-pane"> <div class="ai-pane">
<AiButton /> <AiButton />
<AiPane :aiContent="summarize2" /> <AiPane :aiContent="aiContent.content2" />
</div> </div>
</div> </div>
</div> </div>
...@@ -442,6 +442,7 @@ import setChart from "@/utils/setChart"; ...@@ -442,6 +442,7 @@ import setChart from "@/utils/setChart";
import DefaultIcon2 from "@/assets/icons/default-icon2.png"; import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import tipsTcon from "./assets/images/tips-icon.png"; import tipsTcon from "./assets/images/tips-icon.png";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { getAIReport, getNearYearList } from "@/views/marketAccessRestrictions/utils/index.ts"
import { useGotoNewsDetail } from '@/router/modules/news'; import { useGotoNewsDetail } from '@/router/modules/news';
...@@ -722,17 +723,16 @@ const handleClickPerson = async item => { ...@@ -722,17 +723,16 @@ const handleClickPerson = async item => {
} catch (error) { } } catch (error) { }
}; };
// 获取最近年份列表 const yearList = getNearYearList();
const currentYear = new Date().getFullYear();
const getYearList = (count = 6) => { // 获取AI智能报告
const yearOptions = []; const aiContent = reactive({
for (let i = 0; i < count; i++) { content1: "正在生成...",
const year = currentYear - i; content2: "正在生成...",
yearOptions.push({ label: year.toString(), value: year.toString() }); })
} const onAIReport = (data, key) => {
return yearOptions; getAIReport(data).then(res => { aiContent[key] = res })
}; }
const yearList = getYearList();
// 行政令发布频度 // 行政令发布频度
const chart1Data = ref({ const chart1Data = ref({
...@@ -745,7 +745,6 @@ const box5Params = reactive({ ...@@ -745,7 +745,6 @@ const box5Params = reactive({
proposeName: '', proposeName: '',
loading: false, loading: false,
}) })
const summarize1 = ref()
const handleGetDecreeYearOrder = async () => { const handleGetDecreeYearOrder = async () => {
box5Params.loading = true box5Params.loading = true
try { try {
...@@ -763,61 +762,17 @@ const handleGetDecreeYearOrder = async () => { ...@@ -763,61 +762,17 @@ const handleGetDecreeYearOrder = async () => {
chart1Data.value.dataY = res.data.map(item => { chart1Data.value.dataY = res.data.map(item => {
return item.count; 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) { } catch (error) {
console.error("行政令发布频度error", error); console.error("行政令发布频度error", error);
} }
box5Params.loading = false 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 () => { const handleBox5 = async () => {
await handleGetDecreeYearOrder(); await handleGetDecreeYearOrder();
...@@ -863,7 +818,6 @@ const box6Params = reactive({ ...@@ -863,7 +818,6 @@ const box6Params = reactive({
proposeName: '', proposeName: '',
loading: false, loading: false,
}); });
const summarize2 = ref()
const handleGetDecreeArea = async () => { const handleGetDecreeArea = async () => {
box6Params.loading = true box6Params.loading = true
try { try {
...@@ -880,7 +834,10 @@ const handleGetDecreeArea = async () => { ...@@ -880,7 +834,10 @@ const handleGetDecreeArea = async () => {
value: item.count value: item.count
}; };
}); });
onChartInterpretation({ type: "环形图", name: "领域分布情况", data: res.data }, summarize2) onAIReport({ type: "环形图", name: "领域分布情况", data: res.data }, "content2")
} else {
chart2Data.value = []
aiContent.content2 = ""
} }
} catch (error) { } catch (error) {
console.error("政令科技领域error", error); console.error("政令科技领域error", error);
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<div class="data-item" v-for="(item, index) in props.listData" :key="index"> <div class="data-item" v-for="(item, index) in props.listData" :key="index">
<div class="item-head"> <div class="item-head">
<div class="item-name">{{ `(${onNumToChinese(Number(index)+1)}). ${item.title}` }}</div> <div class="item-name">{{ `(${onNumToChinese(Number(index)+1)}). ${item.title}` }}</div>
<div class="button-box" @click="onNavigateTo(item)"> <div class="button-box" @click="onNavigateTo()">
<div class="button-icon"> <div class="button-icon">
<img src="../assets/icons/open.png" alt="" /> <img src="../assets/icons/open.png" alt="" />
</div> </div>
...@@ -48,8 +48,7 @@ const props = defineProps({ ...@@ -48,8 +48,7 @@ const props = defineProps({
} }
}) })
const onNavigateTo = (item:any) => { const onNavigateTo = () => {
console.log(item)
const page = router.resolve({ const page = router.resolve({
name: "MarketSingleReportOriginal", name: "MarketSingleReportOriginal",
query: { ...route.query } query: { ...route.query }
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<div class="date-icon"> <div class="date-icon">
<img :src="tipsTcon" alt=""> <img :src="tipsTcon" alt="">
</div> </div>
<div class="date-text">近期美国各联邦政府机构市场准入调查数量汇总</div> <div class="date-text">近期美国各联邦政府机构市场准入限制调查数量汇总</div>
<TimeTabPane @time-click="handleGetStatSort" activeTime="近一年" /> <TimeTabPane @time-click="handleGetStatSort" activeTime="近一年" />
</div> </div>
<div class="home-main-header-card-box"> <div class="home-main-header-card-box">
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
<DivideHeader id="position3" class="divide-header" :titleText="'数据总览'"></DivideHeader> <DivideHeader id="position3" class="divide-header" :titleText="'数据总览'"></DivideHeader>
<div class="center-footer"> <div class="center-footer">
<div class="box5"> <div class="box5">
<OverviewNormalBox title="调查数量"> <OverviewNormalBox title="数量变化趋势">
<template #header-icon> <template #header-icon>
<img style="width: 100%; height: 100%;" src="./assets/icons/icon2.svg" alt="" /> <img style="width: 100%; height: 100%;" src="./assets/icons/icon2.svg" alt="" />
</template> </template>
...@@ -91,12 +91,16 @@ ...@@ -91,12 +91,16 @@
<el-empty v-if="!box5ChartData.title.length" description="暂无数据" style="padding: 100px 0 0;" :image-size="100" /> <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%;" ref="box5Ref"></div> <div v-if="box5ChartData.title.length" style="width: 100%; height: 100%;" ref="box5Ref"></div>
</div> </div>
<TipTab style="margin-top: 16px;" /> <TipTab text="美对华发起调查案件数量变化趋势,数据来源:美国国际贸易委员会、商务部、贸易代表办公室官网" style="margin-top: 16px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content5" />
</div>
</div> </div>
</OverviewNormalBox> </OverviewNormalBox>
</div> </div>
<div class="box6"> <div class="box6">
<OverviewNormalBox title="制裁领域分布" width="521px"> <OverviewNormalBox title="领域分布情况" width="521px">
<template #header-icon> <template #header-icon>
<img style="width: 100%; height: 100%;" src="./assets/icons/icon3.svg" alt="" /> <img style="width: 100%; height: 100%;" src="./assets/icons/icon3.svg" alt="" />
</template> </template>
...@@ -112,14 +116,18 @@ ...@@ -112,14 +116,18 @@
<el-empty v-if="!box6Data.title.length" description="暂无数据" style="padding: 100px 0 0;" :image-size="100" /> <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 v-if="box6Data.title.length" style="width: 100%; height: 100%;" id="box6Chart"></div>
</div> </div>
<TipTab /> <TipTab text="美对华发起调查案件领域分布情况,数据来源:美国国际贸易委员会、商务部、贸易代表办公室官网" ellipsis style="padding-right: 50px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content6" />
</div>
</div> </div>
</OverviewNormalBox> </OverviewNormalBox>
</div> </div>
</div> </div>
<div class="center-footer1"> <div class="center-footer1">
<div class="box7"> <div class="box7">
<OverviewNormalBox title="受调查国家分布" width="1064px"> <OverviewNormalBox title="国家分布情况" width="1064px">
<template #header-icon> <template #header-icon>
<img style="width: 100%; height: 100%;" src="./assets/icons/icon4.svg" alt="" /> <img style="width: 100%; height: 100%;" src="./assets/icons/icon4.svg" alt="" />
</template> </template>
...@@ -140,12 +148,16 @@ ...@@ -140,12 +148,16 @@
<el-empty v-if="!box7Data.data.length" description="暂无数据" style="padding: 100px 0 0;" :image-size="100" /> <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 v-if="box7Data.data.length" style="width: 100%; height: 100%;" id="box7Chart"></div>
</div> </div>
<TipTab style="margin-top: 10px;" /> <TipTab :text="`美发起调查案件的被调查国家分布情况,数据来源:${box7TipText}`" style="margin-top: 10px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content7" />
</div>
</div> </div>
</OverviewNormalBox> </OverviewNormalBox>
</div> </div>
<div class="box8"> <div class="box8">
<OverviewNormalBox title="调查结果分布" width="521px"> <OverviewNormalBox title="结果分布情况" width="521px">
<template #header-icon> <template #header-icon>
<img style="width: 100%; height: 100%" src="./assets/icons/icon5.svg" alt="" /> <img style="width: 100%; height: 100%" src="./assets/icons/icon5.svg" alt="" />
</template> </template>
...@@ -161,7 +173,11 @@ ...@@ -161,7 +173,11 @@
<el-empty v-if="!box8Data.length" description="暂无数据" style="padding: 100px 0 0;" :image-size="100" /> <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%;" ref="box8Ref"></div> <div v-if="box8Data.length" style="width: 100%; height: 100%;" ref="box8Ref"></div>
</div> </div>
<TipTab /> <TipTab :text="`美发起调查案件的被调查国家分布情况,数据来源:${box8TipText}`" ellipsis style="padding-right: 50px;" />
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="aiContent.content8" />
</div>
</div> </div>
</OverviewNormalBox> </OverviewNormalBox>
</div> </div>
...@@ -261,7 +277,7 @@ ...@@ -261,7 +277,7 @@
</template> </template>
<script setup> <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 LeftBtn from "@/components/base/pageBtn/LeftBtn.vue";
import RightBtn from "@/components/base/pageBtn/RightBtn.vue"; import RightBtn from "@/components/base/pageBtn/RightBtn.vue";
...@@ -278,7 +294,7 @@ import CarouselItem301 from '@/views/marketAccessRestrictions/marketAccessHome/c ...@@ -278,7 +294,7 @@ import CarouselItem301 from '@/views/marketAccessRestrictions/marketAccessHome/c
import CarouselItem232 from '@/views/marketAccessRestrictions/marketAccessHome/com/CarouselItem232.vue'; import CarouselItem232 from '@/views/marketAccessRestrictions/marketAccessHome/com/CarouselItem232.vue';
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
import { getDateBefore } from "@/views/marketAccessRestrictions/utils/index.ts"; import { getDateBefore, getAIReport, getNearYearList } from "@/views/marketAccessRestrictions/utils/index.ts";
import router from "@/router"; import router from "@/router";
import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate"; import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate";
...@@ -286,6 +302,8 @@ import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChar ...@@ -286,6 +302,8 @@ import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChar
import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js"; import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js";
import getRadarChart from "./utils/radarChart"; import getRadarChart from "./utils/radarChart";
import getBarChart from "./utils/barChart1"; 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 { getPersonSummaryInfo } from "@/api/common/index";
import { import {
...@@ -307,6 +325,17 @@ import tipsTcon from "./assets/icons/tips-icon.png"; ...@@ -307,6 +325,17 @@ import tipsTcon from "./assets/icons/tips-icon.png";
const getCardClass = (code) => ['theme-card', `theme-${code}`] 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 handleToPosi = id => {
const element = document.getElementById(id); const element = document.getElementById(id);
if (element && containerRef.value) { if (element && containerRef.value) {
...@@ -621,19 +650,20 @@ const hadleGetStatNum = async (event) => { ...@@ -621,19 +650,20 @@ const hadleGetStatNum = async (event) => {
try { try {
let byYorM = box5Active.value let byYorM = box5Active.value
const res = await getStatNum({byYorM}); const res = await getStatNum({byYorM});
console.log("调查数量", res); console.log("数量变化趋势", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
if (byYorM === 1) { if (byYorM === 1) {
box5ChartData.value = transformAllData1(res.data); box5ChartData.value = transformAllData1(res.data);
} else { } else {
box5ChartData.value = transformAllData(res.data); box5ChartData.value = transformAllData(res.data);
} }
onAIReport({ type: "折线图", name: "数量变化趋势", data: res.data }, "content5")
} }
} catch (error) {} } catch (error) {}
nextTick(() => { createLineChart(box5Ref, box5ChartData.value) }) nextTick(() => { createLineChart(box5Ref, box5ChartData.value, {unitY:"项"}) })
}; };
// 制裁领域分布 // 领域分布情况
const box6SelectedYear = ref("2025"); const box6SelectedYear = ref("2025");
const handleChangeBox6Year = () => { const handleChangeBox6Year = () => {
handleBox6(); handleBox6();
...@@ -653,7 +683,7 @@ const handleGetStatArea = async () => { ...@@ -653,7 +683,7 @@ const handleGetStatArea = async () => {
}; };
try { try {
const res = await getStatArea(params); const res = await getStatArea(params);
console.log("制裁领域分布", res); console.log("领域分布情况", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const arr = res.data.map(item => { const arr = res.data.map(item => {
return item.areaname; return item.areaname;
...@@ -710,6 +740,17 @@ const handleGetStatArea = async () => { ...@@ -710,6 +740,17 @@ const handleGetStatArea = async () => {
}); });
box6Data.value.maxNum = Math.max(...numArr); 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) { } } catch (error) { }
}; };
...@@ -719,22 +760,20 @@ const handleBox6 = async () => { ...@@ -719,22 +760,20 @@ const handleBox6 = async () => {
setChart(box6Chart, "box6Chart"); setChart(box6Chart, "box6Chart");
}; };
// 受调查国家分布 // 国家分布情况
const box7SelectedSurvey = ref("337"); const box7SelectedSurvey = ref("337");
const box7YearList = ref([ const box7YearList = getNearYearList();
{ 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 box7SelectedYear = ref("2025"); const box7SelectedYear = ref("2025");
const box7Data = reactive({ const box7Data = reactive({
title: [], title: [],
data: [] data: []
}); });
const box7TipText = computed(() => {
if (box7SelectedSurvey.value === "337") return "美国国际贸易委员会官网"
if (box7SelectedSurvey.value === "232") return "美国商务部官网"
if (box7SelectedSurvey.value === "301") return "美国贸易代表办公室官网"
return ""
})
const handleGetBox7Data = async () => { const handleGetBox7Data = async () => {
const params = { const params = {
sortCode: box7SelectedSurvey.value, sortCode: box7SelectedSurvey.value,
...@@ -742,7 +781,7 @@ const handleGetBox7Data = async () => { ...@@ -742,7 +781,7 @@ const handleGetBox7Data = async () => {
}; };
try { try {
const res = await getSearchCountry(params); const res = await getSearchCountry(params);
console.log("受调查国家分布", res); console.log("国家分布情况", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
box7Data.title = res.data.map(item => { box7Data.title = res.data.map(item => {
return { return {
...@@ -753,6 +792,11 @@ const handleGetBox7Data = async () => { ...@@ -753,6 +792,11 @@ const handleGetBox7Data = async () => {
box7Data.data = res.data.map(item => { box7Data.data = res.data.map(item => {
return item.NUM; return item.NUM;
}); });
onAIReport({ type: "柱状图", name: "国家分布情况", data: res.data }, "content7")
} else {
box7Data.title = [];
box7Data.data = [];
aiContent.content7 = "";
} }
} catch (error) { } catch (error) {
console.error("受调查国家分布error", error); console.error("受调查国家分布error", error);
...@@ -770,16 +814,23 @@ const box8SurveyList = ref([ ...@@ -770,16 +814,23 @@ const box8SurveyList = ref([
{ label: "301调查", value: "301" }, { label: "301调查", value: "301" },
{ label: "232调查", value: "232" }, { label: "232调查", value: "232" },
]); ]);
// 调查结果分布 // 结果分布情况
const box8SelectedSurvey = ref("337"); const box8SelectedSurvey = ref("337");
const box8Data = ref([]); const box8Data = ref([]);
const box8Ref = ref(null); 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 () => { const handleGetBox8Data = async () => {
try { try {
const res = await getSearchResult({sortCode: box8SelectedSurvey.value}); const res = await getSearchResult({sortCode: box8SelectedSurvey.value});
console.log("调查结果分布", res); console.log("结果分布情况", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
box8Data.value = res.data.map(item => ({ name: item.RESULTNAME, value: item.RESULTNUM })) box8Data.value = res.data.map(item => ({ name: item.RESULTNAME, value: item.RESULTNUM }))
onAIReport({ type: "环形图", name: "结果分布情况", data: res.data }, "content8")
} else { } else {
box8Data.value = [] box8Data.value = []
} }
...@@ -979,11 +1030,39 @@ onMounted(async () => { ...@@ -979,11 +1030,39 @@ onMounted(async () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 12px 30px 20px; padding: 12px 30px 20px;
position: relative;
.box-echart-content { .box-echart-content {
width: 100%; width: 100%;
height: 20px; height: 20px;
flex: auto; 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 { .home-wrapper {
......
...@@ -15,27 +15,45 @@ const getBarChart = (nameList, valueList) => { ...@@ -15,27 +15,45 @@ const getBarChart = (nameList, valueList) => {
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
splitLine: { name: "项",
show: false 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: { splitLine: {
show: false show: true,
lineStyle: {
color: '#e7f3ff',
type: 'dashed',
}
}, },
axisTick: {
show: false
}, },
xAxis: {
type: 'category',
data: nameList.map(item => item.name),
axisLine: { axisLine: {
show: false show: true,
lineStyle: {
color: '#e7f3ff',
},
}, },
axisLabel: { axisLabel: {
show: true show: true,
textStyle: {
color: 'rgba(95, 101, 108, 1)',
fontFamily: 'Microsoft YaHei',
fontsize: 14,
}
} }
}, },
series: [{ series: [{
......
...@@ -11,12 +11,12 @@ ...@@ -11,12 +11,12 @@
<el-option label="调查中" value="1" /> <el-option label="调查中" value="1" />
<el-option label="调查结束" value="0" /> <el-option label="调查结束" value="0" />
</el-select> </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-option label="全部原告/被告" value="" />
</el-select> </el-select>
<el-select v-model="filterReason" placeholder="全部原因" class="filter-select" clearable> <el-select v-model="filterReason" placeholder="全部原因" class="filter-select" clearable>
<el-option label="全部原因" value="" /> <el-option label="全部原因" value="" />
</el-select> </el-select> -->
</div> </div>
</div> </div>
<div class="select-box"> <div class="select-box">
......
...@@ -11,12 +11,12 @@ ...@@ -11,12 +11,12 @@
<el-option label="调查中" value="1" /> <el-option label="调查中" value="1" />
<el-option label="调查结束" value="0" /> <el-option label="调查结束" value="0" />
</el-select> </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-option label="全部原告/被告" value="" />
</el-select> </el-select>
<el-select v-model="filterReason" placeholder="全部原因" class="filter-select" clearable> <el-select v-model="filterReason" placeholder="全部原因" class="filter-select" clearable>
<el-option label="全部原因" value="" /> <el-option label="全部原因" value="" />
</el-select> </el-select> -->
</div> </div>
</div> </div>
<div class="select-box"> <div class="select-box">
......
...@@ -22,8 +22,8 @@ ...@@ -22,8 +22,8 @@
</div> </div>
</div> </div>
<div class="content-list"> <div class="content-list">
<div class="content-item"> <div class="content-item" v-loading="box1Loading">
<AnalysisBox title="232调查数量年度变化趋势"> <AnalysisBox title="数量变化趋势">
<template #header-btn> <template #header-btn>
<div class="header-btn-box"> <div class="header-btn-box">
<ActionButton :type="activeName === '1' ? 'active' : 'normal'" name="发起调查" @click="onStatNum('1')"></ActionButton> <ActionButton :type="activeName === '1' ? 'active' : 'normal'" name="发起调查" @click="onStatNum('1')"></ActionButton>
...@@ -32,33 +32,48 @@ ...@@ -32,33 +32,48 @@
</template> </template>
<div class="box-main"> <div class="box-main">
<div class="box-head" ref="chart1"></div> <div class="box-head" ref="chart1"></div>
<TipTab style="margin-top: 16px;" /> <TipTab text="美对华232调查案件的数量变化趋势,数据来源:美国商务部官网" style="margin-top: 16px;" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
<div class="content-item"> <div class="content-item" v-loading="box2Loading">
<AnalysisBox title="调查案件领域分布"> <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-main">
<div class="box-head" ref="chart2"></div> <div class="box-head" ref="chart2"></div>
<TipTab style="margin-top: -16px;" /> <TipTab text="美对华232调查案件的领域分布情况,数据来源:美国商务部官网" style="margin-top: -16px;" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
</div> </div>
<div class="content-list"> <div class="content-list">
<div class="content-item"> <div class="content-item" v-loading="box3Loading">
<AnalysisBox title="关税税率"> <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-main">
<div class="box-head" ref="chart3"></div> <div class="box-head" ref="chart3"></div>
<TipTab style="margin-top: -16px;" /> <TipTab text="美对华232调查案件导致的关税变化分布,数据来源:美国商务部官网" style="margin-top: -16px;" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
<div class="content-item"> <div class="content-item" v-loading="box4Loading">
<AnalysisBox title="被调查国家分布"> <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-main">
<div class="box-head" ref="chart4"></div> <div class="box-head" ref="chart4"></div>
<TipTab style="margin-top: -16px;" /> <TipTab text="美232调查所涉及的国家分布情况,数据来源:美国商务部官网" style="margin-top: -16px;" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
...@@ -66,7 +81,7 @@ ...@@ -66,7 +81,7 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, nextTick } from "vue"; import { ref, onMounted, nextTick, reactive } from "vue";
import { import {
getStatCount, getStatCount,
getStatArea, getStatArea,
...@@ -77,6 +92,8 @@ import { ...@@ -77,6 +92,8 @@ import {
import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChart"; import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChart";
import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js"; import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js";
import { getNearYearList } from "@/views/marketAccessRestrictions/utils/index.ts";
const yearList = getNearYearList();
// 数量统计 // 数量统计
const totalCaseNum = ref(0) const totalCaseNum = ref(0)
...@@ -96,71 +113,106 @@ const handleGetStat = async () => { ...@@ -96,71 +113,106 @@ const handleGetStat = async () => {
} catch (error) {} } catch (error) {}
} }
const activeName = ref('1');
const chart1 = ref(null); const chart1 = ref(null);
const activeName = ref('1');
const box1Loading = ref(false);
const onStatNum = async (event) => { const onStatNum = async (event) => {
if (event) activeName.value = event; if (event) activeName.value = event;
box1Loading.value = true;
let chart1Data = { title: [], list: [] };
try { try {
const res = await getStatNum({ sortCode: 232, searchStatus:activeName.value }) const res = await getStatNum({ sortCode: 232, searchStatus:activeName.value })
console.log('232调查数量年度变化趋势', res); console.log('数量变化趋势', res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const chart1Data = { chart1Data.title = res.data.map(item => item.searchYorM),
title: res.data.map(item => item.searchYorM), chart1Data.list = [{ name: "232调查", value: res.data.map(item => item.searchCount) }]
list: [ } else {
{ chart1Data.title = [];
name: "232调查", value: res.data.map(item => item.searchCount) chart1Data.list = [];
}
]
};
nextTick(() => { createLineChart(chart1, chart1Data) });
} }
} catch (error) { } catch (error) {
chart1Data.title = [];
chart1Data.list = [];
} }
nextTick(() => { createLineChart(chart1, chart1Data) });
box1Loading.value = false;
} }
const chart2 = ref(null); const chart2 = ref(null);
const box2Paarams = reactive({
sortCode: 232,
years: '2025'
})
const box2Loading = ref(false);
const handleGetStatArea = async () => { const handleGetStatArea = async () => {
box2Loading.value = true;
let chart2Data = []
try { try {
const res = await getStatArea({ sortCode: "232" }); const res = await getStatArea(box2Paarams);
console.log('调查案件领域分布', res); console.log('领域分布情况', res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
let chart2Data = res.data.map(item => ({ name: item.areaname, value: item.areacount }) ); chart2Data = res.data.map(item => ({ name: item.areaname, value: item.areacount }) );
nextTick(() => { createPieChart(chart2, chart2Data) }); } else {
chart2Data = [];
} }
} catch (error) { } catch (error) {
console.error("获取调查案件领域分布失败", error); chart2Data = [];
} }
nextTick(() => { createPieChart(chart2, chart2Data) });
box2Loading.value = false;
}; };
const chart3 = ref(null); const chart3 = ref(null);
const box3Paarams = reactive({
sortCode: 232,
years: '2025'
})
const box3Loading = ref(false);
const onSearchTariff = async () => { const onSearchTariff = async () => {
box3Loading.value = true;
let chart3Data = []
try { try {
const res = await getSearchTariff({ sortCode: "232" }); const res = await getSearchTariff(box3Paarams);
console.log('关税税率', res); console.log('关税变化分布', res);
if (res.code === 200 && res.data?.length) { if (res.code === 200 && res.data?.length) {
const chart3Data = [ chart3Data = [
{ name: "税率25%+", value: res.data[0].TARIFF25 }, { name: "税率25%+", value: res.data[0].TARIFF25 },
{ name: "税率11%-25%", value: res.data[0].TARIFF11 }, { name: "税率11%-25%", value: res.data[0].TARIFF11 },
{ name: "税率1%-10%", value: res.data[0].TARIFF1 }, { name: "税率1%-10%", value: res.data[0].TARIFF1 },
{ name: "税率0%", value: res.data[0].TARIFF0 }, { name: "税率0%", value: res.data[0].TARIFF0 },
]; ];
nextTick(() => { createPieChart(chart3, chart3Data) }); } else {
chart3Data = [];
} }
}catch (error) {} }catch (error) {
chart3Data = [];
}
nextTick(() => { createPieChart(chart3, chart3Data) });
box3Loading.value = false;
} }
const chart4 = ref(null); const chart4 = ref(null);
const box4Paarams = reactive({
sortCode: 232,
years: '2025'
})
const box4Loading = ref(false);
const handleGetSearchCountry = async () => { const handleGetSearchCountry = async () => {
box4Loading.value = true;
let chart4Data = []
try { try {
const res = await getSearchCountry({ sortCode: 232 }); const res = await getSearchCountry(box4Paarams);
console.log('国家分布情况', res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
let chart4Data = res.data.map(item => ({ name: item.COUNTRY, value: item.NUM }) ); chart4Data = res.data.map(item => ({ name: item.COUNTRY, value: item.NUM }) );
nextTick(() => { createPieChart(chart4, chart4Data) }); } else {
chart4Data = []
} }
} catch (error) { } catch (error) {
console.error("获取受调查国家分布失败", error); chart4Data = []
} }
nextTick(() => { createPieChart(chart4, chart4Data) });
box4Loading.value = false;
}; };
onMounted(() => { onMounted(() => {
...@@ -168,7 +220,6 @@ onMounted(() => { ...@@ -168,7 +220,6 @@ onMounted(() => {
onStatNum() onStatNum()
handleGetStatArea() handleGetStatArea()
onSearchTariff() onSearchTariff()
handleGetSearchCountry() handleGetSearchCountry()
}); });
</script> </script>
......
...@@ -2,44 +2,49 @@ ...@@ -2,44 +2,49 @@
<div class="wrap"> <div class="wrap">
<div class="content-list"> <div class="content-list">
<div class="content-item" v-loading="box1Loading"> <div class="content-item" v-loading="box1Loading">
<AnalysisBox title="对华301调查年度数量趋势"> <AnalysisBox title="数量变化趋势">
<template #header-btn> <template #header-btn>
<div class="warning-text">{{ `${inProgressCount}项调查仍在进行中` }}</div> <div class="warning-text">{{ `${inProgressCount}项调查仍在进行中` }}</div>
</template> </template>
<div class="box-main"> <div class="box-main">
<div class="box-head" ref="box1Chart"></div> <div class="box-head" ref="box1Chart"></div>
<TipTab style="margin-top: 16px" /> <TipTab text="美对华301调查案件的数量变化趋势,数据来源:美国贸易代表办公室官网" style="margin-top: 16px" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
<div class="content-item" v-loading="box2Loading"> <div class="content-item" v-loading="box2Loading">
<AnalysisBox title="301调查国家分布"> <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-main">
<div class="box-head" id="box2Chart"></div> <div class="box-head" id="box2Chart"></div>
<TipTab style="margin-top: 16px" /> <TipTab text="美301调查所涉及的国家分布情况,数据来源:美国贸易代表办公室官网" style="margin-top: 16px" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
</div> </div>
<div class="content-list"> <div class="content-list">
<div class="content-item" v-loading="box3Loading"> <div class="content-item" v-loading="box3Loading">
<AnalysisBox title="301调查方向及结果分布"> <AnalysisBox title="调查方向及结果分布">
<div class="box-main"> <div class="box-main">
<div class="box-head" id="box3Chart"></div> <div class="box-head" id="box3Chart"></div>
<TipTab style="margin-top: 16px;" /> <TipTab text="美301调查方向及结果分布情况,数据来源:美国贸易代表办公室官网" style="margin-top: 16px;" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
<div class="content-item" v-loading="box4Loading"> <div class="content-item" v-loading="box4Loading">
<AnalysisBox title="301调查领域分布"> <AnalysisBox title="领域分布情况">
<template #header-btn> <template #header-btn>
<el-select v-model="selectYear" @change="handleGetStatArea()" placeholder="选择时间" style="width:120px; margin-right:12px;"> <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-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</template> </template>
<div class="box-main"> <div class="box-main">
<div class="box-head" ref="box4Chart"></div> <div class="box-head" ref="box4Chart"></div>
<TipTab style="margin-top: -16px;" /> <TipTab text="美对华301调查案件的领域分布情况,数据来源:美国贸易代表办公室官网" style="margin-top: -16px;" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
...@@ -48,7 +53,7 @@ ...@@ -48,7 +53,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, nextTick } from "vue"; import { ref, onMounted, nextTick, reactive } from "vue";
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
import getBarChart from "./utils/barChart"; import getBarChart from "./utils/barChart";
import getSankeyChart from "./utils/sankey"; import getSankeyChart from "./utils/sankey";
...@@ -60,48 +65,76 @@ import { ...@@ -60,48 +65,76 @@ import {
} from "@/api/marketAccessRestrictions"; } from "@/api/marketAccessRestrictions";
import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChart"; import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChart";
import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js"; import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js";
import { getNearYearList } from "@/views/marketAccessRestrictions/utils/index.ts";
const yearList = getNearYearList();
const inProgressCount = ref(0); const inProgressCount = ref(0);
const box1Loading = ref(false);
const box2Loading = ref(false);
const box3Loading = ref(false);
const box4Loading = ref(false);
const box1Chart = ref(null);
const box1ChartData = ref({ const box1ChartData = ref({
title: [], title: [],
list: [] list: []
}); });
const box1Chart = ref(null); const box1Loading = ref(false);
const handleGetStatNum = async () => { const handleGetStatNum = async () => {
box1Loading.value = true; box1Loading.value = true;
try { try {
const res = await getStatNum({ const res = await getStatNum({ byYorM: "12", sortCode: "301" });
byYorM: "12", console.log('数量变化趋势', res)
sortCode: "301"
});
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const sortedData = res.data.sort((a, b) => parseInt(a.searchYorM) - parseInt(b.searchYorM)); const sortedData = res.data.sort((a, b) => parseInt(a.searchYorM) - parseInt(b.searchYorM));
box1ChartData.value.title = sortedData.map(item => item.searchYorM); box1ChartData.value.title = sortedData.map(item => item.searchYorM);
box1ChartData.value.list = [{ name: "301调查", value: sortedData.map(item => item.searchCount) }] box1ChartData.value.list = [{ name: "301调查", value: sortedData.map(item => item.searchCount) }]
console.log(box1ChartData.value);
inProgressCount.value = res.data.reduce((acc, cur) => acc + (cur.inSearchCount || 0), 0); inProgressCount.value = res.data.reduce((acc, cur) => acc + (cur.inSearchCount || 0), 0);
nextTick(() => { createLineChart(box1Chart, box1ChartData.value) }) nextTick(() => { createLineChart(box1Chart, box1ChartData.value) })
} }
} catch (error) { } catch (error) {
console.error("获取调查年度数量趋势失败", error); console.error("获取调查年度数量趋势失败", error);
} finally { }
box1Loading.value = false; 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;
try {
const res = await getSearchCountry(box2Paarams);
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");
} }
} catch (error) {
console.error("获取受调查国家分布失败", error);
}
box2Loading.value = false;
}; };
const box3ChartData = ref({ const box3ChartData = ref({
nodes: [], nodes: [],
links: [] links: []
}); });
const box3Loading = ref(false);
const handleGetSearchDirection = async () => { const handleGetSearchDirection = async () => {
box3Loading.value = true; box3Loading.value = true;
try { try {
const res = await getSearchDirection({ sortCode: "301" }); const res = await getSearchDirection({ sortCode: "301" });
console.log('调查方向及结果分布', res)
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const nodes = []; const nodes = [];
const links = []; const links = [];
...@@ -130,29 +163,22 @@ const handleGetSearchDirection = async () => { ...@@ -130,29 +163,22 @@ const handleGetSearchDirection = async () => {
} }
} catch (error) { } catch (error) {
console.error("获取调查方向及结果分布失败", error); console.error("获取调查方向及结果分布失败", error);
} finally {
box3Loading.value = false;
} }
box3Loading.value = false;
}; };
// 获取最近年份列表
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 selectYear = ref('2025');
const box4ChartData = ref([]); const box4ChartData = ref([]);
const box4Chart = ref(null) const box4Chart = ref(null)
const box4Paarams = reactive({
sortCode: 301,
years: '2025'
})
const box4Loading = ref(false);
const handleGetStatArea = async () => { const handleGetStatArea = async () => {
box4Loading.value = true; box4Loading.value = true;
try { try {
const res = await getStatArea({years: selectYear.value, sortCode: 301}); const res = await getStatArea(box4Paarams);
console.log('领域分布情况', res)
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
box4ChartData.value = res.data.map(item => ({name: item.areaname, value: item.areacount})); box4ChartData.value = res.data.map(item => ({name: item.areaname, value: item.areacount}));
} else { } else {
...@@ -165,32 +191,6 @@ const handleGetStatArea = async () => { ...@@ -165,32 +191,6 @@ const handleGetStatArea = async () => {
box4Loading.value = false; box4Loading.value = false;
}; };
const box2ChartData = ref({
title: [],
data: []
});
const handleGetSearchCountry = async () => {
box2Loading.value = true;
try {
const res = await getSearchCountry({ sortCode: 301 });
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");
}
} catch (error) {
console.error("获取受调查国家分布失败", error);
} finally {
box2Loading.value = false;
}
};
onMounted(() => { onMounted(() => {
handleGetStatNum(); handleGetStatNum();
handleGetSearchCountry(); handleGetSearchCountry();
......
...@@ -3,18 +3,6 @@ import * as echarts from "echarts"; ...@@ -3,18 +3,6 @@ import * as echarts from "echarts";
const getBarChart = (nameList, valueList) => { const getBarChart = (nameList, valueList) => {
const option = { const option = {
tooltip: {}, tooltip: {},
title: {
text: '单位:万人',
right: 26,
top: 10,
textStyle: {
color: 'rgba(95, 101, 108, 1)',
fontSize: 14,
fontFamily: 'Microsoft YaHei',
fontstyle: 'Regular',
fontWeight: 'normal'
}
},
grid: { grid: {
top: 40, top: 40,
bottom: 0, bottom: 0,
...@@ -24,23 +12,47 @@ const getBarChart = (nameList, valueList) => { ...@@ -24,23 +12,47 @@ const getBarChart = (nameList, valueList) => {
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
name: "项",
nameLocation: 'end',
nameGap: 12,
nameTextStyle: {
color: '#666',
fontSize: 14,
fontWeight: 400,
padding: [0, 0, 6, -26]
},
axisLabel: {
formatter: '{value}',
color: '#666',
fontSize: 14,
fontWeight: 400
},
splitLine: {
show: true,
lineStyle: {
color: '#e7f3ff',
type: 'dashed',
}
},
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
data: nameList.map(item => { data: nameList.map(item => {
return item.name return item.name
}), }),
splitLine: {
show: false
},
axisTick: {
show: false
},
axisLine: { axisLine: {
show: false show: true,
lineStyle: {
color: '#e7f3ff',
},
}, },
axisLabel: { axisLabel: {
show: true show: true,
textStyle: {
color: 'rgba(95, 101, 108, 1)',
fontFamily: 'Microsoft YaHei',
fontsize: 14,
}
} }
}, },
series: [{ series: [{
......
...@@ -23,38 +23,48 @@ ...@@ -23,38 +23,48 @@
</div> </div>
<div class="content-list"> <div class="content-list">
<div class="content-item" v-loading="box1Loading"> <div class="content-item" v-loading="box1Loading">
<AnalysisBox title="美国对华337调查年度趋势"> <AnalysisBox title="数量变化趋势">
<template #header-btn>
<div class="header-btn-box">
<ActionButton :type="box1Paarams.type === '01' ? 'active' : 'normal'" name="按年度" @click="handleGetStatNum('01')"></ActionButton>
<ActionButton :type="box1Paarams.type === '02' ? 'active' : 'normal'" name="按调查" @click="handleGetStatNum('02')"></ActionButton>
</div>
</template>
<div class="box-main"> <div class="box-main">
<div class="box-head" ref="chart1"></div> <div class="box-head" ref="chart1"></div>
<TipTab style="margin-top: 16px;" /> <TipTab text="美对华337调查案件的数量变化趋势,数据来源:美国国际贸易委员会官网" style="margin-top: 16px;" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
<div class="content-item" v-loading="box2Loading"> <div class="content-item" v-loading="box2Loading">
<AnalysisBox title="调查案件领域分布"> <AnalysisBox title="领域分布情况">
<template #header-btn>
<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-main">
<div class="box-head" ref="chart2"></div> <div class="box-head" ref="chart2"></div>
<TipTab style="margin-top: -16px;" /> <TipTab text="美对华337调查案件的领域分布情况,数据来源:美国国际贸易委员会官网" style="margin-top: -16px;" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
</div> </div>
<div class="content-list"> <div class="content-list">
<div class="content-item" v-loading="box3Loading"> <div class="content-item" v-loading="box3Loading">
<AnalysisBox title="中国公司受调查情况"> <AnalysisBox title="中国实体分布情况">
<template #header-btn> <template #header-btn>
<div class="header-btn-box"> <el-select v-model="box3Paarams.years" @change="handleGetStatcnOrgCount()" style="width:120px; margin-right:12px;">
<div class="btn" :class="{ btnActive: btnActiveName === '调查次数' }" @click="handleClickBox3Btn('调查次数')"> <el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
调查次数 </el-select>
</div> <el-select v-model="box3Paarams.type" @change="handleGetStatcnOrgCount()" style="width:120px; margin-right:12px;">
<div class="btn" :class="{ btnActive: btnActiveName === '注册地分布' }" @click="handleClickBox3Btn('注册地分布')"> <el-option label="调查次数" value="01" />
注册地分布 <el-option label="注册地分布" value="02" />
</div> </el-select>
</div>
</template> </template>
<div class="box-main"> <div class="box-main">
<div v-show="btnActiveName === '调查次数'" class="box-head" id="chart3"></div> <div v-show="box3Paarams.type === '01'" class="box-head" id="chart3"></div>
<div v-show="btnActiveName === '注册地分布'" class="box-head2"> <div v-show="box3Paarams.type === '02'" class="box-head2">
<div class="map-box-left"> <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" v-for="(item, index) in mapData" :key="index">
<div class="map-box-left-item-left">{{ index + 1 }}</div> <div class="map-box-left-item-left">{{ index + 1 }}</div>
...@@ -66,15 +76,20 @@ ...@@ -66,15 +76,20 @@
</div> </div>
<div class="map-box-right" id="chartMap"></div> <div class="map-box-right" id="chartMap"></div>
</div> </div>
<TipTab /> <TipTab text="美对华337调查案件的中国实体分布情况,数据来源:美国国际贸易委员会官网" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
<div class="content-item" v-loading="box4Loading"> <div class="content-item" v-loading="box4Loading">
<AnalysisBox title="调查结果分布"> <AnalysisBox title="调查结果分布">
<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-main">
<div class="box-head" ref="chart4"></div> <div class="box-head" ref="chart4"></div>
<TipTab style="margin-top: -16px;" /> <TipTab text="美对华337调查案件的调查结果分布情况,数据来源:美国国际贸易委员会官网" style="margin-top: -16px;" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
...@@ -82,7 +97,7 @@ ...@@ -82,7 +97,7 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, nextTick } from "vue"; import { ref, onMounted, nextTick, reactive } from "vue";
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
import TipTab from "@/components/base/TipTab/index.vue" import TipTab from "@/components/base/TipTab/index.vue"
...@@ -92,12 +107,8 @@ import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChar ...@@ -92,12 +107,8 @@ import createLineChart from "@/views/marketAccessRestrictions/utils/baseLineChar
import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js"; import createPieChart from "@/views/marketAccessRestrictions/utils/basePiechart.js";
import getBarChart from "./utils/barChart"; import getBarChart from "./utils/barChart";
import getMapChart from "./utils/mapChart"; import getMapChart from "./utils/mapChart";
import { getNearYearList } from "@/views/marketAccessRestrictions/utils/index.ts";
const box1Loading = ref(false); const yearList = getNearYearList();
const box2Loading = ref(false);
const box3Loading = ref(false);
const box4Loading = ref(false);
const btnActiveName = ref("调查次数");
const provinceCoords = { const provinceCoords = {
"北京": [116.46, 39.92], "北京": [116.46, 39.92],
...@@ -144,152 +155,156 @@ const windRate = ref('0%') ...@@ -144,152 +155,156 @@ const windRate = ref('0%')
const handleGetStat = async () => { const handleGetStat = async () => {
try { try {
const res = await getStatCount({sortCode: '337'}) const res = await getStatCount({sortCode: '337'})
console.log('337数量统计', res); console.log('数量统计', res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
totalCaseNum.value = res.data.allCaseNum totalCaseNum.value = res.data.allCaseNum
onCaseNum.value = res.data.underCaseNum onCaseNum.value = res.data.underCaseNum
relateCnNum.value = res.data.involvinChinese relateCnNum.value = res.data.involvinChinese
windRate.value = res.data.winRate windRate.value = res.data.winRate
} }
} catch (error) { } catch (error) {}
console.error('337数量统计error', error);
}
} }
handleGetStat()
const handleGetStatcnOrgCount = async (type) => {
box3Loading.value = true;
try {
const res = await getStatcnOrgCount({
type,
sortCode: "337"
});
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");
});
}
}
} catch (error) {
console.error("获取中国公司受调查情况失败", error);
} finally {
box3Loading.value = false;
}
};
const handleClickBox3Btn = name => {
btnActiveName.value = name;
if (name === "调查次数") {
handleGetStatcnOrgCount("01");
} else {
handleGetStatcnOrgCount("02");
}
};
const chart1Data = ref({
title: [],
data: []
});
const chart1 = ref(null); const chart1 = ref(null);
const handleGetStatNum = async () => { const box1Paarams = reactive({
sortCode: 337,
type: '01',
byYorM: '12'
})
const box1Loading = ref(false);
const handleGetStatNum = async (type) => {
if (type) box1Paarams.type = type
box1Loading.value = true; box1Loading.value = true;
let chart1Data = { title: [], list: [] }
try { try {
const res = await getStatNum({ const res = await getStatNum(box1Paarams);
byYorM: "12", console.log('数量变化趋势', res);
sortCode: "337"
});
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const sortedData = res.data.sort((a, b) => parseInt(a.searchYorM) - parseInt(b.searchYorM)); const sortedData = res.data.sort((a, b) => parseInt(a.searchYorM) - parseInt(b.searchYorM));
chart1Data.value.title = sortedData.map(item => item.searchYorM); chart1Data.title = sortedData.map(item => item.searchYorM);
chart1Data.value.list = [ chart1Data.list = [
{ name: "调查数量", value: sortedData.map(item => item.searchCount) } { name: "调查数量", value: sortedData.map(item => item.searchCount) }
]; ];
nextTick(() => { createLineChart(chart1, chart1Data.value) }); } else {
chart1Data.title = [];
chart1Data.list = [];
} }
} catch (error) { } catch (error) {
console.error("获取年度趋势数据失败", error); chart1Data.title = [];
} finally { chart1Data.list = [];
box1Loading.value = false;
} }
nextTick(() => { createLineChart(chart1, chart1Data) });
box1Loading.value = false;
}; };
const chart2Data = ref([]);
const chart2 = ref(null); const chart2 = ref(null);
const box2Paarams = reactive({
sortCode: 337,
years: '2025'
})
const box2Loading = ref(false);
const handleGetStatArea = async () => { const handleGetStatArea = async () => {
box2Loading.value = true; box2Loading.value = true;
let chart2Data = []
try { try {
const res = await getStatArea({ sortCode: "337" }); const res = await getStatArea(box2Paarams);
console.log('调查案件领域分布', res); console.log('领域分布情况', res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
chart2Data.value = res.data.filter(item => item.sortcode === "337" || item.sortname === "337调查").map(item => ({ chart2Data = res.data.map(item => ({ name: item.areaname, value: item.areacount }));
name: item.areaname, } else {
value: item.areacount chart2Data = [];
}));
nextTick(() => { createPieChart(chart2, chart2Data.value) });
} }
} catch (error) { } catch (error) {
console.error("获取调查案件领域分布失败", error); chart2Data = [];
} finally {
box2Loading.value = false;
} }
nextTick(() => { createPieChart(chart2, chart2Data) });
box2Loading.value = false;
}; };
const chart3Data = ref({
name: [],
value: []
});
const mapData = ref([]); 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 chart3Data = { name: [], value: [] }
try {
const res = await getStatcnOrgCount(box3Paarams);
console.log('中国实体分布情况', res);
if (res.code === 200 && res.data) {
chart3Data.name = res.data.map(item => item.ORGNAME)
chart3Data.value = res.data.map(item => item.ORGCOUNT)
} else {
chart3Data.name = [];
chart3Data.value = [];
}
} catch (error) {
chart3Data.name = [];
chart3Data.value = [];
}
nextTick(() => {
let chart3 = getBarChart(chart3Data.name, chart3Data.value);
setChart(chart3, "chart3");
});
} else if (box3Paarams.type === "02") {
try {
const res = await getStatcnOrgCount(box3Paarams);
console.log('中国实体分布情况', res);
if (res.code === 200 && res.data) {
mapData.value = res.data.map(item => {
return {
name: item.ORGPROVINCE,
value: item.PROVINCECOUNT,
coord: provinceCoords[item.ORGPROVINCE] || [0, 0]
};
});
} else {
mapData.value = [];
}
} catch (error) {
mapData.value = [];
}
nextTick(() => {
let chartMap = getMapChart(mapData.value);
setChart(chartMap, "chartMap");
});
}
box3Loading.value = false;
};
const chart4Data = ref([]);
const chart4 = ref(null) const chart4 = ref(null)
const box4Paarams = reactive({
sortCode: 337,
years: '2025'
})
const box4Loading = ref(false);
const handleGetSearchResult = async () => { const handleGetSearchResult = async () => {
box4Loading.value = true; box4Loading.value = true;
let chart4Data = []
try { try {
const res = await getSearchResult({ const res = await getSearchResult(box4Paarams);
sortCode: "337" console.log('调查结果分布', res);
});
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
chart4Data.value = res.data.map(item => ({ chart4Data = res.data.map(item => ({ name: item.RESULTNAME, value: item.RESULTNUM }));
name: item.RESULTNAME, } else {
value: item.RESULTNUM chart4Data = [];
}));
nextTick(() => { createPieChart(chart4, chart4Data.value) });
} }
} catch (error) { } catch (error) {
console.error("获取调查结果分布失败", error); chart4Data = [];
} finally {
box4Loading.value = false;
} }
nextTick(() => { createPieChart(chart4, chart4Data) });
box4Loading.value = false;
}; };
onMounted(() => { onMounted(() => {
handleGetStat()
handleGetStatNum(); handleGetStatNum();
handleGetStatArea(); handleGetStatArea();
handleGetStatcnOrgCount(btnActiveName.value === "调查次数" ? "01" : "02"); handleGetStatcnOrgCount();
handleGetSearchResult(); handleGetSearchResult();
}); });
</script> </script>
...@@ -392,27 +407,8 @@ onMounted(() => { ...@@ -392,27 +407,8 @@ onMounted(() => {
.header-btn-box { .header-btn-box {
display: flex; display: flex;
.btn { gap: 12px;
margin-left: 8px; margin-right: 12px;
height: 28px;
padding: 0 8px;
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);
}
} }
.box-head2 { .box-head2 {
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="page-top"> <div class="page-top">
<div class="head-box"> <div class="head-box">
<div class="head-icon"> <div class="head-icon">
<img :src="codeInfo.sortImageUrl" alt="" /> <img :src="codeInfo.sortImageUrl || Img337" alt="" />
</div> </div>
<div class="head-info"> <div class="head-info">
<div class="head-name one-line-ellipsis">{{ baseInfo.SEARCHNAME }}</div> <div class="head-name one-line-ellipsis">{{ baseInfo.SEARCHNAME }}</div>
...@@ -12,52 +12,255 @@ ...@@ -12,52 +12,255 @@
<div :class="`item-tag tag-${codeInfo.sortCode}`">{{ codeInfo.sortName }}</div> <div :class="`item-tag tag-${codeInfo.sortCode}`">{{ codeInfo.sortName }}</div>
</div> </div>
</div> </div>
<div class="page-down"> <div class="main">
<div class="report-box"> <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> </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> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, reactive } from "vue"; import { computed, ref, onMounted, watch, reactive } from "vue";
import { getSurvyInfo, getSearchBlurb, getOriginalUrl } from "@/api/marketAccessRestrictions/index.js" import pdf from "@/views/thinkTank/reportOriginal/pdf.vue";
import { getSurvyInfo, getOriginalUrl } from "@/api/marketAccessRestrictions/index.js";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); 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({ const codeInfo = reactive({
sortCode: route.query.id, sortCode: route.query.id,
sortName: "", sortName: "",
sortImageUrl: "", sortImageUrl: "",
}) })
const onSurvyInfo = async () => { const onSurvyInfo = async () => {
const res = await getSurvyInfo({sortCode: route.query.id}) try {
console.log("调查分类信息", res) const res = await getSurvyInfo({sortCode: route.query.id});
console.log("获取调查信息", res);
if (res.code == 200) { if (res.code == 200) {
Object.assign(codeInfo, res.data) Object.assign(codeInfo, res.data)
} }
} } catch (error) {
console.error("获取调查信息error", error);
}
};
//获取原文
const baseInfo = reactive({ const baseInfo = reactive({
SEARCHNAME: "调查详情", SEARCHNAME: "调查详情",
SEARCHDATE: "", SEARCHDATE: "",
}) })
const onSearchBlurb = async () => { const onOriginalUrl = async () => {
const res = await getSearchBlurb({sortCode: route.query.id, searchId: route.query.searchId}) try {
console.log("调查简介", res) const res = await getOriginalUrl({id: route.query.searchId});
if (res.code == 200) { console.log("获取原文", res);
baseInfo.SEARCHNAME = res.data.SEARCHNAME if (res.code === 200 && res.data) {
baseInfo.SEARCHDATE = res.data.SEARCHDATE reportUrl.value = res.data.urlZh
reportUrlEn.value = res.data.url
baseInfo.SEARCHNAME = res.data.investTitleZh
baseInfo.SEARCHDATE = res.data.investDate
} }
document.title = baseInfo.SEARCHNAME; document.title = baseInfo.SEARCHNAME;
} } catch (error) {
console.error("获取原文error", error);
}
};
onMounted(() => { onMounted(() => {
onSurvyInfo() onSurvyInfo()
onSearchBlurb() onOriginalUrl()
getOriginalUrl({id: 232})
}); });
</script> </script>
...@@ -67,14 +270,15 @@ onMounted(() => { ...@@ -67,14 +270,15 @@ onMounted(() => {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: white; background-color: #f7f8f9;
.page-top { .page-top {
width: 1600px; width: 100%;
margin: 0 auto;
box-sizing: border-box; box-sizing: border-box;
background-color: white;
.head-box { .head-box {
width: 100%; width: 1600px;
display: flex; display: flex;
margin: 0 auto;
padding: 30px 0 20px; padding: 30px 0 20px;
.head-icon { .head-icon {
width: 54px; width: 54px;
...@@ -162,17 +366,230 @@ onMounted(() => { ...@@ -162,17 +366,230 @@ onMounted(() => {
} }
} }
} }
.page-down {
border-top: 1px solid rgba(230, 231, 232, 1); .main {
background-color: #f7f8f9; margin: 0 auto;
width: 100%; background: rgb(255, 255, 255);
width: 1600px;
height: 20px; height: 20px;
flex: auto; 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 { .report-box {
width: 1600px; 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%; height: 100%;
background-color: white;
margin: 0 auto;
} }
} }
} }
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<div class="data-item" v-for="(item, index) in suggestionList" :key="index"> <div class="data-item" v-for="(item, index) in suggestionList" :key="index">
<div class="item-head"> <div class="item-head">
<div class="item-name">{{ item.title }}</div> <div class="item-name">{{ item.title }}</div>
<div class="button-box"> <div class="button-box" @click="onNavigateTo()">
<div class="button-icon"> <div class="button-icon">
<img src="@/views/marketAccessRestrictions/assets/icons/open.png" alt="" /> <img src="@/views/marketAccessRestrictions/assets/icons/open.png" alt="" />
</div> </div>
...@@ -52,6 +52,7 @@ ...@@ -52,6 +52,7 @@
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import router from "@/router";
import { getReportAnalyze } from "@/api/marketAccessRestrictions/index.js"; import { getReportAnalyze } from "@/api/marketAccessRestrictions/index.js";
import SurveyConclusion from "@/views/marketAccessRestrictions/com/SurveyConclusion.vue"; import SurveyConclusion from "@/views/marketAccessRestrictions/com/SurveyConclusion.vue";
import AiTips from "@/views/marketAccessRestrictions/com/AiTips.vue"; import AiTips from "@/views/marketAccessRestrictions/com/AiTips.vue";
...@@ -65,6 +66,14 @@ const supplyList = ref([]); ...@@ -65,6 +66,14 @@ const supplyList = ref([]);
const box4List = ref([]); const box4List = ref([]);
const suggestionList = ref([]); const suggestionList = ref([]);
const onNavigateTo = () => {
const page = router.resolve({
name: "MarketSingleReportOriginal",
query: { ...route.query }
});
window.open(page.href, "_blank");
}
const getData = async () => { const getData = async () => {
// 行业背景 // 行业背景
getReportAnalyze({ searchId, type: "01" }).then((res) => { getReportAnalyze({ searchId, type: "01" }).then((res) => {
...@@ -119,23 +128,6 @@ const getData = async () => { ...@@ -119,23 +128,6 @@ const getData = async () => {
}); });
}; };
const groupData = (data) => {
const groups = {};
data.forEach((item) => {
const title = item.TITLE || "其他";
if (!groups[title]) {
groups[title] = [];
}
groups[title].push({
title: item.CONTENT
});
});
return Object.keys(groups).map((title) => ({
title,
data: groups[title]
}));
};
const groupSuggestionData = (data) => { const groupSuggestionData = (data) => {
const groups = {}; const groups = {};
data.forEach((item) => { data.forEach((item) => {
...@@ -217,6 +209,7 @@ onMounted(() => { ...@@ -217,6 +209,7 @@ onMounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: 50px; margin-left: 50px;
cursor: pointer;
.button-icon { .button-icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
......
<template> <template>
<div class="wrapper"> <div class="wrapper">
<div class="left"> <div class="page-left">
<AnalysisBox title="基本信息" :showAllBtn="false" height="auto"> <AnalysisBox title="基本信息" :showAllBtn="false" height="auto">
<div class="box1-main"> <div class="box1-main">
<div class="box1-item"> <div class="box1-item">
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
</AnalysisBox> </AnalysisBox>
<SurveyAffiche title="调查公告" :listData="box2Data"></SurveyAffiche> <SurveyAffiche title="调查公告" :listData="box2Data"></SurveyAffiche>
</div> </div>
<div class="right"> <div class="page-right">
<AnalysisBox :showAllBtn="false" height="auto"> <AnalysisBox :showAllBtn="false" height="auto">
<template #custom-title> <template #custom-title>
<div class="btn-box"> <div class="btn-box">
...@@ -91,13 +91,11 @@ ...@@ -91,13 +91,11 @@
<div class="box3-main2-item" v-for="(item, index) in box3Data2" :key="index"> <div class="box3-main2-item" v-for="(item, index) in box3Data2" :key="index">
<div class="box3-main2-item-header"> <div class="box3-main2-item-header">
<div class="title">{{ item.title }}</div> <div class="title">{{ item.title }}</div>
<div class="more"> <div class="more" >
<div class="icon"> <div class="icon">
<img src="./assets/images/open-active.png" alt="" /> <img src="./assets/images/open-active.png" alt="" />
</div> </div>
<div class="text"> <div class="text" @click="onNavigateTo()">{{ "跳转原文" }}</div>
{{ "跳转原文" }}
</div>
</div> </div>
</div> </div>
<div class="box3-main2-item-content"> <div class="box3-main2-item-content">
...@@ -116,6 +114,7 @@ ...@@ -116,6 +114,7 @@
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import router from "@/router";
import { import {
getSearchBlurb, getSearchBlurb,
getRelatedEvents, getRelatedEvents,
...@@ -137,6 +136,14 @@ const handleClickBox3Btn = btn => { ...@@ -137,6 +136,14 @@ const handleClickBox3Btn = btn => {
box3BtnActive.value = btn; box3BtnActive.value = btn;
}; };
const onNavigateTo = () => {
const page = router.resolve({
name: "MarketSingleReportOriginal",
query: { ...route.query }
});
window.open(page.href, "_blank");
}
const box2Data = ref([]); const box2Data = ref([]);
const handleGetSearchBlurb = async () => { const handleGetSearchBlurb = async () => {
try { try {
...@@ -218,13 +225,13 @@ onMounted(() => { ...@@ -218,13 +225,13 @@ onMounted(() => {
width: 1600px; width: 1600px;
margin: 20px auto; margin: 20px auto;
display: flex; display: flex;
.left { .page-left {
width: 520px; width: 520px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
} }
.right { .page-right {
width: 20px; width: 20px;
flex: auto; flex: auto;
display: flex; display: flex;
...@@ -336,7 +343,7 @@ onMounted(() => { ...@@ -336,7 +343,7 @@ onMounted(() => {
padding: 15px 20px; padding: 15px 20px;
.box3-main1 { .box3-main1 {
.box3-main1-item { .box3-main1-item {
margin-bottom: 20px; margin-bottom: 30px;
display: flex; display: flex;
.left { .left {
width: 10px; width: 10px;
...@@ -358,7 +365,6 @@ onMounted(() => { ...@@ -358,7 +365,6 @@ onMounted(() => {
.right { .right {
margin-left: 17px; margin-left: 17px;
.header { .header {
height: 24px;
display: flex; display: flex;
gap: 16px; gap: 16px;
color: var(--color-main-active); color: var(--color-main-active);
...@@ -366,18 +372,16 @@ onMounted(() => { ...@@ -366,18 +372,16 @@ onMounted(() => {
font-style: Bold; font-style: Bold;
font-size: 18px; font-size: 18px;
font-weight: 700; font-weight: 700;
line-height: 24px; line-height: 30px;
letter-spacing: 0px; letter-spacing: 0px;
text-align: justify; text-align: justify;
padding-bottom: 10px;
} }
.content { .content {
border-top: 1px solid #eaecee; border-top: 1px solid #eaecee;
margin-top: 10px; padding-top: 10px;
margin-bottom: 36px;
padding: 10px 0;
width: 971px; width: 971px;
max-height: 60px; max-height: 60px;
min-height: 0;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-style: Regular; font-style: Regular;
...@@ -422,9 +426,11 @@ onMounted(() => { ...@@ -422,9 +426,11 @@ onMounted(() => {
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
gap: 7px; gap: 7px;
cursor: pointer;
.icon { .icon {
width: 12px; width: 12px;
height: 12px; height: 12px;
font-size: 0;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -432,13 +438,10 @@ onMounted(() => { ...@@ -432,13 +438,10 @@ onMounted(() => {
} }
.text { .text {
color: rgba(5, 95, 194, 1); color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
line-height: 14px; line-height: 12px;
letter-spacing: 0px; letter-spacing: 0px;
text-align: justify;
} }
} }
} }
......
...@@ -93,14 +93,14 @@ const createLineChart = (chartDom, data, option={}) => { ...@@ -93,14 +93,14 @@ const createLineChart = (chartDom, data, option={}) => {
{ {
type: 'value', type: 'value',
position: 'left', position: 'left',
name: '数量', name: option.unitY || "数量",
nameLocation: 'end', nameLocation: 'end',
nameGap: 12, nameGap: 12,
nameTextStyle: { nameTextStyle: {
color: '#666', color: '#666',
fontSize: 14, fontSize: 14,
fontWeight: 400, fontWeight: 400,
padding: [0, 0, 6, -20] padding: [0, 0, 6, -26]
}, },
axisLabel: { axisLabel: {
formatter: '{value}', formatter: '{value}',
......
...@@ -42,3 +42,70 @@ export const getDateBefore = (num: number) => { ...@@ -42,3 +42,70 @@ export const getDateBefore = (num: number) => {
return `${year}-${month}-${day}`; 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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论