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

fix 修复bug#20 #21

上级 f9cc4c39
import request from "@/api/request.js";
// 根据行业领域id获取公司列表
// 获取实体列表(按行业/公司名筛选)
/**
* @param {id}
* @param {Object} params
* @param {string} [params.id] - 行业领域id(全部领域不传)
* @param {string} [params.companyName] - 公司名称(搜索框为空不传)
*/
export function getCompanyList(params) {
return request({
method: 'GET',
url: `/api/billImpactAnalysis/industry/company/${params.id}`,
url: `/api/billImpactAnalysis/industry/company`,
params,
})
}
......
......@@ -32,6 +32,7 @@
</div>
</div> -->
<AnalysisBox title="典型阶段耗时">
<div class="analysis-ai-wrapper analysis-ai-wrapper--box1">
<div class="box1-main" :class="{ 'box1-main--full': !timeFooterText }">
<div class="box1-main-center" id="chart1"></div>
<div v-if="timeFooterText" class="box1-main-footer">
......@@ -46,6 +47,14 @@
</div>
</div>
</div>
<div v-if="!aiPaneVisible.box1" class="analysis-ai-tip-row">
<TipTab class="analysis-ai-tip" />
<AiButton class="analysis-ai-tip-action" @mouseenter="handleShowAiPane('box1')" />
</div>
<div v-if="aiPaneVisible.box1" class="analysis-ai-pane" @mouseleave="handleHideAiPane('box1')">
<AiPane :aiContent="overviewAiContent.box1" />
</div>
</div>
</AnalysisBox>
</div>
<div class="box2">
......@@ -80,6 +89,7 @@
</div>
</div> -->
<AnalysisBox title="修正案次数分析">
<div class="analysis-ai-wrapper analysis-ai-wrapper--box2">
<div class="box2-main" :class="{ 'box2-main--full': !amendFooterText }">
<div class="box2-main-center" id="chart2"></div>
<div v-if="amendFooterText" class="box2-main-footer">
......@@ -94,6 +104,14 @@
</div>
</div>
</div>
<div v-if="!aiPaneVisible.box2" class="analysis-ai-tip-row">
<TipTab class="analysis-ai-tip" />
<AiButton class="analysis-ai-tip-action" @mouseenter="handleShowAiPane('box2')" />
</div>
<div v-if="aiPaneVisible.box2" class="analysis-ai-pane" @mouseleave="handleHideAiPane('box2')">
<AiPane :aiContent="overviewAiContent.box2" />
</div>
</div>
</AnalysisBox>
</div>
</div>
......@@ -366,6 +384,7 @@
</div>
</div> -->
<AnalysisBox title="投票分析">
<div class="analysis-ai-wrapper analysis-ai-wrapper--box3">
<div class="vote-legend">
<div class="vote-legend-item">
<span class="vote-legend-dot agree"></span>
......@@ -678,6 +697,14 @@
<img src="../assets/icons/arrow-right.png" alt="" />
</div>
</div>
<div v-if="!aiPaneVisible.box3" class="analysis-ai-tip-row">
<TipTab class="analysis-ai-tip" />
<AiButton class="analysis-ai-tip-action" @mouseenter="handleShowAiPane('box3')" />
</div>
<div v-if="aiPaneVisible.box3" class="analysis-ai-pane" @mouseleave="handleHideAiPane('box3')">
<AiPane :aiContent="overviewAiContent.box3" />
</div>
</div>
</div>
</AnalysisBox>
</div>
......@@ -690,6 +717,10 @@ import { ref, onMounted } from "vue";
import { getBillTimeAnalyze, getBillAmeAnalyzeCount, getBillTp } from "@/api/deepdig";
import getBoxPlotChcart from "./utils/boxplot";
import * as echarts from "echarts";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import TipTab from "@/components/base/TipTab/index.vue";
import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
import icon1 from "./assets/images/icon1.png";
import icon2 from "./assets/images/icon2.png";
......@@ -895,6 +926,31 @@ const timeFooterText = ref("");
const amendFooterText = ref("");
const voteFooterText = ref("");
// AI面板显示状态(box1=典型阶段耗时,box2=修正案次数分析,box3=投票分析)
const aiPaneVisible = ref({
box1: false,
box2: false,
box3: false
});
const overviewAiContent = ref({
box1: "智能总结生成中...",
box2: "智能总结生成中...",
box3: "智能总结生成中..."
});
const aiPaneFetched = ref({
box1: false,
box2: false,
box3: false
});
const aiPaneLoading = ref({
box1: false,
box2: false,
box3: false
});
// 绘制echarts图表
const setChart = (option, chartId) => {
let chartDom = document.getElementById(chartId);
......@@ -991,6 +1047,96 @@ const handleGetBillVoteAnalyze = async () => {
}
};
const buildAiChartPayload = key => {
if (key === "box1") {
return {
type: "箱线图",
name: "典型阶段耗时",
data: {
categories: Array.isArray(chartData1.value?.dataX) ? chartData1.value.dataX : [],
samples: Array.isArray(chartData1.value?.dataY) ? chartData1.value.dataY : []
}
};
}
if (key === "box2") {
return {
type: "箱线图",
name: "修正案次数分析",
data: {
categories: Array.isArray(chartData2.value?.dataX) ? chartData2.value.dataX : [],
samples: Array.isArray(chartData2.value?.dataY) ? chartData2.value.dataY : []
}
};
}
if (key === "box3") {
return {
type: "投票分析",
name: "投票分析",
data: Array.isArray(voteAnalysisList.value) ? voteAnalysisList.value : []
};
}
return { type: "", name: "", data: [] };
};
const requestAiPaneContent = async key => {
if (!key || aiPaneLoading.value[key] || aiPaneFetched.value[key]) return;
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: true };
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成中..." };
try {
const payload = buildAiChartPayload(key);
const res = await getChartAnalysis(
{ text: JSON.stringify(payload) },
{
onChunk: chunk => {
const current = overviewAiContent.value[key];
const base = current === "智能总结生成中..." ? "" : current;
overviewAiContent.value = {
...overviewAiContent.value,
[key]: base + chunk
};
}
}
);
const list = res?.data;
const first = Array.isArray(list) ? list[0] : null;
const interpretation = first?.解读 || first?.["解读"];
if (interpretation) {
overviewAiContent.value = {
...overviewAiContent.value,
[key]: interpretation
};
}
aiPaneFetched.value = { ...aiPaneFetched.value, [key]: true };
} catch (error) {
console.error("获取图表解读失败", error);
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成失败" };
} finally {
aiPaneLoading.value = { ...aiPaneLoading.value, [key]: false };
}
};
const handleShowAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: true
};
requestAiPaneContent(key);
};
const handleHideAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: false
};
};
onMounted(async () => {
await handleGetBillTimeAnalyze();
await handleGetBillAmeAnalyzeCount();
......@@ -1916,4 +2062,38 @@ onMounted(async () => {
width: 200px;
margin-left: 10px;
}
.analysis-ai-wrapper {
position: relative;
height: 100%;
}
.analysis-ai-tip-row {
position: absolute;
left: 0;
bottom: 15px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
}
.analysis-ai-tip-action {
position: absolute;
right: 0px;
}
.analysis-ai-pane {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
z-index: 5;
pointer-events: none;
:deep(.ai-pane-wrapper) {
pointer-events: auto;
}
}
</style>
const resolveCssVarColor = (varName, fallback) => {
try {
if (typeof window === 'undefined' || typeof document === 'undefined') return fallback
const value = window.getComputedStyle(document.documentElement).getPropertyValue(varName)
const trimmed = value ? value.trim() : ''
return trimmed || fallback
} catch (e) {
return fallback
}
}
const getBoxPlotChcart = (data, unit, labelConfig = {}) => {
const primary2 = resolveCssVarColor('--color-primary-2', '#F6FAFF')
const labels = {
max: labelConfig.max || '最大耗时',
q3: labelConfig.q3 || '平均耗时大',
......@@ -16,6 +29,19 @@ const getBoxPlotChcart = (data, unit, labelConfig = {}) => {
// left: 'center'
// }
// ],
graphic: [
{
type: 'text',
// 左上角只标注一次单位(y轴刻度不再逐行带单位)
left: '5%',
top: '0%',
style: {
text: unit,
fill: 'rgba(95, 101, 108, 1)',
font: '14px Microsoft YaHei'
}
}
],
tooltip: {
trigger: 'item',
axisPointer: {
......@@ -61,10 +87,14 @@ const getBoxPlotChcart = (data, unit, labelConfig = {}) => {
type: 'value',
name: '',
axisLabel: {
formatter: (value) => `${value}${unit}`
formatter: (value) => `${value}`
},
splitArea: {
show: true
show: true,
// ECharts绘制到canvas,不能直接识别CSS变量字符串;这里取到真实颜色值后再配置交替背景
areaStyle: {
color: [primary2, '#ffffff']
}
}
},
series: [
......
......@@ -90,10 +90,10 @@
<img class="person-avatar" :src="curPerson.imageUrl || defaultAvatar" alt=""
@click="handleClickAvatar(curPerson)" />
<div class="usr-icon1">
<img src="./assets/images/usr-icon1.png" alt="" />
<img :src="partyIconUrl" alt="" />
</div>
<div class="usr-icon2">
<img src="./assets/images/usr-icon2.png" alt="" />
<img :src="congressIconUrl" alt="" />
</div>
</div>
<div class="info-right">
......@@ -159,6 +159,12 @@ import { getPersonSummaryInfo } from "@/api/common/index";
import defaultAvatar from "../assets/images/default-icon1.png";
import defaultNew from "../assets/images/default-icon-news.png";
import defaultBill from "./assets/images/image1.png"
import defaultUsrIcon1 from "./assets/images/usr-icon1.png";
import defaultUsrIcon2 from "./assets/images/usr-icon2.png";
import cyyIcon from "@/assets/icons/cyy.png";
import zyyIcon from "@/assets/icons/zyy.png";
import ghdIcon from "@/assets/icons/ghd.png";
import mzdIcon from "@/assets/icons/mzd.png";
import { ElMessage } from "element-plus";
const route = useRoute();
......@@ -229,6 +235,22 @@ const basicInfo = ref({});
const riskSignal = computed(() => basicInfo.value?.riskSignalVO || null);
const hylyList = computed(() => (Array.isArray(basicInfo.value?.hylyList) ? basicInfo.value.hylyList : []));
const reportList = computed(() => (Array.isArray(basicInfo.value?.reportList) ? basicInfo.value.reportList : []));
// 提出人头像下方标志:参/众议院 + 党派
const congressIconUrl = computed(() => {
const congress = curPerson.value?.congress;
if (congress === "参议院") return cyyIcon;
if (congress === "众议院") return zyyIcon;
return defaultUsrIcon1;
});
const partyIconUrl = computed(() => {
const dp = curPerson.value?.dp;
if (dp === "共和党") return ghdIcon;
if (dp === "民主党") return mzdIcon;
return defaultUsrIcon2;
});
const reversedStageList = computed(() => {
const list = Array.isArray(basicInfo.value?.stageList) ? basicInfo.value.stageList : [];
return [...list].reverse();
......@@ -764,9 +786,10 @@ onMounted(() => {
.person-box {
width: 500px;
overflow-x: auto;
overflow-x: hidden;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
padding-bottom: 5px;
&::-webkit-scrollbar {
......@@ -783,7 +806,8 @@ onMounted(() => {
}
.person-item {
height: 28px;
min-height: 28px;
height: auto;
box-sizing: border-box;
border: 1px solid var(--btn-plain-border-color);
border-radius: 4px;
......@@ -799,8 +823,12 @@ onMounted(() => {
margin-right: 8px;
padding: 1px 12px;
cursor: pointer;
white-space: nowrap;
flex-shrink: 0;
white-space: normal;
word-break: break-all;
line-height: 18px;
flex-shrink: 1;
max-width: 170px;
text-align: center;
}
.nameItemActive {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论