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

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

Zym dev 查看合并请求 !197
...@@ -93,12 +93,23 @@ export function getChartAnalysis(data, options = {}) { ...@@ -93,12 +93,23 @@ export function getChartAnalysis(data, options = {}) {
if (raw === "[DONE]") return; if (raw === "[DONE]") return;
let chunk = ""; let chunk = "";
// 后端返回格式示例:{"text":"```"} / {"text":"json\n[\n"} // 兼容后端返回格式:
// - {"text":"```"} / {"text":"json\n[\n"}
// - {"type":"reasoning","chunk":"..."}(新格式)
try { try {
const msg = JSON.parse(raw); const msg = JSON.parse(raw);
if (msg && typeof msg === "object" && "text" in msg) { if (Array.isArray(msg?.chunk)) {
safeResolve({ data: msg.chunk });
abortController.abort();
return;
}
if (msg && typeof msg === "object" && "chunk" in msg) {
chunk = typeof msg.chunk === "string" ? msg.chunk : "";
if (chunk) buffer += chunk;
} else if (msg && typeof msg === "object" && "text" in msg) {
chunk = String(msg.text ?? ""); chunk = String(msg.text ?? "");
buffer += chunk; if (chunk) buffer += chunk;
} else { } else {
chunk = raw; chunk = raw;
buffer += raw; buffer += raw;
......
...@@ -28,6 +28,19 @@ export function getBillCount(params) { ...@@ -28,6 +28,19 @@ export function getBillCount(params) {
}) })
} }
// 近期美国国会各委员会涉华提案数量汇总
/**
* @param {Object} params
* @param {string} params.dateDesc - 时间范围:近一周/近一月/近一年
*/
export function getStatisticsBillCountByCommittee(params) {
return request({
method: 'GET',
url: `/api/BillOverview/statisticsBillCountByCommittee`,
params
})
}
// 获取关键条款 // 获取关键条款
export function getBillOverviewKeyTK() { export function getBillOverviewKeyTK() {
return request({ return request({
......
import request from "@/api/request.js"; 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) { export function getCompanyList(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/billImpactAnalysis/industry/company/${params.id}`, url: `/api/billImpactAnalysis/industry/company`,
params, params,
}) })
} }
......
...@@ -10,8 +10,7 @@ ...@@ -10,8 +10,7 @@
</div> </div>
<div <div
class="search-type-tab" class="search-type-tab"
:class="{ active: billSearchType === 'state' }" :class="{ active: billSearchType === 'state', 'is-disabled': true }"
@click="handleChangeBillSearchType('state')"
> >
州议会 州议会
</div> </div>
...@@ -206,6 +205,11 @@ const handleToPosi = id => { ...@@ -206,6 +205,11 @@ const handleToPosi = id => {
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
border-color: rgb(255, 255, 255); border-color: rgb(255, 255, 255);
} }
.search-type-tab.is-disabled {
cursor: not-allowed;
opacity: 0.6;
}
} }
.search-main-with-tabs { .search-main-with-tabs {
......
...@@ -24,7 +24,8 @@ const props = defineProps({ ...@@ -24,7 +24,8 @@ const props = defineProps({
<style lang="scss"> <style lang="scss">
.ai-pane-wrapper { .ai-pane-wrapper {
width: 100%; width: 100%;
height: 156px; min-height: 156px;
height: auto;
background: var(--color-primary-2); background: var(--color-primary-2);
box-sizing: border-box; box-sizing: border-box;
padding: 12px 16px; padding: 12px 16px;
...@@ -57,18 +58,15 @@ const props = defineProps({ ...@@ -57,18 +58,15 @@ const props = defineProps({
.content { .content {
margin-top: 8px; margin-top: 8px;
width: 100%; width: 100%;
height: 90px; min-height: 90px;
height: auto;
box-sizing: border-box; box-sizing: border-box;
padding: 0 12px; padding: 0 12px;
color: var(--color-primary-100); color: var(--color-primary-100);
display: -webkit-box; display: block;
-webkit-line-clamp: 3; overflow: visible;
/* 控制显示的行数 */
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-word; word-break: break-word;
/* 防止长单词溢出 */ white-space: pre-wrap;
} }
} }
</style> </style>
\ No newline at end of file
...@@ -3,13 +3,18 @@ ...@@ -3,13 +3,18 @@
<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">{{ `数据来源:${dataSource},数据时间:${dataTime}` }}</div> <div class="text text-tip-2 text-primary-50-clor">{{ tipText }}</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { computed } from 'vue'
const props = defineProps({ const props = defineProps({
text: {
type: String,
default: ''
},
dataSource: { dataSource: {
type: String, type: String,
default: '美国国会官网' default: '美国国会官网'
...@@ -21,6 +26,8 @@ const props = defineProps({ ...@@ -21,6 +26,8 @@ const props = defineProps({
}) })
const tipText = computed(() => props.text || `数据来源:${props.dataSource},数据时间:${props.dataTime}`)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
<template> <template>
<div class="warnning-pane-wrapper" :style="{ width: width ? width : '1600px', height: height ? height : '116px' }" <div class="warnning-pane-wrapper" :style="{ width: width ? width : '1600px', height: height ? height : 'auto', minHeight: height ? undefined : '116px' }"
:class="{ :class="{
level1: warnningLevel === '特别重大风险', level1: warnningLevel === '特别重大风险',
level2: warnningLevel === '重大风险', level2: warnningLevel === '重大风险',
...@@ -146,15 +146,12 @@ const handleClickPane = () => { ...@@ -146,15 +146,12 @@ const handleClickPane = () => {
.warnning-pane-content{ .warnning-pane-content{
width: calc(100% - 40px); width: calc(100% - 40px);
margin: 0 auto; margin: 0 auto;
height: 60px; margin-bottom: 16px;
display: -webkit-box; min-height: 60px;
/* 2. 设置内部布局方向为垂直 */ height: auto;
-webkit-box-orient: vertical; display: block;
/* 3. 限制显示的行数为 2 行 */ overflow: visible;
-webkit-line-clamp: 2; white-space: pre-wrap;
/* 4. 隐藏超出部分 */ word-break: break-word;
overflow: hidden;
/* 5. 设置文本溢出显示省略号 */
text-overflow: ellipsis;
} }
</style> </style>
\ No newline at end of file
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
<div class="main"> <div class="main">
<div class="item"><div class="item-left">提案人:</div><div class="item-right">{{ item.tcr }}</div></div> <div class="item"><div class="item-left">提案人:</div><div class="item-right">{{ item.tcr }}</div></div>
<div class="item"><div class="item-left">委员会:</div><div class="item-right">{{ item.wyh }}</div></div> <div class="item"><div class="item-left">委员会:</div><div class="item-right">{{ item.wyh }}</div></div>
<div class="item"><div class="item-left">相关领域:</div><div class="item-right1"><div class="tag" v-for="(val, idx) in item.areaList" :key="`${item.billId}-${val}-${idx}`">{{ val }}</div></div></div> <div class="item"><div class="item-left">相关领域:</div><div class="item-right1"><AreaTag v-for="(val, idx) in item.areaList" :key="`${item.billId}-${val}-${idx}`" :tagName="val" /></div></div>
<div class="item"><div class="item-left">最新动议:</div><div class="item-right"><CommonPrompt :content="item.zxdy" /></div></div> <div class="item"><div class="item-left">最新动议:</div><div class="item-right"><CommonPrompt :content="item.zxdy" /></div></div>
<div class="item"> <div class="item">
<div class="item-left">法案进展:</div> <div class="item-left">法案进展:</div>
...@@ -260,6 +260,7 @@ ...@@ -260,6 +260,7 @@
import { computed, onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { getHylyList, getPostOrgList, getPostMemberList, getBills, getBillsPerson, getBillsPersonRel, getBillsIsCnCommittee } from "@/api/bill/billHome"; import { getHylyList, getPostOrgList, getPostMemberList, getBills, getBillsPerson, getBillsPersonRel, getBillsIsCnCommittee } from "@/api/bill/billHome";
import { getPersonSummaryInfo } from "@/api/common/index";
import CommonPrompt from "../commonPrompt/index.vue"; import CommonPrompt from "../commonPrompt/index.vue";
import desc from "./assets/icons/icon-desc.png"; import desc from "./assets/icons/icon-desc.png";
import defaultAvatar from "@/assets/icons/default-icon1.png"; import defaultAvatar from "@/assets/icons/default-icon1.png";
...@@ -268,6 +269,7 @@ import zyyIcon from "@/assets/icons/zyy.png"; ...@@ -268,6 +269,7 @@ import zyyIcon from "@/assets/icons/zyy.png";
import cyyIcon from "@/assets/icons/cyy.png"; import cyyIcon from "@/assets/icons/cyy.png";
import ghdIcon from "@/assets/icons/ghd.png"; import ghdIcon from "@/assets/icons/ghd.png";
import mzdIcon from "@/assets/icons/mzd.png"; import mzdIcon from "@/assets/icons/mzd.png";
import { ElMessage } from "element-plus";
const router = useRouter(); const router = useRouter();
...@@ -306,7 +308,7 @@ const handleClickTab = tab => { ...@@ -306,7 +308,7 @@ const handleClickTab = tab => {
}; };
// sortFun: true 正序 / false 倒序(法案接口字段) // sortFun: true 正序 / false 倒序(法案接口字段)
const releaseTime = ref(true); const releaseTime = ref(false);
const releaseTimeList = ref([ const releaseTimeList = ref([
{ label: "发布时间正序", value: true }, { label: "发布时间正序", value: true },
{ label: "发布时间倒序", value: false } { label: "发布时间倒序", value: false }
...@@ -405,16 +407,48 @@ const handleBillImageError = e => { ...@@ -405,16 +407,48 @@ const handleBillImageError = e => {
img.src = defaultBill; img.src = defaultBill;
}; };
const handleClickAvatar = member => { const handleClickAvatar = async member => {
if (!member?.id) return; if (!member?.id) return;
const personTypeList = JSON.parse(window.sessionStorage.getItem("personTypeList") || "[]");
let type = 0;
let personTypeName = "";
const params = {
personId: member.id
};
try {
const res = await getPersonSummaryInfo(params);
if (res.code === 200 && res.data) {
const arr = personTypeList.filter(item => item.typeId === res.data.personType);
if (arr && arr.length > 0) {
personTypeName = arr[0].typeName;
if (personTypeName === "科技企业领袖") {
type = 1;
} else if (personTypeName === "国会议员") {
type = 2;
} else if (personTypeName === "智库研究人员") {
type = 3;
} else {
personTypeName = "";
ElMessage.warning("找不到当前人员的类型值!");
return;
}
window.sessionStorage.setItem("curTabName", member.name || ""); window.sessionStorage.setItem("curTabName", member.name || "");
const routeData = router.resolve({ const routeData = router.resolve({
path: "/characterPage", path: "/characterPage",
query: { query: {
type,
personId: member.id personId: member.id
} }
}); });
window.open(routeData.href, "_blank"); window.open(routeData.href, "_blank");
} else {
personTypeName = "";
ElMessage.warning("找不到当前人员的类型值!");
}
} else {
ElMessage.warning("找不到当前人员的类型值!");
}
} catch (error) {}
}; };
const getReversedProgress = progress => (Array.isArray(progress) ? [...progress].reverse() : []); const getReversedProgress = progress => (Array.isArray(progress) ? [...progress].reverse() : []);
......
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
<DivideHeader id="position3" class="divide3" :titleText="'数据总览'"></DivideHeader> <DivideHeader id="position3" class="divide3" :titleText="'数据总览'"></DivideHeader>
<div class="center-footer"> <div class="center-footer">
<OverviewCard class="overview-card--double box5" title="涉华法案数量变化趋势" :icon="box5HeaderIcon"> <OverviewCard class="overview-card--double box5" title="数量变化趋势" :icon="box5HeaderIcon">
<template #right> <template #right>
<el-select v-model="box5Select" placeholder="选择领域" @change="handleBox5Change" style="width: 150px"> <el-select v-model="box5Select" placeholder="选择领域" @change="handleBox5Change" style="width: 150px">
<el-option label="全部领域" value="全部领域" /> <el-option label="全部领域" value="全部领域" />
...@@ -146,7 +146,7 @@ ...@@ -146,7 +146,7 @@
<div v-else id="box5Chart" class="overview-chart"></div> <div v-else id="box5Chart" class="overview-chart"></div>
</div> </div>
<div class="overview-tip-row"> <div class="overview-tip-row">
<TipTab class="overview-tip" /> <TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box5')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box5')" />
</div> </div>
<div v-if="aiPaneVisible.box5" class="overview-ai-pane" @mouseleave="handleHideAiPane('box5')"> <div v-if="aiPaneVisible.box5" class="overview-ai-pane" @mouseleave="handleHideAiPane('box5')">
...@@ -154,7 +154,7 @@ ...@@ -154,7 +154,7 @@
</div> </div>
</div> </div>
</OverviewCard> </OverviewCard>
<OverviewCard class="overview-card--single box6" title="涉华法案领域分布" :icon="box6HeaderIcon"> <OverviewCard class="overview-card--single box6" title="领域分布情况" :icon="box6HeaderIcon">
<template #right> <template #right>
<el-select v-model="box9selectetedTime" placeholder="选择时间" style="width: 90px"> <el-select v-model="box9selectetedTime" placeholder="选择时间" style="width: 90px">
<el-option v-for="item in box9YearList" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in box9YearList" :key="item.value" :label="item.label" :value="item.value" />
...@@ -171,7 +171,7 @@ ...@@ -171,7 +171,7 @@
<div v-else id="box9Chart" class="overview-chart"></div> <div v-else id="box9Chart" class="overview-chart"></div>
</div> </div>
<div class="overview-tip-row"> <div class="overview-tip-row">
<TipTab class="overview-tip" /> <TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box6')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box6')" />
</div> </div>
<div v-if="aiPaneVisible.box6" class="overview-ai-pane" @mouseleave="handleHideAiPane('box6')"> <div v-if="aiPaneVisible.box6" class="overview-ai-pane" @mouseleave="handleHideAiPane('box6')">
...@@ -181,7 +181,7 @@ ...@@ -181,7 +181,7 @@
</OverviewCard> </OverviewCard>
</div> </div>
<div class="center-footer1"> <div class="center-footer1">
<OverviewCard class="overview-card--single box7" title="涉华法案提出部门" :icon="box7HeaderIcon"> <OverviewCard class="overview-card--single box7" title="提案委员会分布情况" :icon="box7HeaderIcon">
<template #right> <template #right>
<el-select v-model="box7selectetedTime" placeholder="选择时间" style="width: 90px"> <el-select v-model="box7selectetedTime" placeholder="选择时间" style="width: 90px">
<el-option v-for="item in box7YearList" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in box7YearList" :key="item.value" :label="item.label" :value="item.value" />
...@@ -193,7 +193,7 @@ ...@@ -193,7 +193,7 @@
<div v-else id="box7Chart" class="overview-chart"></div> <div v-else id="box7Chart" class="overview-chart"></div>
</div> </div>
<div class="overview-tip-row"> <div class="overview-tip-row">
<TipTab class="overview-tip" /> <TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box7')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box7')" />
</div> </div>
<div v-if="aiPaneVisible.box7" class="overview-ai-pane" @mouseleave="handleHideAiPane('box7')"> <div v-if="aiPaneVisible.box7" class="overview-ai-pane" @mouseleave="handleHideAiPane('box7')">
...@@ -201,7 +201,7 @@ ...@@ -201,7 +201,7 @@
</div> </div>
</div> </div>
</OverviewCard> </OverviewCard>
<OverviewCard class="overview-card--single box8" title="涉华法案进展分布" :icon="box7HeaderIcon"> <OverviewCard class="overview-card--single box8" title="进展分布情况" :icon="box7HeaderIcon">
<template #right> <template #right>
<el-select v-model="box8selectetedTime" placeholder="选择时间" style="width: 90px"> <el-select v-model="box8selectetedTime" placeholder="选择时间" style="width: 90px">
<el-option v-for="item in box8YearList" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in box8YearList" :key="item.value" :label="item.label" :value="item.value" />
...@@ -216,7 +216,7 @@ ...@@ -216,7 +216,7 @@
</template> </template>
</div> </div>
<div class="overview-tip-row"> <div class="overview-tip-row">
<TipTab class="overview-tip" /> <TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box8')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box8')" />
</div> </div>
<div v-if="aiPaneVisible.box8" class="overview-ai-pane" @mouseleave="handleHideAiPane('box8')"> <div v-if="aiPaneVisible.box8" class="overview-ai-pane" @mouseleave="handleHideAiPane('box8')">
...@@ -224,14 +224,14 @@ ...@@ -224,14 +224,14 @@
</div> </div>
</div> </div>
</OverviewCard> </OverviewCard>
<OverviewCard class="overview-card--single box9" title="涉华法案关键条款" :icon="box7HeaderIcon"> <OverviewCard class="overview-card--single box9" title="关键条款词云" :icon="box7HeaderIcon">
<div class="overview-card-body box9-main"> <div class="overview-card-body box9-main">
<div class="overview-chart-wrap"> <div class="overview-chart-wrap">
<el-empty v-if="!wordCloudHasData" description="暂无数据" :image-size="100" /> <el-empty v-if="!wordCloudHasData" description="暂无数据" :image-size="100" />
<WordCloundChart v-else class="overview-chart" width="100%" height="100%" :data="wordCloudData" /> <WordCloundChart v-else class="overview-chart" width="100%" height="100%" :data="wordCloudData" />
</div> </div>
<div class="overview-tip-row"> <div class="overview-tip-row">
<TipTab class="overview-tip" /> <TipTab class="overview-tip" :text="'图表说明:xxxxx,数据来源:美国国会官网'" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box9')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box9')" />
</div> </div>
<div v-if="aiPaneVisible.box9" class="overview-ai-pane" @mouseleave="handleHideAiPane('box9')"> <div v-if="aiPaneVisible.box9" class="overview-ai-pane" @mouseleave="handleHideAiPane('box9')">
...@@ -261,6 +261,7 @@ import { ...@@ -261,6 +261,7 @@ import {
getBillRiskSignal, getBillRiskSignal,
getHylyList, getHylyList,
getBillOverviewKeyTK, getBillOverviewKeyTK,
getStatisticsBillCountByCommittee,
getBillCount, getBillCount,
getBillPostOrg, getBillPostOrg,
getBillProcess, getBillProcess,
...@@ -356,11 +357,36 @@ const committeeTimeOptions = [ ...@@ -356,11 +357,36 @@ const committeeTimeOptions = [
{ label: "近一月", value: "近一月" }, { label: "近一月", value: "近一月" },
{ label: "近一年", value: "近一年" } { label: "近一年", value: "近一年" }
]; ];
const committeeCardList = ref([ const committeeCardList = ref([]);
{ id: 1, name: "众议院外交委员会", chamber: "众议院", count: 42 },
{ id: 2, name: "参议院军事委员会", chamber: "参议院", count: 28 }, const getChamberLabel = orgType => {
{ id: 3, name: "众议院情报委员会", chamber: "众议院", count: 19 } if (orgType === "Senate") return "参议院";
]); if (orgType === "House") return "众议院";
return orgType || "";
};
const handleGetCommitteeBillCount = async () => {
try {
const res = await getStatisticsBillCountByCommittee({
dateDesc: committeeTimeRange.value
});
if (res.code === 200 && Array.isArray(res.data)) {
committeeCardList.value = res.data
.map(item => ({
id: `${item.orgType || ""}-${item.orgName || ""}`,
name: item.orgName,
chamber: getChamberLabel(item.orgType),
count: Number(item.count || 0)
}))
.sort((a, b) => (b.count || 0) - (a.count || 0))
.slice(0, 3);
} else {
committeeCardList.value = [];
}
} catch (error) {
committeeCardList.value = [];
}
};
const hotBillList = ref([]); // 热门法案列表 const hotBillList = ref([]); // 热门法案列表
const carouselRef = ref(null); const carouselRef = ref(null);
...@@ -1182,6 +1208,14 @@ watch(box8selectetedTime, () => { ...@@ -1182,6 +1208,14 @@ watch(box8selectetedTime, () => {
handleBox8Data(); handleBox8Data();
}); });
watch(
committeeTimeRange,
() => {
handleGetCommitteeBillCount();
},
{ immediate: true }
);
const handleToPosi = id => { const handleToPosi = id => {
const element = document.getElementById(id); const element = document.getElementById(id);
if (element && containerRef.value) { if (element && containerRef.value) {
......
import { MUTICHARTCOLORS } from '../../../../common/constant'
const truncateLabel = (value, maxLen = 6) => { const truncateLabel = (value, maxLen = 6) => {
if (value === null || value === undefined) return '' if (value === null || value === undefined) return ''
const str = String(value) const str = String(value)
...@@ -8,8 +10,9 @@ const truncateLabel = (value, maxLen = 6) => { ...@@ -8,8 +10,9 @@ const truncateLabel = (value, maxLen = 6) => {
const getPieChart = (data, colorList, options = {}) => { const getPieChart = (data, colorList, options = {}) => {
const showCount = options.showCount !== false const showCount = options.showCount !== false
const chartColors = Array.isArray(colorList) && colorList.length ? colorList : MUTICHARTCOLORS
let option = { let option = {
// color: colorList, color: chartColors,
tooltip: showCount tooltip: showCount
? undefined ? undefined
: { : {
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
</div> </div>
</div> --> </div> -->
<AnalysisBox title="典型阶段耗时"> <AnalysisBox title="典型阶段耗时">
<div class="analysis-ai-wrapper analysis-ai-wrapper--box1">
<div class="box1-main" :class="{ 'box1-main--full': !timeFooterText }"> <div class="box1-main" :class="{ 'box1-main--full': !timeFooterText }">
<div class="box1-main-center" id="chart1"></div> <div class="box1-main-center" id="chart1"></div>
<div v-if="timeFooterText" class="box1-main-footer"> <div v-if="timeFooterText" class="box1-main-footer">
...@@ -46,6 +47,14 @@ ...@@ -46,6 +47,14 @@
</div> </div>
</div> </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> </AnalysisBox>
</div> </div>
<div class="box2"> <div class="box2">
...@@ -80,6 +89,7 @@ ...@@ -80,6 +89,7 @@
</div> </div>
</div> --> </div> -->
<AnalysisBox title="修正案次数分析"> <AnalysisBox title="修正案次数分析">
<div class="analysis-ai-wrapper analysis-ai-wrapper--box2">
<div class="box2-main" :class="{ 'box2-main--full': !amendFooterText }"> <div class="box2-main" :class="{ 'box2-main--full': !amendFooterText }">
<div class="box2-main-center" id="chart2"></div> <div class="box2-main-center" id="chart2"></div>
<div v-if="amendFooterText" class="box2-main-footer"> <div v-if="amendFooterText" class="box2-main-footer">
...@@ -94,6 +104,14 @@ ...@@ -94,6 +104,14 @@
</div> </div>
</div> </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> </AnalysisBox>
</div> </div>
</div> </div>
...@@ -366,6 +384,7 @@ ...@@ -366,6 +384,7 @@
</div> </div>
</div> --> </div> -->
<AnalysisBox title="投票分析"> <AnalysisBox title="投票分析">
<div class="analysis-ai-wrapper analysis-ai-wrapper--box3">
<div class="vote-legend"> <div class="vote-legend">
<div class="vote-legend-item"> <div class="vote-legend-item">
<span class="vote-legend-dot agree"></span> <span class="vote-legend-dot agree"></span>
...@@ -678,6 +697,14 @@ ...@@ -678,6 +697,14 @@
<img src="../assets/icons/arrow-right.png" alt="" /> <img src="../assets/icons/arrow-right.png" alt="" />
</div> </div>
</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> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
...@@ -690,6 +717,10 @@ import { ref, onMounted } from "vue"; ...@@ -690,6 +717,10 @@ import { ref, onMounted } from "vue";
import { getBillTimeAnalyze, getBillAmeAnalyzeCount, getBillTp } from "@/api/deepdig"; import { getBillTimeAnalyze, getBillAmeAnalyzeCount, getBillTp } from "@/api/deepdig";
import getBoxPlotChcart from "./utils/boxplot"; import getBoxPlotChcart from "./utils/boxplot";
import * as echarts from "echarts"; 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 icon1 from "./assets/images/icon1.png";
import icon2 from "./assets/images/icon2.png"; import icon2 from "./assets/images/icon2.png";
...@@ -895,6 +926,31 @@ const timeFooterText = ref(""); ...@@ -895,6 +926,31 @@ const timeFooterText = ref("");
const amendFooterText = ref(""); const amendFooterText = ref("");
const voteFooterText = 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图表 // 绘制echarts图表
const setChart = (option, chartId) => { const setChart = (option, chartId) => {
let chartDom = document.getElementById(chartId); let chartDom = document.getElementById(chartId);
...@@ -991,6 +1047,96 @@ const handleGetBillVoteAnalyze = async () => { ...@@ -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 () => { onMounted(async () => {
await handleGetBillTimeAnalyze(); await handleGetBillTimeAnalyze();
await handleGetBillAmeAnalyzeCount(); await handleGetBillAmeAnalyzeCount();
...@@ -1916,4 +2062,38 @@ onMounted(async () => { ...@@ -1916,4 +2062,38 @@ onMounted(async () => {
width: 200px; width: 200px;
margin-left: 10px; 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> </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 getBoxPlotChcart = (data, unit, labelConfig = {}) => {
const primary2 = resolveCssVarColor('--color-primary-2', '#F6FAFF')
const labels = { const labels = {
max: labelConfig.max || '最大耗时', max: labelConfig.max || '最大耗时',
q3: labelConfig.q3 || '平均耗时大', q3: labelConfig.q3 || '平均耗时大',
...@@ -16,6 +29,19 @@ const getBoxPlotChcart = (data, unit, labelConfig = {}) => { ...@@ -16,6 +29,19 @@ const getBoxPlotChcart = (data, unit, labelConfig = {}) => {
// left: 'center' // left: 'center'
// } // }
// ], // ],
graphic: [
{
type: 'text',
// 左上角只标注一次单位(y轴刻度不再逐行带单位)
left: '5%',
top: '0%',
style: {
text: unit,
fill: 'rgba(95, 101, 108, 1)',
font: '14px Microsoft YaHei'
}
}
],
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
axisPointer: { axisPointer: {
...@@ -61,10 +87,14 @@ const getBoxPlotChcart = (data, unit, labelConfig = {}) => { ...@@ -61,10 +87,14 @@ const getBoxPlotChcart = (data, unit, labelConfig = {}) => {
type: 'value', type: 'value',
name: '', name: '',
axisLabel: { axisLabel: {
formatter: (value) => `${value}${unit}` formatter: (value) => `${value}`
}, },
splitArea: { splitArea: {
show: true show: true,
// ECharts绘制到canvas,不能直接识别CSS变量字符串;这里取到真实颜色值后再配置交替背景
areaStyle: {
color: [primary2, '#ffffff']
}
} }
}, },
series: [ series: [
......
<template> <template>
<div class="introduction-wrap"> <div class="introduction-wrap">
<WarningPane <WarningPane v-if="riskSignal" class="risk-signal-pane-top" :warnningLevel="riskSignal.riskLevel"
v-if="riskSignal" :warnningContent="riskSignal.riskContent" />
class="risk-signal-pane-top"
:warnningLevel="riskSignal.riskLevel"
:warnningContent="riskSignal.riskContent"
/>
<div class="introduction-wrap-content"> <div class="introduction-wrap-content">
<div class="introduction-wrap-left"> <div class="introduction-wrap-left">
<div class="introduction-wrap-left-box1"> <div class="introduction-wrap-left-box1">
...@@ -34,7 +30,8 @@ ...@@ -34,7 +30,8 @@
<div class="box1-right-item"> <div class="box1-right-item">
<div class="item-left">委员会报告:</div> <div class="item-left">委员会报告:</div>
<div class="item-right2" v-if="reportList.length"> <div class="item-right2" v-if="reportList.length">
<div class="right2-item" v-for="(item, index) in reportList" :key="getReportKey(item, index)"> <div class="right2-item" v-for="(item, index) in reportList"
:key="getReportKey(item, index)">
{{ item }} {{ item }}
</div> </div>
</div> </div>
...@@ -50,12 +47,8 @@ ...@@ -50,12 +47,8 @@
<div class="box1-right-item"> <div class="box1-right-item">
<div class="item-left">立案流程:</div> <div class="item-left">立案流程:</div>
<div class="item-right4"> <div class="item-right4">
<div <div v-for="(item, index) in reversedStageList" :key="getStageKey(item, index)"
v-for="(item, index) in reversedStageList" class="step" :style="{ zIndex: getStageZIndex(index) }">
:key="getStageKey(item, index)"
class="step"
:style="{ zIndex: getStageZIndex(index) }"
>
<div class="step-box" <div class="step-box"
:class="{ 'step-box-active': index === stageActiveIndex }"> :class="{ 'step-box-active': index === stageActiveIndex }">
{{ item }} {{ item }}
...@@ -86,23 +79,26 @@ ...@@ -86,23 +79,26 @@
<div class="name-box"> <div class="name-box">
<div class="person-box"> <div class="person-box">
<div class="person-item" :class="{ nameItemActive: box3BtnActive === item.name }" <div class="person-item" :class="{ nameItemActive: box3BtnActive === item.name }"
@click="handleClcikBox3Btn(item.name, index)" v-for="(item, index) in personList" :key="index"> @click="handleClcikBox3Btn(item.name, index)"
v-for="(item, index) in personList" :key="index">
{{ item.name }} {{ item.name }}
</div> </div>
</div> </div>
</div> </div>
<div class="info-box"> <div class="info-box">
<div class="info-left"> <div class="info-left">
<img class="person-avatar" :src="curPerson.imageUrl || defaultAvatar" alt="" @click="handleClickAvatar(curPerson)" /> <img class="person-avatar" :src="curPerson.imageUrl || defaultAvatar" alt=""
@click="handleClickAvatar(curPerson)" />
<div class="usr-icon1"> <div class="usr-icon1">
<img src="./assets/images/usr-icon1.png" alt="" /> <img :src="partyIconUrl" alt="" />
</div> </div>
<div class="usr-icon2"> <div class="usr-icon2">
<img src="./assets/images/usr-icon2.png" alt="" /> <img :src="congressIconUrl" alt="" />
</div> </div>
</div> </div>
<div class="info-right"> <div class="info-right">
<div class="info-right-title" @click="handleClickAvatar(curPerson)">{{ curPerson.name }}</div> <div class="info-right-title" @click="handleClickAvatar(curPerson)">{{
curPerson.name }}</div>
<div class="info-right-item"> <div class="info-right-item">
<div class="item-left">英文名:</div> <div class="item-left">英文名:</div>
<div class="item-right">{{ curPerson.ename }}</div> <div class="item-right">{{ curPerson.ename }}</div>
...@@ -123,7 +119,7 @@ ...@@ -123,7 +119,7 @@
</div> </div>
</div> </div>
<div class="right-main-box2" v-if="curPerson.tagList && curPerson.tagList.length"> <div class="right-main-box2" v-if="curPerson.tagList && curPerson.tagList.length">
<div class="tag-box status"v-for="(tag, index) in curPerson.tagList" :key="index"> <div class="tag-box status" v-for="(tag, index) in curPerson.tagList" :key="index">
{{ tag }} {{ tag }}
</div> </div>
</div> </div>
...@@ -159,23 +155,64 @@ import WordCloudMap from "./WordCloudMap.vue"; ...@@ -159,23 +155,64 @@ import WordCloudMap from "./WordCloudMap.vue";
import STimeline from "./STimeline.vue"; import STimeline from "./STimeline.vue";
import WarningPane from "@/components/base/WarningPane/index.vue"; import WarningPane from "@/components/base/WarningPane/index.vue";
import { getBillInfo, getBillPerson, getBillEvent, getBillDyqk } from "@/api/bill"; import { getBillInfo, getBillPerson, getBillEvent, getBillDyqk } from "@/api/bill";
import { getPersonSummaryInfo } from "@/api/common/index";
import defaultAvatar from "../assets/images/default-icon1.png"; import defaultAvatar from "../assets/images/default-icon1.png";
import defaultNew from "../assets/images/default-icon-news.png"; import defaultNew from "../assets/images/default-icon-news.png";
import defaultBill from "./assets/images/image1.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(); const route = useRoute();
const router = useRouter(); const router = useRouter();
// 跳转到人物页面 // 跳转到人物页面
const handleClickAvatar = item => { const handleClickAvatar = async item => {
window.sessionStorage.setItem('curTabName', item.name) if (!item?.id) return;
const personTypeList = JSON.parse(window.sessionStorage.getItem("personTypeList") || "[]");
let type = 0;
let personTypeName = "";
const params = {
personId: item.id
};
try {
const res = await getPersonSummaryInfo(params);
if (res.code === 200 && res.data) {
const arr = personTypeList.filter(val => val.typeId === res.data.personType);
if (arr && arr.length > 0) {
personTypeName = arr[0].typeName;
if (personTypeName === "科技企业领袖") {
type = 1;
} else if (personTypeName === "国会议员") {
type = 2;
} else if (personTypeName === "智库研究人员") {
type = 3;
} else {
personTypeName = "";
ElMessage.warning("找不到当前人员的类型值!");
return;
}
window.sessionStorage.setItem("curTabName", item.name || "");
const routeData = router.resolve({ const routeData = router.resolve({
path: "/characterPage", path: "/characterPage",
query: { query: {
type,
personId: item.id personId: item.id
} }
}); });
window.open(routeData.href, "_blank"); window.open(routeData.href, "_blank");
} else {
personTypeName = "";
ElMessage.warning("找不到当前人员的类型值!");
}
} else {
ElMessage.warning("找不到当前人员的类型值!");
}
} catch (error) {}
}; };
// 获取URL地址里面的billId // 获取URL地址里面的billId
const billId = ref(route.query.billId); const billId = ref(route.query.billId);
...@@ -198,6 +235,22 @@ const basicInfo = ref({}); ...@@ -198,6 +235,22 @@ const basicInfo = ref({});
const riskSignal = computed(() => basicInfo.value?.riskSignalVO || null); const riskSignal = computed(() => basicInfo.value?.riskSignalVO || null);
const hylyList = computed(() => (Array.isArray(basicInfo.value?.hylyList) ? basicInfo.value.hylyList : [])); const hylyList = computed(() => (Array.isArray(basicInfo.value?.hylyList) ? basicInfo.value.hylyList : []));
const reportList = computed(() => (Array.isArray(basicInfo.value?.reportList) ? basicInfo.value.reportList : [])); 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 reversedStageList = computed(() => {
const list = Array.isArray(basicInfo.value?.stageList) ? basicInfo.value.stageList : []; const list = Array.isArray(basicInfo.value?.stageList) ? basicInfo.value.stageList : [];
return [...list].reverse(); return [...list].reverse();
...@@ -733,9 +786,10 @@ onMounted(() => { ...@@ -733,9 +786,10 @@ onMounted(() => {
.person-box { .person-box {
width: 500px; width: 500px;
overflow-x: auto; overflow-x: hidden;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
flex-wrap: wrap;
padding-bottom: 5px; padding-bottom: 5px;
&::-webkit-scrollbar { &::-webkit-scrollbar {
...@@ -752,7 +806,8 @@ onMounted(() => { ...@@ -752,7 +806,8 @@ onMounted(() => {
} }
.person-item { .person-item {
height: 28px; min-height: 28px;
height: auto;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid var(--btn-plain-border-color); border: 1px solid var(--btn-plain-border-color);
border-radius: 4px; border-radius: 4px;
...@@ -768,8 +823,12 @@ onMounted(() => { ...@@ -768,8 +823,12 @@ onMounted(() => {
margin-right: 8px; margin-right: 8px;
padding: 1px 12px; padding: 1px 12px;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: normal;
flex-shrink: 0; word-break: break-all;
line-height: 18px;
flex-shrink: 1;
max-width: 170px;
text-align: center;
} }
.nameItemActive { .nameItemActive {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论