提交 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,19 +32,28 @@
</div>
</div> -->
<AnalysisBox title="典型阶段耗时">
<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">
<div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box-footer-center">
{{ timeFooterText }}
</div>
<div class="box-footer-right">
<img src="../assets/icons/arrow-right.png" alt="" />
<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">
<div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box-footer-center">
{{ timeFooterText }}
</div>
<div class="box-footer-right">
<img src="../assets/icons/arrow-right.png" alt="" />
</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>
......@@ -80,19 +89,28 @@
</div>
</div> -->
<AnalysisBox title="修正案次数分析">
<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">
<div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box-footer-center">
{{ amendFooterText }}
</div>
<div class="box-footer-right">
<img src="../assets/icons/arrow-right.png" alt="" />
<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">
<div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box-footer-center">
{{ amendFooterText }}
</div>
<div class="box-footer-right">
<img src="../assets/icons/arrow-right.png" alt="" />
</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>
......@@ -366,7 +384,8 @@
</div>
</div> -->
<AnalysisBox title="投票分析">
<div class="vote-legend">
<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>
<span>赞成票</span>
......@@ -375,7 +394,7 @@
<span class="vote-legend-dot against"></span>
<span>反对票</span>
</div>
</div>
</div>
<div class="box3-main" :class="{ 'box3-main--full': !voteFooterText }">
<div class="box3-main-center">
<div class="box3-main-center-header">
......@@ -678,7 +697,15 @@
<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>
</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: [
......
......@@ -4,187 +4,93 @@
<AnalysisBox title="涉及行业" :showAllBtn="false" width="100%" height="100%">
<div class="left-main">
<div class="left-center">
<el-select
v-model="curHylyId"
placeholder="请选择行业"
class="left-center-select"
@change="handleIndustryChange"
>
<el-option
v-for="item in industryList"
:key="item.id"
:label="item.name || item.hylymc"
:value="item.id"
/>
<el-select v-model="curHylyId" placeholder="请选择领域" class="left-center-select"
@change="handleIndustryChange">
<el-option v-for="item in industryList" :key="item.id" :label="item.name || item.hylymc"
:value="item.id" />
</el-select>
<el-input
v-model="companySearchKeyword"
placeholder="搜索实体"
class="left-center-search"
:suffix-icon="Search"
clearable
/>
<el-input v-model="companySearchKeyword" placeholder="搜索实体" class="left-center-search"
:suffix-icon="Search" clearable />
</div>
<div class="left-footer">
<div class="item-box">
<div class="item" :class="{ itemActive: companyActiveIndex === idx }"
@click="handleClickCompany(val, idx)" v-for="(val, idx) in curCompanyList" :key="val.id">
<div class="id">{{ (currentPage - 1) * pageSize + idx + 1 }}</div>
<div class="title"
:class="{ titleActive: companyActiveIndex === ((currentPage - 1) * pageSize + idx) }">
{{ val.name }}
</div>
<div class="icon">
<img v-if="val.status === 'up'" :src="upIcon" alt="" />
<img v-if="val.status === 'down'" :src="downIcon" alt="" />
<div class="left-list">
<div class="left-list-title">实体名称</div>
<div class="left-list-content">
<el-empty v-if="!curCompanyList?.length" style="padding: 60px 0;" description="暂无数据"
:image-size="100" />
<el-scrollbar v-else height="100%" always>
<div class="item-box">
<div class="item"
:class="{ itemActive: companyActiveIndex === ((currentPage - 1) * pageSize + idx) }"
@click="handleClickCompany(val, idx)" v-for="(val, idx) in curCompanyList"
:key="val.id">
<div class="item-icon">
<img :src="defaultIcon2" alt="" class="item-img" />
</div>
<div class="title"
:class="{ titleActive: companyActiveIndex === ((currentPage - 1) * pageSize + idx) }">
{{ val.name }}
</div>
<div class="icon">
<img v-if="val.status === 'up'" :src="upIcon" alt="" />
<img v-if="val.status === 'down'" :src="downIcon" alt="" />
</div>
</div>
</div>
</div>
</el-scrollbar>
</div>
<div class="footer-box">
<div class="left">{{ `共 ${filteredCompanyList.length} 项` }}</div>
<div class="right">
<el-pagination @current-change="handleCurrentChange" :pageSize="pageSize"
:current-page="currentPage" size="small" background layout="prev, pager, next"
:total="filteredCompanyList.length" />
</div>
</div>
<div class="left-pagination">
<div class="left-pagination-left">{{ `共 ${filteredCompanyList.length} 项` }}</div>
<div class="left-pagination-right">
<el-pagination @current-change="handleCurrentChange" :pageSize="pageSize"
:current-page="currentPage" size="small" background layout="prev, pager, next"
:total="filteredCompanyList.length" />
</div>
</div>
</div>
</AnalysisBox>
</div>
<div class="right">
<AnalysisBox title="产业链分析" :showAllBtn="false" width="100%" height="100%">
<div class="right-main">
<div class="right-main-content" id="chartGraph">
<div class="right-main-content-header">
<div class="header-item1">
<div class="header-item1-top">{{ "基础支撑" }}</div>
<div class="header-item1-bottom">
<div class="icon">
<img src="./assets/images/warning.png" alt="" />
</div>
<div class="text">
{{ "中国企业45家(51.00%),受制裁3家(7.00%)" }}
</div>
<div class="box2">
<AnalysisBox :showAllBtn="false">
<template #custom-title>
<div class="custom-title">
<div class="title-left">
<div :class="['title-item', {'title-active': contentType==1}]" @click="headerContentType(1)">
<div class="title-icon">
<img :src="contentType==1 ? icon1620 : icon1621" alt="">
</div>
<div>产业链</div>
</div>
<div class="header-item2">
<div class="header-item2-top">{{ "软件算法" }}</div>
<div class="header-item2-bottom">
<div class="icon">
<img src="./assets/images/warning.png" alt="" />
</div>
<div class="text">
{{ "中国企业45家(51.00%),受制裁3家(7.00%)" }}
</div>
</div>
</div>
<div class="header-item3">
<div class="header-item3-top">{{ "行业应用" }}</div>
<div class="header-item3-bottom">
<div class="icon">
<img src="./assets/images/warning.png" alt="" />
</div>
<div class="text">
{{ "中国企业45家(51.00%),受制裁3家(7.00%)" }}
</div>
<div :class="['title-item', {'title-active': contentType==2}]" @click="headerContentType(2)">
<div class="title-icon">
<img :src="contentType==2 ? icon422 : icon423" alt="">
</div>
<div>实体关系</div>
</div>
</div>
<div class="right-main-content-main">
<Fishbone />
<div class="title-right" v-if="contentType==1">
<el-select v-model="industryChain.id" style="width: 100%" @change="onDecreeChainNodes">
<el-option v-for="item in industryChain.list" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
</div>
<div class="box-footer">
<div class="box-footer-left">
<img src="../assets/icons/right-icon1.png" alt="" />
</div>
<div class="box-footer-center">
法案以218:214​(众议院)和51:50​(副总统决胜票)微弱优势强行通过,暴露两党极端对立、党内倒戈频发的特点。
</div>
<div class="box-footer-right">
<img src="../assets/icons/arrow-right.png" alt="" />
</div>
</div>
</div>
</AnalysisBox>
<div class="graph-dialog" v-if="isShowCompanyDialog">
<div class="tool-box">
<div class="tool" @click="handleChangeChart(0)">
<img src="./assets/images/tool1.png" alt="" />
</div>
<div class="tool1" @click="handleChangeChart(1)">
<img src="./assets/images/tool2.png" alt="" />
</div>
<div class="tool" @click="handleChangeChart(0)">
<img src="./assets/images/tool3.png" alt="" />
</div>
</div>
<div class="chart-box" id="graphChart"></div>
</div>
<div class="company-dialog" v-if="isShowCompanyDialog">
<div class="dialog-header">
<div class="logo">
<img :src="companyInfo.logo" alt="" />
</div>
<div class="company-title">{{ companyInfo.name }}</div>
<div class="status-icon">
<img v-if="companyInfo.status === 'up'" :src="upIcon" alt="" />
<img v-if="companyInfo.status === 'down'" :src="downIcon" alt="" />
</div>
<div class="status-rate">
{{ companyInfo.changeRate + "%" }}
</template>
<div class="box2-main">
<AiTips :tips="tips" />
<div class="graph-box" v-if="contentType==1">
<ChartChain :listData="fishbone.list" :baseData="fishbone.base" />
</div>
<div class="close" @click="isShowCompanyDialog = false">
<img :src="closeIcon" alt="" />
<div class="graph-box" v-if="contentType==2 && graphInfo.nodes.length">
<GraphChart :nodes="graphInfo.nodes" :links="graphInfo.links" layoutType="force" />
</div>
</div>
<div class="dialog-main">
<div class="dialog-box1">
<div class="dialog-box1-header">
<div class="icon">
<img :src="companyInfo.data1?.icon" alt="" />
</div>
<div class="dialog-box1-title">{{ companyInfo.data1?.title }}</div>
</div>
<div class="dialog-box1-main">
<div class="item" v-for="(val, idx) in companyInfo.data1?.list" :key="idx">
<div class="item-left">
<!-- <img :src="uncheckIcon" alt=""> -->
<img :src="checkedIcon" alt="" />
</div>
<div class="item-right">
<CommonPrompt :content="val">{{ val }}</CommonPrompt>
</div>
</div>
</div>
</div>
<div class="dialog-box2">
<div class="dialog-box2-header">
<div class="icon">
<img :src="companyInfo.data2?.icon" alt="" />
</div>
<div class="dialog-box2-title">{{ companyInfo.data2?.title }}</div>
</div>
<div class="dialog-box2-main" id="chart2"></div>
</div>
<div class="dialog-box3">
<div class="dialog-box3-header">
<div class="icon">
<img :src="companyInfo.data3?.icon" alt="" />
</div>
<div class="dialog-box3-title">{{ companyInfo.data3?.title }}</div>
</div>
<div class="dialog-box3-main" id="chart3"></div>
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick, computed, watch } from "vue";
import { ref, onMounted, onBeforeUnmount, nextTick, computed, watch, reactive } from "vue";
import { useRoute } from "vue-router";
import * as echarts from "echarts";
......@@ -198,6 +104,7 @@ import getTreeChart from "./utils/treeChart";
import downIcon from "./assets/images/down.png";
import upIcon from "./assets/images/up.png";
import defaultIcon2 from "@/assets/icons/default-icon2.png";
import CompanyLogo from "./assets/images/company-logo.png";
import icon1 from "./assets/images/icon1.png";
import icon2 from "./assets/images/icon2.png";
......@@ -211,8 +118,25 @@ import Fishbone from "./components/fishbone.vue";
import CommonPrompt from "../../commonPrompt/index.vue";
import AnalysisBox from "@/components/base/boxBackground/analysisBox.vue";
import ChartChain from "@/views/decree/decreeLayout/influence/com/ChartChain.vue";
import AiTips from "@/views/decree/decreeLayout/influence/com/AiTips.vue";
import GraphChart from "@/components/base/GraphChart/index.vue";
import {
getDecreeRelatedChain,
getDecreeChainNodes,
getDecreeRelatedEntitie,
} from "@/api/decree/influence";
import icon422 from "@/views/decree/decreeLayout/influence/assets/images/icon422.png";
import icon423 from "@/views/decree/decreeLayout/influence/assets/images/icon423.png";
import icon1620 from "@/views/decree/decreeLayout/influence/assets/images/icon1620.png";
import icon1621 from "@/views/decree/decreeLayout/influence/assets/images/icon1621.png";
import company from "@/views/decree/decreeLayout/influence/assets/images/company.png";
import CompanyImg from "./assets/images/symbol.png";
const ALL_INDUSTRY_VALUE = "__all_industry__";
const isShowCompanyDialog = ref(false);
const handleIndustryChange = () => {
......@@ -237,12 +161,164 @@ const handleClickCompany = (val, index) => {
companyActiveIndex.value = (currentPage.value - 1) * pageSize.value + index;
if (val) {
curCompanyId.value = val.id;
handleGetCompanyDetail();
// handleGetCompanyDetail();
companyInfo.value.name = val.name;
companyInfo.value.status = val.status || companyInfo.value.status;
headerChartData(val);
}
isShowCompanyDialog.value = true;
// isShowCompanyDialog.value = true;
};
const tips =
"这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。";
const contentType = ref(1);
const entityInfo = reactive({
id: "",
node: { id: "", companyName: "" },
});
// 产业链
const industryChain = reactive({
list: [],
id: "",
});
// 产业链鱼骨图
const fishbone = reactive({
list: [],
base: [],
});
// 实体关系
const graphInfo = reactive({
nodes: [],
links: [],
});
const onDecreeRelatedChain = async (id) => {
try {
const res = await getDecreeRelatedChain({ id });
if (res.code === 200) {
industryChain.list = res.data || [];
if (industryChain.list.length) onDecreeChainNodes(industryChain.list[0].id);
}
} catch (error) {
console.log("获取产业链失败", error);
}
};
const onDecreeChainNodes = async (id) => {
industryChain.id = id;
try {
const res = await getDecreeChainNodes({ id });
if (res.code === 200) {
let obj = res.data.chains.reduce((result, item) => {
result["chain-" + item.id] = { ...item, children: [] };
return result;
}, {});
res.data.children.forEach((item) => {
if (item.companyId == entityInfo.id) {
obj["chain-" + item.chainId].children.push({ ...item, back: true });
} else if (obj["chain-" + item.chainId]?.children?.length < 10) {
obj["chain-" + item.chainId].children.push(item);
}
});
fishbone.list = Object.values(obj);
fishbone.base = (res.data.levelInfos || []).map((item, index) => {
return { ...item, name: ["上游", "中游", "下游"][index] };
});
}
} catch (error) {
console.log("获取产业链鱼骨图失败", error);
}
};
const onFormatLink = (item, index) => {
return {
id: `link-${index + 1}`,
source: item.id + "",
target: entityInfo.id + "",
label: {
show: true,
color: "#055fc2",
backgroundColor: "#eef7ff",
borderWidth: 0,
offset: [0, 15],
formatter: item.relation,
},
lineStyle: { color: "#B9DCFF", type: "solid", opacity: 1 },
};
};
const onWordWrap = (word, num) => {
const list = (word || "").split("");
let label = "";
for (let i = 0; i < list.length; i++) {
if (i % num === 0 && i !== 0) {
label += "\n";
}
label += list[i];
}
return label;
};
const onFormatNode = (item) => {
let leader = item.id == entityInfo.id;
return {
id: item.id + "",
name: onWordWrap(item.companyName || item.name || "", 7),
label: {
show: true,
color: "#3b414b",
fontSize: leader ? 18 : 14,
fontWeight: leader ? 700 : 400,
fontFamily: "Source Han Sans CN",
},
symbolSize: 40,
symbol: `image://${company}`,
};
};
const onDecreeRelatedEntitie = async (id) => {
try {
const res = await getDecreeRelatedEntitie({ id });
if (res.code === 200) {
graphInfo.links = (res.data || []).map(onFormatLink);
graphInfo.nodes = (res.data || []).map(onFormatNode);
if (entityInfo.node?.id) graphInfo.nodes.unshift(onFormatNode(entityInfo.node));
}
} catch (error) {
console.log("获取实体关系失败", error);
}
};
const headerChartData = (row) => {
entityInfo.id = row.id;
entityInfo.node = row;
industryChain.id = "";
fishbone.list = [];
fishbone.base = [];
graphInfo.nodes = [];
graphInfo.links = [];
switch (contentType.value) {
case 1:
onDecreeRelatedChain(row.id)
break;
case 2:
onDecreeRelatedEntitie(row.id)
break;
}
};
const headerContentType = (type) => {
contentType.value = type;
headerChartData(entityInfo.node);
};
const pageSize = ref(10);
......@@ -250,6 +326,7 @@ const currentPage = ref(1);
const companyList = ref([]); // 企业列表
const companySearchKeyword = ref(""); // 企业搜索关键词
let companySearchTimer = null;
// 根据关键词筛选后的企业列表
const filteredCompanyList = computed(() => {
......@@ -272,9 +349,21 @@ const handleCurrentChange = page => {
currentPage.value = page;
};
// 搜索关键词变化时重置到第一页
// 搜索关键词变化时重置到第一页,并通过接口重新拉取列表
watch(companySearchKeyword, () => {
currentPage.value = 1;
if (companySearchTimer) {
clearTimeout(companySearchTimer);
}
companySearchTimer = setTimeout(() => {
handleGetCompanyListById();
}, 300);
});
onBeforeUnmount(() => {
if (companySearchTimer) {
clearTimeout(companySearchTimer);
}
});
const industryList = ref([
......@@ -321,27 +410,36 @@ const companyInfo = ref({
const handleGetHylyList = async () => {
try {
const res = await getHylyList();
// console.log("行业领域字典列表", res);
industryList.value = res.data
if (res.data && res.data.length > 0) {
curHylyId.value = res.data[0].id;
if (res.code === 200 && Array.isArray(res.data)) {
industryList.value = [{ id: ALL_INDUSTRY_VALUE, name: "全部领域" }, ...res.data];
curHylyId.value = ALL_INDUSTRY_VALUE;
return;
}
} catch (error) { }
industryList.value = [];
curHylyId.value = "";
} catch (error) {
industryList.value = [];
curHylyId.value = "";
}
};
const curHylyId = ref("");
// 根据行业领域id获取公司列表
const handleGetCompanyListById = async () => {
const params = {
id: curHylyId.value
};
const params = {};
const trimmedCompanyName = companySearchKeyword.value.trim();
if (curHylyId.value && curHylyId.value !== ALL_INDUSTRY_VALUE) {
params.id = curHylyId.value;
}
if (trimmedCompanyName) {
params.companyName = trimmedCompanyName;
}
try {
const res = await getCompanyList(params);
// console.log('根据行业id获取公司里列表', res);
if (res.code === 200 && res.data.length) {
if (res.code === 200 && Array.isArray(res.data) && res.data.length) {
companyList.value = res.data;
companySearchKeyword.value = "";
currentPage.value = 1;
nextTick(() => {
handleClickCompany(companyList.value[0], 0);
......@@ -349,7 +447,9 @@ const handleGetCompanyListById = async () => {
} else {
companyList.value = [];
}
} catch (error) { }
} catch (error) {
companyList.value = [];
}
};
// 根据法案id,公司id,行业领域id获取公司的详情
......@@ -357,7 +457,7 @@ const handleGetCompanyDetail = async () => {
const params = {
billId: window.sessionStorage.getItem("billId"),
companyId: curCompanyId.value,
id: curHylyId.value
id: curHylyId.value === ALL_INDUSTRY_VALUE ? "" : curHylyId.value
};
try {
const res = await getCompanyDetail(params);
......@@ -711,15 +811,28 @@ onMounted(async () => {
flex-direction: column;
height: 100%;
overflow: hidden;
padding: 10px 16px;
}
.left-center {
flex: 1;
min-height: 0;
margin: 12px 17px 0;
flex-shrink: 0;
height: 32px;
min-height: 32px;
display: flex;
align-items: flex-start;
gap: 12px;
align-items: center;
gap: 16px;
margin-bottom: 10px;
:deep(.el-input__wrapper),
:deep(.el-select__wrapper) {
height: 32px;
}
:deep(.el-input__inner),
:deep(.el-select__selection) {
height: 32px;
line-height: 32px;
}
.left-center-select {
width: 150px;
......@@ -731,48 +844,76 @@ onMounted(async () => {
min-width: 0;
:deep(.el-input__wrapper) {
border: 1px solid var(--bg-black-10);
box-shadow: none;
background-color: var(--el-fill-color-blank);
border-radius: var(--el-border-radius-base);
box-shadow: 0 0 0 1px var(--el-border-color) inset;
border: none;
box-sizing: border-box;
}
}
}
.left-footer {
flex-shrink: 0;
margin: 0 auto;
width: 446px;
height: 520px;
.left-list {
flex: 1;
min-height: 0;
overflow: hidden;
display: flex;
flex-direction: column;
.left-list-title {
width: 100%;
text-align: left;
color: var(--text-primary-80-color);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
border-top: 1px solid rgba(240, 242, 244, 1);
padding: 12px 0;
}
.left-list-content {
flex: 1;
min-height: 0;
}
.item-box {
height: 480px;
overflow: hidden;
width: 446px;
margin: 0 auto;
.item {
width: 100%;
height: 48px;
border-radius: 4px;
border-bottom: 1px solid rgba(243, 243, 244, 1);
border-bottom: 1px solid rgba(240, 242, 244, 1);
border-top: 1px solid transparent;
display: flex;
cursor: pointer;
&:hover {
background: rgba(246, 251, 255, 1);
background-color: #f7f8fa;
}
&:first-child {
border-top-color: rgba(240, 242, 244, 1);
}
.id {
.item-icon {
margin-left: 12px;
margin-top: 8px;
width: 24px;
height: 24px;
border-radius: 12px;
background: rgba(231, 241, 255);
text-align: center;
line-height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 12px;
font-weight: 400;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
.item-img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.title {
......@@ -804,31 +945,53 @@ onMounted(async () => {
}
.itemActive {
background: rgba(246, 251, 255, 1);
background-color: rgba(5, 95, 194, 0.05);
border-top-color: rgba(174, 214, 255, 1);
border-bottom-color: rgba(174, 214, 255, 1);
.item-icon {
background: rgba(5, 95, 194, 0.1);
}
}
.titleActive {
color: rgba(22, 119, 255, 1) !important;
}
}
}
.footer-box {
display: flex;
justify-content: space-between;
.left-pagination {
flex-shrink: 0;
margin: 0 auto;
width: 446px;
height: 65px;
overflow: hidden;
display: flex;
align-items: flex-start;
.left {
width: 100px;
height: 20px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
justify-content: space-between;
width: 100%;
box-sizing: border-box;
.right {
flex: 300px;
}
.left-pagination-left {
width: 100px;
height: 18px;
margin-top: 25px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
overflow: hidden;
white-space: nowrap;
}
.left-pagination-right {
flex: 1;
min-width: 0;
display: flex;
justify-content: flex-end;
margin-top: 23px;
}
}
}
......@@ -1362,5 +1525,78 @@ onMounted(async () => {
}
}
}
.box2 {
margin-top: 16px;
margin-left: 16px;
width: 1247px;
height: 847px;
position: relative;
.custom-title {
display: flex;
justify-content: space-between;
align-items: flex-end;
width: 100%;
height: 100%;
padding: 0 20px;
.title-left {
display: flex;
border: 1px solid rgb(5, 95, 194);
color: rgb(5, 95, 194);
border-radius: 16px;
width: 240px;
height: 32px;
overflow: hidden;
cursor: pointer;
.title-item {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
width: 50%;
font-size: 16px;
line-height: 16px;
font-family: "Microsoft YaHei";
.title-icon {
width: 14px;
height: 14px;
font-size: 0;
img {
width: 100%;
height: 100%;
}
}
}
.title-active {
background-color: rgb(5, 95, 194);
color: white;
}
}
.title-right {
width: 180px;
}
}
.box2-main {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 16px 20px;
.graph-box {
height: 20px;
flex: auto;
margin-top: 16px;
}
}
}
}
</style>
......@@ -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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论