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

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

feat 法案首页新增loading,更改法案阶段字典 查看合并请求 !286
流水线 #246 已通过 于阶段
in 4 分 25 秒
......@@ -143,6 +143,7 @@ export function getChartAnalysis(data, options = {}) {
: typeof options?.onInterpretationDelta === "function"
? options.onInterpretationDelta
: null;
const externalSignal = options?.signal;
return new Promise((resolve, reject) => {
let buffer = "";
let latestInterpretation = "";
......@@ -150,16 +151,32 @@ export function getChartAnalysis(data, options = {}) {
let lastStreamedInterpretationLen = 0;
let settled = false;
const abortController = new AbortController();
const externalAbortHandler = () => {
abortController.abort();
};
if (externalSignal) {
if (externalSignal.aborted) {
abortController.abort();
} else {
externalSignal.addEventListener("abort", externalAbortHandler, { once: true });
}
}
const safeResolve = value => {
if (settled) return;
settled = true;
if (externalSignal) {
externalSignal.removeEventListener("abort", externalAbortHandler);
}
resolve(value);
};
const safeReject = err => {
if (settled) return;
settled = true;
if (externalSignal) {
externalSignal.removeEventListener("abort", externalAbortHandler);
}
reject(err);
};
......
......@@ -107,11 +107,12 @@ export function getBillPersonAnalyzeDy(params) {
* @param {id}
* @header token
*/
export function getBillContentId(params) {
export function getBillContentId(params, config = {}) {
return request({
method: 'GET',
url: `/api/billInfoBean/contentId/${params.id}`,
params,
signal: config.signal
})
}
......@@ -120,11 +121,12 @@ export function getBillContentId(params) {
* @param {billId,id,cRelated,currentPage,pageSize,domainNameList,measuresNameList,content}
* @header token
*/
export function getBillContentTk(params) {
export function getBillContentTk(params, config = {}) {
return request({
method: 'GET',
url: `/api/billInfoBean/content/tk/${params.billId}/${params.id}`,
params,
signal: config.signal
})
}
......@@ -133,11 +135,12 @@ export function getBillContentTk(params) {
* @param {billId,versionId,cRelated}
* @header token
*/
export function getBillContentXzfs(params) {
export function getBillContentXzfs(params, config = {}) {
return request({
method: 'GET',
url: `/api/billInfoBean/content/xzfs/${params.billId}/${params.versionId}`,
params,
signal: config.signal
})
}
......@@ -146,11 +149,12 @@ export function getBillContentXzfs(params) {
* @param {billId,versionId,cRelated}
* @header token
*/
export function getBillHyly(params) {
export function getBillHyly(params, config = {}) {
return request({
method: 'GET',
url: `/api/billInfoBean/content/hyly/${params.billId}/${params.versionId}`,
params,
signal: config.signal
})
}
......
......@@ -3,8 +3,14 @@ import request from "@/api/request.js";
// 涉华法案领域分布
/**
* @param {Object} params
* @param {string} params.year - 年份
* @param {string} [params.status] - 状态:提出法案/通过法案
* @param {string} params.year - 年份(2022-2026)
* @param {string} [params.status] - 法案状态:
* - 提案
* - 众议院通过
* - 参议院通过
* - 分歧已解决
* - 呈交总统
* - 完成立法
*/
export function getBillIndustry(params) {
return request({
......
......@@ -49,7 +49,8 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
containLabel: true
},
legend: {
data: ['提出法案', '通过法案', '众议院通过', '参议院通过', '双院通过'],
// 图例顺序:提出法案、众议院通过、参议院通过、解决分歧、完成立法
data: ['提出法案', '众议院通过', '参议院通过', '解决分歧', '完成立法'],
show: true,
top: 10,
icon: 'circle',
......@@ -126,7 +127,8 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
data: proposedData
},
{
name: '通过法案',
// 众议院通过
name: '众议院通过',
type: 'line',
smooth: true,
symbol: 'emptyCircle',
......@@ -137,10 +139,11 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
itemStyle: {
color: lineColors[1]
},
data: passData
data: houseData
},
{
name: '众议院通过',
// 参议院通过
name: '参议院通过',
type: 'line',
smooth: true,
symbol: 'emptyCircle',
......@@ -151,10 +154,11 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
itemStyle: {
color: lineColors[2]
},
data: houseData
data: senateData
},
{
name: '参议院通过',
// 解决分歧
name: '解决分歧',
type: 'line',
smooth: true,
symbol: 'emptyCircle',
......@@ -165,10 +169,11 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
itemStyle: {
color: lineColors[3]
},
data: senateData
data: hsData
},
{
name: '双院通过',
// 完成立法
name: '完成立法',
type: 'line',
smooth: true,
symbol: 'emptyCircle',
......@@ -179,7 +184,7 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
itemStyle: {
color: lineColors[4]
},
data: hsData
data: passData
}
]
}
......
......@@ -27,29 +27,17 @@
</div>
<div class="left-box-bottom" v-if="showTabs">
<template v-if="isLoading">
<div class="left-box-bottom-item is-skeleton" v-for="n in 4" :key="n">
<div class="icon">
<el-skeleton-item class="skeleton-tab-icon" variant="text" />
</div>
<div class="name">
<el-skeleton-item class="skeleton-tab-text" variant="text" />
</div>
<div class="left-box-bottom-item"
:class="{ leftBoxBottomItemActive: activeTitle === item.name }" v-for="item in tabs"
:key="item.path" @click="emit('tab-click', item)">
<div class="icon">
<img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" />
<img v-else :src="item.icon" alt="" />
</div>
</template>
<template v-else>
<div class="left-box-bottom-item"
:class="{ leftBoxBottomItemActive: activeTitle === item.name }" v-for="item in tabs"
:key="item.path" @click="emit('tab-click', item)">
<div class="icon">
<img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" />
<img v-else :src="item.icon" alt="" />
</div>
<div class="name" :class="{ nameActive: activeTitle === item.name }">
{{ item.name }}
</div>
<div class="name" :class="{ nameActive: activeTitle === item.name }">
{{ item.name }}
</div>
</template>
</div>
</div>
</div>
......@@ -73,30 +61,18 @@
</div>
<div class="right-box-bottom" v-if="showActions">
<template v-if="isLoading">
<div class="btn3 is-skeleton">
<div class="icon">
<el-skeleton-item class="skeleton-action-icon" variant="text" />
</div>
<div class="text">
<el-skeleton-item class="skeleton-action-text" variant="text" />
</div>
<div class="btn2" @click="emit('open-analysis', 'forsee')">
<div class="icon">
<img :src="btnIconForsee" alt="" />
</div>
</template>
<template v-else>
<div class="btn2" @click="emit('open-analysis', 'forsee')">
<div class="icon">
<img :src="btnIconForsee" alt="" />
</div>
<div class="text">{{ "进展预测" }}</div>
</div>
<div class="btn3" @click="emit('open-analysis', 'analysis')">
<div class="icon">
<img :src="btnIconAnalysis" alt="" />
</div>
<div class="text">{{ "分析报告" }}</div>
<div class="text">{{ "进展预测" }}</div>
</div>
<div class="btn3" @click="emit('open-analysis', 'analysis')">
<div class="icon">
<img :src="btnIconAnalysis" alt="" />
</div>
</template>
<div class="text">{{ "分析报告" }}</div>
</div>
</div>
</div>
</div>
......
......@@ -150,8 +150,8 @@
</template>
<script setup>
import { ref, onMounted, computed, watch } from "vue";
import { useRoute } from "vue-router";
import { ref, onMounted, onBeforeUnmount, computed, watch } from "vue";
import { useRoute, onBeforeRouteLeave } from "vue-router";
import * as echarts from "echarts";
import { Search } from "@element-plus/icons-vue";
import getPieChart from "./utils/piechart";
......@@ -165,6 +165,31 @@ import { extractTextEntity } from "@/api/intelligent/index";
const route = useRoute();
const pageAbortController = new AbortController();
const isRequestCanceled = error => {
return (
error?.code === "ERR_CANCELED" ||
error?.name === "CanceledError" ||
error?.name === "AbortError" ||
(typeof error?.message === "string" && /canceled|aborted/i.test(error.message))
);
};
const getPageSignal = () => pageAbortController.signal;
const stopCurrentPageRequests = () => {
pageAbortController.abort();
// 让旧请求回调全部失效,避免离开页面后回写状态
tkRequestToken.value += 1;
xzfsRequestToken.value += 1;
hylyRequestToken.value += 1;
entityRequestToken.value += 1;
termsLoading.value = false;
limitLoading.value = false;
domainLoading.value = false;
aiPaneLoading.value = { domain: false, limit: false };
};
const curBill = ref("");
const curBillId = ref(null);
......@@ -294,7 +319,7 @@ const ensureEntitiesForTerms = async terms => {
try {
const results = await Promise.all(
tasks.map(async item => {
const res = await extractTextEntity(item.text);
const res = await extractTextEntity(item.text, { signal: getPageSignal() });
const entities = normalizeEntities(res?.result ?? res?.data?.result ?? res?.data ?? res);
return { key: item.key, entities };
})
......@@ -434,6 +459,7 @@ const requestAiPaneContent = async key => {
const res = await getChartAnalysis(
{ text: JSON.stringify(payload) },
{
signal: getPageSignal(),
onChunk: chunk => {
const current = overviewAiContent.value[key];
const base = current === "智能总结生成中..." ? "" : current;
......@@ -455,6 +481,7 @@ const requestAiPaneContent = async key => {
}
aiPaneFetched.value = { ...aiPaneFetched.value, [key]: true };
} catch (error) {
if (isRequestCanceled(error)) return;
console.error("获取图表解读失败", error);
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成失败" };
} finally {
......@@ -518,7 +545,7 @@ const handleGetBillList = async () => {
id: route.query.billId
};
try {
const res = await getBillContentId(params);
const res = await getBillContentId(params, { signal: getPageSignal() });
console.log("法案id列表", res);
const rawList = Array.isArray(res?.data) ? res.data : [];
const seen = new Set();
......@@ -579,7 +606,7 @@ const handleGetBillContentTk = async cRelated => {
params.content = searchKeyword.value.trim();
}
try {
const res = await getBillContentTk(params);
const res = await getBillContentTk(params, { signal: getPageSignal() });
if (currentToken !== tkRequestToken.value) {
return;
}
......@@ -627,6 +654,9 @@ const handleGetBillContentTk = async cRelated => {
total.value = 0;
}
} catch (error) {
if (isRequestCanceled(error)) {
return;
}
if (currentToken !== tkRequestToken.value) {
return;
}
......@@ -652,7 +682,7 @@ const handleGetBillContentXzfs = async () => {
};
try {
const res = await getBillContentXzfs(params);
const res = await getBillContentXzfs(params, { signal: getPageSignal() });
if (currentToken !== xzfsRequestToken.value) {
return;
}
......@@ -679,6 +709,9 @@ const handleGetBillContentXzfs = async () => {
let chart1 = getPieChart(chart1Data.value, chart1ColorList.value);
setChart(chart1, "chart1");
} catch (error) {
if (isRequestCanceled(error)) {
return;
}
if (currentToken !== xzfsRequestToken.value) {
return;
}
......@@ -701,7 +734,7 @@ const handleGetBillHyly = async () => {
};
try {
const res = await getBillHyly(params);
const res = await getBillHyly(params, { signal: getPageSignal() });
if (currentToken !== hylyRequestToken.value) {
return;
}
......@@ -729,6 +762,9 @@ const handleGetBillHyly = async () => {
let chart2 = getPieChart(chart2Data.value, chart2ColorList.value);
setChart(chart2, "chart2");
} catch (error) {
if (isRequestCanceled(error)) {
return;
}
if (currentToken !== hylyRequestToken.value) {
return;
}
......@@ -745,6 +781,14 @@ onMounted(async () => {
await handleGetBillContentXzfs();
await handleGetBillHyly();
});
onBeforeRouteLeave(() => {
stopCurrentPageRequests();
});
onBeforeUnmount(() => {
stopCurrentPageRequests();
});
</script>
<style lang="scss" scoped>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论