提交 80947d07 authored 作者: coderBryanFu's avatar coderBryanFu

feat:预警面板组件新增展开功能,更新数据资源库

...@@ -14,6 +14,7 @@ lerna-debug.log* ...@@ -14,6 +14,7 @@ lerna-debug.log*
# Dependencies # Dependencies
node_modules node_modules
*node_modules
.pnpm .pnpm
.npm .npm
......
...@@ -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>
<p class="p-regular-rereg"> <p class="p-regular-rereg">
<span class="text-regular" v-for="(segment, index) in processedText" :key="index"> <span class="text-regular" v-for="(segment, index) in processedText" :key="index">
<a v-if="segment.isEntity" :href="`https://cn.bing.com/search?q=${segment.entity?.text_span}`" <span v-if="segment.isEntity" @click="$emit('onEntityClick', segment.entity)" class="entity-link">
class="entity-link" target="_blank" rel="noopener noreferrer">
{{ segment.entity?.text_span }} {{ segment.entity?.text_span }}
<img :src="SearchIcon" :width="10" :height="10" alt="search" /> <img :src="SearchIcon" :width="10" :height="10" alt="search" />
</a> </span>
<span v-else> <span v-else>
{{ segment.text }} {{ segment.text }}
</span> </span>
...@@ -13,110 +12,112 @@ ...@@ -13,110 +12,112 @@
</p> </p>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { TextEntity } from '@/api/intelligent'; import { TextEntity } from "@/api/intelligent";
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted } from "vue";
import SearchIcon from './images/search.png' import SearchIcon from "./images/search.png";
export interface ProcessedTextSegment { export interface ProcessedTextSegment {
text: string text: string;
isEntity: boolean isEntity: boolean;
entity?: TextEntity entity?: TextEntity;
} }
const props = defineProps({ const props = defineProps({
text: { text: {
type: String, type: String,
default: '' default: ""
}, },
entities: { entities: {
type: Array<TextEntity>, type: Array<TextEntity>,
default: () => [] default: () => []
} }
}) });
const emit = defineEmits(["onEntityClick"]);
// 处理后的文本段 // 处理后的文本段
const processedText = ref<ProcessedTextSegment[]>([]) const processedText = ref<ProcessedTextSegment[]>([]);
// 处理文本,识别并替换实体 // 处理文本,识别并替换实体
const processText = () => { const processText = () => {
console.log('props.entities.length', props.entities.length) console.log("props.entities.length", props.entities.length);
if (!props.text || !props.entities) { if (!props.text || !props.entities) {
// console.log('props.text', props.entities.length) // console.log('props.text', props.entities.length)
processedText.value = [{ text: '', isEntity: false }] processedText.value = [{ text: "", isEntity: false }];
return return;
} }
const result = [] const result = [];
let currentPosition = 0 let currentPosition = 0;
// 按实体文本长度排序,优先匹配长文本 // 按实体文本长度排序,优先匹配长文本
const sortedEntities = [...props.entities].sort((a, b) => const sortedEntities = [...props.entities].sort((a, b) => b.text_span.length - a.text_span.length);
b.text_span.length - a.text_span.length
)
while (currentPosition < props.text.length) { while (currentPosition < props.text.length) {
let matched = false let matched = false;
for (const entity of sortedEntities) { for (const entity of sortedEntities) {
const entityText = entity.text_span const entityText = entity.text_span;
const endPosition = currentPosition + entityText.length const endPosition = currentPosition + entityText.length;
if (props.text.substring(currentPosition, endPosition) === entityText) { if (props.text.substring(currentPosition, endPosition) === entityText) {
// 如果当前位置是实体,添加到结果 // 如果当前位置是实体,添加到结果
result.push({ result.push({
isEntity: true, isEntity: true,
entity: { ...entity } entity: { ...entity }
}) });
currentPosition = endPosition currentPosition = endPosition;
matched = true matched = true;
break break;
} }
} }
if (!matched) { if (!matched) {
// 如果不是实体,收集普通文本 // 如果不是实体,收集普通文本
let nextEntityStart = props.text.length let nextEntityStart = props.text.length;
for (const entity of sortedEntities) { for (const entity of sortedEntities) {
const pos = props.text.indexOf(entity.text_span, currentPosition) const pos = props.text.indexOf(entity.text_span, currentPosition);
if (pos !== -1 && pos < nextEntityStart) { if (pos !== -1 && pos < nextEntityStart) {
nextEntityStart = pos nextEntityStart = pos;
} }
} }
if (nextEntityStart > currentPosition) { if (nextEntityStart > currentPosition) {
const plainText = props.text.substring(currentPosition, nextEntityStart) const plainText = props.text.substring(currentPosition, nextEntityStart);
result.push({ result.push({
text: plainText, text: plainText,
isEntity: false isEntity: false
}) });
currentPosition = nextEntityStart currentPosition = nextEntityStart;
} else { } else {
// 没有更多实体,添加剩余文本 // 没有更多实体,添加剩余文本
const remainingText = props.text.substring(currentPosition) const remainingText = props.text.substring(currentPosition);
if (remainingText) { if (remainingText) {
result.push({ result.push({
text: remainingText, text: remainingText,
isEntity: false isEntity: false
}) });
} }
currentPosition = props.text.length currentPosition = props.text.length;
} }
} }
} }
processedText.value = result processedText.value = result;
} };
// 监听文本和实体变化 // 监听文本和实体变化
watch(() => props.text, processText) watch(() => props.text, processText);
watch(() => props.entities, processText, { deep: true }) watch(() => props.entities, processText, { deep: true });
// 初始化处理 // 初始化处理
onMounted(processText) onMounted(processText);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@use '@/styles/common.scss'; @use "@/styles/common.scss";
.entity-link { .entity-link {
color: var(--color-primary-100); color: var(--color-primary-100);
&:hover {
cursor: pointer;
}
} }
.p-regular-rereg { .p-regular-rereg {
......
<template> <template>
<div class="full-width"> <div class="full-width">
<div class="flex-display" style="align-items: center;"> <div class="flex-display" style="align-items: center">
<common-text class="text-title-3-bold" color="var(--text-primary-80-color)">{{ isOpenTranslation <common-text class="text-title-3-bold" color="var(--text-primary-80-color)">{{
? '中文' : '原文' }}</common-text> isOpenTranslation ? "中文" : "原文"
<div class="flex-fill" style="margin: 0 10px;"> }}</common-text>
<div class="flex-fill" style="margin: 0 10px">
<el-divider></el-divider> <el-divider></el-divider>
</div> </div>
<el-button v-if="showMoreVisible" @click="() => { showMore = !showMore; updateText() }"> <el-button
{{ showMore ? '收起' : '展开' }} v-if="showMoreVisible"
@click="
() => {
showMore = !showMore;
updateText();
}
"
>
{{ showMore ? "收起" : "展开" }}
<el-icon> <el-icon>
<arrow-up v-if="showMore" /> <arrow-up v-if="showMore" />
<arrow-down v-else /> <arrow-down v-else />
...@@ -17,21 +26,24 @@ ...@@ -17,21 +26,24 @@
<el-row :gutter="32"> <el-row :gutter="32">
<el-col :span="textColSpan" v-for="(item, index) in allTexts" :key="index"> <el-col :span="textColSpan" v-for="(item, index) in allTexts" :key="index">
<!-- <p class="p-news-content"> {{ item }}</p> --> <!-- <p class="p-news-content"> {{ item }}</p> -->
<intelligent-entity-text :text="item" <intelligent-entity-text
:entities="isHighlightEntity ? textEntities : []"></intelligent-entity-text> :text="item"
@on-entity-click="e => $emit('onEntityClick', e)"
:entities="isHighlightEntity ? textEntities : []"
></intelligent-entity-text>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import '@/styles/container.scss'; import "@/styles/container.scss";
import '@/styles/common.scss'; import "@/styles/common.scss";
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted } from "vue";
import { TextEntity } from '@/api/intelligent'; import { TextEntity } from "@/api/intelligent";
import IntelligentEntityText from '@/components/base/texts/IntelligentEntityText.vue'; import IntelligentEntityText from "@/components/base/texts/IntelligentEntityText.vue";
import { ElIcon, ElButton, ElDivider, ElRow, ElCol } from 'element-plus'; import { ElIcon, ElButton, ElDivider, ElRow, ElCol } from "element-plus";
import CommonText from './CommonText.vue'; import CommonText from "./CommonText.vue";
const allTexts = ref([]); const allTexts = ref([]);
const textColSpan = ref(12); const textColSpan = ref(12);
...@@ -64,30 +76,30 @@ const props = defineProps({ ...@@ -64,30 +76,30 @@ const props = defineProps({
type: Array<TextEntity>, type: Array<TextEntity>,
default: () => [] default: () => []
} }
}) });
const emit = defineEmits(["onEntityClick"]);
function updateText() { function updateText() {
const tempTexts = [] const tempTexts = [];
const tempRaws = props.textsRaw ?? [] const tempRaws = props.textsRaw ?? [];
const tempTranslates = props.textsTranslate ?? [] const tempTranslates = props.textsTranslate ?? [];
hasTranslation.value = tempTranslates.length > 0 hasTranslation.value = tempTranslates.length > 0;
if (hasTranslation.value && props.isOpenTranslation) { if (hasTranslation.value && props.isOpenTranslation) {
// 遍历原始文本和翻译文本,将它们交替添加到 tempTexts 中,并保持原始文本和翻译文本的的数量一致 // 遍历原始文本和翻译文本,将它们交替添加到 tempTexts 中,并保持原始文本和翻译文本的的数量一致
const maxCount = Math.max(tempRaws.length, tempTranslates.length) const maxCount = Math.max(tempRaws.length, tempTranslates.length);
for (let i = 0; i < maxCount; i++) { for (let i = 0; i < maxCount; i++) {
if (i < tempTranslates.length) { if (i < tempTranslates.length) {
tempTexts.push(tempTranslates[i]); tempTexts.push(tempTranslates[i]);
} else { } else {
tempTexts.push(''); tempTexts.push("");
} }
if (i < tempRaws.length) { if (i < tempRaws.length) {
tempTexts.push(tempRaws[i]); tempTexts.push(tempRaws[i]);
} else { } else {
tempTexts.push(''); tempTexts.push("");
} }
} }
console.log(tempTexts.length) console.log(tempTexts.length);
textColSpan.value = 12; textColSpan.value = 12;
showMoreVisible.value = tempTexts.length > 6; showMoreVisible.value = tempTexts.length > 6;
allTexts.value = showMore.value ? tempTexts : tempTexts.slice(0, 6); allTexts.value = showMore.value ? tempTexts : tempTexts.slice(0, 6);
...@@ -98,12 +110,14 @@ function updateText() { ...@@ -98,12 +110,14 @@ function updateText() {
} }
} }
watch(() => [props.textsRaw, props.textsTranslate, props.isOpenTranslation], () => { watch(
() => [props.textsRaw, props.textsTranslate, props.isOpenTranslation],
() => {
updateText(); updateText();
}) }
);
onMounted(() => { onMounted(() => {
updateText(); updateText();
}) });
</script> </script>
// 综合搜索 // 综合搜索
const ComprehensiveSearch = () => import('@/views/comprehensiveSearch/index.vue') const ComprehensiveSearch = () => import("@/views/comprehensiveSearch/index.vue");
const SearchResults = () => import('@/views/comprehensiveSearch/searchResults/index.vue') const SearchResults = () => import("@/views/comprehensiveSearch/searchResults/index.vue");
const Chat = () => import('@/views/comprehensiveSearch/chat/index.vue') const Chat = () => import("@/views/comprehensiveSearch/chat/index.vue");
const comprehensiveSearchRoutes = [ const comprehensiveSearchRoutes = [
// 综合搜索 // 综合搜索
...@@ -29,19 +29,19 @@ const comprehensiveSearchRoutes = [ ...@@ -29,19 +29,19 @@ const comprehensiveSearchRoutes = [
meta: { meta: {
title: "智能问答" title: "智能问答"
} }
}, }
];
]
import { useGotoPage } from "../common.js"; import { useGotoPage } from "../common.js";
export function useGotoComprehensiveSearch() { export function useGotoComprehensiveSearch() {
const gotoPage = useGotoPage(); const gotoPage = useGotoPage();
return (isNewTabs = true) => gotoPage("/comprehensiveSearch/", {}, isNewTabs) return (isNewTabs = true) => gotoPage("/comprehensiveSearch/", {}, isNewTabs);
} }
export function useGotoSearchResults() { export function useGotoSearchResults() {
const gotoPage = useGotoPage(); const gotoPage = useGotoPage();
return (isNewTabs = true) => gotoPage("/searchResults/", {searchText, areaName}, isNewTabs)
return (searchText, areaName, isNewTabs = true) => gotoPage("/searchResults/", { searchText, areaName }, isNewTabs);
} }
export default comprehensiveSearchRoutes export default comprehensiveSearchRoutes;
\ 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);
...@@ -1184,6 +1210,14 @@ watch(box8selectetedTime, () => { ...@@ -1184,6 +1210,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="industry-wrap"> <div class="industry-wrap">
<div class="left"> <div class="left">
<div class="box-header"> <AnalysisBox title="涉及行业" :showAllBtn="false" width="100%" height="100%">
<div class="header-left"></div> <div class="left-main">
<div class="title">涉及行业</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="left-top" id="chart1"></div>
<div class="left-center"> <div class="left-center">
<div class="left-center-btn-box"> <el-select v-model="curHylyId" placeholder="请选择领域" class="left-center-select"
<div @change="handleIndustryChange">
class="left-center-btn" <el-option v-for="item in industryList" :key="item.id" :label="item.name || item.hylymc"
:class="{ btnActive: box1BtnActive === index }" :value="item.id" />
v-for="(item, index) in industryList" </el-select>
:key="index" <el-input v-model="companySearchKeyword" placeholder="搜索实体" class="left-center-search"
@click="handleClickBox1Btn(item, index)" :suffix-icon="Search" clearable />
> </div>
{{ item.name }} <div class="left-list">
</div> <div class="left-list-title">实体名称</div>
</div> <div class="left-list-content">
</div> <el-empty v-if="!curCompanyList?.length" style="padding: 60px 0;" description="暂无数据"
<div class="left-footer"> :image-size="100" />
<el-scrollbar v-else height="100%" always>
<div class="item-box"> <div class="item-box">
<div <div class="item"
class="item" :class="{ itemActive: companyActiveIndex === ((currentPage - 1) * pageSize + idx) }"
:class="{ itemActive: companyActiveIndex === idx }" @click="handleClickCompany(val, idx)" v-for="(val, idx) in curCompanyList"
@click="handleClickCompany(val, idx)" :key="val.id">
v-for="(val, idx) in curCompanyList" <div class="item-icon">
:key="idx" <img :src="defaultIcon2" alt="" class="item-img" />
> </div>
<div class="id">{{ (currentPage - 1) * pageSize + idx + 1 }}</div> <div class="title"
<div class="title" :class="{ titleActive: companyActiveIndex === ((currentPage - 1) * pageSize + idx) }"> :class="{ titleActive: companyActiveIndex === ((currentPage - 1) * pageSize + idx) }">
{{ val.name }} {{ val.name }}
</div> </div>
<div class="icon"> <div class="icon">
...@@ -49,174 +36,66 @@ ...@@ -49,174 +36,66 @@
</div> </div>
</div> </div>
</div> </div>
<div class="footer-box"> </el-scrollbar>
<div class="left">{{ `共 ${companyList.length} 项` }}</div>
<div class="right">
<el-pagination
@current-change="handleCurrentChange"
:pageSize="pageSize"
:current-page="currentPage"
size="small"
background
layout="prev, pager, next"
:total="companyList.length"
/>
</div>
</div>
</div>
</div>
<div class="right">
<div class="box-header">
<div class="header-left"></div>
<div class="title">产业链分析</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<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>
</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>
</div>
</div>
<div class="right-main-content-main">
<Fishbone />
</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>
<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>
<div class="chart-box" id="graphChart"></div>
</div> </div>
<div class="company-dialog" v-if="isShowCompanyDialog"> <div class="left-pagination">
<div class="dialog-header"> <div class="left-pagination-left">{{ `共 ${filteredCompanyList.length} 项` }}</div>
<div class="logo"> <div class="left-pagination-right">
<img :src="companyInfo.logo" alt="" /> <el-pagination @current-change="handleCurrentChange" :pageSize="pageSize"
:current-page="currentPage" size="small" background layout="prev, pager, next"
:total="filteredCompanyList.length" />
</div> </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>
<div class="status-rate">
{{ companyInfo.changeRate + "%" }}
</div> </div>
<div class="close" @click="isShowCompanyDialog = false"> </AnalysisBox>
<img :src="closeIcon" alt="" />
</div> </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 class="dialog-main"> <div>产业链</div>
<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>
<div class="dialog-box1-main"> <div :class="['title-item', {'title-active': contentType==2}]" @click="headerContentType(2)">
<div class="item" v-for="(val, idx) in companyInfo.data1?.list" :key="idx"> <div class="title-icon">
<div class="item-left"> <img :src="contentType==2 ? icon422 : icon423" alt="">
<!-- <img :src="uncheckIcon" alt=""> -->
<img :src="checkedIcon" alt="" />
</div> </div>
<div class="item-right"> <div>实体关系</div>
<CommonPrompt :content="val">{{ val }}</CommonPrompt>
</div> </div>
</div> </div>
<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> </div>
<div class="dialog-box2"> </template>
<div class="dialog-box2-header"> <div class="box2-main">
<div class="icon"> <AiTips :tips="tips" />
<img :src="companyInfo.data2?.icon" alt="" /> <div class="graph-box" v-if="contentType==1">
</div> <ChartChain :listData="fishbone.list" :baseData="fishbone.base" />
<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 class="graph-box" v-if="contentType==2 && graphInfo.nodes.length">
<GraphChart :nodes="graphInfo.nodes" :links="graphInfo.links" layoutType="force" />
</div> </div>
</div> </div>
</AnalysisBox>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, nextTick, computed } from "vue"; import { ref, onMounted, onBeforeUnmount, nextTick, computed, watch, reactive } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import * as echarts from "echarts"; import * as echarts from "echarts";
const route = useRoute(); const route = useRoute();
import { getCompanyList, getIndustryHyly, getHylyList, getCompanyDetail } from "@/api/influence"; import { getCompanyList, getHylyList, getCompanyDetail } from "@/api/influence";
import getBarChart from "./utils/barChart";
import getLineChart from "./utils/lineChart"; import getLineChart from "./utils/lineChart";
import getBarChart1 from "./utils/barChart1"; import getBarChart1 from "./utils/barChart1";
import getGraph from "./utils/graph"; import getGraph from "./utils/graph";
...@@ -225,6 +104,7 @@ import getTreeChart from "./utils/treeChart"; ...@@ -225,6 +104,7 @@ import getTreeChart from "./utils/treeChart";
import downIcon from "./assets/images/down.png"; import downIcon from "./assets/images/down.png";
import upIcon from "./assets/images/up.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 CompanyLogo from "./assets/images/company-logo.png";
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";
...@@ -233,18 +113,33 @@ import uncheckIcon from "./assets/images/uncheck.png"; ...@@ -233,18 +113,33 @@ import uncheckIcon from "./assets/images/uncheck.png";
import checkedIcon from "./assets/images/checked.png"; import checkedIcon from "./assets/images/checked.png";
import closeIcon from "./assets/images/close.png"; import closeIcon from "./assets/images/close.png";
import { Search } from "@element-plus/icons-vue";
import Fishbone from "./components/fishbone.vue"; import Fishbone from "./components/fishbone.vue";
import CommonPrompt from "../../commonPrompt/index.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"; import CompanyImg from "./assets/images/symbol.png";
const ALL_INDUSTRY_VALUE = "__all_industry__";
const isShowCompanyDialog = ref(false); const isShowCompanyDialog = ref(false);
const box1BtnActive = ref(0); const handleIndustryChange = () => {
const handleClickBox1Btn = (industry, index) => {
box1BtnActive.value = index;
curHylyId.value = industry.id;
handleGetCompanyListById(); handleGetCompanyListById();
}; };
...@@ -258,32 +153,6 @@ const setChart = (option, chartId) => { ...@@ -258,32 +153,6 @@ const setChart = (option, chartId) => {
return chart; return chart;
}; };
const chart1Data = ref({
name: [],
value: [],
});
const showAllChart1 = ref(false);
const fullChart1Data = ref({ name: [], value: [] });
// const handleToggleChart1 = () => {
// showAllChart1.value = !showAllChart1.value;
// renderChart1();
// };
const renderChart1 = () => {
let dataName = fullChart1Data.value.name;
let dataValue = fullChart1Data.value.value;
// if (!showAllChart1.value && dataName.length > 5) {
// dataName = dataName.slice(0, 5);
// dataValue = dataValue.slice(0, 5);
// }
let chart1 = getBarChart(dataName, dataValue);
setChart(chart1, "chart1");
};
const industryActiveIndex = ref(0); const industryActiveIndex = ref(0);
const companyActiveIndex = ref(0); const companyActiveIndex = ref(0);
const curCompanyId = ref(""); const curCompanyId = ref("");
...@@ -292,23 +161,187 @@ const handleClickCompany = (val, index) => { ...@@ -292,23 +161,187 @@ const handleClickCompany = (val, index) => {
companyActiveIndex.value = (currentPage.value - 1) * pageSize.value + index; companyActiveIndex.value = (currentPage.value - 1) * pageSize.value + index;
if (val) { if (val) {
curCompanyId.value = val.id; curCompanyId.value = val.id;
handleGetCompanyDetail(); // handleGetCompanyDetail();
companyInfo.value.name = val.name; companyInfo.value.name = val.name;
companyInfo.value.status = val.status || companyInfo.value.status; 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); const pageSize = ref(10);
const currentPage = ref(1); const currentPage = ref(1);
const companyList = ref([]); // 企业列表 const companyList = ref([]); // 企业列表
// 当前企业列表 const companySearchKeyword = ref(""); // 企业搜索关键词
let companySearchTimer = null;
// 根据关键词筛选后的企业列表
const filteredCompanyList = computed(() => {
const keyword = companySearchKeyword.value?.trim().toLowerCase() || "";
if (!keyword) return companyList.value;
return companyList.value.filter((item) =>
(item.name || "").toLowerCase().includes(keyword)
);
});
// 当前页企业列表(分页 + 筛选)
const curCompanyList = computed(() => { const curCompanyList = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value; const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value; const endIndex = startIndex + pageSize.value;
return companyList.value.slice(startIndex, endIndex); return filteredCompanyList.value.slice(startIndex, endIndex);
}); });
// 处理页码改变事件 // 处理页码改变事件
...@@ -316,6 +349,23 @@ const handleCurrentChange = page => { ...@@ -316,6 +349,23 @@ const handleCurrentChange = page => {
currentPage.value = 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([ const industryList = ref([
// { // {
// "id": 1, // "id": 1,
...@@ -360,25 +410,35 @@ const companyInfo = ref({ ...@@ -360,25 +410,35 @@ const companyInfo = ref({
const handleGetHylyList = async () => { const handleGetHylyList = async () => {
try { try {
const res = await getHylyList(); const res = await getHylyList();
// console.log("行业领域字典列表", res); if (res.code === 200 && Array.isArray(res.data)) {
industryList.value = res.data industryList.value = [{ id: ALL_INDUSTRY_VALUE, name: "全部领域" }, ...res.data];
if (res.data && res.data.length > 0) { curHylyId.value = ALL_INDUSTRY_VALUE;
curHylyId.value = res.data[0].id; return;
}
industryList.value = [];
curHylyId.value = "";
} catch (error) {
industryList.value = [];
curHylyId.value = "";
} }
} catch (error) {}
}; };
const curHylyId = ref(""); const curHylyId = ref("");
// 根据行业领域id获取公司列表 // 根据行业领域id获取公司列表
const handleGetCompanyListById = async () => { const handleGetCompanyListById = async () => {
const params = { const params = {};
id: curHylyId.value const trimmedCompanyName = companySearchKeyword.value.trim();
}; if (curHylyId.value && curHylyId.value !== ALL_INDUSTRY_VALUE) {
params.id = curHylyId.value;
}
if (trimmedCompanyName) {
params.companyName = trimmedCompanyName;
}
try { try {
const res = await getCompanyList(params); const res = await getCompanyList(params);
// console.log('根据行业id获取公司里列表', res); // 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; companyList.value = res.data;
currentPage.value = 1; currentPage.value = 1;
nextTick(() => { nextTick(() => {
...@@ -387,27 +447,9 @@ const handleGetCompanyListById = async () => { ...@@ -387,27 +447,9 @@ const handleGetCompanyListById = async () => {
} else { } else {
companyList.value = []; companyList.value = [];
} }
} catch (error) {} } catch (error) {
}; companyList.value = [];
}
// 根据法案ID 获取行业领域统计
const handleGetIndustryHyly = async () => {
const params = {
id: window.sessionStorage.getItem("billId")
};
try {
const res = await getIndustryHyly(params);
// console.log('行业领域统计', res);
const data = res.data || [];
data.sort((a, b) => b.companyNum - a.companyNum);
fullChart1Data.value.name = data.map(item => {
return item.hylyName;
});
fullChart1Data.value.value = data.map(item => {
return item.companyNum;
});
renderChart1();
} catch (error) {}
}; };
// 根据法案id,公司id,行业领域id获取公司的详情 // 根据法案id,公司id,行业领域id获取公司的详情
...@@ -415,7 +457,7 @@ const handleGetCompanyDetail = async () => { ...@@ -415,7 +457,7 @@ const handleGetCompanyDetail = async () => {
const params = { const params = {
billId: window.sessionStorage.getItem("billId"), billId: window.sessionStorage.getItem("billId"),
companyId: curCompanyId.value, companyId: curCompanyId.value,
id: curHylyId.value id: curHylyId.value === ALL_INDUSTRY_VALUE ? "" : curHylyId.value
}; };
try { try {
const res = await getCompanyDetail(params); const res = await getCompanyDetail(params);
...@@ -554,7 +596,7 @@ const handleGetCompanyDetail = async () => { ...@@ -554,7 +596,7 @@ const handleGetCompanyDetail = async () => {
} else { } else {
companyInfo.value = {}; companyInfo.value = {};
} }
} catch (error) {} } catch (error) { }
}; };
...@@ -750,10 +792,6 @@ onMounted(async () => { ...@@ -750,10 +792,6 @@ onMounted(async () => {
setChart(graphChart, "graphChart"); setChart(graphChart, "graphChart");
await handleGetHylyList(); await handleGetHylyList();
handleGetCompanyListById(); handleGetCompanyListById();
await handleGetIndustryHyly();
// let chart1 = getBarChart(chart1Data.value.name, chart1Data.value.value);
// setChart(chart1, "chart1");
}); });
</script> </script>
...@@ -762,138 +800,122 @@ onMounted(async () => { ...@@ -762,138 +800,122 @@ onMounted(async () => {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
.box-header {
height: 56px; .left {
margin-top: 16px;
width: 480px;
height: 848px;
.left-main {
display: flex; display: flex;
position: relative; flex-direction: column;
.header-left { height: 100%;
margin-top: 18px; overflow: hidden;
width: 8px; padding: 10px 16px;
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
line-height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
} }
.header-right {
position: absolute; .left-center {
top: 14px; flex-shrink: 0;
right: 12px; height: 32px;
min-height: 32px;
display: flex; display: flex;
justify-content: flex-end; align-items: center;
gap: 4px; gap: 16px;
.more-btn { margin-bottom: 10px;
font-size: 14px;
color: #1677ff; :deep(.el-input__wrapper),
cursor: pointer; :deep(.el-select__wrapper) {
line-height: 28px; height: 32px;
} }
.icon {
width: 28px; :deep(.el-input__inner),
height: 28px; :deep(.el-select__selection) {
img { height: 32px;
width: 100%; line-height: 32px;
height: 100%;
} }
.left-center-select {
width: 150px;
flex-shrink: 0;
} }
.left-center-search {
flex: 1;
min-width: 0;
:deep(.el-input__wrapper) {
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 {
margin-top: 16px;
width: 480px;
height: 848px;
background: rgba(255, 255, 255);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.left-top {
margin: 0 auto;
width: 446px;
height: 200px;
box-sizing: border-box;
border: 1px solid rgba(231, 241, 255, 1);
border-radius: 4px;
background: linear-gradient(180deg, rgba(246, 251, 255, 1), rgba(246, 251, 255, 0) 100%);
} }
.left-center {
margin-top: 12px; .left-list {
height: 56px; flex: 1;
margin-left: 17px; min-height: 0;
overflow-x: hidden; overflow: hidden;
width: 445px;
overflow-x: auto;
.left-center-btn-box {
width: 1000px;
margin-top: 10px;
height: 28px;
display: flex; display: flex;
flex-wrap: nowrap; flex-direction: column;
.left-center-btn {
margin-right: 4px; .left-list-title {
height: 28px; width: 100%;
// width: 70px; text-align: left;
text-align: center; color: var(--text-primary-80-color);
padding: 0 5px;
box-sizing: border-box;
border-radius: 4px;
border: 1px solid var(--btn-plain-border-color);
background: var(--btn-plain-bg-color);
color: var(--btn-plain-text-color);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-size: 14px; font-size: 16px;
font-weight: 400; font-weight: 700;
line-height: 28px; border-top: 1px solid rgba(240, 242, 244, 1);
cursor: pointer; padding: 12px 0;
// overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.btnActive {
border: 1px solid var(--btn-active-border-color);
background: var(--btn-active-bg-color);
color: var(--btn-active-text-color);
}
} }
.left-list-content {
flex: 1;
min-height: 0;
} }
.left-footer {
margin: 0 auto;
width: 446px;
height: 520px;
overflow: hidden;
.item-box { .item-box {
height: 480px; width: 446px;
overflow: hidden; margin: 0 auto;
.item { .item {
width: 100%; width: 100%;
height: 48px; height: 48px;
border-radius: 4px; 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; display: flex;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: rgba(246, 251, 255, 1); background-color: #f7f8fa;
} }
.id {
&:first-child {
border-top-color: rgba(240, 242, 244, 1);
}
.item-icon {
margin-left: 12px; margin-left: 12px;
margin-top: 8px; margin-top: 8px;
width: 24px; width: 24px;
height: 24px; height: 24px;
border-radius: 12px; border-radius: 12px;
background: rgba(231, 241, 255); background: rgba(231, 241, 255);
text-align: center; display: flex;
line-height: 24px; align-items: center;
color: rgba(95, 101, 108, 1); justify-content: center;
font-family: Microsoft YaHei; overflow: hidden;
font-size: 12px;
font-weight: 400; .item-img {
width: 100%;
height: 100%;
object-fit: contain;
} }
}
.title { .title {
margin-top: 9px; margin-top: 9px;
margin-left: 10px; margin-left: 10px;
...@@ -908,51 +930,79 @@ onMounted(async () => { ...@@ -908,51 +930,79 @@ onMounted(async () => {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.icon { .icon {
margin-left: 7px; margin-left: 7px;
margin-top: 6px; margin-top: 6px;
width: 8px; width: 8px;
height: 6px; height: 6px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
} }
.itemActive { .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 { .titleActive {
color: rgba(22, 119, 255, 1) !important; color: rgba(22, 119, 255, 1) !important;
} }
} }
.footer-box { }
.left-pagination {
flex-shrink: 0;
margin: 0 auto;
width: 446px;
height: 65px;
overflow: hidden;
display: flex; display: flex;
align-items: flex-start;
justify-content: space-between; justify-content: space-between;
.left { width: 100%;
box-sizing: border-box;
.left-pagination-left {
width: 100px; width: 100px;
height: 20px; height: 18px;
margin-top: 25px;
color: rgba(95, 101, 108, 1); color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
line-height: 20px; line-height: 18px;
} overflow: hidden;
.right { white-space: nowrap;
flex: 300px;
} }
.left-pagination-right {
flex: 1;
min-width: 0;
display: flex;
justify-content: flex-end;
margin-top: 23px;
} }
} }
} }
.right { .right {
margin-top: 16px; margin-top: 16px;
margin-left: 16px; margin-left: 16px;
width: 1247px; width: 1247px;
height: 847px; height: 847px;
background: rgba(255, 255, 255);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
position: relative; position: relative;
.graph-dialog { .graph-dialog {
width: 740px; width: 740px;
height: 723px; height: 723px;
...@@ -961,6 +1011,7 @@ onMounted(async () => { ...@@ -961,6 +1011,7 @@ onMounted(async () => {
top: 55px; top: 55px;
z-index: 99999; z-index: 99999;
background: #fff; background: #fff;
.tool-box { .tool-box {
position: absolute; position: absolute;
left: 11px; left: 11px;
...@@ -975,31 +1026,37 @@ onMounted(async () => { ...@@ -975,31 +1026,37 @@ onMounted(async () => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
.tool { .tool {
width: 14px; width: 14px;
height: 14px; height: 14px;
cursor: pointer; cursor: pointer;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.tool1 { .tool1 {
width: 16px; width: 16px;
height: 16px; height: 16px;
cursor: pointer; cursor: pointer;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
} }
.chart-box { .chart-box {
width: 640px; width: 640px;
height: 640px; height: 640px;
margin: 50px auto; margin: 50px auto;
} }
} }
.company-dialog { .company-dialog {
position: absolute; position: absolute;
z-index: 999999; z-index: 999999;
...@@ -1011,22 +1068,26 @@ onMounted(async () => { ...@@ -1011,22 +1068,26 @@ onMounted(async () => {
border: 1px solid rgba(230, 231, 232, 1); border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px; border-radius: 4px;
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
.dialog-header { .dialog-header {
height: 54px; height: 54px;
box-sizing: border-box; box-sizing: border-box;
border-bottom: 1px solid rgba(243, 243, 244, 1); border-bottom: 1px solid rgba(243, 243, 244, 1);
display: flex; display: flex;
position: relative; position: relative;
.logo { .logo {
width: 48px; width: 48px;
height: 48px; height: 48px;
margin-left: 18px; margin-left: 18px;
margin-top: 3px; margin-top: 3px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.company-title { .company-title {
margin-left: 16px; margin-left: 16px;
margin-top: 16px; margin-top: 16px;
...@@ -1037,16 +1098,19 @@ onMounted(async () => { ...@@ -1037,16 +1098,19 @@ onMounted(async () => {
font-weight: 600; font-weight: 600;
line-height: 22px; line-height: 22px;
} }
.status-icon { .status-icon {
margin-left: 12px; margin-left: 12px;
margin-top: 12px; margin-top: 12px;
width: 8px; width: 8px;
height: 6px; height: 6px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.status-rate { .status-rate {
width: 41px; width: 41px;
margin-left: 4px; margin-left: 4px;
...@@ -1058,33 +1122,40 @@ onMounted(async () => { ...@@ -1058,33 +1122,40 @@ onMounted(async () => {
font-weight: 400; font-weight: 400;
line-height: 22px; line-height: 22px;
} }
.close { .close {
position: absolute; position: absolute;
right: 15px; right: 15px;
top: 19px; top: 19px;
width: 16px; width: 16px;
height: 16px; height: 16px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
} }
.dialog-main { .dialog-main {
margin-top: 13px; margin-top: 13px;
.dialog-box1 { .dialog-box1 {
.dialog-box1-header { .dialog-box1-header {
margin-top: 10px; margin-top: 10px;
display: flex; display: flex;
.icon { .icon {
margin-left: 16px; margin-left: 16px;
width: 18px; width: 18px;
height: 18px; height: 18px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.dialog-box1-title { .dialog-box1-title {
margin-top: 2px; margin-top: 2px;
margin-left: 6px; margin-left: 6px;
...@@ -1097,34 +1168,42 @@ onMounted(async () => { ...@@ -1097,34 +1168,42 @@ onMounted(async () => {
text-align: left; text-align: left;
} }
} }
.dialog-box1-main { .dialog-box1-main {
margin-top: 4px; margin-top: 4px;
margin-left: 18px; margin-left: 18px;
max-height: 120px; max-height: 120px;
overflow-y: auto; overflow-y: auto;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 4px; width: 4px;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
border-radius: 10px; border-radius: 10px;
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
border-radius: 0; border-radius: 0;
background: rgba(0, 0, 0, 0.1); background: rgba(0, 0, 0, 0.1);
} }
.item { .item {
display: flex; display: flex;
.item-left { .item-left {
margin-top: 13px; margin-top: 13px;
width: 14px; width: 14px;
height: 14px; height: 14px;
border-radius: 4px; border-radius: 4px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.item-right { .item-right {
margin-top: 8px; margin-top: 8px;
margin-left: 9px; margin-left: 9px;
...@@ -1139,21 +1218,26 @@ onMounted(async () => { ...@@ -1139,21 +1218,26 @@ onMounted(async () => {
} }
} }
} }
.dialog-box2 { .dialog-box2 {
margin-left: 16px; margin-left: 16px;
margin-top: 50px; margin-top: 50px;
.dialog-box2-header { .dialog-box2-header {
margin-top: 10px; margin-top: 10px;
display: flex; display: flex;
.icon { .icon {
margin-left: 16px; margin-left: 16px;
width: 18px; width: 18px;
height: 18px; height: 18px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.dialog-box2-title { .dialog-box2-title {
margin-top: 2px; margin-top: 2px;
margin-left: 6px; margin-left: 6px;
...@@ -1166,6 +1250,7 @@ onMounted(async () => { ...@@ -1166,6 +1250,7 @@ onMounted(async () => {
text-align: left; text-align: left;
} }
} }
.dialog-box2-main { .dialog-box2-main {
margin-top: 16px; margin-top: 16px;
width: 446px; width: 446px;
...@@ -1176,21 +1261,26 @@ onMounted(async () => { ...@@ -1176,21 +1261,26 @@ onMounted(async () => {
background: linear-gradient(180deg, rgba(246, 251, 255, 1), rgba(246, 251, 255, 0) 100%); background: linear-gradient(180deg, rgba(246, 251, 255, 1), rgba(246, 251, 255, 0) 100%);
} }
} }
.dialog-box3 { .dialog-box3 {
margin-top: 16px; margin-top: 16px;
margin-left: 16px; margin-left: 16px;
.dialog-box3-header { .dialog-box3-header {
margin-top: 10px; margin-top: 10px;
display: flex; display: flex;
.icon { .icon {
margin-left: 16px; margin-left: 16px;
width: 18px; width: 18px;
height: 18px; height: 18px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.dialog-box3-title { .dialog-box3-title {
margin-top: 2px; margin-top: 2px;
margin-left: 6px; margin-left: 6px;
...@@ -1203,6 +1293,7 @@ onMounted(async () => { ...@@ -1203,6 +1293,7 @@ onMounted(async () => {
text-align: left; text-align: left;
} }
} }
.dialog-box3-main { .dialog-box3-main {
margin-top: 16px; margin-top: 16px;
width: 446px; width: 446px;
...@@ -1220,18 +1311,22 @@ onMounted(async () => { ...@@ -1220,18 +1311,22 @@ onMounted(async () => {
width: 1218px; width: 1218px;
height: 784px; height: 784px;
margin: 5px auto; margin: 5px auto;
.right-main-content { .right-main-content {
width: 1150px; width: 1150px;
height: 720px; height: 720px;
margin: 4px 0 10px; margin: 4px 0 10px;
.right-main-content-header { .right-main-content-header {
height: 84px; height: 84px;
margin-left: 14px; margin-left: 14px;
margin-top: 16px; margin-top: 16px;
// background: orange; // background: orange;
display: flex; display: flex;
.header-item1 { .header-item1 {
width: 408px; width: 408px;
.header-item1-top { .header-item1-top {
position: relative; position: relative;
z-index: 3; z-index: 3;
...@@ -1246,18 +1341,22 @@ onMounted(async () => { ...@@ -1246,18 +1341,22 @@ onMounted(async () => {
font-weight: 700; font-weight: 700;
line-height: 28px; line-height: 28px;
} }
.header-item1-bottom { .header-item1-bottom {
display: flex; display: flex;
justify-content: center; justify-content: center;
.icon { .icon {
margin-top: 9px; margin-top: 9px;
width: 16px; width: 16px;
height: 16px; height: 16px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.text { .text {
margin-top: 7px; margin-top: 7px;
margin-left: 8px; margin-left: 8px;
...@@ -1270,9 +1369,11 @@ onMounted(async () => { ...@@ -1270,9 +1369,11 @@ onMounted(async () => {
} }
} }
} }
.header-item2 { .header-item2 {
margin-left: -8px; margin-left: -8px;
width: 408px; width: 408px;
.header-item2-top { .header-item2-top {
position: relative; position: relative;
z-index: 2; z-index: 2;
...@@ -1287,18 +1388,22 @@ onMounted(async () => { ...@@ -1287,18 +1388,22 @@ onMounted(async () => {
font-weight: 700; font-weight: 700;
line-height: 28px; line-height: 28px;
} }
.header-item2-bottom { .header-item2-bottom {
display: flex; display: flex;
justify-content: center; justify-content: center;
.icon { .icon {
margin-top: 9px; margin-top: 9px;
width: 16px; width: 16px;
height: 16px; height: 16px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.text { .text {
margin-top: 7px; margin-top: 7px;
margin-left: 8px; margin-left: 8px;
...@@ -1311,9 +1416,11 @@ onMounted(async () => { ...@@ -1311,9 +1416,11 @@ onMounted(async () => {
} }
} }
} }
.header-item3 { .header-item3 {
margin-left: -8px; margin-left: -8px;
width: 408px; width: 408px;
.header-item3-top { .header-item3-top {
position: relative; position: relative;
z-index: 1; z-index: 1;
...@@ -1328,18 +1435,22 @@ onMounted(async () => { ...@@ -1328,18 +1435,22 @@ onMounted(async () => {
font-weight: 700; font-weight: 700;
line-height: 28px; line-height: 28px;
} }
.header-item3-bottom { .header-item3-bottom {
display: flex; display: flex;
justify-content: center; justify-content: center;
.icon { .icon {
margin-top: 9px; margin-top: 9px;
width: 16px; width: 16px;
height: 16px; height: 16px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.text { .text {
margin-top: 7px; margin-top: 7px;
margin-left: 8px; margin-left: 8px;
...@@ -1353,12 +1464,15 @@ onMounted(async () => { ...@@ -1353,12 +1464,15 @@ onMounted(async () => {
} }
} }
} }
.right-main-content-main { .right-main-content-main {
height: 600px; height: 600px;
margin-top: 20px; margin-top: 20px;
} }
// background: orange; // background: orange;
} }
.box-footer { .box-footer {
width: 1218px; width: 1218px;
height: 40px; height: 40px;
...@@ -1367,16 +1481,19 @@ onMounted(async () => { ...@@ -1367,16 +1481,19 @@ onMounted(async () => {
margin: 0 auto; margin: 0 auto;
margin-top: 6px; margin-top: 6px;
background: rgba(246, 251, 255, 1); background: rgba(246, 251, 255, 1);
.box-footer-left { .box-footer-left {
margin-top: 10px; margin-top: 10px;
margin-left: 12px; margin-left: 12px;
width: 19px; width: 19px;
height: 20px; height: 20px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.box-footer-center { .box-footer-center {
margin-left: 13px; margin-left: 13px;
margin-top: 8px; margin-top: 8px;
...@@ -1388,6 +1505,7 @@ onMounted(async () => { ...@@ -1388,6 +1505,7 @@ onMounted(async () => {
font-weight: 400; font-weight: 400;
line-height: 24px; line-height: 24px;
} }
.box-footer-right { .box-footer-right {
margin-left: 13px; margin-left: 13px;
margin-top: 8px; margin-top: 8px;
...@@ -1398,12 +1516,86 @@ onMounted(async () => { ...@@ -1398,12 +1516,86 @@ onMounted(async () => {
background: #e7f1ff; background: #e7f1ff;
box-sizing: border-box; box-sizing: border-box;
padding: 3px; padding: 3px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
.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 { img {
width: 100%; width: 100%;
height: 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;
}
} }
} }
} }
......
<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 {
......
...@@ -77,7 +77,7 @@ import { useRoute } from "vue-router"; ...@@ -77,7 +77,7 @@ import { useRoute } from "vue-router";
import AiBox from "@/components/AiBox.vue"; import AiBox from "@/components/AiBox.vue";
import { getPersonType } from "@/api/common/index"; import { getPersonType } from "@/api/common/index";
// import { useDraggable } from "@vueuse/core"; // import { useDraggable } from "@vueuse/core";
import ModuleHeader from '@/components/base/ModuleHeader/index.vue' import ModuleHeader from '@/components/base/moduleHeader/index.vue'
import Menu1 from "@/assets/icons/overview/menu1.png"; import Menu1 from "@/assets/icons/overview/menu1.png";
import Menu2 from "@/assets/icons/overview/menu2.png"; import Menu2 from "@/assets/icons/overview/menu2.png";
...@@ -120,7 +120,7 @@ const handleGetPersonType = async () => { ...@@ -120,7 +120,7 @@ const handleGetPersonType = async () => {
personTypeList.value = []; personTypeList.value = [];
} }
window.sessionStorage.setItem("personTypeList", JSON.stringify(personTypeList.value)); window.sessionStorage.setItem("personTypeList", JSON.stringify(personTypeList.value));
} catch (error) {} } catch (error) { }
}; };
const isCurrentOverview = computed(() => { const isCurrentOverview = computed(() => {
...@@ -298,7 +298,7 @@ body { ...@@ -298,7 +298,7 @@ body {
text-align: justify; text-align: justify;
} }
.el-popper[data-popper-placement^="top"] > .el-popper__arrow:before { .el-popper[data-popper-placement^="top"]>.el-popper__arrow:before {
display: none; display: none;
} }
......
<template> <template>
<el-scrollbar> <el-scrollbar>
<div class="flex-display common-page top-box-news-deatail" style="align-items: center;"> <div class="flex-display common-page top-box-news-deatail" style="align-items: center">
<!-- <color-svg :svg-url="NewsLogo" :size="72" style="margin-right:10px"></color-svg> --> <!-- <color-svg :svg-url="NewsLogo" :size="72" style="margin-right:10px"></color-svg> -->
<img :src="NewsLogo" style="margin-right:24px"> <img :src="NewsLogo" style="margin-right: 24px" />
<el-space direction="vertical" class="flex-fill" alignment="flex-start"> <el-space direction="vertical" class="flex-fill" alignment="flex-start">
<common-text class="text-title-1-bold" color="var(--text-primary-80-color)"> <common-text class="text-title-1-bold" color="var(--text-primary-80-color)">
{{ newsDetail.titleZh }} {{ newsDetail.titleZh }}
...@@ -28,38 +28,63 @@ ...@@ -28,38 +28,63 @@
</div> </div>
<div class="flex-display common-page content-box-news-detail"> <div class="flex-display common-page content-box-news-detail">
<el-space direction="vertical" class="background-as-card flex-fill" fill alignment="flex-start"> <el-space direction="vertical" class="background-as-card flex-fill" fill alignment="flex-start">
<div style="margin-top: 10px; margin-right: 24px;gap:10px" class="flex-display"> <div style="margin-top: 10px; margin-right: 24px; gap: 10px" class="flex-display">
<color-prefix-title height="20px"> <color-prefix-title height="20px">
<div class="text-title-2-bold">新闻内容</div> <div class="text-title-2-bold">新闻内容</div>
</color-prefix-title> </color-prefix-title>
<div class="flex-fill"></div> <div class="flex-fill"></div>
<el-switch v-model="isHightLightEntity" active-text="高亮实体" @change="handleHighlightEntity" /> <el-switch v-model="isHightLightEntity" active-text="高亮实体" @change="handleHighlightEntity" />
<el-button v-if="textZns.length > 0" :type="isOpenTranslation ? 'primary' : ''" plain <el-button
@click="handleTranslation"> v-if="textZns.length > 0"
<color-svg :svg-url="TranslationSvg" color="var(--color-primary-100)" :size="18" :type="isOpenTranslation ? 'primary' : ''"
style="margin-right:10px"></color-svg> plain
@click="handleTranslation"
>
<color-svg
:svg-url="TranslationSvg"
color="var(--color-primary-100)"
:size="18"
style="margin-right: 10px"
></color-svg>
译文 译文
</el-button> </el-button>
</div> </div>
<text-translate-pane class="common-padding-h" :texts-raw="textEns" :texts-translate="textZns" <text-translate-pane
:text-entities="textEntities" :is-open-translation="isOpenTranslation" class="common-padding-h"
:is-highlight-entity="isHightLightEntity"> :texts-raw="textEns"
:texts-translate="textZns"
:text-entities="textEntities"
:is-open-translation="isOpenTranslation"
:is-highlight-entity="isHightLightEntity"
@on-entity-click="e => gotoSearchResults(e.text_span, '')"
>
</text-translate-pane> </text-translate-pane>
<div> <div>
<img v-if="newsDetail.coverUrl" class="common-padding" :src="newsDetail.coverUrl" :width="320" <img
:height="240" /> v-if="newsDetail.coverUrl"
class="common-padding"
:src="newsDetail.coverUrl"
:width="320"
:height="240"
/>
</div> </div>
</el-space> </el-space>
<el-space direction="vertical" class="background-as-card relation-news-box" fill> <el-space direction="vertical" class="background-as-card relation-news-box" fill>
<el-space style="margin-top: 10px;"> <el-space style="margin-top: 10px">
<color-prefix-title height="20px"> <color-prefix-title height="20px">
<div class="text-title-2-bold">相关新闻</div> <div class="text-title-2-bold">相关新闻</div>
</color-prefix-title> </color-prefix-title>
</el-space> </el-space>
<el-space v-if="relationNews?.length > 0" direction="vertical" fill class="common-padding"> <el-space v-if="relationNews?.length > 0" direction="vertical" fill class="common-padding">
<news-item-mini v-for="item in relationNews" :key="item.newsId" :news="item" :img="item.newsImage" <news-item-mini
:title="item.newsTitle" :from="`${item.newsDate} · ${item.newsOrg}`" v-for="item in relationNews"
@click="gotoNewsDetail(item.newsId)" /> :key="item.newsId"
:news="item"
:img="item.newsImage"
:title="item.newsTitle"
:from="`${item.newsDate} · ${item.newsOrg}`"
@click="gotoNewsDetail(item.newsId)"
/>
</el-space> </el-space>
<el-empty v-else style=""></el-empty> <el-empty v-else style=""></el-empty>
</el-space> </el-space>
...@@ -68,21 +93,22 @@ ...@@ -68,21 +93,22 @@
</template> </template>
<script setup> <script setup>
import { getNewsDetail } from "@/api/news/newsBrief"; import { getNewsDetail } from "@/api/news/newsBrief";
import '@/styles/container.scss'; import "@/styles/container.scss";
import '@/styles/common.scss'; import "@/styles/common.scss";
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { ElSpace, ElButton, ElScrollbar, ElSwitch, ElEmpty, ElImage } from "element-plus"; import { ElSpace, ElButton, ElScrollbar, ElSwitch, ElEmpty, ElImage } from "element-plus";
import CommonText from "@/components/base/texts/CommonText.vue"; import CommonText from "@/components/base/texts/CommonText.vue";
import AreaTag from "@/components/base/AreaTag/index.vue"; import AreaTag from "@/components/base/AreaTag/index.vue";
import ColorPrefixTitle from '@/components/base/texts/ColorPrefixTitle.vue'; import ColorPrefixTitle from "@/components/base/texts/ColorPrefixTitle.vue";
import { getRelationNews } from "@/api/news/newsDetail"; import { getRelationNews } from "@/api/news/newsDetail";
import NewsItemMini from "@/components/base/newsList/NewsItemMini.vue"; import NewsItemMini from "@/components/base/newsList/NewsItemMini.vue";
import ColorSvg from "@/components/base/images/ColorSvg.vue"; import ColorSvg from "@/components/base/images/ColorSvg.vue";
import TranslationSvg from './assets/images/翻译 1.svg'; import TranslationSvg from "./assets/images/翻译 1.svg";
import NewsLogo from './assets/images/组合 293.svg'; import NewsLogo from "./assets/images/组合 293.svg";
import { extractTextEntity } from "@/api/intelligent"; import { extractTextEntity } from "@/api/intelligent";
import { useGotoNewsDetail } from "@/router/modules/news"; import { useGotoNewsDetail } from "@/router/modules/news";
import { useGotoSearchResults } from "@/router/modules/comprehensiveSearch";
import TextTranslatePane from "@/components/base/texts/TextTranslatePane.vue"; import TextTranslatePane from "@/components/base/texts/TextTranslatePane.vue";
const newsDetail = ref({}); const newsDetail = ref({});
...@@ -94,35 +120,34 @@ const textZns = ref([]); ...@@ -94,35 +120,34 @@ const textZns = ref([]);
const textEns = ref([]); const textEns = ref([]);
const route = useRoute(); const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail(); const gotoNewsDetail = useGotoNewsDetail();
const gotoSearchResults = useGotoSearchResults();
onMounted(async () => { onMounted(async () => {
const params = { const params = {
newsId: route.params.id newsId: route.params.id
} };
const { data: newsDetailData } = await getNewsDetail(params); const { data: newsDetailData } = await getNewsDetail(params);
newsDetail.value = newsDetailData ?? {}; newsDetail.value = newsDetailData ?? {};
textZns.value = newsDetail.value?.contentZh?.split('\n') ?? []; textZns.value = newsDetail.value?.contentZh?.split("\n") ?? [];
textEns.value = newsDetail.value?.content?.split('\n') ?? []; textEns.value = newsDetail.value?.content?.split("\n") ?? [];
const { data: relationNewsData } = await getRelationNews(params); const { data: relationNewsData } = await getRelationNews(params);
relationNews.value = relationNewsData ?? []; relationNews.value = relationNewsData ?? [];
await handleHighlightEntity(); await handleHighlightEntity();
}); });
async function handleHighlightEntity() { async function handleHighlightEntity() {
if (textEntities.value.length > 0) return if (textEntities.value.length > 0 || (!newsDetail.value?.contentZh && !newsDetail.value?.content)) return;
const { result: entityDataZh } = await extractTextEntity(newsDetail.value?.contentZh ?? ''); const { result: entityDataZh } = await extractTextEntity(newsDetail.value.contentZh ?? "");
textEntities.value = [...entityDataZh ?? []] textEntities.value = [...(entityDataZh ?? [])];
if (newsDetail.value.contentZh !== newsDetail.value.content) { if (newsDetail.value.contentZh !== newsDetail.value.content) {
const { result: entityData } = await extractTextEntity(newsDetail.value?.content ?? ''); const { result: entityData } = await extractTextEntity(newsDetail.value.content ?? "");
textEntities.value = [...textEntities.value, ...entityData ?? []] textEntities.value = [...textEntities.value, ...(entityData ?? [])];
} }
console.log(isHightLightEntity.value) console.log(isHightLightEntity.value);
} }
function handleTranslation() { function handleTranslation() {
isOpenTranslation.value = !isOpenTranslation.value; isOpenTranslation.value = !isOpenTranslation.value;
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import url("./style.css"); @import url("./style.css");
......
...@@ -25,18 +25,19 @@ ...@@ -25,18 +25,19 @@
<img src="@/assets/icons/subject-icon.png" /> <img src="@/assets/icons/subject-icon.png" />
</template> </template>
<el-space :size="16" direction="vertical" fill class="full-width common-padding"> <el-space :size="16" direction="vertical" fill class="full-width common-padding">
<el-space v-for="(item, index) in subjectData.slice(0, 3)" :key="index" class="mouse-hover" <el-space v-for="(item, index) in subjectData.slice(0, 3)" :key="index"
@click="() => gotoNewsDetail(item.newsId)" alignment="center"> @click="() => gotoNewsDetail(item.newsId)" alignment="center">
<common-text class="text-bold" <common-text class="text-bold text-hover"
:color="index === 0 ? 'var(--color-red-100)' : (index === 1 ? 'var(--color-orange-100)' : 'var(--text-primary-65-color)')"> :color="index === 0 ? 'var(--color-red-100)' : (index === 1 ? 'var(--color-orange-100)' : 'var(--text-primary-65-color)')">
{{ `${index + 1}` }} {{ `${index + 1}` }}
</common-text> </common-text>
<common-text class="text-bold" color="var(--text-primary-80-color)">{{ <common-text class="text-bold text-hover" color="var(--text-primary-80-color)">{{
item.newsTitle item.newsTitle
}}</common-text> }}</common-text>
</el-space> </el-space>
<el-space v-for="(item, index) in subjectData.slice(3)" :key="index" class="mouse-hover"> <el-space v-for="(item, index) in subjectData.slice(3)" :key="index"
<common-text class="text-regular" color="var(--text-primary-80-color)">{{ @click="() => gotoNewsDetail(item.newsId)">
<common-text class="text-regular text-hover" color="var(--text-primary-80-color)">{{
"• " + item.newsTitle "• " + item.newsTitle
}}</common-text> }}</common-text>
</el-space> </el-space>
...@@ -48,6 +49,7 @@ ...@@ -48,6 +49,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import '@/styles/common.scss';
import '@/styles/container.scss'; import '@/styles/container.scss';
import '@/styles/radio.scss'; import '@/styles/radio.scss';
import { useGotoNewsModule, useGotoNewsDetail } from "@/router/modules/news"; import { useGotoNewsModule, useGotoNewsDetail } from "@/router/modules/news";
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论