提交 15c01871 authored 作者: caijian's avatar caijian

Merge branch 'cj_dev'

<template>
<div class="policy-list">
<div v-for="item in props.policyList" :key="item.id" class="policy-item">
<el-image :src="$withFallbackImage(item.imageUrl, item.content) " class="item-cover" fit="cover"/>
<div class="item-details">
<h3 class="item-title">{{ item.name }}</h3>
<div class="item-content"> {{ item.times }} · {{ item.content }} <el-icon><Link /></el-icon></div>
<div class="item-tags">
<el-tag v-for="tag in item.tags" :key="tag" class="custom-tag">{{ tag }}</el-tag>
<div
v-for="(item, index) in policyList"
:key="index"
class="policy-item"
>
<div class="item-left">
<div class="report-cover">
<img :src="$withFallbackImage(item.imageUrl, index)" alt="Report Cover" />
</div>
<div
v-if="item.relatedBill"
class="related-bill-box"
:class="`status-bg-${item.status}`"
>
<span>{{ item.relatedBill.text }}</span>
<div class="status-badge" :class="`status-color-${item.status}`">
<span class="badge-dot"></span>
{{ getStatusInfo(item.status).text }}
</div>
</div>
<div class="item-right">
<h3 class="item-title">
{{ item.content }}
</h3>
<div class="item-meta">
<span class="meta-date">{{ formatDate(item.times) }}</span>
<span class="meta-divider">·</span>
<span class="meta-source">
{{ item.name }}
<el-icon class="link-icon"><TopRight /></el-icon>
</span>
</div>
<div class="item-tags" v-if="item.tags && item.tags.length">
<span v-for="(tag, tIndex) in item.tags" :key="tIndex" class="tag-pill">
{{ tag }}
</span>
</div>
<div v-else class="related-bill-box status-bg-unimplemented">
<span>不存在相关提案。</span>
<div class="status-badge status-color-unimplemented">
<span class="badge-dot"></span>
{{ item.status }}
<div class="item-actions" v-if="item.statusRaw">
<div
v-for="(statusItem, sIndex) in parseStatus(item.statusRaw)"
:key="sIndex"
class="status-link"
>
<span class="status-type">{{ statusItem.type }}</span>
<span class="status-year">{{ statusItem.year }}</span>
<span class="status-name">{{ statusItem.name }}</span>
<el-icon class="arrow-icon"><Right /></el-icon>
</div>
</div>
</div>
......@@ -33,35 +48,53 @@
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Link } from '@element-plus/icons-vue'
const props = defineProps({
policyList: {
type: Array,
default: () => []
}
})
// --- Status Styling Helper ---
const getStatusInfo = (status) => {
switch (status) {
case 'implemented':
return { text: '已实施', color: '#e66657', bgColor: '#fdeeed' };
case 'partial':
return { text: '部分实施', color: '#d38f24', bgColor: '#fcf3e4' };
case 'unimplemented':
return { text: '未实施', color: '#409eff', bgColor: '#ecf5ff' };
default:
return { text: '未知', color: '#909399', bgColor: '#f4f4f5' };
}
};
<script setup lang="ts">
import { TopRight, Right } from '@element-plus/icons-vue'
interface PolicyItem {
content: string;
statusRaw: string; // 原始的长字符串
name: string;
times: string;
tags?: string[];
coverUrl?: string;
}
const props = defineProps<{
policyList: PolicyItem[]
}>()
// 格式化日期:2025-06-26 -> 2025年6月26日
const formatDate = (dateStr: string) => {
if (!dateStr) return '';
const date = new Date(dateStr);
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日`;
}
// 解析状态字符串
// 输入: "法案 2024 《芯片科学法案》; 政令 2025 《推动美国...》"
// 输出: 数组对象
const parseStatus = (raw: string) => {
if (!raw) return [];
// 按分号分割多个条目
const items = raw.split(/[;;]/).map(s => s.trim()).filter(s => s);
return items.map(itemStr => {
// 简单正则匹配: "类型 年份 《名称》"
// 注意:这里假设数据格式比较规范,实际需根据后端数据调整
// 尝试移除书名号进行提取
const cleanStr = itemStr.replace(/[《》]/g, '');
const parts = cleanStr.split(' ');
return {
type: parts[0] || '政策',
year: parts[1] || '',
name: parts.slice(2).join(' ') || cleanStr // 剩余部分作为名称
}
});
}
</script>
<style scoped>
/* --- Policy List Styles --- */
.policy-list {
display: flex;
flex-direction: column;
......@@ -70,139 +103,143 @@ const getStatusInfo = (status) => {
.policy-item {
display: flex;
padding: 20px 0;
border-bottom: 1px solid #e4e7ed;
border-bottom: 1px solid #ebeef5;
gap: 16px;
transition: background-color 0.2s;
}
.policy-item:last-child {
border-bottom: none;
}
.item-cover {
width: 80px;
height: 80px;
margin-right: 20px;
/* 左侧封面 */
.item-left {
flex-shrink: 0;
border-radius: 4px;
border: 1px solid #ebeef5;
}
.item-details {
flex-grow: 1;
.report-cover {
width: 60px;
height: 80px;
background-color: #f2f3f5;
border: 1px solid #e4e7ed;
border-radius: 2px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.report-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 右侧内容 */
.item-right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* 1. 标题 */
.item-title {
margin: 0 0 6px 0;
font-size: 16px;
font-weight: 600;
color: #303133;
margin: 0 0 8px;
cursor: pointer;
font-weight: 700;
color: #1a1a1a;
line-height: 1.4;
cursor: pointer;
}
.item-title:hover {
color: #409eff;
color: #409EFF;
}
.item-content {
color: #909399;
font-size: 14px;
/* 2. 元数据 */
.item-meta {
display: flex;
align-items: center;
font-size: 13px;
color: #606266;
margin-bottom: 8px;
}
.item-tags {
margin-bottom: 12px;
}
.custom-tag {
margin-right: 8px;
background-color: #f0f2f5;
color: #606266;
border-color: #e4e7ed;
font-size: 12px;
.meta-divider {
margin: 0 8px;
font-weight: bold;
}
.related-bill-box {
.meta-source {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
line-height: 1.4;
gap: 4px;
cursor: pointer;
}
.meta-source:hover {
color: #409EFF;
}
.status-badge {
.link-icon {
font-size: 12px;
}
/* 3. 标签 */
.item-tags {
display: flex;
align-items: center;
font-size: 13px;
padding: 4px 8px;
border-radius: 4px;
white-space: nowrap;
font-weight: 500;
gap: 8px;
margin-bottom: 10px;
}
.badge-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background-color: currentColor;
margin-right: 6px;
.tag-pill {
background-color: #f2f3f5;
color: #5e6d82;
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
}
/* Dynamic status colors */
.status-bg-implemented {
background-color: #fdeeed;
/* 4. 底部状态链接 */
.item-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.status-color-implemented {
color: #e66657;
.status-link {
display: inline-flex;
align-items: center;
background-color: #ecf5ff; /* 浅蓝色背景 */
color: #409EFF; /* 蓝色文字 */
padding: 4px 12px;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.status-bg-partial {
background-color: #fcf3e4;
.status-link:hover {
background-color: #d9ecff;
}
.status-color-partial {
color: #d38f24;
.status-type {
font-weight: bold;
margin-right: 4px;
}
.status-bg-unimplemented {
background-color: #ecf5ff;
.status-year {
margin-right: 4px;
}
.status-color-unimplemented {
color: #409eff;
.status-name {
margin-right: 4px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.policy-item {
flex-direction: column;
gap: 12px;
padding: 16px 0;
}
.item-cover {
width: 100%;
height: 120px;
margin-right: 0;
margin-bottom: 12px;
}
.item-title {
font-size: 15px;
}
.related-bill-box {
flex-direction: column;
align-items: flex-start;
gap: 8px;
padding: 12px;
}
.status-badge {
align-self: flex-end;
}
.arrow-icon {
margin-left: 4px;
font-size: 12px;
}
</style>
</style>
\ No newline at end of file
......@@ -82,7 +82,7 @@ import PolicyList from './PolicyList.vue';
import CardTitle from './CardTitle.vue';
import { getOverviewPolicy } from '@/api'
import PolicyOverview from '@/views/thinkTank/components/PolicyOverview.vue'
import { mockPolicyList } from '@/views/thinkTank/mockData';
const props = defineProps({
showSearch: {
type: Boolean,
......@@ -120,7 +120,8 @@ const getPolicies = async () => {
researchTypeIds: activeTechField.value,
statusList: activeStatus.value,
})
policies.value = data
// policies.value = data
policies.value = mockPolicyList
}
......
<template>
<div class="desc-tab">
<!-- 上半部分:基本信息和概况 -->
<!-- 上半部分:基本信息和经费来源 -->
<div class="top-section">
<!-- 左侧:基本信息卡片 -->
<div class="basic-info-card">
<!-- 基本信息 cardtitle -->
<CardTitle title="基本信息" />
<div class="tank-image">
<img src="https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=400" alt="智库建筑" />
......@@ -41,58 +40,19 @@
<div class="branch-list">
<div class="branch-item" v-for="(item, key) in branchInfo" :key="key">
<span class="location">{{ key }}</span>
<span class="desc">{{ item .join('、')}}</span>
<span class="desc">{{ item.join('、') }}</span>
</div>
</div>
</div>
</div>
<!-- 右侧:经费概况和研究领域 -->
<div class="right-section">
<!-- 经费概况 -->
<div class="funding-overview">
<div class="section-header">
<CardTitle title="经费来源" />
<el-icon class="expand-icon"><MoreFilled /></el-icon>
</div>
<div class="funding-content">
<!-- 左侧总计信息 -->
<div class="funding-left">
<!-- 总计 -->
<div class="funding-total">
<div class="total-label">总计</div>
<div class="total-amount">{{ fundTotal.totalJe }}美元</div>
</div>
<!-- 主要分类 -->
<div class="funding-categories">
<div class="category-group">
<div class="category-header">
<div class="category-title">政府部门</div>
<div class="category-amount">{{ fundTotal.zfJe }}美元</div>
</div>
</div>
<div class="category-group">
<div class="category-header">
<div class="category-title">其他机构</div>
<div class="category-amount">{{ fundTotal.otherJe }}美元</div>
</div>
</div>
</div>
</div>
<!-- 右侧ECharts环形饼图 -->
<div class="funding-right">
<div ref="fundingChart" class="funding-chart"></div>
</div>
</div>
</div>
<!-- 右侧:经费来源 -->
<div class="funding-section">
<FundingSource style="margin-bottom: 20px"/>
<!-- 中间部分:研究领域演变 -->
<div class="timeline-section">
<div class="section-header">
<CardTitle title="研究领域概览" />
<CardTitle title="研究领域演变" />
<el-icon class="expand-icon"><MoreFilled /></el-icon>
</div>
......@@ -102,7 +62,6 @@
<div class="period-title">{{ value.time }}</div>
<div class="period-desc">{{ value.describe }}</div>
</div>
</div>
<div class="timeline-line">
......@@ -113,28 +72,37 @@
</div>
</div>
</div>
<!-- 核心研究人员 -->
<!-- 底部:核心研究人员 -->
<div class="core-researchers-section">
<div class="section-header">
<div class="section-header">
<CardTitle title="核心研究人员" />
<el-icon class="more-icon">
<MoreFilled />
</el-icon>
</div>
<div class="researchers-grid">
<div class="researchers-column">
<el-icon class="more-icon"><MoreFilled /></el-icon>
</div>
<div class="researchers-content">
<!-- 左侧:树状图 -->
<div class="researchers-treemap">
<div ref="researcherChart" class="researcher-chart"></div>
</div>
<!-- 右侧:人员列表 -->
<div class="researchers-list">
<div
v-for="researcher in coreResearchers"
:key="researcher.id"
class="researcher-item"
>
<div class="researcher-avatar">
<el-image :src="$withFallbackImage(researcher.avatar, researcher.name) " :alt="researcher.name" />
<el-image :src="$withFallbackImage(researcher.avatar, researcher.name)" :alt="researcher.name" />
</div>
<div class="researcher-info">
<h4 class="researcher-name">{{ researcher.name }}</h4>
<p class="researcher-position">{{ researcher.describe }}</p>
<div class="researcher-previous" v-if="researcher.previousRoles && researcher.previousRoles.length">
<span class="previous-label">之前:</span>
<span class="previous-roles">{{ researcher.previousRoles.join('、') }}</span>
</div>
<p class="researcher-position">{{ researcher.currentPosition }}</p>
</div>
</div>
</div>
......@@ -151,6 +119,9 @@
import { ref, onMounted, nextTick } from 'vue'
import { MoreFilled } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import CardTitle from '@/components/CardTitle.vue'
import FundingSource from './FundingSource.vue'
import {
getThinkTankBasicInfo,
getThinkTankBranchInfo,
......@@ -160,15 +131,30 @@ import {
getThinkTankPersonList
} from '@/api'
import { useRoute } from 'vue-router'
import {
mockRandBasicInfo,
mockRandBranchInfo,
mockRandFundsSource,
mockRandFundsByType,
mockRandFundsByEntity,
mockRandFundTotal,
mockRandResearchAreas,
mockRandCoreResearchers,
mockRandResearcherCategories
} from '../mockData'
// 组件状态
const activeTimelinePeriod = ref(0)
const fundingChart = ref(null)
const researcherChart = ref(null)
const route = useRoute()
// 经费数据
const fundingData = ref([])
const fundsByType = ref([])
const fundsByEntity = ref([])
// 初始化ECharts图表
// 初始化经费饼图
const initFundingChart = () => {
if (!fundingChart.value) return
......@@ -183,7 +169,7 @@ const initFundingChart = () => {
{
name: '经费来源',
type: 'pie',
radius: ['50%', '70%'], // 环形图
radius: ['50%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
......@@ -192,38 +178,10 @@ const initFundingChart = () => {
borderWidth: 2
},
label: {
show: true,
position: 'outside',
formatter: (params) => {
return `{amount|${params.value}万} {percent|${params.percent}%}\n{name|${params.name}}`
},
rich: {
amount: {
fontSize: 12,
fontWeight: 'bold',
color: '#1f2937'
},
percent: {
fontSize: 11,
color: '#6b7280'
},
name: {
fontSize: 11,
color: '#4b5563',
lineHeight: 16
}
},
lineHeight: 14
show: false
},
labelLine: {
show: true,
length: 20,
length2: 15,
smooth: 0.2,
lineStyle: {
color: '#d1d5db',
width: 1
}
show: false
},
data: fundingData.value.map(item => ({
value: item.value,
......@@ -235,83 +193,105 @@ const initFundingChart = () => {
chart.setOption(option)
// 响应式处理
const handleResize = () => {
chart.resize()
}
window.addEventListener('resize', handleResize)
// 组件销毁时清理
return () => {
window.removeEventListener('resize', handleResize)
chart.dispose()
}
}
// 时间线数据
const timelinePeriods = ref([
{
title: '1940s-1950s',
description: '军事战略研究与国家安全研究,包括核战略、数学大战略分析'
},
{
title: '1960s-1970s',
description: '扩展至社会科学领域,包括教育政策、医疗卫生、城市问题研究'
},
{
title: '1980s-1990s',
description: '增加国际发展研究,关注苏联解体后的地缘政治格局'
},
{
title: '2000s-2010s',
description: '重点关注反恐战略,网络安全、能源政策和气候变化'
},
{
title: '2020s-现在',
description: '聚焦人工智能、大数据分析、全球竞争与合作关系'
// 初始化研究人员树状图
const initResearcherChart = () => {
if (!researcherChart.value) return
const chart = echarts.init(researcherChart.value)
// 将分类数据转换为树状图格式
const treemapData = []
Object.keys(mockRandResearcherCategories).forEach(category => {
const children = Object.keys(mockRandResearcherCategories[category]).map(item => ({
name: item,
value: mockRandResearcherCategories[category][item]
}))
treemapData.push({
name: category,
value: children.reduce((sum, item) => sum + item.value, 0),
children: children
})
})
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c}人'
},
series: [
{
type: 'treemap',
data: treemapData,
roam: false,
nodeClick: false,
breadcrumb: {
show: false
},
label: {
show: true,
formatter: '{b}\n{c}人',
fontSize: 12
},
upperLabel: {
show: true,
height: 30
},
itemStyle: {
borderColor: '#fff',
borderWidth: 2,
gapWidth: 2
}
}
]
}
])
// 旧的经费数据(已被上面的ECharts数据替代)
// const fundingData = ref({
// total: '4.358亿美元',
// government: '3.271亿美元',
// other: '1.087亿美元'
// })
chart.setOption(option)
const handleResize = () => {
chart.resize()
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
chart.dispose()
}
}
// 研究领域数据
const researchAreas = ref([])
const thinkTankInfo = ref({})
const getThinkTankInfo = async () => {
const { data } = await getThinkTankBasicInfo({ id: route.params.id })
console.log(data)
thinkTankInfo.value = data
thinkTankInfo.value = mockRandBasicInfo
}
const branchInfo = ref({})
const getBranchInfo = async () => {
const { data } = await getThinkTankBranchInfo({ id: route.params.id })
// 以 area 为分组,将city 以逗号分隔
let branchInfoObj = {}
data.forEach(item => {
if (!branchInfoObj[item.area]) {
branchInfoObj[item.area] = []
}
branchInfoObj[item.area].push(item.city)
})
branchInfo.value = branchInfoObj
branchInfo.value = mockRandBranchInfo
}
const getFundsSource = async () => {
const { data } = await getThinkTankFundsSource({ id: route.params.id })
console.log('getFundsSource', data)
fundingData.value = data.map(item => ({
fundingData.value = mockRandFundsSource.map(item => ({
value: item.amount,
name: item.institution,
// color: item.percent
}))
initFundingChart()
fundsByType.value = mockRandFundsByType
fundsByEntity.value = mockRandFundsByEntity
nextTick(() => {
initFundingChart()
})
}
const fundTotal = ref({
......@@ -320,23 +300,21 @@ const fundTotal = ref({
otherJe: 0
})
const getFundTotal = async () => {
const { data } = await getThinkTankFundsTotal({ id: route.params.id })
console.log('getFundTotal', data)
fundTotal.value = data
fundTotal.value = mockRandFundTotal
}
const getResearchArea = async () => {
const { data } = await getThinkTankResearchArea({ id: route.params.id })
console.log('getResearchArea', data)
researchAreas.value = data
researchAreas.value = mockRandResearchAreas
}
const getPersonList = async () => {
const { data } = await getThinkTankPersonList({ id: route.params.id })
console.log('getPersonList', data)
coreResearchers.value = data
coreResearchers.value = mockRandCoreResearchers
nextTick(() => {
initResearcherChart()
})
}
// 初始化图表
// 初始化
onMounted(async () => {
getThinkTankInfo()
getBranchInfo()
......@@ -348,9 +326,21 @@ onMounted(async () => {
// 核心研究人员数据
const coreResearchers = ref([])
// 格式化货币显示
const formatCurrency = (amount) => {
if (!amount) return '0美元'
const formatted = (amount / 100000000).toFixed(2)
return `${formatted}亿美元`
}
// 格式化金额(万美元)
const formatAmount = (amount) => {
return `${amount}万`
}
</script>
<style scoped>
<style scoped lang="scss">
.desc-tab {
min-height: 100vh;
}
......@@ -360,6 +350,7 @@ const coreResearchers = ref([])
display: flex;
gap: 20px;
align-items: flex-start;
margin-bottom: 20px;
}
/* 基本信息卡片 */
......@@ -406,11 +397,10 @@ const coreResearchers = ref([])
font-size: 14px;
}
.additional-info h4 {
font-size: 16px;
font-weight: 600;
color: #111827;
margin: 0 0 16px 0;
.additional-info {
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid #e5e7eb;
}
.branch-list {
......@@ -434,16 +424,12 @@ const coreResearchers = ref([])
color: #6b7280;
}
/* 右侧区域 */
.right-section {
display: flex;
flex-direction: column;
gap: 20px;
/* 经费来源区域 */
.funding-section {
flex: 1;
}
/* 通用卡片样式 */
.funding-overview,
.research-overview {
.funding-overview {
background: white;
border-radius: 12px;
padding: 24px;
......@@ -457,154 +443,102 @@ const coreResearchers = ref([])
margin-bottom: 20px;
}
.section-header h3 {
font-size: 18px;
font-weight: 600;
color: #111827;
margin: 0;
}
.expand-icon {
color: #9ca3af;
cursor: pointer;
font-size: 18px;
}
/* 经费概况 */
.funding-content {
/* 经费汇总 */
.funding-summary {
display: flex;
gap: 40px;
align-items: flex-start;
}
.funding-left {
flex: 0 0 280px;
min-width: 0;
gap: 30px;
margin-bottom: 24px;
padding-bottom: 20px;
border-bottom: 1px solid #e5e7eb;
}
.funding-total {
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #e5e7eb;
.summary-item {
flex: 1;
}
.total-label {
.summary-label {
font-size: 14px;
color: #6b7280;
margin-bottom: 4px;
margin-bottom: 8px;
}
.total-amount {
font-size: 24px;
.summary-amount {
font-size: 20px;
font-weight: 700;
color: #1f2937;
}
.funding-categories {
margin-bottom: 20px;
}
.category-group {
margin-bottom: 16px;
}
.category-header {
/* 经费详情 */
.funding-details {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
}
.category-title {
font-size: 16px;
font-weight: 600;
color: #374151;
}
.category-amount {
font-size: 16px;
font-weight: 600;
color: #dc2626;
}
.funding-right {
flex: 1;
position: relative;
min-height: 400px;
}
.funding-chart {
width: 100%;
height: 400px;
min-height: 400px;
}
/* 研究领域概览 */
.research-content {
display: flex;
align-items: center;
gap: 24px;
}
.research-chart {
flex-shrink: 0;
}
.research-list {
flex: 1;
gap: 20px;
align-items: flex-start;
}
.research-item {
.funding-table-left,
.funding-table-right {
flex: 0 0 200px;
display: flex;
align-items: center;
flex-direction: column;
gap: 12px;
margin-bottom: 12px;
}
.research-item:last-child {
margin-bottom: 0;
.table-item {
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px 0;
border-bottom: 1px solid #f3f4f6;
}
.color-dot {
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
.table-item:last-child {
border-bottom: none;
}
.research-info {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
.table-name {
font-size: 13px;
color: #374151;
line-height: 1.4;
}
.research-info .percentage {
.table-amount {
font-size: 14px;
font-weight: 600;
color: #111827;
min-width: 30px;
font-size: 14px;
}
.research-info .name {
color: #374151;
font-size: 14px;
.table-percent {
font-size: 12px;
color: #6b7280;
}
/* 饼图样式 */
.pie-chart {
position: relative;
.funding-chart-wrapper {
flex: 1;
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
.pie-chart svg {
transform: rotate(-90deg);
.funding-chart {
width: 100%;
height: 300px;
min-height: 300px;
}
/* 时间线部分 */
/* 研究领域演变 */
.timeline-section {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
margin-bottom: 20px;
}
.timeline-container {
......@@ -654,7 +588,7 @@ const coreResearchers = ref([])
top: 0;
left: 0;
height: 100%;
width: 20%;
width: 100%;
background: linear-gradient(90deg, #3b82f6 0%, #1d4ed8 100%);
border-radius: 2px;
}
......@@ -683,9 +617,8 @@ const coreResearchers = ref([])
box-shadow: 0 0 0 2px #3b82f6;
}
/* 核心研究人员样式 */
/* 核心研究人员 */
.core-researchers-section {
margin-top: 20px;
background: white;
border-radius: 12px;
padding: 24px;
......@@ -693,46 +626,50 @@ const coreResearchers = ref([])
margin-bottom: 30px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #111827;
margin: 0;
}
.more-icon {
color: #6b7280;
cursor: pointer;
font-size: 16px;
font-size: 18px;
}
.more-icon:hover {
color: #374151;
}
.researchers-grid {
.researchers-content {
display: flex;
gap: 30px;
align-items: flex-start;
}
.researchers-treemap {
flex: 1;
min-height: 400px;
}
.researcher-chart {
width: 100%;
height: 400px;
min-height: 400px;
}
/* 每行两个div */
.researchers-column {
.researchers-list {
flex: 0 0 400px;
display: flex;
flex-wrap: wrap;
flex-direction: column;
gap: 20px;
width: 100%;
}
.researcher-item {
width: 48%;
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid #f3f4f6;
}
.researcher-item:last-child {
border-bottom: none;
}
.researcher-avatar {
......@@ -756,13 +693,27 @@ const coreResearchers = ref([])
font-size: 16px;
font-weight: 600;
color: #111827;
margin: 0 0 4px 0;
margin: 0 0 6px 0;
line-height: 1.3;
}
.researcher-previous {
font-size: 12px;
color: #6b7280;
margin-bottom: 4px;
}
.previous-label {
color: #9ca3af;
}
.previous-roles {
color: #6b7280;
}
.researcher-position {
font-size: 13px;
color: #6b7280;
color: #374151;
margin: 0;
line-height: 1.4;
word-wrap: break-word;
......@@ -774,24 +725,23 @@ const coreResearchers = ref([])
flex-direction: column;
}
.funding-content,
.research-content {
.funding-details {
flex-direction: column;
text-align: center;
}
.funding-left {
.funding-table-left,
.funding-table-right {
flex: none;
width: 100%;
}
.funding-right {
min-height: 300px;
.funding-chart-wrapper {
min-height: 250px;
}
.funding-chart {
height: 300px;
min-height: 300px;
height: 250px;
min-height: 250px;
}
.timeline-periods {
......@@ -803,9 +753,13 @@ const coreResearchers = ref([])
display: none;
}
.researchers-grid {
grid-template-columns: 1fr;
gap: 20px;
.researchers-content {
flex-direction: column;
}
.researchers-list {
flex: none;
width: 100%;
}
}
......@@ -816,20 +770,11 @@ const coreResearchers = ref([])
.basic-info-card,
.funding-overview,
.research-overview,
.timeline-section,
.core-researchers-section {
padding: 16px;
}
.researchers-grid {
gap: 16px;
}
.researchers-column {
gap: 16px;
}
.researcher-item {
gap: 10px;
padding: 8px 0;
......
<template>
<div class="funding-source-container">
<div class="chart-header">
<CardTitle title="经费来源" />
<div class="header-icons">
<el-icon><Coin /></el-icon>
<el-icon><Download /></el-icon>
<el-icon><Star /></el-icon>
</div>
</div>
<div class="chart-body">
<div class="stats-panel">
<div class="stat-card total-card">
<div class="label">总计</div>
<div class="value">4.358亿美元</div>
</div>
<div class="stat-card govt-card">
<div class="label">政府部门</div>
<div class="value">3.271亿美元</div>
</div>
<div class="stat-card other-card">
<div class="label">其他机构</div>
<div class="value">1.087亿美元</div>
</div>
</div>
<div class="chart-panel" ref="chartRef"></div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import * as echarts from 'echarts';
import { Coin, Download, Star } from '@element-plus/icons-vue';
import CardTitle from '@/components/CardTitle.vue';
// 1. 模拟数据
// 注意:为了还原图表,这里的数据是凑出来的近似值,确保比例看起来像截图
const chartData = [
// 右侧数据 (通常从12点顺时针开始)
{ value: 7830, name: '美国国土安全部', percent: '21%' },
{ value: 7290, name: '美国办公室国防部长和...', percent: '21%' },
{ value: 6740, name: '美国卫生与公众服务部...', percent: '18%' },
{ value: 4840, name: '美国空军', percent: '18%' },
{ value: 3880, name: '美国陆军', percent: '16%' },
{ value: 3520, name: '捐款', percent: '16%' },
// 左侧数据
{ value: 3110, name: '基金', percent: '14%' },
{ value: 2905, name: '大学', percent: '12%' },
{ value: 2840, name: '私营部门', percent: '12%' },
{ value: 2400, name: '州和地方政府机构', percent: '12%' },
{ value: 2130, name: '其他非营利组织', percent: '11%' },
{ value: 2060, name: '非美国政府机构和国际...', percent: '8%' },
{ value: 1850, name: '其他联邦机构', percent: '8%' },
{ value: 1200, name: '其他', percent: '8%' },
];
// 颜色盘 (从截图吸取的近似色)
const colorPalette = [
'#8cbbf1', // 浅蓝
'#a5d67d', // 浅绿
'#f6c469', // 橙黄
'#fdf27e', // 黄色
'#94e6d6', // 青绿
'#6b85ef', // 深蓝紫
'#d3d7fd', // 极浅蓝
'#d9f3b2', // 极浅绿
'#eb7d7d', // 红
'#a28ee3', // 紫
'#f4a678', // 橙
'#6ba7f5', // 蓝
'#f5a8a8', // 浅红
];
const chartRef = ref(null);
let myChart = null;
const initChart = () => {
if (!chartRef.value) return;
myChart = echarts.init(chartRef.value);
const option = {
color: colorPalette,
tooltip: {
trigger: 'item',
formatter: '{b}: {c}万 ({d}%)'
},
series: [
{
name: '经费来源',
type: 'pie',
radius: ['45%', '60%'], // 环形图半径
center: ['50%', '50%'], // 居中
data: chartData,
// 标签配置
label: {
show: true,
position: 'outside',
formatter: function (params) {
// 这里的逻辑是为了模仿截图:右边的文字名字在右侧,左边的文字名字在左侧
// 简单的判断逻辑:基于 ECharts 内部计算的 label 角度,或者根据数据索引
// 这里我们构建一个富文本结构
return `{name|${params.name}}\n{val|${params.value}万} {pct|${params.data.percent}}`;
},
// 关键配置:使用 edge 对齐方式让标签像表格一样排列在两侧
alignTo: 'edge',
edgeDistance: 10, // 距离容器边缘的距离
minMargin: 5,
lineHeight: 20,
rich: {
name: {
fontSize: 13,
fontWeight: 'bold',
color: '#333',
padding: [0, 5]
},
val: {
fontSize: 12,
color: '#666'
},
pct: {
fontSize: 12,
color: '#666',
padding: [0, 5]
}
}
},
// 引导线配置
labelLine: {
length: 15,
length2: 60, // 第二段线长一点,以便连接到边缘
maxSurfaceAngle: 80
},
// 每一项的样式
itemStyle: {
borderColor: '#fff',
borderWidth: 2
}
}
]
};
myChart.setOption(option);
};
// 响应式处理
const resizeHandler = () => {
if (myChart) {
myChart.resize();
}
};
onMounted(() => {
nextTick(() => {
initChart();
window.addEventListener('resize', resizeHandler);
});
});
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler);
if (myChart) {
myChart.dispose();
}
});
</script>
<style lang="scss" scoped>
.funding-source-container {
width: 100%;
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
// 头部样式
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.title-wrapper {
display: flex;
align-items: center;
.blue-bar {
width: 4px;
height: 18px;
background-color: #409eff; // Element Plus Primary Blue
margin-right: 8px;
border-radius: 2px;
}
.title-text {
font-size: 18px;
font-weight: 700;
color: #303133;
}
}
.header-icons {
display: flex;
gap: 15px;
color: #909399;
cursor: pointer;
.el-icon {
font-size: 18px;
&:hover {
color: #409eff;
}
}
}
}
// 主体布局
.chart-body {
display: flex;
flex-direction: row;
height: 450px; // 固定一个高度给图表展示
// 左侧统计面板
.stats-panel {
width: 200px;
display: flex;
flex-direction: column;
justify-content: center;
gap: 20px;
flex-shrink: 0;
.stat-card {
padding: 15px;
border-radius: 6px;
display: flex;
flex-direction: column;
gap: 8px;
.label {
font-size: 14px;
}
.value {
font-size: 20px;
font-weight: 800;
}
// 不同卡片的特定样式
&.total-card {
background-color: #eef6ff; // 浅蓝背景
.label { color: #409eff; }
.value { color: #185ebd; }
}
&.govt-card {
background-color: #fff2f2; // 浅红背景
.label { color: #f56c6c; }
.value { color: #c43e3e; }
}
&.other-card {
background-color: #f0f9eb; // 浅绿背景
.label { color: #67c23a; }
.value { color: #3a8e1e; }
}
}
}
// 右侧图表区域
.chart-panel {
flex: 1;
min-width: 0; // 防止 flex 子项溢出
height: 100%;
}
}
}
// 移动端适配微调
@media (max-width: 768px) {
.chart-body {
flex-direction: column !important;
height: auto !important;
.stats-panel {
width: 100% !important;
flex-direction: row !important;
overflow-x: auto;
padding-bottom: 10px;
}
.chart-panel {
height: 400px !important;
}
}
}
</style>
\ No newline at end of file
......@@ -72,6 +72,7 @@ import { onMounted, ref } from 'vue';
import { Search } from '@element-plus/icons-vue';
import { getThinkTankReport } from '@/api';
import { useRoute } from 'vue-router';
import { mockReportList } from '../mockData';
const route = useRoute();
const props = defineProps({
......@@ -102,7 +103,8 @@ const reportList = ref([
onMounted(() => {
getThinkTankReport({ id: route.params.id }).then(res => {
reportList.value = res.data;
// reportList.value = res.data;
reportList.value = mockReportList;
});
})
</script>
......@@ -145,8 +147,11 @@ onMounted(() => {
.report-card {
cursor: pointer;
border-radius: 10px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
border: 1px solid #e4e7ed;
padding: 10px;
}
.report-card:hover {
......@@ -173,9 +178,9 @@ onMounted(() => {
/* overflow: hidden; */
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-line-clamp: 1;
overflow: hidden;
-webkit-box-orient: vertical;
min-height: 42px;
}
.card-meta {
......
......@@ -50,7 +50,7 @@ import PolicyTab from '@/components/PolicyTab.vue';
import DescTab from './DescTab.vue';
import { getThinkTankSummary } from '@/api';
import { useRoute } from 'vue-router';
import { mockThinkTankList } from '../mockData';
// --- Component State ---
const activeTab = ref('reports');
const route = useRoute();
......@@ -77,13 +77,16 @@ const currentComponent = computed(() => {
onMounted(() => {
getThinkTankSummary({ id: route.params.id }).then(res => {
console.log(res.data);
summary.value = res.data || {};
// summary.value = res.data || {};
summary.value = mockThinkTankList[0];
});
});
</script>
<style scoped>
/* 变量 1200px - 定义在组件根元素上 */
.page-container {
--max-width: 1650px;
background-color: #f5f7fa;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
min-height: 100vh;
......@@ -99,7 +102,7 @@ onMounted(() => {
}
.header-container {
max-width: 1200px;
max-width: var(--max-width);
margin: 0 auto;
padding: 24px 24px 0;
display: flex;
......@@ -116,7 +119,7 @@ onMounted(() => {
}
.tabs-container {
max-width: 1200px;
max-width: var(--max-width);
margin: 0 auto;
padding: 0 24px;
}
......@@ -212,7 +215,7 @@ onMounted(() => {
}
.content-container {
max-width: 1200px;
max-width: var(--max-width);
width: 100%;
background-color: #fff;
padding: 24px;
......
......@@ -162,4 +162,279 @@ export const mockReportList = [
"thinkTankName": "兰德科技智库",
imageUrl: report12
}
]
\ No newline at end of file
]
export const mockPolicyList = [
{
"content": "创建并定制针对人工智能技术的验证、确认与评估技术。",
"status": "法案 2024 《芯片科学法案》; 政令 2025 《推动美国人工智能技术栈出口》",
"reportId": null,
"name": "保持美国在人工智能与机器学习领域的优势",
"times": "2025-06-26",
tags: ['人工智能', '机器学习']
},
{
"content": "为运用人工智能的新作战概念建立开发、测试与评估流程。",
"status": "法案 2024 《芯片科学法案》; 政令 2025 《关于优化美军作战决策结构的建议》",
"reportId": null,
"name": "保持美国在人工智能与机器学习领域的优势",
"times": "2025-06-26",
tags: ['人工智能', '机器学习']
},
{
"content": "通过制定和维护一个前瞻性的人工智能发展路线图来管理预期,该路线图应阐明国防部在近期(一至两年)、中期(三至五年)和远期(六至十年)部署人工智能的现实目标",
"status": "法案 2024 《芯片科学法案》; 政令 2025 《关于优化美军作战决策结构的建议》",
"reportId": null,
"name": "保持美国在人工智能与机器学习领域的优势",
"times": "2025-06-26",
tags: ['人工智能', '机器学习']
},
{
"content": "考虑采取更全面的方法来打击全球供应链中强迫劳动使用的选项。",
"status": "法案 2024 《维吾尔强迫劳动预防法》",
"reportId": null,
"name": "美国贸易执法是否发挥了作用,能否做得更多?",
"times": "2025-03-15",
tags: ['人工智能', '机器学习']
},
{
"content": "与利益相关者共同收集证据,为关于贸易执法的公共讨论提供信息。",
"status": "法案 2024 《维吾尔强迫劳动预防法》",
"reportId": null,
"name": "美国贸易执法是否发挥了作用,能否做得更多?",
"times": "2025-03-15"
},
{
"content": "推动清洁能源生产供内用,化石燃料重新配置出口。",
"status": "法案 2024 《重塑美国人口结构法案》; 法案 2025 《开放人才法案》; 政令 2025 《推动美国人工智能技术栈出口》",
"reportId": null,
"name": "美国贸易执法是否发挥了作用,能否做得更多?",
"times": "2025-06-26"
},
{
"content": "允许OPT的国际学生出国旅行并持多次入境签证重新进入美国。",
"status": "法案 2024 《重塑美国人口结构法案》; 法案 2025 《开放人才法案》",
"reportId": null,
"name": "中美经济竞争:复杂经济和地缘政治关系中的收益与风险",
"times": "2025-06-26"
}
]
// 兰德公司详情模拟数据
export const mockRandCorporationDetail = {
id: 1,
name: "兰德科技智库",
ename: "RAND Corporation",
describe: "兰德公司(RAND Corporation)成立于1948年,是全球最负盛名的政策研究智库之一。作为一家非营利性研究机构,兰德公司致力于通过客观分析和有效解决方案来改善政策和决策。公司总部位于美国加利福尼亚州圣莫尼卡,在华盛顿特区、匹兹堡、波士顿、新奥尔良等地设有办事处。兰德公司拥有超过1900名员工,包括来自50多个国家的学者、分析师和研究人员。",
country: "美国",
url: "https://www.rand.org",
logo: rand,
tags: [
"国家安全",
"科技政策",
"医疗卫生",
"能源政策",
"公共安全",
"国防研究",
"国际关系",
"经济政策"
],
// 扩展信息
founded: "1948年",
headquarters: "美国加利福尼亚州圣莫尼卡",
employees: "1900+",
researchAreas: [
"国家安全与国防",
"科技与创新政策",
"医疗卫生政策",
"能源与环境",
"教育与劳动力",
"国际关系与外交",
"经济与金融",
"公共安全与司法"
],
notableAchievements: [
"为美国国防部提供战略分析和政策建议",
"在人工智能、网络安全等前沿科技领域开展深入研究",
"发布多份关于中美科技竞争的重要报告",
"在医疗卫生政策研究方面具有国际影响力"
]
}
// DescTab 组件所需的兰德公司详细数据
// 基本信息
export const mockRandBasicInfo = {
country: "美国",
foundingDate: "1948年",
position: "美国加利福尼亚州圣莫尼卡",
nature: "非营利性研究与分析机构",
memnum: "约1,700名员工",
budget: "约3.5亿美元"
}
// 分支机构信息(按地区分组)
export const mockRandBranchInfo = {
"北美": ["圣莫尼卡(总部)", "华盛顿特区", "匹兹堡", "波士顿"],
"欧洲": ["英国剑桥", "比利时布鲁塞尔"],
"中东": ["卡塔尔多哈"],
"澳大利亚": ["堪培拉"]
}
// 经费来源数据(用于饼图,单位:万美元)
// 总计:43580万美元 = 4.358亿美元
// 政府部门:32710万美元 = 3.271亿美元
// 其他机构:10870万美元 = 1.087亿美元
// 按来源类型分类(左侧表格)
export const mockRandFundsByType = [
{ name: "基金", amount: 3110, percent: 14 },
{ name: "大学", amount: 2905, percent: 12 },
{ name: "私营部门", amount: 2840, percent: 12 },
{ name: "州和地方政府机构", amount: 2400, percent: 12 },
{ name: "其他非营利组织", amount: 2130, percent: 11 },
{ name: "非美国政府机构和国际...", amount: 2060, percent: 8 },
{ name: "其他联邦机构", amount: 1850, percent: 8 },
{ name: "其他", amount: 1200, percent: 8 }
]
// 按具体政府实体分类(右侧表格)
export const mockRandFundsByEntity = [
{ name: "美国国土安全部", amount: 7830, percent: 21 },
{ name: "美国办公室国防部长和...", amount: 7290, percent: 21 },
{ name: "美国卫生与公众服务部...", amount: 6740, percent: 18 },
{ name: "美国空军", amount: 4840, percent: 18 },
{ name: "美国陆军", amount: 3880, percent: 16 },
{ name: "捐款", amount: 3520, percent: 16 }
]
// 用于饼图的完整数据
export const mockRandFundsSource = [
{ amount: 7830, name: "美国国土安全部", institution: "美国国土安全部" },
{ amount: 7290, name: "美国办公室国防部长", institution: "美国办公室国防部长" },
{ amount: 6740, name: "美国卫生与公众服务部", institution: "美国卫生与公众服务部" },
{ amount: 4840, name: "美国空军", institution: "美国空军" },
{ amount: 3880, name: "美国陆军", institution: "美国陆军" },
{ amount: 3520, name: "捐款", institution: "捐款" },
{ amount: 3110, name: "基金", institution: "基金" },
{ amount: 2905, name: "大学", institution: "大学" },
{ amount: 2840, name: "私营部门", institution: "私营部门" },
{ amount: 2400, name: "州和地方政府机构", institution: "州和地方政府机构" },
{ amount: 2130, name: "其他非营利组织", institution: "其他非营利组织" },
{ amount: 2060, name: "非美国政府机构和国际组织", institution: "非美国政府机构和国际组织" },
{ amount: 1850, name: "其他联邦机构", institution: "其他联邦机构" },
{ amount: 1200, name: "其他", institution: "其他" }
]
// 经费总计(单位:美元)
export const mockRandFundTotal = {
totalJe: 435800000, // 4.358亿美元
zfJe: 327100000, // 3.271亿美元
otherJe: 108700000 // 1.087亿美元
}
// 研究领域数据
export const mockRandResearchAreas = [
{
id: 1,
time: "1940s-1950s",
describe: "专注于军事战略和国家安全研究,包括核战略、航空航天技术和系统分析"
},
{
id: 2,
time: "1960s-1970s",
describe: "扩展至社会科学领域,包括教育政策、医疗卫生、城市问题和刑事司法"
},
{
id: 3,
time: "1980s-1990s",
describe: "增加国际政策研究,关注苏联解体后的地缘政治变化和技术政策"
},
{
id: 4,
time: "2000s-2010s",
describe: "重点关注反恐战略、网络安全、能源政策和气候变化"
},
{
id: 5,
time: "2020s-现在",
describe: "聚焦人工智能、大数据分析、全球公共卫生和新兴技术政策"
}
]
// 核心研究人员(包含之前的职位信息)
export const mockRandCoreResearchers = [
{
id: 1,
name: "杰森·马西尼",
nameEn: "Jason Massini",
previousRoles: ["哈佛大学经济系", "美国财政部"],
currentPosition: "总裁兼首席执行官",
avatar: null
},
{
id: 2,
name: "安德鲁·R·霍恩",
nameEn: "Andrew R. Horne",
previousRoles: ["白宫科技政策办公室"],
currentPosition: "高级副总裁,研究与分析",
avatar: null
},
{
id: 3,
name: "梅丽莎·罗",
nameEn: "Melissa Luo",
previousRoles: ["美国国防部"],
currentPosition: "副总裁,全球研究人才",
avatar: null
},
{
id: 4,
name: "安妮塔·钱德拉",
nameEn: "Anita Chandra",
previousRoles: ["哈佛大学经济系", "美国商务部"],
currentPosition: "副总裁兼主任,兰德社会与经济福祉",
avatar: null
},
{
id: 5,
name: "Timothy Marler",
nameEn: "Timothy Marler",
previousRoles: ["斯坦福大学"],
currentPosition: "高级研究员,人工智能与机器学习",
avatar: null
}
]
// 研究人员背景分类(用于树状图)
export const mockRandResearcherCategories = {
"政府部门及国家实验室": {
"商务部": 12,
"财政部": 8,
"能源部": 15,
"国家能源技术实验室": 6,
"其他": 10
},
"领先科技企业": {
"谷歌": 18,
"微软": 14,
"英伟达": 9,
"英特尔": 11,
"亚马逊": 13,
"其他": 7
},
"顶尖大学与研究机构": {
"哈佛大学": 22,
"加州大学": 19,
"斯坦福大学": 16,
"麻省理工学院": 14,
"其他": 12
},
"国际人才": {
"印度": 15,
"日本": 12,
"德国": 10,
"中国": 8,
"其他": 6
}
}
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论