提交 fead13ed authored 作者: 刘宇琪's avatar 刘宇琪

fix: 科技人物样式修改

上级 b450f7fd
......@@ -20,11 +20,15 @@
<button
:class="['news-tab', { active: activeTab === 'local' }]"
@click="$emit('update:activeTab', 'local')"
> 报告</button>
> 智库报告</button>
<button
:class="['news-tab', { active: activeTab === 'capital' }]"
@click="$emit('update:activeTab', 'capital')"
> 项目</button>
> 调查项目</button>
<button
:class="['news-tab', { active: activeTab === 'hearing' }]"
@click="$emit('update:activeTab', 'hearing')"
> 听证会</button>
</div>
<div class="news-sort" ref="sortDropdownRef">
......
......@@ -6,7 +6,9 @@
:sort-by="sortBy"
@update:sort="updateSort"
/>
<div class="news-body">
<!-- 智库报告 / 调查项目 -->
<div class="news-body" v-if="activeTab !== 'hearing'">
<NewsSidebar
:domain-options="domainOptions"
:time-options="timeOptions"
......@@ -30,6 +32,77 @@
/>
</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>
</template>
......@@ -40,6 +113,7 @@ import NewsTopBar from './NewsTopBar.vue'
import NewsSidebar from './NewsSidebar.vue'
import NewsCard from './NewsCard.vue'
import NewsPagination from './NewsPagination.vue'
import AreaTag from '@/components/base/AreaTag/index.vue'
import { getIndustryKeyList } from '@/api/bill/billHome.js'
import { getFindingsReport, getInvestigationProject } from '@/api/characterPage/characterPage.js'
......@@ -63,6 +137,57 @@ const timeOptions = ref([])
const selectedDomains = 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() {
const res = await getIndustryKeyList()
if (res.code === 200 && res.data) {
......@@ -186,8 +311,10 @@ watch(activeTab, (val) => {
currentPage.value = 1
if (val === 'local') {
loadNews()
} else {
} else if (val === 'capital') {
loadProjects()
} else if (val === 'hearing') {
loadMockHearings()
}
})
......@@ -245,4 +372,148 @@ onMounted(async () => {
align-content: 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>
......@@ -12,12 +12,12 @@ export const CHARACTER_CONFIG = {
useImageProxy: false,
headerTagType: "areaTag",
wordCloudTitle: "科技观点",
yearDefault: "全部时间",
yearDefault: "全部",
yearBuildMode: "dynamic",
yearStaticOptions: [],
showFundSource: false,
resumeMode: "inline",
resumeTitle: "职业履历",
resumeTitle: "生涯履历",
resumeHeight: "1336px",
companySectionTitle: "实体信息",
basicInfoFields: [
......@@ -51,13 +51,13 @@ export const CHARACTER_CONFIG = {
headerTagType: "inline",
wordCloudTitle: "科技观点",
yearDefault: "全部",
yearBuildMode: "static",
yearStaticOptions: ["全部", "2025", "2024", "2023", "2022", "2021", "2020"],
yearBuildMode: "dynamic",
yearStaticOptions: [],
showFundSource: true,
resumeMode: "inline",
resumeTitle: "职业履历",
resumeTitle: "生涯履历",
resumeHeight: "1556px",
companySectionTitle: "社交媒体",
companySectionTitle: "实体信息",
basicInfoFields: [
{ label: "出生日期:", key: "birthday", type: "text" },
{ label: "现任职位:", key: "positionTitle", type: "text" },
......@@ -89,13 +89,13 @@ export const CHARACTER_CONFIG = {
headerTagType: "areaTag",
wordCloudTitle: "核心观点",
yearDefault: "全部",
yearBuildMode: "static",
yearStaticOptions: ["全部", "2026", "2025", "2024", "2023", "2022", "2021"],
yearBuildMode: "dynamic",
yearStaticOptions: [],
showFundSource: false,
resumeMode: "card",
resumeTitle: "政治履历",
resumeMode: "inline",
resumeTitle: "生涯履历",
resumeHeight: null,
companySectionTitle: "社交媒体",
companySectionTitle: "实体信息",
basicInfoFields: [
{ label: "出生日期:", key: "birthday", type: "text" },
{ label: "现任职位:", key: "positionTitle", type: "text" },
......
......@@ -151,7 +151,7 @@
<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="baseInfo">
<div v-for="field in config.basicInfoFields" :key="field.key" class="baseInfo-item">
......@@ -183,7 +183,7 @@
</div>
<div class="company">
<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"
class="company-item">
<img :src="item.imageUrl ? item.imageUrl : DefaultIcon2" alt="" />
......@@ -193,6 +193,7 @@
</div>
</div>
</div>
<el-empty v-else description="暂无数据" :image-size="60" />
</div>
</div>
</AnalysisBox>
......
<template>
<div class="character-page">
<img src="./assets/images/background.png" alt="" class="bg" />
<ModuleHeader />
<!-- 主要内容 -->
<div class="main">
<unified-character :type="type" :person-id="personId" />
......@@ -13,7 +12,6 @@
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import UnifiedCharacter from './components/unified/index.vue';
import ModuleHeader from '@/components/base/moduleHeader/index.vue';
import { getCharacterGlobalInfo } from "@/api/characterPage/characterPage.js";
......
......@@ -11,16 +11,19 @@
<div class="table-body">
<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;">
<img :src="item.avatar" class="avatar" alt="avatar" />
<div class="person-tags">
<div class="person-tag-bg" v-for="(tag, tIdx) in item.tags" :key="tIdx">
<img :src="getTagIconUrl(tag)" class="tag-icon" alt="tag" />
<div class="person-tag-bg" v-if="item.party === 'Republican' || item.party === '共和党'">
<img :src="getTagIconUrl('1')" class="tag-icon" alt="tag" />
</div>
<div class="person-tag-bg" v-if="item.party && item.party !== 'Republican' && item.party !== '共和党'">
<img :src="getTagIconUrl('2')" class="tag-icon" alt="tag" />
</div>
</div>
<div class="person-info" @click="handleClickToCharacter(item.personId)">
</div>
<div class="person-info">
<div class="person-name">{{ item.name }}</div>
<div class="person-position">{{ item.position }}</div>
</div>
......@@ -67,11 +70,8 @@ watch(() => [props.persontypeid,props.yearSelect], (val) => {
})
const getTagIconUrl = (tag) => {
// 用 import.meta.glob 预加载所有图标,支持动态匹配
const icons = import.meta.glob('../assets/images/header-icon*.png', { eager: true, as: 'url' });
// 拼接对应路径,匹配预加载的图标
const iconPath = `../assets/images/header-icon${tag}.png`;
// 兜底:如果没有对应tag的图片,返回默认图或空
return icons[iconPath] || '';
};
// 获取主要人物涉华观点统计
......@@ -90,7 +90,7 @@ const handlegetMainCharactersViewFn = async () => {
avatar: item.personImage,
name: item.personName,
position: item.positionTitle,
tags: ["1", "2"],
party: item.party || '',
chinaRelatedCount: item.remarksCount,
mediaQuoteCount: item.remarksCount,
personId:item.personId
......@@ -197,6 +197,7 @@ const getProgress = count => (count / getMaxCount()) * 100;
/* 人物列样式 */
.col-person {
align-items: flex-start;
cursor: pointer;
}
.avatar {
......@@ -244,9 +245,9 @@ const getProgress = count => (count / getMaxCount()) * 100;
.person-tags {
display: flex;
justify-content: space-between;
margin-top: -20px;
width: 42px;
text-align: center;
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论