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

feat 法案首页新增loading,更改法案阶段字典

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