提交 44759732 authored 作者: 付康's avatar 付康

合并分支 'liuyuqi' 到 'pre'

Liuyuqi 查看合并请求 !340
流水线 #457 已通过 于阶段
in 3 分 48 秒
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<TextStyle /> <TextStyle />
<div class="text-title-1-show">通用样式/组件</div> <div class="text-title-1-show">通用样式/组件</div>
<div style="position: relative; min-height: 700px;"> <div style="position: relative; min-height: 700px;">
<el-tabs tabPosition="left" class="tabs-nav-no-wrap left-float-nav-tabs"> <el-tabs tabPosition="left" class="tabs-nav-no-wrap left-float-nav-tabs dev-style-tabs">
<el-tab-pane label="通用" lazy> <el-tab-pane label="通用" lazy>
<common-page /> <common-page />
</el-tab-pane> </el-tab-pane>
...@@ -113,4 +113,10 @@ import WorkingBox from './WorkingBox/index.vue' ...@@ -113,4 +113,10 @@ import WorkingBox from './WorkingBox/index.vue'
.box { .box {
padding-bottom: 20px; padding-bottom: 20px;
} }
.dev-style-tabs {
:deep(.el-tabs__content) {
display: block !important;
}
}
</style> </style>
\ No newline at end of file
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
<img src="./assets/icon-star.svg" alt="" class="btn-icon" /> <img src="./assets/icon-star.svg" alt="" class="btn-icon" />
</div> </div>
<div class="mainBox"> <div class="mainBox">
<div class="graph" id="relGraph"></div> <div v-show="activeIndex !== '关系图'" class="graph" id="relGraph"></div>
<RelationChart v-if="activeIndex === '关系图' && graphData.rootId" :graph-data="graphData" />
</div> </div>
</div> </div>
</template> </template>
...@@ -27,6 +28,7 @@ import { ref, onMounted, onBeforeUnmount, watch } from "vue"; ...@@ -27,6 +28,7 @@ import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { getCharacterGlobalInfo, getCharacterRelation } from "@/api/characterPage/characterPage.js"; import { getCharacterGlobalInfo, getCharacterRelation } from "@/api/characterPage/characterPage.js";
import "default-passive-events"; import "default-passive-events";
import RelationChart from '@/components/base/RelationChart/index.vue';
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
...@@ -95,6 +97,7 @@ const nodes = ref([]); ...@@ -95,6 +97,7 @@ const nodes = ref([]);
const links = ref([]); const links = ref([]);
const characterInfo = ref({}); const characterInfo = ref({});
const CharacterRelation = ref([]); const CharacterRelation = ref([]);
const graphData = ref({ rootId: '', nodes: [], lines: [] });
const list = ref([ const list = ref([
{ {
...@@ -108,8 +111,8 @@ const list = ref([ ...@@ -108,8 +111,8 @@ const list = ref([
icon: new URL("./assets/icon-force.svg", import.meta.url).href, icon: new URL("./assets/icon-force.svg", import.meta.url).href,
}, },
{ {
value: "树形布局", value: "关系图",
label: "树形布局", label: "关系图",
icon: new URL("./assets/icon-tree.svg", import.meta.url).href, icon: new URL("./assets/icon-tree.svg", import.meta.url).href,
}, },
]); ]);
...@@ -204,6 +207,15 @@ const getCharacterRelationFn = async () => { ...@@ -204,6 +207,15 @@ const getCharacterRelationFn = async () => {
nodes.value = [centerNode]; nodes.value = [centerNode];
links.value = []; links.value = [];
} }
// 构建关系图数据
const gNodes = [{ id: 'center', text: characterInfo.value.name || '' }];
const gLines = [];
CharacterRelation.value.forEach((item, index) => {
gNodes.push({ id: `node_${index}`, text: item.name });
gLines.push({ from: 'center', to: `node_${index}`, text: item.relation || '' });
});
graphData.value = { rootId: 'center', nodes: gNodes, lines: gLines };
}; };
// ========== 渲染图表 ========== // ========== 渲染图表 ==========
......
...@@ -84,7 +84,7 @@ export function useBills() { ...@@ -84,7 +84,7 @@ export function useBills() {
} }
if (filters.selectedTimeRanges.length > 0 && !filters.selectedTimeRanges.includes('all')) { if (filters.selectedTimeRanges.length > 0 && !filters.selectedTimeRanges.includes('all')) {
params.years = filters.selectedTimeRanges params.years = filters.selectedTimeRanges.map(Number)
} }
params.isCN = filters.isCN params.isCN = filters.isCN
......
...@@ -68,10 +68,9 @@ ...@@ -68,10 +68,9 @@
color: 'rgba(59, 65, 75, 1)' color: 'rgba(59, 65, 75, 1)'
}" :row-class-name="tableRowClassName" :row-style="{ height: '60px' }" size="large"> }" :row-class-name="tableRowClassName" :row-style="{ height: '60px' }" size="large">
<el-table-column prop="rank" label="排名" width="100" align="center" /> <el-table-column prop="rank" label="排名" width="100" align="center" />
<el-table-column prop="contributor" label="贡献者" min-width="300" /> <el-table-column prop="contributor" label="贡献者" min-width="200" />
<el-table-column prop="totalAmount" label="总捐款" width="150" /> <el-table-column prop="totalAmount" label="总捐款" width="150" />
<el-table-column prop="individualAmount" label="个人捐款" width="150" /> <el-table-column prop="donationYear" label="捐款年份" width="150" />
<el-table-column prop="pacsAmount" label="PACs捐款" width="150" />
</el-table> </el-table>
<div class="table-pagination"> <div class="table-pagination">
<span class="table-pagination-total">共{{ fundTotal }}项</span> <span class="table-pagination-total">共{{ fundTotal }}项</span>
...@@ -109,7 +108,7 @@ ...@@ -109,7 +108,7 @@
<img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img <img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img
src="./assets/type2.png" alt="" v-else /> src="./assets/type2.png" alt="" v-else />
</div> </div>
<div class="content"> <div class="content" style="cursor: pointer;" @click="gotoNewsDetail(item.newsId)">
<div <div
:class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }"> :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }">
...@@ -389,7 +388,9 @@ const handleBillMoreClick = (bill) => { ...@@ -389,7 +388,9 @@ const handleBillMoreClick = (bill) => {
}; };
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useGotoNewsDetail } from '@/router/modules/news.js';
const route = useRoute(); const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail();
const personId = ref(route.query.personId || "Y000064"); const personId = ref(route.query.personId || "Y000064");
const newsTab = ref('history') const newsTab = ref('history')
// 处理图片代理 // 处理图片代理
...@@ -490,24 +491,17 @@ const handleChangeYear = () => { ...@@ -490,24 +491,17 @@ const handleChangeYear = () => {
const yearList = ref([ const yearList = ref([]);
{ const generateYearList = () => {
label: "全部", const currentYear = new Date().getFullYear();
value: 'all' const list = [{ label: "全部", value: 'all' }];
}, for (let i = 0; i < 5; i++) {
{ const year = currentYear - i;
label: "2025", list.push({ label: String(year), value: year });
value: 2025
},
{
label: "2024",
value: 2024
},
{
label: "2023",
value: 2023
} }
]); yearList.value = list;
};
generateYearList();
const CharacterFundSource = ref([]); const CharacterFundSource = ref([]);
const getCharacterFundSourceFn = async () => { const getCharacterFundSourceFn = async () => {
...@@ -516,7 +510,7 @@ const getCharacterFundSourceFn = async () => { ...@@ -516,7 +510,7 @@ const getCharacterFundSourceFn = async () => {
pageSize: 4, pageSize: 4,
currentPage: fundCurrentPage.value - 1, currentPage: fundCurrentPage.value - 1,
}; };
if (selectedOption.value !== '全部') { if (selectedOption.value !== 'all') {
params.year = selectedOption.value; params.year = selectedOption.value;
} }
...@@ -528,10 +522,9 @@ const getCharacterFundSourceFn = async () => { ...@@ -528,10 +522,9 @@ const getCharacterFundSourceFn = async () => {
CharacterFundSource.value = res.data.content.map((item, index) => { CharacterFundSource.value = res.data.content.map((item, index) => {
return { return {
rank: index + 1, rank: index + 1,
contributor: item.orgName, contributor: item.companyName,
totalAmount: item.totalDonation, totalAmount: item.totalDonation,
individualAmount: item.personalDonation, donationYear: item.donationYear
pacsAmount: item.pacsDonation
} }
}); });
...@@ -627,7 +620,8 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -627,7 +620,8 @@ const getCharacterLatestDynamicFn = async () => {
time: item.time, time: item.time,
industryList: item.industryList || ["人工智能"], industryList: item.industryList || ["人工智能"],
orgName: item.orgName, orgName: item.orgName,
remarks: item.remarks remarks: item.remarks,
newsId: item.newsId
})); }));
total.value = res.data.totalElements; total.value = res.data.totalElements;
} else { } else {
...@@ -730,7 +724,7 @@ const info = ref(["人物详情", "历史提案", "人物关系" ]); ...@@ -730,7 +724,7 @@ const info = ref(["人物详情", "历史提案", "人物关系" ]);
const infoActive = ref("人物详情"); const infoActive = ref("人物详情");
const num = ref(['全部', "2025", "2024", "2023", "2022", "2021", "2020"]); const num = ref(['全部', "2025", "2024", "2023", "2022", "2021", "2020"]);
const numActive = ref("全部"); const numActive = ref("全部");
const selectedOption = ref("全部"); const selectedOption = ref("all");
const dialogVisible = ref(false); const dialogVisible = ref(false);
const currentTag = ref(null) const currentTag = ref(null)
......
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
<img src="./assets/icon-star.svg" alt="" class="btn-icon" /> <img src="./assets/icon-star.svg" alt="" class="btn-icon" />
</div> </div>
<div class="mainBox"> <div class="mainBox">
<div class="graph" id="relGraph"></div> <div v-show="activeIndex !== '关系图'" class="graph" id="relGraph"></div>
<RelationChart v-if="activeIndex === '关系图' && graphData.rootId" :graph-data="graphData" />
</div> </div>
</div> </div>
</template> </template>
...@@ -27,6 +28,7 @@ import { ref, onMounted, onBeforeUnmount, watch } from 'vue' ...@@ -27,6 +28,7 @@ import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { getCharacterGlobalInfo, getCharacterRelation } from '@/api/characterPage/characterPage.js' import { getCharacterGlobalInfo, getCharacterRelation } from '@/api/characterPage/characterPage.js'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import RelationChart from '@/components/base/RelationChart/index.vue'
const route = useRoute() const route = useRoute()
const personId = ref(route.query.personId || 'Y000064') const personId = ref(route.query.personId || 'Y000064')
...@@ -79,6 +81,7 @@ const nodes = ref([]) ...@@ -79,6 +81,7 @@ const nodes = ref([])
const links = ref([]) const links = ref([])
const characterInfo = ref({}) const characterInfo = ref({})
const CharacterRelation = ref([]) const CharacterRelation = ref([])
const graphData = ref({ rootId: '', nodes: [], lines: [] })
const list = ref([ const list = ref([
{ {
...@@ -92,8 +95,8 @@ const list = ref([ ...@@ -92,8 +95,8 @@ const list = ref([
icon: new URL('./assets/icon-force.svg', import.meta.url).href, icon: new URL('./assets/icon-force.svg', import.meta.url).href,
}, },
{ {
value: '树形布局', value: '关系图',
label: '树形布局', label: '关系图',
icon: new URL('./assets/icon-tree.svg', import.meta.url).href, icon: new URL('./assets/icon-tree.svg', import.meta.url).href,
}, },
]) ])
...@@ -193,6 +196,15 @@ const getCharacterRelationFn = async () => { ...@@ -193,6 +196,15 @@ const getCharacterRelationFn = async () => {
nodes.value = [centerNode] nodes.value = [centerNode]
links.value = [] links.value = []
} }
// 构建关系图数据
const gNodes = [{ id: 'center', text: characterInfo.value.name || '' }]
const gLines = []
CharacterRelation.value.forEach((item, index) => {
gNodes.push({ id: `node_${index}`, text: item.name })
gLines.push({ from: 'center', to: `node_${index}`, text: item.relation || '' })
})
graphData.value = { rootId: 'center', nodes: gNodes, lines: gLines }
} }
const handleChangeLayout = (value) => { const handleChangeLayout = (value) => {
......
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
<img src="./assets/type1.png" alt="" v-if="item.remarks === true" /> <img src="./assets/type1.png" alt="" v-if="item.remarks === true" />
<img src="./assets/type2.png" alt="" v-else /> <img src="./assets/type2.png" alt="" v-else />
</div> </div>
<div class="content"> <div class="content" style="cursor: pointer;" @click="gotoNewsDetail(item.newsId)">
<div <div
:class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }"> :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }">
<p v-if="item.remarks === true" class="content-title1">{{ item.content }}</p> <p v-if="item.remarks === true" class="content-title1">{{ item.content }}</p>
...@@ -273,7 +273,9 @@ import DefaultIcon1 from '@/assets/icons/default-icon1.png' ...@@ -273,7 +273,9 @@ import DefaultIcon1 from '@/assets/icons/default-icon1.png'
import DefaultIcon2 from '@/assets/icons/default-icon2.png' import DefaultIcon2 from '@/assets/icons/default-icon2.png'
import { Close } from '@element-plus/icons-vue' import { Close } from '@element-plus/icons-vue'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useGotoNewsDetail } from '@/router/modules/news.js';
const route = useRoute(); const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail();
const areaList = ref([]) const areaList = ref([])
const personId = ref(route.query.personId || "Y000064"); const personId = ref(route.query.personId || "Y000064");
const wordloading=ref(false) const wordloading=ref(false)
...@@ -545,7 +547,8 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -545,7 +547,8 @@ const getCharacterLatestDynamicFn = async () => {
time: item.time, time: item.time,
industryList: item.industryList || ["人工智能"], industryList: item.industryList || ["人工智能"],
orgName: item.orgName, orgName: item.orgName,
remarks: item.remarks remarks: item.remarks,
newsId: item.newsId
})); }));
total.value = res.data.totalElements; total.value = res.data.totalElements;
} else { } else {
......
...@@ -20,11 +20,15 @@ ...@@ -20,11 +20,15 @@
<button <button
:class="['news-tab', { active: activeTab === 'local' }]" :class="['news-tab', { active: activeTab === 'local' }]"
@click="$emit('update:activeTab', 'local')" @click="$emit('update:activeTab', 'local')"
> 报告</button> > 智库报告</button>
<button <button
:class="['news-tab', { active: activeTab === 'capital' }]" :class="['news-tab', { active: activeTab === 'capital' }]"
@click="$emit('update:activeTab', 'capital')" @click="$emit('update:activeTab', 'capital')"
> 项目</button> > 调查项目</button>
<button
:class="['news-tab', { active: activeTab === 'hearing' }]"
@click="$emit('update:activeTab', 'hearing')"
> 听证会</button>
</div> </div>
<div class="news-sort" ref="sortDropdownRef"> <div class="news-sort" ref="sortDropdownRef">
......
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
:sort-by="sortBy" :sort-by="sortBy"
@update:sort="updateSort" @update:sort="updateSort"
/> />
<div class="news-body">
<!-- 智库报告 / 调查项目 -->
<div class="news-body" v-if="activeTab !== 'hearing'">
<NewsSidebar <NewsSidebar
:domain-options="domainOptions" :domain-options="domainOptions"
:time-options="timeOptions" :time-options="timeOptions"
...@@ -30,6 +32,77 @@ ...@@ -30,6 +32,77 @@
/> />
</div> </div>
</div> </div>
<!-- 听证会 -->
<div class="hearing-body" v-else>
<div class="hearing-left">
<div class="hearing-filter-box">
<div class="hearing-filter-header">
<div class="hearing-filter-icon"></div>
<div class="hearing-filter-title">科技领域</div>
</div>
<div class="hearing-filter-main">
<el-checkbox-group class="hearing-checkbox-group" :model-value="hearingSelectedDomains" @change="handleHearingDomainChange">
<el-checkbox class="hearing-filter-checkbox" label="全部领域">全部领域</el-checkbox>
<el-checkbox v-for="item in domainOptions" :key="item.id" class="hearing-filter-checkbox" :label="item.label">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="hearing-filter-box">
<div class="hearing-filter-header">
<div class="hearing-filter-icon"></div>
<div class="hearing-filter-title">发布时间</div>
</div>
<div class="hearing-filter-main">
<el-checkbox-group class="hearing-checkbox-group" :model-value="hearingSelectedTimes" @change="handleHearingTimeChange">
<el-checkbox class="hearing-filter-checkbox" label="全部时间">全部时间</el-checkbox>
<el-checkbox v-for="item in timeOptions" :key="item.id" class="hearing-filter-checkbox" :label="item.label">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
<div class="hearing-right">
<div class="hearing-card-box">
<div class="hearing-card-content">
<template v-if="hearingList.length > 0">
<div v-for="(item, index) in hearingList" :key="item.id">
<div class="hearing-card-item">
<img class="hearing-card-img" :src="item.coverImgUrl" alt="" />
<div class="hearing-card-text">
<div class="hearing-card-title">{{ item.titleZh }}</div>
<div class="hearing-card-time">
{{ item.testimonyDate + ' · ' + item.thinkTankName + ' · ' + item.committeeZh }}
</div>
<div class="hearing-card-tags" v-if="item.domains && item.domains.length">
<AreaTag v-for="(tag, i) in item.domains" :key="i" :tagName="tag" />
</div>
</div>
</div>
<div class="hearing-divider" v-if="index !== hearingList.length - 1"></div>
</div>
</template>
<el-empty v-else description="暂无数据" :image-size="80" />
</div>
</div>
<div class="hearing-footer" v-if="hearingList.length > 0">
<div class="hearing-footer-info">共 {{ hearingTotal }} 篇国会听证会</div>
<el-pagination
:page-size="hearingPageSize"
background
layout="prev, pager, next"
:total="hearingTotal"
@current-change="handleHearingPageChange"
:current-page="hearingCurrentPage"
/>
</div>
</div>
</div>
</div> </div>
</template> </template>
...@@ -40,6 +113,7 @@ import NewsTopBar from './NewsTopBar.vue' ...@@ -40,6 +113,7 @@ import NewsTopBar from './NewsTopBar.vue'
import NewsSidebar from './NewsSidebar.vue' import NewsSidebar from './NewsSidebar.vue'
import NewsCard from './NewsCard.vue' import NewsCard from './NewsCard.vue'
import NewsPagination from './NewsPagination.vue' import NewsPagination from './NewsPagination.vue'
import AreaTag from '@/components/base/AreaTag/index.vue'
import { getIndustryKeyList } from '@/api/bill/billHome.js' import { getIndustryKeyList } from '@/api/bill/billHome.js'
import { getFindingsReport, getInvestigationProject } from '@/api/characterPage/characterPage.js' import { getFindingsReport, getInvestigationProject } from '@/api/characterPage/characterPage.js'
...@@ -63,6 +137,57 @@ const timeOptions = ref([]) ...@@ -63,6 +137,57 @@ const timeOptions = ref([])
const selectedDomains = ref(['all']) const selectedDomains = ref(['all'])
const selectedTimes = ref(['all']) const selectedTimes = ref(['all'])
// ====== 听证会 mock 数据 ======
const hearingSelectedDomains = ref(['全部领域'])
const hearingSelectedTimes = ref(['全部时间'])
const hearingCurrentPage = ref(1)
const hearingPageSize = 10
const hearingTotal = ref(0)
const hearingList = ref([])
const MOCK_HEARINGS = [
{ id: 1, titleZh: '人工智能在国防领域的应用前景与风险', testimonyDate: '2026-03-15', thinkTankName: '兰德公司', committeeZh: '众议院军事委员会', coverImgUrl: '', domains: ['人工智能', '国防安全'] },
{ id: 2, titleZh: '半导体供应链安全:对华出口管制的成效评估', testimonyDate: '2026-02-28', thinkTankName: '战略与国际研究中心', committeeZh: '参议院外交委员会', coverImgUrl: '', domains: ['半导体', '国际贸易'] },
{ id: 3, titleZh: '量子计算技术发展与美国国家安全战略', testimonyDate: '2026-01-20', thinkTankName: '布鲁金斯学会', committeeZh: '众议院科学委员会', coverImgUrl: '', domains: ['量子计算', '国家安全'] },
{ id: 4, titleZh: '中美科技竞争背景下的新兴技术标准制定', testimonyDate: '2025-12-10', thinkTankName: '卡内基国际和平基金会', committeeZh: '参议院商务委员会', coverImgUrl: '', domains: ['技术标准', '中美关系'] },
{ id: 5, titleZh: '生物技术发展与全球健康安全治理', testimonyDate: '2025-11-05', thinkTankName: '兰德公司', committeeZh: '众议院能源与商业委员会', coverImgUrl: '', domains: ['生物技术', '公共卫生'] },
{ id: 6, titleZh: '太空领域军备控制的挑战与机遇', testimonyDate: '2025-10-18', thinkTankName: '战略与国际研究中心', committeeZh: '参议院军事委员会', coverImgUrl: '', domains: ['太空技术', '国防安全'] },
{ id: 7, titleZh: '5G/6G网络基础设施安全与数据隐私保护', testimonyDate: '2025-09-22', thinkTankName: '布鲁金斯学会', committeeZh: '众议院能源与商业委员会', coverImgUrl: '', domains: ['通信技术', '数据安全'] },
{ id: 8, titleZh: '气候变化对国家安全的影响及应对策略', testimonyDate: '2025-08-14', thinkTankName: '卡内基国际和平基金会', committeeZh: '参议院环境与公共工程委员会', coverImgUrl: '', domains: ['气候变化', '能源技术'] },
]
function loadMockHearings() {
hearingTotal.value = MOCK_HEARINGS.length
const start = (hearingCurrentPage.value - 1) * hearingPageSize
const end = start + hearingPageSize
hearingList.value = MOCK_HEARINGS.slice(start, end).map(item => ({
...item,
coverImgUrl: item.coverImgUrl || 'https://via.placeholder.com/56x77?text=Hearing'
}))
}
function handleHearingDomainChange(val) {
if (val.includes('全部领域')) {
hearingSelectedDomains.value = ['全部领域']
} else {
hearingSelectedDomains.value = val.length > 0 ? val : ['全部领域']
}
}
function handleHearingTimeChange(val) {
if (val.includes('全部时间')) {
hearingSelectedTimes.value = ['全部时间']
} else {
hearingSelectedTimes.value = val.length > 0 ? val : ['全部时间']
}
}
function handleHearingPageChange(page) {
hearingCurrentPage.value = page
loadMockHearings()
}
// ====== 原有逻辑 ======
async function loadFilterOptions() { async function loadFilterOptions() {
const res = await getIndustryKeyList() const res = await getIndustryKeyList()
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
...@@ -86,8 +211,8 @@ function buildParams({ includeSearchText = false } = {}) { ...@@ -86,8 +211,8 @@ function buildParams({ includeSearchText = false } = {}) {
currentPage: currentPage.value - 1, currentPage: currentPage.value - 1,
pageSize, pageSize,
sortFun: sortBy.value === 'publishTimeAsc', sortFun: sortBy.value === 'publishTimeAsc',
industryIds: selectedDomains.value.includes('all') ? '' : JSON.stringify(selectedDomains.value), industryIds: selectedDomains.value.includes('all') ? '' : selectedDomains.value,
years: selectedTimes.value.includes('all') ? '' : JSON.stringify(selectedTimes.value), years: selectedTimes.value.includes('all') ? '' : selectedTimes.value.map(Number),
} }
if (includeSearchText) { if (includeSearchText) {
params.searchText = searchText.value params.searchText = searchText.value
...@@ -186,8 +311,10 @@ watch(activeTab, (val) => { ...@@ -186,8 +311,10 @@ watch(activeTab, (val) => {
currentPage.value = 1 currentPage.value = 1
if (val === 'local') { if (val === 'local') {
loadNews() loadNews()
} else { } else if (val === 'capital') {
loadProjects() loadProjects()
} else if (val === 'hearing') {
loadMockHearings()
} }
}) })
...@@ -245,4 +372,148 @@ onMounted(async () => { ...@@ -245,4 +372,148 @@ onMounted(async () => {
align-content: flex-start; align-content: flex-start;
align-items: flex-start; align-items: flex-start;
} }
/* ====== 听证会布局 ====== */
.hearing-body {
display: flex;
gap: 16px;
align-items: flex-start;
}
.hearing-left {
width: 360px;
flex-shrink: 0;
padding-bottom: 24px;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
background: rgba(255, 255, 255, 1);
}
.hearing-filter-box {
margin-top: 16px;
}
.hearing-filter-header {
display: flex;
gap: 17px;
padding-left: 16px;
}
.hearing-filter-icon {
width: 8px;
height: 16px;
background: rgba(5, 95, 194, 1);
border-radius: 0 4px 4px 0;
margin-top: 4px;
}
.hearing-filter-title {
height: 24px;
color: rgba(5, 95, 194, 1);
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
}
.hearing-filter-main {
margin-left: 24px;
margin-top: 12px;
}
.hearing-checkbox-group {
display: grid;
grid-template-columns: repeat(2, 160px);
gap: 8px 4px;
}
.hearing-filter-checkbox {
width: 160px;
height: 24px;
margin-right: 0 !important;
}
:deep(.hearing-filter-checkbox .el-checkbox__label) {
font-size: 16px;
}
.hearing-right {
flex: 1;
min-width: 0;
}
.hearing-card-box {
background: rgba(255, 255, 255, 1);
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
}
.hearing-card-content {
padding: 33px 36px 27px 37px;
}
.hearing-card-item {
width: 100%;
display: flex;
}
.hearing-card-img {
width: 56px;
height: 77px;
margin-right: 22px;
flex-shrink: 0;
background: rgba(234, 236, 238, 1);
border-radius: 4px;
object-fit: cover;
}
.hearing-card-text {
display: flex;
flex-direction: column;
}
.hearing-card-title {
color: rgb(59, 65, 75);
font-size: 18px;
font-weight: 700;
line-height: 22px;
margin-bottom: 2px;
cursor: pointer;
}
.hearing-card-time {
color: rgb(95, 101, 108);
font-size: 14px;
font-weight: 400;
line-height: 22px;
margin-bottom: 7px;
}
.hearing-card-tags {
display: flex;
gap: 8px;
height: 24px;
}
.hearing-divider {
height: 1px;
background: rgb(234, 236, 238);
margin: 16px 0;
}
.hearing-footer {
margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.hearing-footer-info {
color: rgba(132, 136, 142, 1);
font-size: 14px;
font-weight: 400;
line-height: 18px;
}
</style> </style>
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
<img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img <img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img
src="./assets/type2.png" alt="" v-else /> src="./assets/type2.png" alt="" v-else />
</div> </div>
<div class="content"> <div class="content" style="cursor: pointer;" @click="gotoNewsDetail(item.newsId)">
<div <div
:class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }"> :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }">
<p v-if="item.remarks === true" class="content-title1"> <p v-if="item.remarks === true" class="content-title1">
...@@ -202,7 +202,9 @@ import DefaultIcon1 from '@/assets/icons/default-icon1.png' ...@@ -202,7 +202,9 @@ import DefaultIcon1 from '@/assets/icons/default-icon1.png'
import DefaultIcon2 from '@/assets/icons/default-icon2.png' import DefaultIcon2 from '@/assets/icons/default-icon2.png'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useGotoNewsDetail } from '@/router/modules/news.js';
const route = useRoute(); const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail();
const personId = ref(route.query.personId || "Y000064"); const personId = ref(route.query.personId || "Y000064");
const boxHeight = computed(() => { const boxHeight = computed(() => {
if(characterBasicInfo.value.organizationList==undefined) return '625px' if(characterBasicInfo.value.organizationList==undefined) return '625px'
...@@ -370,7 +372,8 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -370,7 +372,8 @@ const getCharacterLatestDynamicFn = async () => {
time: item.time, time: item.time,
industryList: item.industryList || ["人工智能"], industryList: item.industryList || ["人工智能"],
orgName: item.orgName, orgName: item.orgName,
remarks: item.remarks remarks: item.remarks,
newsId: item.newsId
})); }));
total.value = res.data.totalElements; total.value = res.data.totalElements;
} else { } else {
......
...@@ -12,12 +12,12 @@ export const CHARACTER_CONFIG = { ...@@ -12,12 +12,12 @@ export const CHARACTER_CONFIG = {
useImageProxy: false, useImageProxy: false,
headerTagType: "areaTag", headerTagType: "areaTag",
wordCloudTitle: "科技观点", wordCloudTitle: "科技观点",
yearDefault: "全部时间", yearDefault: "全部",
yearBuildMode: "dynamic", yearBuildMode: "dynamic",
yearStaticOptions: [], yearStaticOptions: [],
showFundSource: false, showFundSource: false,
resumeMode: "inline", resumeMode: "inline",
resumeTitle: "职业履历", resumeTitle: "生涯履历",
resumeHeight: "1336px", resumeHeight: "1336px",
companySectionTitle: "实体信息", companySectionTitle: "实体信息",
basicInfoFields: [ basicInfoFields: [
...@@ -51,13 +51,13 @@ export const CHARACTER_CONFIG = { ...@@ -51,13 +51,13 @@ export const CHARACTER_CONFIG = {
headerTagType: "inline", headerTagType: "inline",
wordCloudTitle: "科技观点", wordCloudTitle: "科技观点",
yearDefault: "全部", yearDefault: "全部",
yearBuildMode: "static", yearBuildMode: "dynamic",
yearStaticOptions: ["全部", "2025", "2024", "2023", "2022", "2021", "2020"], yearStaticOptions: [],
showFundSource: true, showFundSource: true,
resumeMode: "inline", resumeMode: "inline",
resumeTitle: "职业履历", resumeTitle: "生涯履历",
resumeHeight: "1556px", resumeHeight: "1556px",
companySectionTitle: "社交媒体", companySectionTitle: "实体信息",
basicInfoFields: [ basicInfoFields: [
{ label: "出生日期:", key: "birthday", type: "text" }, { label: "出生日期:", key: "birthday", type: "text" },
{ label: "现任职位:", key: "positionTitle", type: "text" }, { label: "现任职位:", key: "positionTitle", type: "text" },
...@@ -89,13 +89,13 @@ export const CHARACTER_CONFIG = { ...@@ -89,13 +89,13 @@ export const CHARACTER_CONFIG = {
headerTagType: "areaTag", headerTagType: "areaTag",
wordCloudTitle: "核心观点", wordCloudTitle: "核心观点",
yearDefault: "全部", yearDefault: "全部",
yearBuildMode: "static", yearBuildMode: "dynamic",
yearStaticOptions: ["全部", "2026", "2025", "2024", "2023", "2022", "2021"], yearStaticOptions: [],
showFundSource: false, showFundSource: false,
resumeMode: "card", resumeMode: "inline",
resumeTitle: "政治履历", resumeTitle: "生涯履历",
resumeHeight: null, resumeHeight: null,
companySectionTitle: "社交媒体", companySectionTitle: "实体信息",
basicInfoFields: [ basicInfoFields: [
{ label: "出生日期:", key: "birthday", type: "text" }, { label: "出生日期:", key: "birthday", type: "text" },
{ label: "现任职位:", key: "positionTitle", type: "text" }, { label: "现任职位:", key: "positionTitle", type: "text" },
......
...@@ -4,6 +4,20 @@ ...@@ -4,6 +4,20 @@
<div class="header"> <div class="header">
<div class="avatar"> <div class="avatar">
<el-avatar :size="160" shape="circle" :src="avatarUrl" /> <el-avatar :size="160" shape="circle" :src="avatarUrl" />
<div class="person-tags">
<div class="person-tag-bg" v-if="characterInfo.party === 'Republican' || characterInfo.party === '共和党'">
<img :src="getTagIconUrl('1')" class="tag-icon" alt="" />
</div>
<div class="person-tag-bg" v-else-if="characterInfo.party && characterInfo.party !== 'Republican' && characterInfo.party !== '共和党'">
<img :src="getTagIconUrl('2')" class="tag-icon" alt="" />
</div>
<div class="person-tag-bg person-tag-right" v-if="isHouse(characterInfo.congress)">
<img :src="getCongressIconUrl('congress1')" class="tag-icon" alt="" />
</div>
<div class="person-tag-bg person-tag-right" v-else-if="isSenate(characterInfo.congress)">
<img :src="getCongressIconUrl('congress2')" class="tag-icon" alt="" />
</div>
</div>
</div> </div>
<div class="info"> <div class="info">
<p class="name-cn">{{ characterInfo.name }}</p> <p class="name-cn">{{ characterInfo.name }}</p>
...@@ -81,10 +95,9 @@ ...@@ -81,10 +95,9 @@
color: 'rgba(59, 65, 75, 1)' color: 'rgba(59, 65, 75, 1)'
}" :row-class-name="tableRowClassName" :row-style="{ height: '50px' }" size="large"> }" :row-class-name="tableRowClassName" :row-style="{ height: '50px' }" size="large">
<el-table-column prop="rank" label="排名" width="80" align="center" /> <el-table-column prop="rank" label="排名" width="80" align="center" />
<el-table-column prop="contributor" label="贡献者" min-width="300" /> <el-table-column prop="contributor" label="贡献者" min-width="200" />
<el-table-column prop="totalAmount" label="总捐款" width="150" align="right" /> <el-table-column prop="totalAmount" label="总捐款" width="150" align="right" />
<el-table-column prop="individualAmount" label="个人捐款" width="150" align="right" /> <el-table-column prop="donationYear" label="捐款年份" width="150" align="right" />
<el-table-column prop="pacsAmount" label="PACs捐款" width="150" align="right" />
</el-table> </el-table>
<div class="table-pagination"> <div class="table-pagination">
<span class="table-pagination-total">共{{ fundTotal }}项</span> <span class="table-pagination-total">共{{ fundTotal }}项</span>
...@@ -122,7 +135,7 @@ ...@@ -122,7 +135,7 @@
</div> </div>
<div class="timeline-line-bottom" v-if="idx !== CharacterLatestDynamic.length - 1"></div> <div class="timeline-line-bottom" v-if="idx !== CharacterLatestDynamic.length - 1"></div>
</div> </div>
<div class="content"> <div class="content" :style="{ cursor: item.remarks === false ? 'pointer' : 'default' }" @click="item.remarks === false && gotoNewsDetail(item.newsId)">
<div :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }"> <div :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }">
<p v-if="item.remarks === true" class="content-title1">{{ item.content }}</p> <p v-if="item.remarks === true" class="content-title1">{{ item.content }}</p>
<p v-else class="content-title2">{{ item.title }}</p> <p v-else class="content-title2">{{ item.title }}</p>
...@@ -151,7 +164,7 @@ ...@@ -151,7 +164,7 @@
<div class="right"> <div class="right">
<!-- 基本信息 --> <!-- 基本信息 -->
<AnalysisBox title="基本信息" width="520px" :height="boxHeight" :show-all-btn="false" class="right-top" v-if="characterBasicInfo"> <AnalysisBox title="基本信息" width="520px" :show-all-btn="false" class="right-top auto-height-box" v-if="characterBasicInfo">
<div class="main-content"> <div class="main-content">
<div class="baseInfo"> <div class="baseInfo">
<div v-for="field in config.basicInfoFields" :key="field.key" class="baseInfo-item"> <div v-for="field in config.basicInfoFields" :key="field.key" class="baseInfo-item">
...@@ -183,7 +196,7 @@ ...@@ -183,7 +196,7 @@
</div> </div>
<div class="company"> <div class="company">
<div class="company-title">{{ config.companySectionTitle }}</div> <div class="company-title">{{ config.companySectionTitle }}</div>
<div class="company-content"> <div class="company-content" v-if="characterBasicInfo.organizationList && characterBasicInfo.organizationList.length > 0">
<div v-for="item in characterBasicInfo.organizationList" :key="item" <div v-for="item in characterBasicInfo.organizationList" :key="item"
class="company-item"> class="company-item">
<img :src="item.imageUrl ? item.imageUrl : DefaultIcon2" alt="" /> <img :src="item.imageUrl ? item.imageUrl : DefaultIcon2" alt="" />
...@@ -193,6 +206,7 @@ ...@@ -193,6 +206,7 @@
</div> </div>
</div> </div>
</div> </div>
<el-empty v-else description="暂无数据" :image-size="60" />
</div> </div>
</div> </div>
</AnalysisBox> </AnalysisBox>
...@@ -322,6 +336,7 @@ ...@@ -322,6 +336,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, nextTick } from "vue"; import { ref, computed, onMounted, nextTick } from "vue";
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useGotoNewsDetail } from '@/router/modules/news.js';
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"; import WordCloudChart from "@/components/base/WordCloundChart/index.vue";
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'; import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue';
import AreaTag from '@/components/base/AreaTag/index.vue'; import AreaTag from '@/components/base/AreaTag/index.vue';
...@@ -350,6 +365,7 @@ const props = defineProps({ ...@@ -350,6 +365,7 @@ const props = defineProps({
}); });
const route = useRoute(); const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail();
const config = computed(() => CHARACTER_CONFIG[Number(props.type)] || CHARACTER_CONFIG[1]); const config = computed(() => CHARACTER_CONFIG[Number(props.type)] || CHARACTER_CONFIG[1]);
const personIdRef = ref(props.personId || route.query.personId || "Y000064"); const personIdRef = ref(props.personId || route.query.personId || "Y000064");
...@@ -383,6 +399,26 @@ const typeIcon2 = computed(() => typeAssets.value.type2 || ''); ...@@ -383,6 +399,26 @@ const typeIcon2 = computed(() => typeAssets.value.type2 || '');
const resumeIcon01 = computed(() => typeAssets.value.icon01 || ''); const resumeIcon01 = computed(() => typeAssets.value.icon01 || '');
const resumeIcon02 = computed(() => typeAssets.value.icon02 || ''); const resumeIcon02 = computed(() => typeAssets.value.icon02 || '');
// 政党/国会图标(同 technologyFigures/PersonTable 逻辑)
const getTagIconUrl = (tag) => {
const icons = import.meta.glob('../../../technologyFigures/assets/images/header-icon*.png', { eager: true, as: 'url' });
const iconPath = `../../../technologyFigures/assets/images/header-icon${tag}.png`;
return icons[iconPath] || '';
};
const getCongressIconUrl = (type) => {
const icons = import.meta.glob('../../../technologyFigures/assets/images/congress*.svg', { eager: true, as: 'url' });
const iconPath = `../../../technologyFigures/assets/images/${type}.svg`;
return icons[iconPath] || '';
};
const isHouse = (text) => {
if (!text) return false;
return text.includes('众议院') || text.toLowerCase().includes('house');
};
const isSenate = (text) => {
if (!text) return false;
return text.includes('参议院') || text.toLowerCase().includes('senate');
};
// 子组件 // 子组件
import CharacterRelationships from '../techLeader/components/characterRelationships/index.vue'; import CharacterRelationships from '../techLeader/components/characterRelationships/index.vue';
import RelevantSituation from '../techLeader/components/relevantSituation/index.vue'; import RelevantSituation from '../techLeader/components/relevantSituation/index.vue';
...@@ -438,12 +474,17 @@ const fundCurrentPage = ref(1); ...@@ -438,12 +474,17 @@ const fundCurrentPage = ref(1);
const fundPageSize = ref(4); const fundPageSize = ref(4);
const fundTotal = ref(0); const fundTotal = ref(0);
const selectedOption = ref("all"); const selectedOption = ref("all");
const yearList = ref([ const yearList = ref([]);
{ label: "全部", value: "all" }, const generateYearList = () => {
{ label: "2025", value: 2025 }, const currentYear = new Date().getFullYear();
{ label: "2024", value: 2024 }, const list = [{ label: "全部", value: "all" }];
{ label: "2023", value: 2023 } for (let i = 0; i < 5; i++) {
]); const year = currentYear - i;
list.push({ label: String(year), value: year });
}
yearList.value = list;
};
generateYearList();
const isChecked = ref(false); const isChecked = ref(false);
const related = ref('N'); const related = ref('N');
...@@ -577,7 +618,8 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -577,7 +618,8 @@ const getCharacterLatestDynamicFn = async () => {
time: item.time, time: item.time,
industryList: item.industryList || ["人工智能"], industryList: item.industryList || ["人工智能"],
orgName: item.orgName, orgName: item.orgName,
remarks: item.remarks remarks: item.remarks,
newsId: item.newsId
})); }));
total.value = res.data.totalElements; total.value = res.data.totalElements;
} else { } else {
...@@ -632,10 +674,9 @@ const getCharacterFundSourceFn = async () => { ...@@ -632,10 +674,9 @@ const getCharacterFundSourceFn = async () => {
fundTotal.value = res.data.totalElements; fundTotal.value = res.data.totalElements;
CharacterFundSource.value = res.data.content.map((item, index) => ({ CharacterFundSource.value = res.data.content.map((item, index) => ({
rank: index + 1, rank: index + 1,
contributor: item.orgName, contributor: item.companyName,
totalAmount: item.totalDonation, totalAmount: item.totalDonation,
individualAmount: item.personalDonation, donationYear: item.donationYear
pacsAmount: item.pacsDonation
})); }));
} }
} catch (error) { } } catch (error) { }
...@@ -754,13 +795,41 @@ onMounted(() => { ...@@ -754,13 +795,41 @@ onMounted(() => {
width: 160px; width: 160px;
height: 160px; height: 160px;
margin-right: 24px; margin-right: 24px;
overflow: hidden; position: relative;
flex-shrink: 0;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
/* 政党/国会标签 */
.person-tags {
display: flex;
justify-content: center;
gap: 6px;
margin-top: -23px;
width: 160px;
position: relative;
z-index: 1;
}
.person-tag-bg {
width: 36px;
height: 36px;
background: rgba(255, 255, 255, 0.8);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.tag-icon {
width: 28px;
height: 28px;
object-fit: contain;
}
} }
.info { .info {
......
<template> <template>
<div class="character-page"> <div class="character-page">
<img src="./assets/images/background.png" alt="" class="bg" /> <img src="./assets/images/background.png" alt="" class="bg" />
<ModuleHeader />
<!-- 主要内容 --> <!-- 主要内容 -->
<div class="main"> <div class="main">
<unified-character :type="type" :person-id="personId" /> <unified-character :type="type" :person-id="personId" />
...@@ -13,7 +12,6 @@ ...@@ -13,7 +12,6 @@
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import UnifiedCharacter from './components/unified/index.vue'; import UnifiedCharacter from './components/unified/index.vue';
import ModuleHeader from '@/components/base/moduleHeader/index.vue';
import { getCharacterGlobalInfo } from "@/api/characterPage/characterPage.js"; import { getCharacterGlobalInfo } from "@/api/characterPage/characterPage.js";
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -11,16 +11,25 @@ ...@@ -11,16 +11,25 @@
<div class="table-body"> <div class="table-body">
<div class="table-row" v-for="(item, index) in personList" :key="index"> <div class="table-row" v-for="(item, index) in personList" :key="index">
<!-- 人物信息列 --> <!-- 人物信息列 -->
<div class="row-col col-person"> <div class="row-col col-person" @click="handleClickToCharacter(item.personId)">
<div style="margin: 7px 12px 7px 24px;"> <div style="margin: 7px 12px 7px 24px; position: relative;">
<img :src="item.avatar" class="avatar" alt="avatar" /> <img :src="item.avatar" class="avatar" alt="avatar" />
<div class="person-tags"> <div class="person-tags">
<div class="person-tag-bg" v-for="(tag, tIdx) in item.tags" :key="tIdx"> <div class="person-tag-bg" v-if="item.party === 'Republican' || item.party === '共和党'">
<img :src="getTagIconUrl(tag)" class="tag-icon" alt="tag" /> <img :src="getTagIconUrl('1')" class="tag-icon" alt="tag" />
</div> </div>
<div class="person-tag-bg" v-else-if="item.party && item.party !== 'Republican' && item.party !== '共和党'">
<img :src="getTagIconUrl('2')" class="tag-icon" alt="tag" />
</div> </div>
<div class="person-tag-bg person-tag-right" v-if="isHouse(item.congress)">
<img :src="getCongressIconUrl('congress1')" class="tag-icon" alt="congress" />
</div> </div>
<div class="person-info" @click="handleClickToCharacter(item.personId)"> <div class="person-tag-bg person-tag-right" v-else-if="isSenate(item.congress)">
<img :src="getCongressIconUrl('congress2')" class="tag-icon" alt="congress" />
</div>
</div>
</div>
<div class="person-info">
<div class="person-name">{{ item.name }}</div> <div class="person-name">{{ item.name }}</div>
<div class="person-position">{{ item.position }}</div> <div class="person-position">{{ item.position }}</div>
</div> </div>
...@@ -67,13 +76,23 @@ watch(() => [props.persontypeid,props.yearSelect], (val) => { ...@@ -67,13 +76,23 @@ watch(() => [props.persontypeid,props.yearSelect], (val) => {
}) })
const getTagIconUrl = (tag) => { const getTagIconUrl = (tag) => {
// 用 import.meta.glob 预加载所有图标,支持动态匹配
const icons = import.meta.glob('../assets/images/header-icon*.png', { eager: true, as: 'url' }); const icons = import.meta.glob('../assets/images/header-icon*.png', { eager: true, as: 'url' });
// 拼接对应路径,匹配预加载的图标
const iconPath = `../assets/images/header-icon${tag}.png`; const iconPath = `../assets/images/header-icon${tag}.png`;
// 兜底:如果没有对应tag的图片,返回默认图或空
return icons[iconPath] || ''; return icons[iconPath] || '';
}; };
const getCongressIconUrl = (type) => {
const icons = import.meta.glob('../assets/images/congress*.svg', { eager: true, as: 'url' });
const iconPath = `../assets/images/${type}.svg`;
return icons[iconPath] || '';
};
const isHouse = (congress) => {
if (!congress) return false;
return congress.includes('众议院') || congress.toLowerCase().includes('house');
};
const isSenate = (congress) => {
if (!congress) return false;
return congress.includes('参议院') || congress.toLowerCase().includes('senate');
};
// 获取主要人物涉华观点统计 // 获取主要人物涉华观点统计
const handlegetMainCharactersViewFn = async () => { const handlegetMainCharactersViewFn = async () => {
const params = { const params = {
...@@ -90,7 +109,8 @@ const handlegetMainCharactersViewFn = async () => { ...@@ -90,7 +109,8 @@ const handlegetMainCharactersViewFn = async () => {
avatar: item.personImage, avatar: item.personImage,
name: item.personName, name: item.personName,
position: item.positionTitle, position: item.positionTitle,
tags: ["1", "2"], party: item.party || '',
congress: item.congress || '',
chinaRelatedCount: item.remarksCount, chinaRelatedCount: item.remarksCount,
mediaQuoteCount: item.remarksCount, mediaQuoteCount: item.remarksCount,
personId:item.personId personId:item.personId
...@@ -197,6 +217,7 @@ const getProgress = count => (count / getMaxCount()) * 100; ...@@ -197,6 +217,7 @@ const getProgress = count => (count / getMaxCount()) * 100;
/* 人物列样式 */ /* 人物列样式 */
.col-person { .col-person {
align-items: flex-start; align-items: flex-start;
cursor: pointer;
} }
.avatar { .avatar {
...@@ -244,12 +265,15 @@ const getProgress = count => (count / getMaxCount()) * 100; ...@@ -244,12 +265,15 @@ const getProgress = count => (count / getMaxCount()) * 100;
.person-tags { .person-tags {
display: flex; display: flex;
justify-content: space-between;
margin-top: -20px; margin-top: -20px;
width: 42px; width: 42px;
text-align: center; text-align: center;
} }
.person-tag-right {
}
.person-tag-bg { .person-tag-bg {
/* 椭圆 6 */ /* 椭圆 6 */
width: 20px; width: 20px;
......
...@@ -227,6 +227,12 @@ const handlePageChange = p => { ...@@ -227,6 +227,12 @@ const handlePageChange = p => {
.source-library-avatar-wrapper { .source-library-avatar-wrapper {
flex-shrink: 0; flex-shrink: 0;
margin-right: 18px; margin-right: 18px;
width: 92px;
height: 121px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
} }
.person-tags { .person-tags {
...@@ -252,10 +258,9 @@ const handlePageChange = p => { ...@@ -252,10 +258,9 @@ const handlePageChange = p => {
} }
.source-library-avatar { .source-library-avatar {
/* 椭圆 142 */ max-width: 100%;
width: 92px; max-height: 100%;
height: 121px; object-fit: contain;
// border-radius: 50%;
} }
.source-library-text-content { .source-library-text-content {
......
...@@ -343,7 +343,7 @@ ...@@ -343,7 +343,7 @@
<script setup> <script setup>
import RiskSignal from "@/components/base/riskSignal/index.vue"; import RiskSignal from "@/components/base/riskSignal/index.vue";
import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/index.vue"; import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/index.vue";
import { onMounted, ref } from "vue"; import { onMounted, ref, watch } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import scrollToTop from "@/utils/scrollToTop"; import scrollToTop from "@/utils/scrollToTop";
import DivideHeader from "@/components/DivideHeader.vue"; import DivideHeader from "@/components/DivideHeader.vue";
...@@ -371,6 +371,7 @@ import PersonTable from "./component/PersonTable.vue"; ...@@ -371,6 +371,7 @@ import PersonTable from "./component/PersonTable.vue";
import SourceLibrary from "./component/SourceLibrary.vue"; import SourceLibrary from "./component/SourceLibrary.vue";
import { useContainerScroll } from "@/hooks/useScrollShow"; import { useContainerScroll } from "@/hooks/useScrollShow";
import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate"; import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate";
import tipsIcon from "./assets/images/tips-icon.png";
const router = useRouter(); const router = useRouter();
...@@ -502,30 +503,25 @@ const handlegetCharacterOpinionWordCloudFn = async () => { ...@@ -502,30 +503,25 @@ const handlegetCharacterOpinionWordCloudFn = async () => {
// 科技人物观点涉及领域变化趋势 // 科技人物观点涉及领域变化趋势
const box6Chart = ref({ const box6Chart = ref({
title: ["2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025"], title: [],
data: [ data: []
{ name: "人工智能", value: [150, 70, 80, 90, 75, 85, 100, 140, 130, 150, 190, 170] },
{ name: "集成电路", value: [70, 85, 120, 115, 100, 120, 160, 160, 160, 160, 165, 175] },
{ name: "量子科技", value: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 15] },
{ name: "生物科技", value: [5, 0, 10, 15, 18, 20, 25, 30, 30, 35, 40, 45] },
{ name: "通信网络", value: [40, 35, 45, 48, 45, 45, 50, 55, 50, 50, 55, 50] },
{ name: "能源", value: [90, 70, 75, 70, 65, 75, 80, 80, 75, 85, 80, 70] }
]
}); });
const box6HasData = ref(true); const box6HasData = ref(true);
const handlegetOptionAreaChangeFn = async () => { const handlegetOptionAreaChangeFn = async (timeRange) => {
const currentYear = new Date().getFullYear();
const startYear = timeRange === "近五年" ? currentYear - 4 : currentYear - 9;
const params = { const params = {
startTime: "2015-01-01" startTime: `${startYear}-01-01`
}; };
try { try {
const res = await getOptionAreaChange(params); const res = await getOptionAreaChange(params);
console.log("科技人物观点涉及领域变化趋势", res); console.log("科技人物观点涉及领域变化趋势", res);
if (res.code === 200 && res.data && res.data.length > 0) { if (res.code === 200 && res.data && res.data.length > 0) {
box6HasData.value = true; box6HasData.value = true;
const sortedData = res.data.sort((a, b) => a.year.localeCompare(b.year)); const sortedData = res.data.sort((a, b) => a.year - b.year);
//提取年份作为 title //提取年份作为 title
const years = sortedData.map(item => item.year); const years = sortedData.map(item => String(item.year));
//收集所有行业名称 //收集所有行业名称
const allIndustries = new Set(); const allIndustries = new Set();
sortedData.forEach(yearData => { sortedData.forEach(yearData => {
...@@ -562,8 +558,8 @@ const handlegetOptionAreaChangeFn = async () => { ...@@ -562,8 +558,8 @@ const handlegetOptionAreaChangeFn = async () => {
// 科技人物类型 // 科技人物类型
const chart7Data = ref({ const chart7Data = ref({
name: ["国会议员", "行政主官", "科技领袖", "顶尖科学家", "其他"], name: [],
value: [482, 41, 83, 201, 25] value: []
}); });
const handlegetPersonTypeCountFn = async () => { const handlegetPersonTypeCountFn = async () => {
try { try {
...@@ -884,6 +880,11 @@ const options = [ ...@@ -884,6 +880,11 @@ const options = [
const viewOption = ref(["行政主管", "国会议员", "科技领袖", "顶尖科学家"]); const viewOption = ref(["行政主管", "国会议员", "科技领袖", "顶尖科学家"]);
const viewSelect = ref("国会议员"); const viewSelect = ref("国会议员");
watch(areaSelect, async (newVal) => {
await handlegetOptionAreaChangeFn(newVal);
const chart6 = getMultiLineChart(box6Chart.value.title, ...box6Chart.value.data.map(d => d.value));
setChart(chart6, "box6Chart");
});
onMounted(async () => { onMounted(async () => {
...@@ -891,7 +892,7 @@ onMounted(async () => { ...@@ -891,7 +892,7 @@ onMounted(async () => {
curnews.value = newsDynamics.value[0]; curnews.value = newsDynamics.value[0];
handleGetBillRiskSignalFn(); handleGetBillRiskSignalFn();
await handlegetCharacterOpinionWordCloudFn(); await handlegetCharacterOpinionWordCloudFn();
await handlegetOptionAreaChangeFn(); await handlegetOptionAreaChangeFn(areaSelect.value);
await handlegetPersonTypeCountFn(); await handlegetPersonTypeCountFn();
handlegetareaTypeFn(); handlegetareaTypeFn();
await handlgetPersonTypeFn(); await handlgetPersonTypeFn();
...@@ -2707,7 +2708,7 @@ onMounted(async () => { ...@@ -2707,7 +2708,7 @@ onMounted(async () => {
} }
.data-origin-box { .data-origin-box {
display: flex; display: none;
align-items: center; align-items: center;
padding: 8px 24px; padding: 8px 24px;
box-sizing: border-box; box-sizing: border-box;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论