提交 1363ec3d authored 作者: coderBryanFu's avatar coderBryanFu

ui通用组件统一全局base文件夹

上级 ebc89c2c
......@@ -14,6 +14,7 @@ node_modules
# Build outputs
dist
dist.rar
dist-ssr
*.local
......
......@@ -51,7 +51,7 @@
</div>
</div>
</div> -->
<NavBarV2/>
<ModuleHeader/>
<div class="main-container">
<router-view />
</div>
......
<template>
<div class="tag-wrapper" :class="classObject">
{{ tagName }}
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
const props = defineProps({
tagName: {
type: String,
default: '标签名称'
}
})
const classObject = computed(() => ({
'tag1': props.tagName === '人工智能',
'tag2': props.tagName === '生物科技',
'tag3': props.tagName === '新一代通信网络',
'tag4': props.tagName === '量子科技',
'tag5': props.tagName === '新能源',
'tag6': props.tagName === '集成电路',
'tag7': props.tagName === '海洋',
'tag8': props.tagName === '先进制造',
'tag9': props.tagName === '新材料',
'tag10': props.tagName === '航空航天',
'tag11': props.tagName === '太空',
'tag12': props.tagName === '深海',
'tag13': props.tagName === '极地',
'tag14': props.tagName === '核',
'tag15': props.tagName === '其他',
}))
</script>
<style lang="scss" scoped>
.tag-wrapper {
height: 24px;
padding: 0 8px;
line-height: 24px;
text-align: center;
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
letter-spacing: 0px;
box-sizing: border-box;
border-radius: 4px;
border: 1px solid rgba(183, 235, 143, 1);
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
}
.tag1 {
border: 1px solid rgba(255, 163, 158, 1);
background: rgba(255, 241, 240, 1);
color: rgba(245, 34, 45, 1);
}
.tag2 {
border: 1px solid rgba(135, 232, 222, 1);
background: rgba(230, 255, 251, 1);
color: rgba(19, 168, 168, 1);
}
.tag3 {
border: 1px solid rgba(174, 214, 255, 1);
background: rgba(246, 250, 255, 1);
color: rgba(5, 95, 194, 1);
}
.tag4 {
border: 1px solid rgba(211, 173, 247, 1);
background: rgba(249, 240, 255, 1);
color: rgba(114, 46, 209, 1);
}
.tag5 {
border: 1px solid rgba(183, 235, 143, 1);
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
}
.tag6 {
border: 1px solid rgba(145, 202, 255, 1);
background: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1);
}
.tag7 {
border: 1px solid rgba(156, 207, 245, 1);
background: rgba(241, 247, 250, 1);
color: rgba(15, 120, 199, 1);
}
.tag8 {
border: 1px solid rgba(255, 229, 143, 1);
background: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1);
}
.tag9 {
border: 1px solid rgba(255, 213, 145, 1);
background: rgba(255, 247, 230, 1);
color: rgba(250, 140, 22, 1);
}
.tag10 {
border: 1px solid rgba(173, 198, 255, 1);
background: rgba(240, 245, 255, 1);
color: rgba(47, 84, 235, 1);
}
.tag11 {
border: 1px solid rgba(173, 198, 255, 1);
background: rgba(240, 245, 255, 1);
color: rgba(47, 84, 235, 1);
}
.tag12 {
border: 1px solid rgba(116, 146, 203, 1);
background: rgba(230, 244, 255, 1);
color: rgba(73, 104, 161, 1);
}
.tag13 {
border: 1px solid rgba(214, 228, 255, 1);
background: rgba(240, 245, 255, 1);
color: rgba(133, 165, 255, 1);
}
.tag14 {
border: 1px solid rgba(255, 187, 150, 1);
background: rgba(255, 242, 232, 1);
color: rgba(250, 84, 28, 1);
}
.tag15 {
border: 1px solid rgba(183, 235, 143, 1);
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
}
</style>
\ No newline at end of file
<template>
<div class="analysis-box-wrapper" :style="{ width: width ? width : '100%', height: height ? height : '100%' }">
<div class="wrapper-header">
<div class="header-icon"></div>
<div class="header-title">{{ title }}</div>
<div class="header-btn" v-if="!showAllBtn">
<slot name="headerBtn"></slot>
</div>
<div class="header-btn1" v-else>
<slot name="headerBtn"></slot>
</div>
<div class="header-right">
<div class="header-right-btn" @click="handleSave" v-if="showAllBtn">
<img src="@/assets/icons/box-header-icon1.png" alt="">
</div>
<div class="header-right-btn" @click="handleDownload">
<img src="@/assets/icons/box-header-icon2.png" alt="">
</div>
<div class="header-right-btn" @click="handleCollect">
<img src="@/assets/icons/box-header-icon3.png" alt="">
</div>
</div>
</div>
<div class="wrapper-main">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { ref, computed } from 'vue'
const props = defineProps({
title: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
},
showAllBtn: {
type: Boolean,
default: true
}
})
const handleSave = () => {
ElMessage.success('保存当前内容')
// emit('save')
}
const handleDownload = () => {
ElMessage.success('下载当前内容')
// emit('download')
}
const handleCollect = () => {
ElMessage.success('收藏当前内容')
// emit('collect')
}
const emit = defineEmits(['save','download','collect'])
</script>
<style lang="scss" scoped>
.analysis-box-wrapper {
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.wrapper-header {
height: 45px;
display: flex;
box-sizing: border-box;
position: relative;
.header-icon {
margin-top: 18px;
width: 8px;
height: 20px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.header-title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
// color: var(--color-main-active);
// font-family: Source Han Sans CN;
// font-size: 20px;
// font-weight: 700;
// line-height: 26px;
// letter-spacing: 0px;
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.header-btn {
position: absolute;
top: 14px;
right: 84px;
// display: flex;
// justify-content: flex-end;
// gap: 8px;
}
.header-btn1 {
position: absolute;
top: 14px;
right: 104px;
}
.header-right {
position: absolute;
top: 14px;
right: 14px;
height: 28px;
display: flex;
justify-content: flex-end;
gap: 4px;
.header-right-btn {
width: 28px;
height: 28px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
}
.wrapper-main {
height: calc(100% - 45px);
overflow: hidden;
// overflow-y: auto;
padding: 5px auto;
}
}
</style>
<template>
<div class="overview-box-wrapper" :style="{ width: width ? width : '640px', height: height ? height : '415px' }">
<div class="wrapper-header">
<div class="header-icon"></div>
<div class="header-title">{{ title }}</div>
<div class="header-right">
<div class="header-right-btn" @click="handleSave">
<img src="@/assets/icons/box-header-icon1.png" alt="">
</div>
<div class="header-right-btn" @click="handleDownload">
<img src="@/assets/icons/box-header-icon2.png" alt="">
</div>
<div class="header-right-btn" @click="handleCollect">
<img src="@/assets/icons/box-header-icon3.png" alt="">
</div>
</div>
</div>
<div class="wrapper-main">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
title: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
}
})
const handleSave = () => {
alert('save')
}
const handleDownload = () => {
alert('download')
}
const handleCollect = () => {
alert('collect')
}
</script>
<style lang="scss" scoped>
.overview-box-wrapper {
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.wrapper-header {
height: 45px;
display: flex;
box-sizing: border-box;
.header-icon {
margin-top: 18px;
width: 8px;
height: 20px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.header-title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
color: var(--color-main-active);
font-family: Source Han Sans CN;
font-style: Bold;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
height: 28px;
display: flex;
justify-content: flex-end;
gap: 4px;
.header-right-btn {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
}
}
.wrapper-main {
height: calc(100% - 45px);
overflow: hidden;
overflow-y: auto;
box-sizing: border-box;
padding: 5px 10px;
}
}
</style>
<template>
<div class="box4">
<div class="box4-header">
<div class="header-icon">
<img src="./image1.png" alt="" />
</div>
<div class="header-title">{{ "社交媒体" }}</div>
<div class="more" @click="handleToMoreNews">{{ "更多 +" }}</div>
</div>
<div class="box4-main">
<div class="message-bubble" v-for="(item, index) in messageList" :key="index" @click="handleClickPerson(item)">
<div class="avatar-container">
<img :src="item[props.imageUrl] || avatarUser" :alt="item[props.name]" class="avatar" />
<div class="avatar-containerOne" v-if="isRepublicanParty"><img src="./image2.png" alt=""
class="avatar-imageOne" /></div>
<div class="avatar-containerTwo" v-if="isUnitedStatesSenate"><img src="./image3.png" alt=""
class="avatar-imageTwo" /></div>
</div>
<div class="bubble-container">
<div class="bubble">
<div class="bubble-header">
<span class="name">{{ item[props.name] }}</span>
<span class="meta">{{ item[props.time] }} · {{ item[props.source] }}</span>
</div>
<div class="bubble-content">
{{ item[props.content] }}
</div>
<div class="triangle"></div>
</div>
</div>
</div>
<!-- <MessageBubble v-for="(item, index) in messageList" @click="handleClickPsserson(item)"
@info-click="handleMediaClick(item)" :key="index" :avatar="item.img ? item.img : DefaultIcon1" :name="item.name"
:time="item.time" :source="item.source" :content="item.content" /> -->
<!-- <div class="box4-main-item" v-for="(item, index) in messageList" :key="index">
<div class="left" @click="handleClickPerson(item)">
<img :src="item.img ? item.img : DefaultIcon1" alt="" />
</div>
<div class="right">
<div class="right-top">
<div class="name">{{ item.name }}</div>
<div class="time">{{ item.time }}</div>
</div>
<div class="content">{{ item.content }}</div>
</div>
</div> -->
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import avatarUser from "@/assets/images/avatar_user.png";
const emit = defineEmits(["click", "info-click"]);
const props = defineProps({
isRepublicanParty: {
type: Boolean,
default: false
},
isUnitedStatesSenate: {
type: Boolean,
default: false
},
messageList: {
type: Array,
default: () => []
},
imageUrl: {
type: String,
default: "imageUrl"
},
name: {
type: String,
default: "name"
},
time: {
type: String,
default: "time"
},
source: {
type: String,
default: "source"
},
content: {
type: String,
default:
"content"
}
});
const formattedTime = computed((index) => {
const date = new Date(index);
if (isNaN(date.getTime())) {
return index; // 如果不是有效日期,返回原值
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate());
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
// return `${year}年${month}月${day}日 ${hours}:${minutes}:${seconds}`;
return `${month}${day}${hours}:${minutes}`;
});
const handleClickPerson = (item) => {
emit("person-click", item);
};
const handleInfoClick = (item) => {
emit("info-click", item);
};
const handleToMoreNews = (item) => {
emit("more-click", item);
};
</script>
<style scoped>
.box4 {
margin-left: 20px;
width: 792px;
height: 450px;
border-radius: 10px;
box-shadow: 0 0 20px 0 rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
border: 1px solid rgb(234, 236, 238);
.box4-header {
width: 792px;
height: 48px;
border-bottom: 1px solid rgb(234, 236, 238);
display: flex;
box-sizing: border-box;
position: relative;
.header-icon {
margin-left: 18px;
margin-top: 14px;
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
}
.header-title {
width: 80px;
margin-top: 11px;
margin-left: 18px;
height: 26px;
color: rgb(5, 95, 194);
font-family: "Source Han Sans CN";
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
}
.more {
width: 45px;
height: 24px;
position: absolute;
top: 12px;
right: 27px;
color: rgb(5, 95, 194);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
text-align: right;
}
}
.box4-main {
height: 402px;
overflow-y: auto;
box-sizing: border-box;
padding-bottom: 8px;
padding-left: 21px;
padding-top: 23px;
.message-bubble {
display: flex;
max-width: 740px;
margin-bottom: 15px;
.avatar-container {
flex-shrink: 0;
flex-grow: 0;
flex: 0;
width: 42px;
height: 42px;
margin-right: 14.5px;
cursor: pointer;
position: relative;
.avatar-containerOne,
.avatar-containerTwo {
display: inline-block;
position: absolute;
}
.avatar-containerOne {
left: 2px;
top: 29px;
.avatar-imageOne {
width: 20px;
height: 20px;
}
}
.avatar-containerTwo {
right: 2px;
top: 29px;
.avatar-imageTwo {
width: 20px;
height: 20px;
}
}
.avatar {
width: 42px;
height: 42px;
border-radius: 50%;
object-fit: cover;
}
}
.bubble-container {
flex: 1;
position: relative;
.bubble {
background-color: rgba(246, 250, 255, 1);
border-radius: 12px;
padding: 12px 12px;
position: relative;
border: 1px solid rgba(231, 243, 255, 1);
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
.bubble-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
.name {
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
.meta {
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: right;
}
}
.bubble-content {
color: rgba(59, 65, 75, 1);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.triangle {
position: absolute;
left: -9px;
/* 向左偏移1px,给描边留出空间 */
top: 15px;
width: 0;
height: 0;
/* 外层:描边颜色的三角形(比内层大1px) */
border-top: 9px solid transparent;
border-bottom: 9px solid transparent;
border-right: 9px solid rgb(231, 243, 255);
}
/* 内层:原有颜色的三角形,覆盖在外层上面,模拟描边效果 */
.triangle::after {
content: '';
position: absolute;
top: -8px;
/* 向上偏移1px,对齐中心 */
left: 1px;
/* 向右偏移1px,露出外层的描边 */
width: 0;
height: 0;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 8px solid rgba(246, 250, 255, 1);
}
}
}
}
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.message-bubble {
max-width: 100%;
}
.bubble-header {
flex-direction: column;
align-items: flex-start;
}
.meta {
margin-top: 4px;
}
}
</style>
<template>
<div class="box3">
<div class="box3-header">
<div class="box3-header-left">
<div class="box3-header-icon">
<img src="./image1.png" alt="" />
</div>
<div class="box3-header-title">{{ "新闻资讯" }}</div>
<div class="more" @click="handleToMoreNews">{{ "更多 +" }}</div>
</div>
</div>
<div class="box3-main">
<div class="box3-item" v-for="(news, index) in newsList" :key="index" @click="handleToNewsAnalysis(news)">
<div class="left">
<img :src="news[props.img] ? news[props.img] : DefaultIconNews" alt="" />
</div>
<div class="right">
<div class="right-top">
<div class="title"><span class="text-inner">{{ news[props.title] }}</span></div>
<div class="time">{{ news[props.from] }}</div>
</div>
<div class="right-footer">{{ news[props.content] }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import DefaultIconNews from "@/assets/icons/default-icon-news.png";
const props = defineProps({
// 新闻列表数据
newsList: {
type: Array,
default: () => []
},
img: {
type: String,
default: 'img'
},
title: {
type: String,
default: "title"
},
from: {
type: String,
default: "from"
},
content: {
type: String,
default: "content"
},
});
const emit = defineEmits(['item-click', 'more-click']);
const handleToMoreNews = () => {
emit('more-click')
};
const handleToNewsAnalysis = (item, index) => {
emit('item-click', item, index)
};
</script>
<style lang="scss" scoped>
.box3 {
width: 792px !important;
height: 450px !important;
border-radius: 10px !important;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1) !important;
background: rgba(255, 255, 255, 1) !important;
display: flex !important;
flex-direction: column;
gap: 0 !important;
overflow: hidden;
.box3-header {
height: 48px !important;
border-bottom: 1px solid rgba(234, 236, 238, 1) !important;
margin: 0 !important;
display: flex !important;
justify-content: space-between !important;
position: relative !important;
width: 100%;
box-sizing: border-box;
.box3-header-left {
display: flex !important;
.box3-header-icon {
margin-left: 19px !important;
margin-top: 14px !important;
width: 24px !important;
height: 24px !important;
img {
width: 100% !important;
height: 100% !important;
}
}
.box3-header-title {
margin-top: 11px !important;
margin-left: 17px !important;
height: 26px !important;
color: var(--color-main-active) !important;
font-family: 'Source Han Sans CN' !important;
font-size: 20px !important;
font-weight: 700 !important;
line-height: 26px !important;
}
}
.more {
width: 45px;
height: 24px;
position: absolute;
top: 12px;
right: 27px;
color: rgba(20, 89, 187, 1);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
}
}
.box3-main {
height: 401px;
overflow-y: auto;
overflow-x: hidden;
padding: 6px 0;
.box3-item {
display: flex;
height: 78px;
width: 749px;
margin-left: 21px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
cursor: pointer;
&:hover {
.right-top .title {
color: rgb(5, 95, 194) !important;
font-weight: 700;
}
.right-top .text-inner {
border-bottom-color: rgb(5, 95, 194) !important;
}
}
.left {
width: 72px;
height: 48px;
margin-top: 15px;
img {
width: 100%;
height: 100%;
}
}
.right {
width: 657px;
margin-left: 20px;
.right-top {
width: 657px;
display: flex;
justify-content: space-between;
.title {
margin-top: 14px;
width: 500px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 700;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.text-inner {
border-bottom: 1px solid transparent;
}
}
.time {
width: 157px;
text-align: right;
height: 22px;
margin-top: 14px;
color: rgba(95, 101, 108, 1);
font-family: 'Source Han Sans CN';
font-size: 14px;
font-weight: 400;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.right-footer {
width: 657px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="left-btn-wrapper">
<img src="@/assets/images/icon/card-btn-left.png" alt="">
</div>
</template>
<script setup>
</script>
<style lang="scss" scoped>
.left-btn-wrapper {
width: 24px;
height: 48px;
img {
width: 100%;
height: 100%;
}
}
</style>
\ No newline at end of file
<template>
<div class="right-btn-wrapper">
<img src="@/assets/images/icon/card-btn-right.png" alt="">
</div>
</template>
<script setup>
</script>
<style lang="scss" scoped>
.right-btn-wrapper {
width: 24px;
height: 48px;
img {
width: 100%;
height: 100%;
}
}
</style>
\ No newline at end of file
<template>
<div class="box2">
<div class="box2-header">
<div class="icon">
<img src="./image1.png" alt="" />
</div>
<div class="title">
<div class="text">{{ title }}</div>
<div class="num">{{ list.length }}</div>
</div>
</div>
<div class="box2-main">
<div class="box2-main-item" v-for="(item, index) in list" :key="index" @click="handleItemClick(item, index)">
<div :class="{
itemLeftStatus1: item[props.riskLevel] === '特别重大',
itemLeftStatus2: item[props.riskLevel] === '重大风险',
itemLeftStatus3: item[props.riskLevel] === '较大风险',
itemLeftStatus4: item[props.riskLevel] === '一般风险' || !item[props.riskLevel],
itemLeftStatus5: item[props.riskLevel] === '低风险',
}">
{{ item[props.riskLevel] || "暂无数据" }}
</div>
<div class="item-right">
<div class="text"> <span class="text-inner">{{ item[props.name] }}</span></div>
<div class="time">{{ item[props.postDate] }}</div>
</div>
</div>
</div>
<div class="box2-footer" @click="handleMoreClick" v-if="showMore">
<div class="icon">
<img src="./image2.png" alt="" />
</div>
<div class="text">{{ moreText }}</div>
</div>
</div>
</template>
<script setup>
import { ElMessage } from "element-plus";
// 接收父组件传递的参数
const props = defineProps({
// 标题(默认“风险信号”)
title: {
type: String,
default: "风险信号"
},
// 风险信号列表数据
list: {
type: Array,
default: () => []
},
// “查看更多”文本(默认“查看更多”)
moreText: {
type: String,
default: "查看更多"
},
//控制“查看更多”是否显示,默认显示
showMore: {
type: Boolean,
default: true
},
name: {
type: String,
default: "name"
},
postDate: {
type: String,
default: "postDate"
},
riskLevel: {
type: String,
default: "riskLevel"
},
});
// 定义自定义事件,把点击事件传递给父组件
const emit = defineEmits(['item-click', 'more-click']);
// 点击单条风险信号
const handleItemClick = (item, index) => {
emit('item-click', item, index)
};
// 点击“查看更多”
const handleMoreClick = () => {
emit('more-click')
};
</script>
<style scoped lang="scss">
.risk-status-base {
width: 40px;
height: 40px;
border-radius: 20px;
font-size: 12px;
font-weight: 400;
line-height: 14px;
box-sizing: border-box;
padding: 6px 4px;
text-align: center;
}
.itemLeftStatus1 {
color: rgb(206, 79, 81) !important;
background: rgba(255, 241, 240, 1) !important;
@extend .risk-status-base
}
.itemLeftStatus2 {
color: rgba(250, 140, 22, 1) !important;
background: rgba(255, 247, 230, 1) !important;
@extend .risk-status-base
}
.itemLeftStatus3 {
color: rgba(212, 177, 6, 1) !important;
background: rgba(254, 255, 230, 1) !important;
@extend .risk-status-base
}
.itemLeftStatus4 {
color: rgba(82, 196, 26, 1) !important;
background: rgba(246, 255, 237, 1) !important;
@extend .risk-status-base
}
.itemLeftStatus5 {
color: rgba(22, 119, 255, 1) !important;
background: rgba(230, 244, 255, 1) !important;
@extend .risk-status-base
}
.box2 {
width: 520px;
height: 450px;
border-radius: 10px;
position: relative;
background: rgba(255, 255, 255, 1);
padding: 0;
box-shadow: 0 0 20px 0 rgba(25, 69, 130, 0.1);
border: 1px solid rgba(234, 236, 238, 1);
box-sizing: border-box;
overflow: hidden;
.box2-header {
height: 48px;
display: flex;
border-bottom: 1px solid rgba(240, 242, 244, 1);
.icon {
width: 24px;
height: 24px;
margin-left: 18px;
margin-top: 14px;
margin-bottom: 10px;
img {
width: 100%;
height: 100%;
}
}
.title {
display: flex;
width: 148px;
background: rgb(206, 79, 81);
margin-left: 18px;
.text {
margin-left: 16px;
height: 48px;
color: rgba(255, 255, 255, 1);
font-family: 'Source Han Sans CN';
font-size: 20px;
font-weight: 700;
line-height: 48px;
}
.num {
width: 24px;
height: 20px;
line-height: 20px;
text-align: center;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 12px;
margin-left: 15px;
margin-top: 15px;
border-radius: 100px;
background: rgba(255, 255, 255, 0.3);
}
}
}
.box2-main {
box-sizing: border-box;
padding-left: 23px;
padding-right: 30px;
overflow-y: auto;
width: 520px;
height: calc(100% - 160px);
border-radius: 4px;
.box2-main-item {
width: 463px;
height: 48px;
border-radius: 2px;
position: relative;
display: flex;
align-items: center;
cursor: pointer;
&:hover {
.item-right .text {
color: rgb(5, 95, 194) !important;
font-weight: 700;
}
.item-right .text-inner {
border-bottom-color: rgb(5, 95, 194) !important;
}
}
.item-left {
margin-top: 4px;
margin-left: 0px;
margin-bottom: 4px;
width: 40px;
height: 40px;
border-radius: 20px;
color: rgba(82, 196, 26, 1);
background: rgba(246, 255, 237, 1);
font-family: Microsoft YaHei;
font-size: 12px;
font-weight: 400;
line-height: 14px;
box-sizing: border-box;
padding: 6px 4px;
text-align: center;
flex-shrink: 0;
}
.item-right {
margin-left: 12px;
height: 46px;
display: flex;
align-items: center;
flex: 1;
background: transparent;
padding: 0;
border-bottom: 1px solid #EAECEE;
box-sizing: border-box;
overflow: hidden; // 保证右侧不会溢出
.text {
padding-top: 8px;
padding-bottom: 8px;
flex: 1 1 auto;
min-width: 0;
height: 100%;
background: transparent;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
letter-spacing: 0px;
text-align: left;
color: rgb(59, 65, 75);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
flex-shrink: 1;
.text-inner {
border-bottom: 1px solid transparent;
}
}
.time {
flex: 0 0 auto;
margin-left: 12px;
padding-top: 11px;
padding-bottom: 11px;
height: 100%;
flex-shrink: 0;
background: transparent;
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: right;
box-sizing: border-box;
color: rgb(132, 136, 142);
white-space: nowrap;
}
}
}
}
.box2-footer {
position: absolute;
left: 26px;
right: 20px;
bottom: 20px;
width: 460px;
height: 42px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
border-radius: 6px;
background: var(--color-main-active);
cursor: pointer;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 8px;
color: rgba(255, 255, 255, 1);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
</style>
\ No newline at end of file
......@@ -12,11 +12,15 @@ import "./styles/main.css";
import '@/assets/fonts/font.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import AreaTag from '@/components/areaTag.vue'
import leftBtn from "@/components/pageBtn/leftBtn.vue";
import rightBtn from "@/components/pageBtn/rightBtn.vue";
import OverviewBox from '@/components/BoxBackground/overviewBox.vue'
import AnalysisBox from '@/components/BoxBackground/analysisBox.vue'
import AreaTag from '@/components/base/areaTag/AreaTag.vue'
import leftBtn from "@/components/base/pageBtn/leftBtn.vue";
import rightBtn from "@/components/base/pageBtn/rightBtn.vue";
import OverviewBox from '@/components/base/boxBackground/overviewBox.vue'
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'
import NewsList from '@/components/base/newsList/NewsList.vue'
import ModuleHeader from '@/components/base/moduleHeader/index.vue'
import RiskSignal from "@/components/base/riskSignal/RiskSignal.vue";
import MessageBubble from "@/components/base/messageBubble/MessageBubble.vue";
// 引入 Pinia 实例
import pinia from './stores'
......@@ -36,8 +40,13 @@ app.use(ElementPlus, {
app.use(pinia) // 挂载 Pinia
app.component("CardTitle", CardTitle);
app.component('AreaTag', AreaTag) // 领域标签
app.component('leftBtn', leftBtn)
app.component('rightBtn', rightBtn)
app.component('leftBtn', leftBtn) // 向左按钮
app.component('rightBtn', rightBtn) // 向右按钮
app.component('OverviewBox', OverviewBox) // 概览页模块背景
app.component('AnalysisBox', AnalysisBox) // 分析页模块背景
app.component('ModuleHeader', ModuleHeader) // 模块头部
app.component('RiskSignal', RiskSignal) // 风险信号
app.component('NewsList', NewsList) // 新闻资讯
app.component('MessageBubble', MessageBubble) // 社交媒体
app.mount("#app");
......@@ -709,7 +709,7 @@ import Ghd from "@/assets/icons/ghd.png";
import Mzd from "@/assets/icons/mzd.png";
import { ElMessage } from "element-plus";
import AreaTag from "@/components/areaTag.vue";
import AreaTag from "@/components/AreaTag.vue";
const searchBillText = ref("");
......
......@@ -262,7 +262,7 @@
<template #reference>
<div class="info-content">{{ item.content ? item.content : "暂无数据" }}</div>
</template>
</el-popover> -->
</el-popover> -->
</div>
</div>
</div>
......@@ -1409,16 +1409,13 @@ onMounted(async () => {
}
.home-main {
position: relative;
width: 100%;
height: 100%;
overflow-y: auto;
// background: url("./assets/images/background.png");
// background-size: 100% 100%;
.home-top-bg {
background:
url("./assets/images/background.png"),
linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%);
background: url("./assets/images/background.png"), linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%);
background-size: 100% 100%;
position: absolute;
width: 100%;
......
<template>
<div class="home-wrapper">
=======
<div class="search-header" v-show="isShow">
<!-- <div class="search-header" v-show="isShow">
<div class="home-main-header-center">
<el-input v-model="searchExportControlText" @keyup.enter="handleSearch" style="width: 800px; height: 100%"
placeholder="搜索出口管制" />
......@@ -40,7 +37,7 @@
</div>
</div>
</div>
</div>
</div> -->
<!-- <div class="home-header" v-show="!isShow">
<div class="header-left">
......
<template>
<div class="effect-analysis">
<div class="placeholder-card">
<div class="card-header">
<CardTitle title="影响分析" />
<el-icon class="expand-icon"><TrendCharts /></el-icon>
</div>
<div class="card-body">
<div class="placeholder-content">
<h3 class="placeholder-title">影响分析功能开发中</h3>
<el-empty description="暂无数据" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { TrendCharts } from '@element-plus/icons-vue'
import CardTitle from '@/components/CardTitle.vue'
</script>
<style scoped>
.effect-analysis {
/* padding: 20px 0; */
}
.placeholder-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
min-height: 500px;
}
.card-header {
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-body {
padding: 40px 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.expand-icon {
color: #999;
}
.placeholder-content {
display: flex;
flex-direction: column;
align-items: center;
max-width: 400px;
}
.placeholder-icon {
margin-bottom: 24px;
color: #d3d3d3;
}
.placeholder-title {
font-size: 20px;
color: #333;
margin: 0 0 16px 0;
}
.placeholder-description {
font-size: 14px;
color: #666;
margin-bottom: 20px;
line-height: 1.6;
}
.feature-list {
list-style: none;
padding: 0;
margin: 0 0 30px 0;
text-align: left;
}
.feature-list li {
padding: 8px 0;
color: #666;
font-size: 14px;
position: relative;
padding-left: 20px;
}
.feature-list li::before {
content: "•";
color: #409eff;
font-weight: bold;
position: absolute;
left: 0;
}
</style>
<template>
<div class="overview-container">
<el-row :gutter="20">
<!-- 左侧内容 -->
<el-col :span="8">
<!-- 内容摘要 -->
<div class="content-card">
<div class="card-header">
<CardTitle title="内容摘要" />
<el-icon class="expand-icon"><ArrowRight /></el-icon>
</div>
<div class="card-body">
<div class="summary-content">
<div v-html="props.reportSummary.summary"></div>
<el-button type="text" class="more-btn">查看更多 ></el-button>
</div>
</div>
</div>
<!-- 涉及科技领域 -->
<div class="content-card tech-fields" style="margin-top: 20px;">
<div class="card-header">
<CardTitle title="涉及科技领域" />
<el-icon class="expand-icon"><ArrowRight /></el-icon>
</div>
<div class="card-body">
<div class="tech-tags">
<el-tag type="primary" size="small" v-for="tag in props.reportSummary.researchTypes" :key="tag">{{ tag }}</el-tag>
</div>
<WordCloud :words="props.reportSummary.researchTypes" />
</div>
</div>
</el-col>
<!-- 右侧主要内容 -->
<el-col :span="16">
<div class="content-card main-content-card">
<div class="card-header">
<CardTitle title="主要内容" />
<div class="content-actions">
<el-button type="plain" size="small">核心发现</el-button>
<el-button type="plain" size="small">政策建议</el-button>
<el-icon class="expand-icon"><Paperclip /></el-icon>
</div>
</div>
<div class="card-body">
<div class="main-content-list">
<div v-for="(item, index) in mainContentList" :key="item.id" class="content-item">
<!-- 左侧:序号 -->
<div class="item-number">{{ item.serialNum }}</div>
<!-- 中间:标题和内容 -->
<div class="item-main">
<h4 class="item-title">{{ item.content }}</h4>
<p class="item-content">{{ item.econtent }}</p>
</div>
<!-- 右侧:标签 -->
<div class="item-tags">
<el-tag
v-for="tag in item.tags"
:key="tag.name"
:type="tag.type"
size="small"
class="content-tag"
>
{{ tag.name }}
</el-tag>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="currentPage"
:page-size="pageSize"
:total="totalElements"
layout="prev, pager, next"
:pager-count="5"
small
/>
<span class="total-info">{{ totalElements }}</span>
</div>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Paperclip } from '@element-plus/icons-vue'
import CardTitle from '@/components/CardTitle.vue'
import { mockMainContentList } from '../mockData'
import WordCloud from './WordCloud.vue'
const props = defineProps({
reportSummary: {
type: Object,
required: true
},
content: {
type: Object,
required: true
}
})
const currentPage = ref(1)
const totalElements = ref(6)
const pageSize = ref(10)
// 标签类型映射 - 根据限制方式设置不同的标签颜色
const getTagType = (tagName) => {
const tagTypeMap = {
'出口管制': 'danger',
'资本管制': 'warning',
'技术封锁': 'danger',
'金融制裁': 'danger',
'对台军售': 'warning',
'关税贸易': 'primary',
'供应链打击': 'warning'
}
return tagTypeMap[tagName] || 'info'
}
// 处理接口数据的方法
const processApiData = (apiResponse) => {
if (!apiResponse || !apiResponse.content) return []
return apiResponse.content.map(item => ({
id: item.id,
title: item.content, // 使用content作为标题
content: item.content, // 内容
econtent: item.econtent,
serialNum: item.serialNum,
tags: item.xzfsList.map(tag => ({
name: tag,
type: getTagType(tag)
}))
}))
}
// 更新分页信息的方法
const updatePagination = (apiResponse) => {
if (apiResponse) {
totalElements.value = apiResponse.totalElements || 0
pageSize.value = apiResponse.size || 10
currentPage.value = (apiResponse.number || 0) + 1 // API返回的是0基础的页码
}
}
const mainContentList = ref(mockMainContentList)
</script>
<style scoped>
.overview-container {
padding: 0;
}
.content-card {
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.card-header {
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-body {
padding: 20px;
}
.expand-icon {
color: #999;
cursor: pointer;
margin-left: 10px;
}
.summary-content {
line-height: 1.8;
color: #666;
}
.summary-content p {
margin-bottom: 16px;
text-align: justify;
}
.more-btn {
color: #409eff;
padding: 0;
}
.tech-tags {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 10px;
}
.tag-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.tech-tag {
background: #f5f7fa;
border: 1px solid #dcdfe6;
color: #606266;
border-radius: 4px;
padding: 4px 8px;
font-size: 12px;
}
.tech-tag.ai-chip {
background: #fff7e6;
border-color: #ffd591;
color: #fa8c16;
}
.content-actions {
display: flex;
align-items: center;
}
.main-content-list {
margin-bottom: 20px;
}
.content-item {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #f0f0f0;
gap: 16px;
}
.content-item:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.item-number {
width: 28px;
height: 28px;
background: #409eff;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 600;
flex-shrink: 0;
margin-top: 2px;
}
.item-main {
flex: 1;
min-width: 0;
}
.item-title {
margin: 0 0 8px 0;
font-size: 16px;
font-weight: 600;
color: #1f2937;
line-height: 1.4;
}
.item-content {
margin: 0;
line-height: 1.6;
color: #4b5563;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.item-tags {
display: flex;
flex-direction: column;
gap: 6px;
flex-shrink: 0;
min-width: 120px;
align-items: flex-end;
}
.content-tag {
font-size: 12px;
height: 22px;
line-height: 20px;
padding: 0 8px;
white-space: nowrap;
}
.pagination-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 20px;
border-top: 1px solid #f0f0f0;
}
.total-info {
font-size: 12px;
color: #999;
}
.pagination-wrapper :deep(.el-pagination) {
--el-pagination-font-size: 12px;
}
.pagination-wrapper :deep(.el-pagination .el-pager li) {
min-width: 28px;
height: 28px;
line-height: 28px;
}
.pagination-wrapper :deep(.el-pagination .btn-prev),
.pagination-wrapper :deep(.el-pagination .btn-next) {
height: 28px;
line-height: 28px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.content-item {
flex-direction: column;
gap: 12px;
margin-bottom: 20px;
padding-bottom: 20px;
}
.item-number {
align-self: flex-start;
}
.item-main {
order: 2;
}
.item-tags {
order: 3;
flex-direction: row;
align-items: flex-start;
flex-wrap: wrap;
min-width: auto;
}
.item-title {
font-size: 15px;
}
.item-content {
font-size: 13px;
}
}
</style>
<template>
<div class="policy-tracking">
<el-row :gutter="20">
<!-- 左侧:政策建议落实情况 -->
<el-col :span="14">
<div class="policy-card">
<div class="card-header">
<CardTitle title="政策建议落实情况" />
<div class="filter-buttons">
<el-button
:class="{ active: activeFilter === 'all' }"
@click="setFilter('all')"
size="small"
text
>
全部
</el-button>
<el-button
:class="{ active: activeFilter === 'implemented' }"
@click="setFilter('implemented')"
size="small"
text
>
已实施
</el-button>
<el-button
:class="{ active: activeFilter === 'partial' }"
@click="setFilter('partial')"
size="small"
text
>
部分实施
</el-button>
<el-button
:class="{ active: activeFilter === 'pending' }"
@click="setFilter('pending')"
size="small"
text
>
未实施
</el-button>
</div>
</div>
<div class="card-body">
<div class="policy-list">
<div
v-for="(item, index) in policyList"
:key="index"
class="policy-item"
@click="openPolicyDetail(item)"
>
<div class="policy-number">{{ index + 1 }}</div>
<div class="policy-content">
<p class="policy-text">{{ item.text }}</p>
<div class="policy-tags">
<el-tag size="small" v-for="tag in item.tags" :key="tag">{{ tag }}</el-tag>
</div>
<div class="policy-related-bill" v-if="getRelatedBills(item).length > 0">
<div
v-for="(bill, billIndex) in getRelatedBills(item)"
:key="billIndex"
class="bill-item"
>
<span class="bill-tag">法案</span>
<span class="bill-year">{{ bill.year }}</span>
<span class="bill-title">{{ bill.title }}</span>
<el-icon class="bill-arrow"><ArrowRight /></el-icon>
</div>
</div>
</div>
<div class="policy-status">
<el-tag :type="getStatusType(item.status)" size="small">{{ item.status }}</el-tag>
</div>
</div>
</div>
</div>
</div>
</el-col>
<!-- 右侧:相关政策动态 -->
<el-col :span="10">
<div class="policy-dynamics-card">
<div class="card-header">
<CardTitle title="相关政策动态" />
<el-button type="text" size="small" class="more-link">
查看更多
<el-icon><ArrowRight /></el-icon>
</el-button>
</div>
<div class="card-body">
<!-- 暂未开发 -->
<!-- <el-empty description="暂无数据" /> -->
<div class="dynamics-list">
<div
v-for="(item, index) in policyDynamics"
:key="index"
class="dynamics-item"
>
<div class="dynamics-content">
<div class="dynamics-header">
<div class="dynamics-dot"></div>
<h4 class="dynamics-title">{{ item.title }}</h4>
<span class="dynamics-date">{{ item.date }}</span>
</div>
<p class="dynamics-description">{{ item.description }}</p>
<div class="dynamics-image" v-if="item.image">
<img :src="item.image" :alt="item.title" />
</div>
</div>
</div>
</div>
</div>
</div>
</el-col>
</el-row>
<!-- 政策详情弹窗 -->
<el-dialog
v-model="showDetailModal"
title="智库政策追踪"
width="800px"
class="policy-detail-dialog"
@close="closeDetailModal"
>
<div v-if="selectedPolicy" class="policy-detail-content">
<!-- 政策标题 -->
<h3 class="policy-detail-title">{{ selectedPolicy.text }}</h3>
<!-- 标签区域 -->
<div class="policy-detail-tags">
<el-tag size="small">{{ selectedPolicy.category }}</el-tag>
<el-tag size="small">{{ selectedPolicy.subcategory }}</el-tag>
</div>
<!-- 政策详细内容 -->
<div class="policy-detail-body">
<p class="policy-description">
{{ selectedPolicy.detailedDescription || selectedPolicy.text }}
</p>
</div>
<!-- 相关法案信息 -->
<div v-if="selectedPolicy.relatedBill" class="related-bill-section">
<div class="bill-info">
<span class="bill-label">相关立法已通过:</span>
<span class="bill-name">{{ selectedPolicy.relatedBill }}</span>
<el-tag
:type="getStatusType(selectedPolicy.status)"
size="small"
class="bill-status"
>
{{ selectedPolicy.status }}
</el-tag>
</div>
</div>
<!-- 右侧信息区域 -->
<div class="policy-side-info">
<div class="think-tank-info">
<div class="think-tank-cover">
<img src="https://picsum.photos/120/160?random=7" alt="智库报告" />
</div>
<div class="think-tank-details">
<h4 class="think-tank-title">兰德科技智库</h4>
<p class="publish-date">2025年6月26日</p>
<p class="report-title">中美经济竞争:复杂经济和地缘政治关系中的收益与风险</p>
<el-button type="primary" size="small" class="download-btn">
<el-icon><Download /></el-icon>
下载智库报告原文
</el-button>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ArrowRight, Download } from '@element-plus/icons-vue'
import CardTitle from '@/components/CardTitle.vue'
import PolicyOverview from '../components/PolicyOverview.vue'
// props
const props = defineProps({
policy: {
type: Array,
required: true
}
})
const activeFilter = ref('all')
const showDetailModal = ref(false)
const selectedPolicy = ref(null)
// 政策建议落实情况数据
const policyList = ref([
{
text: '允许OPT的国际学生出国旅行并持多次入境签证重新进入美国。',
tags: ['人才交流', '移民政策'],
subcategory: '移民政策',
relatedBills: [
{ year: '2024', title: '重塑美国人口结构法案' },
{ year: '2025', title: '开放人才法案' }
],
status: '已实施',
detailedDescription: '当前基于就业的绿卡上限设定为140,000个。每个国家的上限设定为总数的7%。这意味着每年只有大约9,800名在美国的中国工人可以通过就业优先类别成为合法永久居民。这个上限造成了符合要求的中国申请者的积压,他们必须等待多年才能获得绿卡批准。在这种情况中,高技能移民报告他们有意愿离开第三国或返回原籍国。美国可以从自己1992年通过的《华人学生保护法》中汲取训练。该法案使某些中国公民免于上限和其他要求。因此,54,000名中国移民住于了这个机会。受过教育的中国移民的回归率较低于今天,尽管有美国移民身份可以让他们提高工人在中国劳动力市场的价值并降低回中国的风险来同时间注,但持有绿卡可以对回归的效果太大,因为维持绿卡需要满足国际任务要求。'
},
{
text: '增加中国公民可获得 H-1B 签证数量。',
tags: ['人才交流', '移民政策'],
subcategory: '移民政策',
relatedBills: [
{ year: '2024', title: '调整签证政策以吸纳外国重点领域人才法案' }
],
status: '部分实施'
},
{
text: '通过就业偏好类别增加卡的数量。',
tags: ['人才交流', '移民政策'],
subcategory: '移民政策',
relatedBills: [
{ year: '2024', title: '重塑美国人口结构法案' }
],
status: '已实施'
},
{
text: '推动清洁能源生产的内,化石燃料重新配置出口。',
tags: ['人才交流', '移民政策'],
subcategory: '移民政策',
relatedBills: [],
status: '未实施'
},
{
text: '支持国内核工业商业化扩张。',
tags: ['人才交流', '移民政策'],
subcategory: '移民政策',
relatedBills: [
{ year: '2024', title: '重塑美国人口结构法案' }
],
status: '已实施'
},
{
text: '在美国和友好国家建立关键矿产加工能力',
tags: ['人才交流', '移民政策'],
subcategory: '移民政策',
relatedBills: [
{ year: '2024', title: '重塑美国人口结构法案' },
{ year: '2025', title: '开放人才法案' }
],
status: '已实施'
}
])
// 获取相关法案列表(兼容旧数据格式)
const getRelatedBills = (item) => {
if (item.relatedBills && Array.isArray(item.relatedBills)) {
return item.relatedBills
}
// 兼容旧格式:relatedBill 字符串
if (item.relatedBill) {
const match = item.relatedBill.match(/^(\d{4})(.+)$/)
if (match) {
return [{ year: match[1], title: match[2] }]
}
}
return []
}
// 相关政策动态数据
const policyDynamics = ref([
{
title: '减税与就业法案的出台与立法博弈',
date: '2025-08-30',
description: '2017年,美国通过了一项旨在根本性以来最为重大的税制改革——《减税与就业法案》...',
image: 'https://picsum.photos/80/60?random=1'
},
{
title: '信用评级机构穆迪下调美国信用评级',
date: '2025-05-16',
description: '2025年5月16日,信用评级机构穆迪自2011年以来第三次下调美国主权信用评级,首...',
image: 'https://picsum.photos/80/60?random=2'
},
{
title: '马斯克与特朗普首次会',
date: '2025-05-16',
description: '马斯克5月30日高任政府效率部负责人时与特朗普首次会面别,但6月3日美然批评OBB...',
image: 'https://picsum.photos/80/60?random=3'
},
{
title: '马斯克成立"美国党"',
date: '2025-05-16',
description: '7月5日(法案签署次日),马斯克宣布成立新政党"美国党",计划参与2026年中期...',
image: 'https://picsum.photos/80/60?random=4'
},
{
title: '"90天关税暂缓期"即将到期',
date: '2025-05-16',
description: '法案通过议会美国对多国"90天关税暂缓期"即将到期(2025年7月9日),欧盟...',
image: 'https://picsum.photos/80/60?random=5'
},
{
title: '减税与就业法案的出台与立法博弈',
date: '2025-08-30',
description: '2017年,美国通过了一项旨在根本性以来最为重大的税制改革——《减税与就业法案》...',
image: 'https://picsum.photos/80/60?random=6'
}
])
// 过滤后的政策列表
const filteredPolicyList = computed(() => {
if (activeFilter.value === 'all') {
return policyList.value
}
const statusMap = {
'implemented': '已实施',
'partial': '部分实施',
'pending': '未实施'
}
return policyList.value.filter(item => item.status === statusMap[activeFilter.value])
})
// 设置过滤器
const setFilter = (filter) => {
activeFilter.value = filter
}
// 获取状态标签类型
const getStatusType = (status) => {
const typeMap = {
'已实施': 'success',
'部分实施': 'warning',
'未实施': 'info'
}
return typeMap[status] || 'info'
}
// 打开政策详情弹窗
const openPolicyDetail = (policy) => {
selectedPolicy.value = policy
showDetailModal.value = true
}
// 关闭政策详情弹窗
const closeDetailModal = () => {
showDetailModal.value = false
selectedPolicy.value = null
}
</script>
<style scoped>
.policy-tracking {
padding: 0;
}
.policy-card,
.policy-dynamics-card {
border: 1px solid #e4e7ed;
border-radius: 8px;
height: fit-content;
}
.policy-card,
.policy-dynamics-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.policy-card .card-header,
.policy-dynamics-card .card-header {
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.policy-card .card-body,
.policy-dynamics-card .card-body {
padding: 20px;
max-height: 600px;
overflow-y: auto;
}
.filter-buttons {
display: flex;
gap: 8px;
}
.filter-buttons .el-button {
color: #666;
padding: 4px 8px;
border-radius: 4px;
border: 1px solid transparent;
}
.filter-buttons .el-button.active {
color: #409eff;
background: #ecf5ff;
border-color: #b3d8ff;
}
.more-link {
color: #666;
display: flex;
align-items: center;
gap: 4px;
}
/* 左侧政策列表样式 */
.policy-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.policy-item {
display: flex;
gap: 12px;
padding-bottom: 20px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background-color 0.2s;
}
.policy-item:hover {
background-color: #f8f9fa;
}
.policy-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.policy-item:last-child:hover {
padding-bottom: 12px;
}
.policy-number {
width: 24px;
height: 24px;
background: #409eff;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
margin-top: 2px;
}
.policy-content {
flex: 1;
}
.policy-text {
margin: 0 0 12px 0;
line-height: 1.6;
color: #333;
font-size: 14px;
}
.policy-tags {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.policy-related-bill {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-top: 8px;
}
.bill-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
background: #fff;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.bill-item:hover {
background: #f5f7fa;
}
.bill-tag {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 2px 8px;
background: #a8d5ff;
color: #fff;
font-size: 12px;
font-weight: 500;
border-radius: 3px;
white-space: nowrap;
}
.bill-year {
font-size: 13px;
font-weight: 500;
color: #1e3a8a;
white-space: nowrap;
}
.bill-title {
font-size: 13px;
font-weight: 500;
color: #1e3a8a;
white-space: nowrap;
}
.bill-arrow {
width: 18px;
height: 18px;
background: #a8d5ff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #1e3a8a;
font-size: 12px;
flex-shrink: 0;
}
.policy-status {
/* background: #f8f9fa; */
padding: 8px 12px;
border-radius: 4px;
/* border-left: 3px solid #409eff; */
}
.status-info {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.status-label {
font-size: 12px;
color: #666;
}
.status-bill {
font-size: 12px;
color: #409eff;
font-weight: 500;
}
.status-tag {
margin-left: auto;
}
/* 右侧动态列表样式 */
.dynamics-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.dynamics-item {
position: relative;
padding-left: 16px;
}
.dynamics-item:not(:last-child)::before {
content: '';
position: absolute;
left: 6px;
top: 20px;
bottom: -16px;
width: 1px;
background: #e4e7ed;
}
.dynamics-content {
background: white;
position: relative;
}
.dynamics-header {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 8px;
position: relative;
}
.dynamics-dot {
width: 8px;
height: 8px;
background: #409eff;
border-radius: 50%;
flex-shrink: 0;
margin-top: 6px;
position: absolute;
left: -12px;
z-index: 1;
}
.dynamics-title {
font-size: 14px;
font-weight: 600;
color: #409eff;
margin: 0;
flex: 1;
line-height: 1.4;
}
.dynamics-date {
font-size: 12px;
color: #999;
flex-shrink: 0;
}
.dynamics-description {
font-size: 13px;
color: #666;
line-height: 1.5;
margin: 0 0 8px 0;
max-width: 320px;
}
.dynamics-image {
width: 80px;
height: 60px;
border-radius: 4px;
overflow: hidden;
float: right;
margin-left: 12px;
margin-top: -40px;
}
.dynamics-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 弹窗样式 */
.policy-detail-dialog :deep(.el-dialog) {
border-radius: 8px;
}
.policy-detail-dialog :deep(.el-dialog__header) {
padding: 20px 24px 16px;
border-bottom: 1px solid #e4e7ed;
}
.policy-detail-dialog :deep(.el-dialog__title) {
font-size: 18px;
font-weight: 600;
color: #333;
}
.policy-detail-dialog :deep(.el-dialog__body) {
padding: 24px;
}
.policy-detail-content {
display: flex;
flex-direction: column;
gap: 20px;
position: relative;
}
.policy-detail-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin: 0;
line-height: 1.5;
padding-right: 140px;
}
.policy-detail-tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.policy-detail-body {
margin: 16px 0;
}
.policy-description {
font-size: 14px;
color: #666;
line-height: 1.8;
margin: 0;
text-align: justify;
padding-right: 140px;
}
.related-bill-section {
background: #f8f9fa;
padding: 16px;
border-radius: 6px;
border-left: 3px solid #409eff;
margin-right: 140px;
}
.bill-info {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.bill-label {
font-size: 14px;
color: #666;
}
.bill-name {
font-size: 14px;
color: #409eff;
font-weight: 500;
}
.bill-status {
margin-left: auto;
}
/* 右侧信息区域 */
.policy-side-info {
position: absolute;
top: 0;
right: 0;
width: 120px;
}
.think-tank-info {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.think-tank-cover {
width: 120px;
height: 160px;
border-radius: 6px;
overflow: hidden;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.think-tank-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
.think-tank-details {
width: 100%;
}
.think-tank-title {
font-size: 12px;
font-weight: 600;
color: #333;
margin: 0 0 4px 0;
}
.publish-date {
font-size: 11px;
color: #999;
margin: 0 0 8px 0;
}
.report-title {
font-size: 11px;
color: #666;
line-height: 1.4;
margin: 0 0 12px 0;
display: -webkit-box;
-webkit-line-clamp: 3;
line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.download-btn {
font-size: 10px;
padding: 4px 8px;
height: auto;
display: flex;
align-items: center;
gap: 4px;
}
.download-btn .el-icon {
font-size: 12px;
}
/* 响应式调整 */
@media (max-width: 768px) {
.policy-tracking :deep(.el-row) {
flex-direction: column;
}
.policy-tracking :deep(.el-col) {
width: 100% !important;
margin-bottom: 20px;
}
.policy-detail-title,
.policy-description {
padding-right: 0;
}
.related-bill-section {
margin-right: 0;
}
.policy-side-info {
position: static;
width: 100%;
margin-top: 20px;
}
.think-tank-info {
flex-direction: row;
text-align: left;
gap: 16px;
}
.think-tank-cover {
width: 80px;
height: 100px;
margin-bottom: 0;
}
.think-tank-details {
flex: 1;
}
}
</style>
\ No newline at end of file
<template>
<div class="word-cloud-container">
<svg :width="width" :height="height" ref="svgRef">
</svg>
<div v-if="!words || words.length === 0" class="no-data-message">
{{ emptyText }}
</div>
</div>
</template>
<script>
import { defineComponent, ref, watch, onMounted, nextTick } from 'vue';
import * as d3 from 'd3';
import cloud from 'd3-cloud';
// 默认颜色数组,您可以根据需要扩展
const defaultColors = [
'#f56c6c', // 红色
'#e6a23c', // 橙色
'#67c23a', // 绿色
'#409eff', // 蓝色
'#909399', // 灰色
'#b3a2c7', // 紫色
'#49a37e', // 军绿
];
export default defineComponent({
name: 'WordCloud',
props: {
// 词汇数组,例如:['通用人工智能', 'AI芯片', '出口管制']
words: {
type: Array,
required: true,
default: () => [],
},
// 词云容器的宽度
width: {
type: [Number, String],
default: 600,
},
// 词云容器的高度
height: {
type: [Number, String],
default: 400,
},
// 词汇的最小字号
minFontSize: {
type: Number,
default: 16,
},
// 词汇的最大字号
maxFontSize: {
type: Number,
default: 50,
},
// 词汇颜色数组
colors: {
type: Array,
default: () => defaultColors,
},
// 词汇为空时的提示文本
emptyText: {
type: String,
default: '暂无词云数据',
},
},
setup(props) {
const svgRef = ref(null);
/**
* @description 根据词汇数组,计算词频,并转换成d3-cloud所需的格式
* @param {Array<string>} rawWords 原始词汇数组
* @returns {Array<{text: string, size: number}>} 转换后的数据
*/
const prepareData = (rawWords) => {
if (!rawWords || rawWords.length === 0) return [];
// 1. 计算词频
const wordCounts = rawWords.reduce((acc, word) => {
acc[word] = (acc[word] || 0) + 1;
return acc;
}, {});
// 2. 转换为d3-cloud所需的格式,并找出最大/最小词频
let minCount = Infinity;
let maxCount = -Infinity;
const data = Object.entries(wordCounts).map(([text, count]) => {
if (count < minCount) minCount = count;
if (count > maxCount) maxCount = count;
return { text, count };
});
// 3. 将词频映射到字号范围
const sizeScale = d3
.scaleLinear()
.domain([minCount, maxCount])
.range([props.minFontSize, props.maxFontSize]);
return data.map((d) => ({
...d,
size: sizeScale(d.count), // size就是字号
}));
};
/**
* @description 绘制词云
* @param {Array<{text: string, size: number, x: number, y: number, rotate: number}>} words 布局后的词汇数据
*/
const drawCloud = (words) => {
const svg = d3.select(svgRef.value);
// 清空SVG内容
svg.selectAll('*').remove();
// 创建一个<g>元素并平移到中心
const g = svg
.append('g')
.attr(
'transform',
`translate(${props.width / 2}, ${props.height / 2})`
);
// 绑定数据并创建<text>元素
const wordElements = g.selectAll('text').data(words, (d) => d.text);
// 进入(Enter)阶段:添加新的<text>元素
wordElements
.enter()
.append('text')
.style('font-size', (d) => `${d.size}px`)
.style('fill', (_, i) => props.colors[i % props.colors.length]) // 循环使用颜色
.attr('text-anchor', 'middle') // 文本居中
// *********** 关键修改:移除悬浮样式和交互,保持水平布局 ***********
.attr('transform', (d) => `translate(${d.x}, ${d.y})`) // 移除rotate属性
.text((d) => d.text);
// ************************************************************
// 退出(Exit)阶段:移除多余的<text>元素(如果更新时词汇减少)
wordElements.exit().remove();
};
/**
* @description 启动d3-cloud布局引擎
* @param {Array<{text: string, count: number, size: number}>} data 待布局的词汇数据
*/
const generateLayout = (data) => {
if (!data || data.length === 0) {
d3.select(svgRef.value).selectAll('*').remove();
return;
}
const layout = cloud()
.size([props.width, props.height]) // 词云的尺寸
.words(data) // 传入词汇数据
.padding(5) // 词汇之间的最小间距
// *********** 关键修改:固定旋转角度为 0,实现水平布局 ***********
.rotate(0)
// ************************************************************
.font('Impact') // 字体
.fontSize((d) => d.size) // 使用计算出的字号
.on('end', drawCloud); // 布局计算完成后调用drawCloud
layout.start(); // 启动布局
};
/**
* @description 核心渲染函数,处理数据并启动布局
*/
const renderWordCloud = () => {
// 保证在DOM更新后执行
nextTick(() => {
const processedData = prepareData(props.words);
generateLayout(processedData);
});
};
onMounted(renderWordCloud);
// 监听props变化,重新渲染词云
watch(() => [props.words, props.width, props.height, props.minFontSize, props.maxFontSize], () => {
renderWordCloud();
}, { deep: true });
return {
svgRef,
renderWordCloud,
};
},
});
</script>
<style lang="scss">
.word-cloud-container {
display: flex;
justify-content: center;
align-items: center;
position: relative;
width: v-bind(width + 'px'); // 动态绑定宽度
height: v-bind(height + 'px'); // 动态绑定高度
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
// 词云SVG的样式
svg {
display: block; // 移除底部空白
}
// 词汇文本的通用样式
text {
font-weight: bold;
// *********** 关键修改:移除transition和cursor,避免交互 ***********
}
.no-data-message {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
color: #909399;
background-color: #f5f7fa;
}
}
</style>
\ No newline at end of file
<template>
<div class="page-container">
<!-- Header 占满宽度 -->
<header class="report-header">
<div class="header-container">
<div class="header-content">
<!-- 左侧封面图片 -->
<div class="cover-image-area">
<div class="book-cover">
<div class="cover-title">{{ reportSummary.name }}</div>
<div class="cover-subtitle">{{ reportSummary.ename }}</div>
<div class="cover-globe"></div>
</div>
</div>
<!-- 中间标题区域 -->
<div class="title-area">
<h1 class="main-title">{{ reportSummary.name }}</h1>
<h2 class="sub-title">{{ reportSummary.ename }}</h2>
<div class="tags-area">
<span
v-for="(tag, index) in reportSummary.tags"
:key="tag"
class="tag-item"
:class="{ 'tag-primary': index % 2 === 0, 'tag-danger': index % 2 === 1 }"
>
{{ tag }}
</span>
</div>
</div>
<!-- 右侧元信息 -->
<div class="meta-info">
<div class="source-info">
<el-icon class="source-icon"><TrendCharts /></el-icon>
<span class="source-name">{{ reportSummary.thinkTankName }}</span>
</div>
<div class="publish-date">{{ reportSummary.times }}</div>
</div>
</div>
</div>
<!-- Tab切换区域 -->
<div class="header-tabs">
<div class="tabs-container">
<el-tabs v-model="activeTab" class="header-tab-nav">
<el-tab-pane label="内容摘要" name="summary">
<template #label>
<span class="tab-label">
<el-icon><Document /></el-icon>
内容摘要
</span>
</template>
</el-tab-pane>
<el-tab-pane label="政策追踪" name="policy">
<template #label>
<span class="tab-label">
<el-icon><Search /></el-icon>
政策追踪
</span>
</template>
</el-tab-pane>
<!-- <el-tab-pane label="影响分析" name="analysis">
<template #label>
<span class="tab-label">
<el-icon><TrendCharts /></el-icon>
影响分析
</span>
</template>
</el-tab-pane> -->
</el-tabs>
<div class="action-buttons">
<!-- <el-button type="plain" size="default">
<el-icon><Link /></el-icon>
查看官网
</el-button> -->
<el-button type="plain" size="default">
<el-icon><Download /></el-icon>
下载原文
</el-button>
<el-button type="primary" size="default">
<el-icon><DataAnalysis /></el-icon>
分析报告
</el-button>
</div>
</div>
</div>
</header>
<!-- Main Content 最大宽度1200px并居中 -->
<main class="main-content">
<div class="content-container">
<!-- 根据activeTab显示不同的组件 -->
<Overview v-if="activeTab === 'summary'" :reportSummary="reportSummary" :content="content" />
<Policy v-else-if="activeTab === 'policy'" :policy="policy" />
<Effect v-else-if="activeTab === 'analysis'" />
</div>
</main>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import {
Document,
Search,
TrendCharts,
Link,
Download,
DataAnalysis
} from '@element-plus/icons-vue'
import {
getThinkTankReportSummary,
getThinkTankReportContent,
getThinkTankReportPolicy
} from '@/api'
import { useRoute } from 'vue-router'
import { mockContent } from '../mockData'
// 引入组件
import Overview from './Overview.vue'
import Policy from './Policy.vue'
import Effect from './Effect.vue'
const activeTab = ref('summary')
const route = useRoute()
const reportSummary = ref({})
const content = ref({})
const policy = ref([])
const getReportSummary = async () => {
const { data } = await getThinkTankReportSummary({ id: route.params.id })
console.log('getReportSummary', data)
reportSummary.value = data
reportSummary.value = {
thinkTankName: '兰德公司',
name: '美国经济竞争分析',
ename: 'Analysis of US Economic Competition',
tags: ['美国', '经济竞争', '政治竞争', '军事竞争'],
times: '2025-11-27',
summary: mockContent,
researchTypes: ['人工智能',
'军事与安全',
'半导体与高科技',
'经济与贸易',
'国际规则与多边体系',
'地缘政治'],
}
}
const getContent = async () => {
const { data } = await getThinkTankReportContent({ id: route.params.id })
console.log('getContent', data)
content.value = data
}
const getPolicy = async () => {
const { data } = await getThinkTankReportPolicy({ id: route.params.id })
console.log('getPolicy', data)
policy.value = data
}
onMounted(() => {
getReportSummary()
getContent()
getPolicy()
})
</script>
<style scoped>
.page-container {
background-color: #f5f7fa;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
min-height: 100vh;
width: 100%;
--max-width: 1650px;
}
/* Header Styles - 占满宽度 */
.report-header {
background-color: #fff;
width: 100%;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.header-container {
max-width: var(--max-width);
margin: 0 auto;
padding: 32px 24px 0;
}
.header-content {
display: flex;
align-items: flex-start;
gap: 24px;
}
/* 左侧封面图片区域 */
.cover-image-area {
flex-shrink: 0;
}
.book-cover {
width: 88px;
height: 115px;
background: linear-gradient(135deg, #d4a574 0%, #c8965f 100%);
border-radius: 4px;
padding: 16px 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.cover-title {
font-size: 14px;
font-weight: 600;
color: #fff;
line-height: 1.3;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.cover-subtitle {
font-size: 10px;
color: rgba(255, 255, 255, 0.9);
line-height: 1.2;
margin-top: 4px;
}
.cover-globe {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 60px;
background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.3) 0%, transparent 70%);
border-radius: 50%;
opacity: 0.6;
}
.cover-globe::before {
content: '';
position: absolute;
top: 20%;
left: 30%;
width: 40%;
height: 30%;
background: rgba(255, 255, 255, 0.2);
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
}
.cover-globe::after {
content: '';
position: absolute;
top: 50%;
right: 20%;
width: 35%;
height: 25%;
background: rgba(255, 255, 255, 0.15);
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
}
/* 中间标题区域 */
.title-area {
flex: 1;
min-width: 0;
}
.main-title {
font-size: 28px;
font-weight: 700;
color: #2c3e50;
margin: 0 0 10px 0;
line-height: 1.4;
}
.sub-title {
font-size: 16px;
color: #7f8c8d;
font-weight: 400;
margin: 0 0 16px 0;
line-height: 1.5;
}
.tags-area {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.tag-item {
display: inline-block;
padding: 4px 12px;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
border: 1.5px solid;
background: transparent;
}
.tag-primary {
color: #409eff;
border-color: #409eff;
}
.tag-danger {
color: #f56c6c;
border-color: #f56c6c;
}
/* 右侧元信息 */
.meta-info {
flex-shrink: 0;
text-align: right;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 12px;
}
.source-info {
display: flex;
align-items: center;
gap: 6px;
color: #606266;
}
.source-icon {
font-size: 18px;
color: #8B5CF6;
}
.source-name {
font-size: 14px;
color: #606266;
font-weight: 500;
}
.publish-date {
color: #606266;
font-size: 14px;
font-weight: 400;
}
/* Header Tabs区域 */
.header-tabs {
background-color: #fff;
width: 100%;
margin-top: 20px;
}
.tabs-container {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 24px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #e4e7ed;
}
.header-tab-nav {
flex: 1;
margin: 0;
}
.header-tab-nav :deep(.el-tabs__header) {
margin: 0;
border-bottom: none;
}
.header-tab-nav :deep(.el-tabs__nav-wrap) {
padding: 0;
}
.header-tab-nav :deep(.el-tabs__nav-wrap::after) {
display: none;
}
.header-tab-nav :deep(.el-tabs__item) {
font-size: 16px;
font-weight: 500;
color: #606266;
padding: 16px 24px;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
}
.header-tab-nav :deep(.el-tabs__item:hover) {
color: #409eff;
}
.header-tab-nav :deep(.el-tabs__item.is-active) {
color: #409eff;
border-bottom-color: #409eff;
font-weight: 600;
}
.header-tab-nav :deep(.el-tabs__active-bar) {
display: none;
}
.tab-label {
display: flex;
align-items: center;
gap: 5px;
}
.action-buttons {
display: flex;
align-items: center;
}
.action-buttons .el-button {
display: flex;
align-items: center;
gap: 4px;
}
/* Main Content Styles - 最大宽度1200px并居中 */
.main-content {
width: 100%;
display: flex;
justify-content: center;
padding: 0 24px;
}
.content-container {
max-width: var(--max-width);
width: 100%;
border-radius: 8px;
}
/* 响应式设计 */
@media (max-width: 1240px) {
.header-container {
padding: 24px 20px 0;
}
.tabs-container {
padding: 0 20px;
}
}
@media (max-width: 1040px) {
.content-container {
margin: 0 20px;
}
.main-content {
padding: 0;
}
}
@media (max-width: 768px) {
.header-container {
padding: 20px 16px 0;
}
.header-content {
flex-direction: column;
gap: 20px;
}
.cover-image-area {
align-self: center;
}
.book-cover {
width: 100px;
height: 140px;
}
.title-area {
text-align: center;
}
.main-title {
font-size: 22px;
}
.sub-title {
font-size: 14px;
}
.tags-area {
justify-content: center;
}
.meta-info {
align-items: center;
text-align: center;
}
.tabs-container {
flex-direction: column;
gap: 16px;
align-items: stretch;
padding: 0 16px;
}
.header-tab-nav :deep(.el-tabs__item) {
padding: 12px 16px;
font-size: 14px;
}
.action-buttons {
justify-content: center;
}
.content-container {
margin: 0 16px;
padding: 16px;
}
}
@media (max-width: 480px) {
.header-container {
padding: 16px 12px 0;
}
.book-cover {
width: 80px;
height: 120px;
padding: 12px 8px;
}
.cover-title {
font-size: 11px;
}
.cover-subtitle {
font-size: 8px;
}
.cover-globe {
width: 45px;
height: 45px;
bottom: 15px;
}
.main-title {
font-size: 20px;
}
.sub-title {
font-size: 13px;
}
.tag-item {
font-size: 12px;
padding: 3px 10px;
}
.source-icon {
font-size: 16px;
}
.source-name {
font-size: 13px;
}
.publish-date {
font-size: 13px;
}
.tabs-container {
padding: 0 12px;
}
.header-tab-nav :deep(.el-tabs__item) {
padding: 10px 12px;
font-size: 13px;
}
.content-container {
margin: 0 12px;
padding: 12px;
}
.action-buttons {
flex-wrap: wrap;
gap: 8px;
}
.action-buttons .el-button {
font-size: 12px;
}
}
</style>
\ No newline at end of file
<template>
<div class="desc-tab">
<!-- 上半部分:基本信息和经费来源 -->
<div class="top-section">
<!-- 左侧:基本信息卡片 -->
<div class="basic-info-card">
<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="智库建筑" />
</div>
<div class="info-content">
<div class="info-row">
<span class="label">国家:</span>
<span class="value">{{ thinkTankInfo.country }}</span>
</div>
<div class="info-row">
<span class="label">成立时间:</span>
<span class="value">{{ thinkTankInfo.foundingDate }}</span>
</div>
<div class="info-row">
<span class="label">总部位置:</span>
<span class="value">{{ thinkTankInfo.position }}</span>
</div>
<div class="info-row">
<span class="label">机构性质:</span>
<span class="value">{{ thinkTankInfo.nature }}</span>
</div>
<div class="info-row">
<span class="label">员工数量:</span>
<span class="value">{{ thinkTankInfo.memnum }}</span>
</div>
<div class="info-row">
<span class="label">年度预算:</span>
<span class="value">{{ thinkTankInfo.budget }}</span>
</div>
</div>
<div class="additional-info">
<CardTitle title="全球分支机构" />
<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>
</div>
</div>
</div>
</div>
<!-- 右侧:经费来源 -->
<div class="funding-section">
<FundingSource style="margin-bottom: 20px"/>
<!-- 中间部分:研究领域演变 -->
<div class="timeline-section">
<div class="section-header">
<CardTitle title="研究领域演变" />
<el-icon class="expand-icon"><MoreFilled /></el-icon>
</div>
<div class="timeline-container">
<div class="timeline-periods">
<div class="period-item active" v-for="value in researchAreas" :key="value.id">
<div class="period-title">{{ value.time }}</div>
<div class="period-desc">{{ value.describe }}</div>
</div>
</div>
<div class="timeline-line">
<div class="timeline-progress"></div>
<div class="timeline-dots">
<div class="dot active" v-for="value in researchAreas" :key="value.id"></div>
</div>
</div>
</div>
</div>
<!-- 底部:核心研究人员 -->
<div class="core-researchers-section">
<div class="section-header">
<CardTitle title="核心研究人员" />
<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" />
</div>
<div class="researcher-info">
<h4 class="researcher-name">{{ researcher.name }}</h4>
<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>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
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,
getThinkTankFundsSource,
getThinkTankFundsTotal,
getThinkTankResearchArea,
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([])
// 初始化经费饼图
const initFundingChart = () => {
if (!fundingChart.value) return
const chart = echarts.init(fundingChart.value)
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c}万 ({d}%)'
},
series: [
{
name: '经费来源',
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 2,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false
},
labelLine: {
show: false
},
data: fundingData.value.map(item => ({
value: item.value,
name: item.name,
}))
}
]
}
chart.setOption(option)
const handleResize = () => {
chart.resize()
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
chart.dispose()
}
}
// 初始化研究人员树状图
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
}
}
]
}
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 () => {
thinkTankInfo.value = mockRandBasicInfo
}
const branchInfo = ref({})
const getBranchInfo = async () => {
branchInfo.value = mockRandBranchInfo
}
const getFundsSource = async () => {
fundingData.value = mockRandFundsSource.map(item => ({
value: item.amount,
name: item.institution,
}))
fundsByType.value = mockRandFundsByType
fundsByEntity.value = mockRandFundsByEntity
nextTick(() => {
initFundingChart()
})
}
const fundTotal = ref({
totalJe: 0,
zfJe: 0,
otherJe: 0
})
const getFundTotal = async () => {
fundTotal.value = mockRandFundTotal
}
const getResearchArea = async () => {
researchAreas.value = mockRandResearchAreas
}
const getPersonList = async () => {
coreResearchers.value = mockRandCoreResearchers
nextTick(() => {
initResearcherChart()
})
}
// 初始化
onMounted(async () => {
getThinkTankInfo()
getBranchInfo()
getFundsSource()
getFundTotal()
getResearchArea()
getPersonList()
})
// 核心研究人员数据
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 lang="scss">
.desc-tab {
min-height: 100vh;
}
/* 上半部分布局 */
.top-section {
display: flex;
gap: 20px;
align-items: flex-start;
margin-bottom: 20px;
}
/* 基本信息卡片 */
.basic-info-card {
min-width: 360px;
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
height: auto;
}
.tank-image {
margin-bottom: 20px;
margin-top: 10px;
}
.tank-image img {
width: 100%;
height: 180px;
object-fit: cover;
border-radius: 8px;
}
.info-content {
margin-bottom: 24px;
}
.info-row {
display: flex;
margin-bottom: 12px;
align-items: center;
}
.info-row .label {
font-weight: 500;
color: #374151;
min-width: 80px;
font-size: 14px;
}
.info-row .value {
color: #111827;
font-size: 14px;
}
.additional-info {
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid #e5e7eb;
}
.branch-list {
display: flex;
flex-direction: column;
margin-top: 10px;
gap: 8px;
}
.branch-item {
margin-bottom: 8px;
font-size: 14px;
}
.branch-item .location {
font-weight: 500;
color: #374151;
}
.branch-item .desc {
color: #6b7280;
}
/* 经费来源区域 */
.funding-section {
flex: 1;
}
.funding-overview {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.expand-icon {
color: #9ca3af;
cursor: pointer;
font-size: 18px;
}
/* 经费汇总 */
.funding-summary {
display: flex;
gap: 30px;
margin-bottom: 24px;
padding-bottom: 20px;
border-bottom: 1px solid #e5e7eb;
}
.summary-item {
flex: 1;
}
.summary-label {
font-size: 14px;
color: #6b7280;
margin-bottom: 8px;
}
.summary-amount {
font-size: 20px;
font-weight: 700;
color: #1f2937;
}
/* 经费详情 */
.funding-details {
display: flex;
gap: 20px;
align-items: flex-start;
}
.funding-table-left,
.funding-table-right {
flex: 0 0 200px;
display: flex;
flex-direction: column;
gap: 12px;
}
.table-item {
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px 0;
border-bottom: 1px solid #f3f4f6;
}
.table-item:last-child {
border-bottom: none;
}
.table-name {
font-size: 13px;
color: #374151;
line-height: 1.4;
}
.table-amount {
font-size: 14px;
font-weight: 600;
color: #111827;
}
.table-percent {
font-size: 12px;
color: #6b7280;
}
.funding-chart-wrapper {
flex: 1;
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
.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 {
position: relative;
}
.timeline-periods {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.period-item {
flex: 1;
padding: 0 16px;
text-align: center;
}
.period-item.active .period-title {
color: #3b82f6;
font-weight: 600;
}
.period-title {
font-size: 16px;
font-weight: 500;
color: #374151;
margin-bottom: 8px;
}
.period-desc {
font-size: 13px;
color: #6b7280;
line-height: 1.4;
}
.timeline-line {
position: relative;
height: 4px;
background-color: #e5e7eb;
border-radius: 2px;
margin: 0 60px;
}
.timeline-progress {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: linear-gradient(90deg, #3b82f6 0%, #1d4ed8 100%);
border-radius: 2px;
}
.timeline-dots {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
display: flex;
justify-content: space-between;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #e5e7eb;
border: 2px solid white;
box-shadow: 0 0 0 2px #e5e7eb;
}
.dot.active {
background-color: #3b82f6;
box-shadow: 0 0 0 2px #3b82f6;
}
/* 核心研究人员 */
.core-researchers-section {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
margin-bottom: 30px;
}
.more-icon {
color: #6b7280;
cursor: pointer;
font-size: 18px;
}
.more-icon:hover {
color: #374151;
}
.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;
}
.researchers-list {
flex: 0 0 400px;
display: flex;
flex-direction: column;
gap: 20px;
}
.researcher-item {
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 {
flex-shrink: 0;
}
.researcher-avatar .el-image {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
border: 2px solid #f3f4f6;
}
.researcher-info {
flex: 1;
min-width: 0;
}
.researcher-name {
font-size: 16px;
font-weight: 600;
color: #111827;
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: #374151;
margin: 0;
line-height: 1.4;
word-wrap: break-word;
}
/* 响应式设计 */
@media (max-width: 1024px) {
.top-section {
flex-direction: column;
}
.funding-details {
flex-direction: column;
}
.funding-table-left,
.funding-table-right {
flex: none;
width: 100%;
}
.funding-chart-wrapper {
min-height: 250px;
}
.funding-chart {
height: 250px;
min-height: 250px;
}
.timeline-periods {
flex-direction: column;
gap: 20px;
}
.timeline-line {
display: none;
}
.researchers-content {
flex-direction: column;
}
.researchers-list {
flex: none;
width: 100%;
}
}
@media (max-width: 768px) {
.desc-tab {
padding: 16px;
}
.basic-info-card,
.funding-overview,
.timeline-section,
.core-researchers-section {
padding: 16px;
}
.researcher-item {
gap: 10px;
padding: 8px 0;
}
.researcher-avatar .el-image {
width: 40px;
height: 40px;
}
.researcher-name {
font-size: 14px;
}
.researcher-position {
font-size: 12px;
}
}
</style>
\ No newline at end of file
<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
<template>
<el-row :gutter="24">
<el-col :span="6">
<aside class="filter-sidebar">
<div class="filter-group">
<CardTitle title="报告类型" style="margin-bottom: 10px"/>
<el-checkbox-group v-model="selectedReportTypes">
<el-checkbox label="研究报告" />
<el-checkbox label="简报" />
<el-checkbox label="会议记录" />
<el-checkbox label="期刊文章" />
</el-checkbox-group>
</div>
<div class="filter-group">
<CardTitle title="研究类型" style="margin-bottom: 10px"/>
<el-checkbox-group v-model="selectedResearchTypes">
<el-checkbox label="人工智能" />
<el-checkbox label="半导体/芯片" />
<el-checkbox label="能源与气候" />
<el-checkbox label="国际关系" />
<el-checkbox label="经济决策" />
<el-checkbox label="国防与安全" />
</el-checkbox-group>
</div>
<div class="filter-group">
<CardTitle title="作者" style="margin-bottom: 10px"/>
<el-input v-model="authorName" placeholder="输入作者名称" />
</div>
</aside>
</el-col>
<el-col :span="18">
<div class="report-content">
<div class="content-toolbar">
<el-input
v-model="searchQuery"
placeholder="按报告标题检索"
:prefix-icon="Search"
style="width: 240px;"
/>
<div class="sort-options">
<el-select v-model="sourceType" placeholder="来源类型" style="width: 120px; margin-right: 10px;">
<el-option label="全部" value="all"></el-option>
<el-option label="原创" value="original"></el-option>
</el-select>
<el-select v-model="publishDateSort" placeholder="发布时间" style="width: 120px;">
<el-option label="最新" value="desc"></el-option>
<el-option label="最早" value="asc"></el-option>
</el-select>
</div>
</div>
<el-row :gutter="20">
<el-col :span="8" v-for="report in reportList" :key="report.id" class="card-col">
<el-card shadow="hover" class="report-card" :body-style="{ padding: '0px' }">
<el-image :src="$withFallbackImage(report.imageUrl, report.id) " fit="cover" class="card-image" lazy></el-image>
<div class="card-content">
<h4 class="card-title">{{ report.name }}</h4>
<div class="card-meta">
<span class="card-date">{{ report.times }}</span>
<span class="card-source">{{ props.thinkTankName }}</span>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</template>
<script setup lang="ts">
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({
thinkTankName: {
type: String,
required: true
}
});
// Filters State
const selectedReportTypes = ref(['研究报告', '简报']);
const selectedResearchTypes = ref(['人工智能']);
const authorName = ref('');
// Toolbar State
const searchQuery = ref('');
const sourceType = ref('all');
const publishDateSort = ref('desc');
// Mock Data for Report Cards
const reportList = ref([
{ id: 1, title: '中国对AI的转型产业政策', date: '2025年6月26日', source: '兰德科技智库', imageUrl: 'https://images.unsplash.com/photo-1611162617474-5b21e879e113?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=600' },
{ id: 2, title: '中美对抗、竞争和合作将跨越人工智能通用领域的五个...', date: '2025年6月26日', source: '兰德科技智库', imageUrl: 'https://images.unsplash.com/photo-1574015974293-817f0e62f0f3?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=600' },
{ id: 3, title: '中国、智慧城市和中东:地区和美国的选择', date: '2025年6月26日', source: '兰德科技智库', imageUrl: 'https://images.unsplash.com/photo-1593989931843-3f17374312b3?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=600' },
{ id: 4, title: '中国对AI的转型产业政策', date: '2025年6月26日', source: '兰德科技智库', imageUrl: 'https://images.unsplash.com/photo-1695423635441-89d968a528f4?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=600' },
{ id: 5, title: '中美经济竞争:复杂经济和地缘政治关系中的收益...', date: '2025年6月26日', source: '兰德科技智库', imageUrl: 'https://images.unsplash.com/photo-1616121213037-1439247c6178?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=600' },
{ id: 6, title: '中国、智慧城市和中东:留给地区和美国的选择', date: '2025年6月26日', source: '兰德科技智库', imageUrl: 'https://images.unsplash.com/photo-1542372147-7a2846215392?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=600' },
]);
onMounted(() => {
getThinkTankReport({ id: route.params.id }).then(res => {
// reportList.value = res.data;
reportList.value = mockReportList;
});
})
</script>
<style scoped>
.filter-sidebar .filter-group {
margin-bottom: 24px;
}
.filter-sidebar .filter-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin: 0 0 12px 0;
}
.filter-sidebar .el-checkbox-group {
display: flex;
flex-direction: column;
}
.filter-sidebar .el-checkbox {
margin-bottom: 10px;
}
.filter-sidebar .el-checkbox:last-child {
margin-bottom: 0;
}
/* Right Content Grid Styles */
.content-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.card-col {
margin-bottom: 20px;
}
.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 {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.card-image {
width: 100%;
height: 160px;
display: block;
}
.card-content {
padding: 16px;
}
.card-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin: 0 0 10px 0;
line-height: 1.4;
/* overflow: hidden; */
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
overflow: hidden;
-webkit-box-orient: vertical;
}
.card-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #909399;
}
</style>
\ No newline at end of file
<template>
<div class="page-container">
<!-- Header 占满宽度 -->
<header class="think-tank-header">
<div class="header-container">
<div class="header-left">
<el-image :src="$withFallbackImage(summary.logo, summary.name) " :alt="summary.name" class="logo" />
<div class="info">
<h1 class="title"> {{ summary.name }} <span class="subtitle">{{ summary.ename }}</span></h1>
<p class="description">
{{ summary.describe }}
</p>
<div class="tags">
<el-tag effect="light" class="custom-tag" v-for="tag in summary.tags" :key="tag">{{ tag }}</el-tag>
</div>
</div>
</div>
<div class="header-right">
<el-button type="primary" :icon="Collection">查看报告合集</el-button>
</div>
</div>
<!-- Tab切换区域 -->
<div class="header-tabs">
<div class="tabs-container">
<el-tabs v-model="activeTab" class="header-tab-nav">
<el-tab-pane label="智库报告" name="reports"></el-tab-pane>
<el-tab-pane label="政策追踪" name="policy"></el-tab-pane>
<el-tab-pane label="智库百科" name="wiki"></el-tab-pane>
</el-tabs>
</div>
</div>
</header>
<!-- Main Content 最大宽度1000px并居中 -->
<main class="main-content">
<div class="content-container">
<!-- 根据activeTab切换组件 -->
<component :is="currentComponent" :thinkTankName="summary.name" />
</div>
</main>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { Collection } from '@element-plus/icons-vue';
import ReportTab from './ReportTab.vue';
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();
// --- Component Map ---
const componentMap = {
reports: ReportTab,
policy: PolicyTab,
wiki: DescTab
};
const summary = ref({
name: '',
describe: '',
ename: '',
url: '',
tags: []
});
// --- Computed Properties ---
const currentComponent = computed(() => {
return componentMap[activeTab.value] || ReportTab;
});
onMounted(() => {
getThinkTankSummary({ id: route.params.id }).then(res => {
console.log(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;
width: 100%;
}
/* Header Styles - 占满宽度 */
.think-tank-header {
background-color: #fff;
width: 100%;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.header-container {
max-width: var(--max-width);
margin: 0 auto;
padding: 24px 24px 0;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
/* Header Tabs区域 */
.header-tabs {
background-color: #fff;
/* border-top: 1px solid #e4e7ed; */
width: 100%;
margin-top: 20px;
}
.tabs-container {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 24px;
}
.header-tab-nav {
margin: 0;
}
.header-tab-nav :deep(.el-tabs__header) {
margin: 0;
border-bottom: none;
}
.header-tab-nav :deep(.el-tabs__nav-wrap) {
padding: 0;
}
.header-tab-nav :deep(.el-tabs__nav-wrap::after) {
display: none;
}
.header-tab-nav :deep(.el-tabs__item) {
font-size: 16px;
font-weight: 500;
color: #606266;
padding: 16px 24px;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
}
.header-tab-nav :deep(.el-tabs__item:hover) {
color: #409eff;
}
.header-tab-nav :deep(.el-tabs__item.is-active) {
color: #409eff;
border-bottom-color: #409eff;
font-weight: 600;
}
.header-tab-nav :deep(.el-tabs__active-bar) {
display: none;
}
.header-left {
display: flex;
align-items: flex-start;
}
.logo {
width: 64px;
height: 64px;
border-radius: 8px;
margin-right: 20px;
flex-shrink: 0;
}
.info .title {
font-size: 20px;
font-weight: 600;
color: #303133;
margin: 0 0 8px 0;
}
.info .subtitle {
font-size: 14px;
color: #909399;
font-weight: normal;
margin-left: 8px;
}
.info .description {
font-size: 14px;
color: #606266;
line-height: 1.6;
margin: 0 0 12px 0;
max-width: 600px;
}
.custom-tag {
background-color: #ecf5ff;
border-color: #d9ecff;
color: #409eff;
margin-right: 8px;
}
/* Main Content Styles - 最大宽度1000px并居中 */
.main-content {
width: 100%;
display: flex;
justify-content: center;
padding: 0 24px;
}
.content-container {
max-width: var(--max-width);
width: 100%;
background-color: #fff;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
/* 响应式设计 */
@media (max-width: 1240px) {
.header-container {
padding: 24px 20px 0;
}
.tabs-container {
padding: 0 20px;
}
}
@media (max-width: 1040px) {
.content-container {
margin: 0 20px;
}
.main-content {
padding: 0;
}
}
@media (max-width: 768px) {
.header-container {
flex-direction: column;
gap: 16px;
padding: 16px 16px 0;
}
.tabs-container {
padding: 0 16px;
}
.header-tab-nav :deep(.el-tabs__item) {
padding: 12px 16px;
font-size: 14px;
}
.header-left {
flex-direction: column;
align-items: flex-start;
}
.logo {
margin-right: 0;
margin-bottom: 12px;
}
.header-right {
align-self: flex-start;
}
.content-container {
margin: 0 16px;
padding: 16px;
}
.info .description {
max-width: none;
}
}
@media (max-width: 480px) {
.header-container {
padding: 12px 12px 0;
}
.tabs-container {
padding: 0 12px;
}
.header-tab-nav :deep(.el-tabs__item) {
padding: 10px 12px;
font-size: 13px;
}
.content-container {
margin: 0 12px;
padding: 12px;
}
.info .title {
font-size: 18px;
}
.info .subtitle {
display: block;
margin-left: 0;
margin-top: 4px;
}
}
</style>
\ No newline at end of file
<template>
<div class="dashboard-container">
<div class="top-row">
<el-card class="chart-card" shadow="hover">
<template #header>
<div class="card-header">
<div class="title">
<el-icon color="#409EFF"><TrendCharts /></el-icon>
<span>政策建议趋势分布</span>
</div>
<el-select v-model="lineChartPeriod" size="small" style="width: 100px">
<el-option label="近十年" value="10years" />
<el-option label="近五年" value="5years" />
</el-select>
</div>
</template>
<div ref="lineChartRef" class="chart-container"></div>
</el-card>
<el-card class="chart-card" shadow="hover">
<template #header>
<div class="card-header">
<div class="title">
<el-icon color="#409EFF"><PieChart /></el-icon>
<span>政策建议领域分布</span>
</div>
<el-select v-model="pieChartYear" size="small" style="width: 100px">
<el-option label="2025年" value="2025" />
<el-option label="2024年" value="2024" />
</el-select>
</div>
</template>
<div ref="pieChartRef" class="chart-container"></div>
</el-card>
</div>
<div class="bottom-row">
<el-card class="chart-card" shadow="hover">
<template #header>
<div class="card-header">
<div class="title">
<el-icon color="#409EFF"><Share /></el-icon>
<span>智库资金流向</span>
</div>
</div>
</template>
<div ref="sankeyChartRef" class="chart-container"></div>
</el-card>
<el-card class="chart-card" shadow="hover">
<template #header>
<div class="card-header">
<div class="title">
<el-icon color="#409EFF"><Opportunity /></el-icon>
<span>智库研究热点</span>
</div>
<el-select v-model="hotspotPeriod" size="small" style="width: 100px">
<el-option label="近一年" value="1year" />
<el-option label="近半年" value="halfyear" />
</el-select>
</div>
</template>
<div class="list-container">
<div v-for="(item, index) in hotspotList" :key="index" class="list-item">
<div class="rank-wrapper">
<span :class="['rank-num', index < 3 ? 'top-rank' : 'normal-rank']">
{{ index + 1 }}
</span>
</div>
<div class="content-text" :title="item.title">{{ item.title }}</div>
<div class="report-count">{{ item.count }}份报告 ></div>
</div>
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import * as echarts from 'echarts';
import {
TrendCharts,
PieChart,
Share,
Opportunity
} from '@element-plus/icons-vue';
// --- 状态管理 ---
const lineChartPeriod = ref('10years');
const pieChartYear = ref('2025');
const hotspotPeriod = ref('1year');
// --- DOM 引用 ---
const lineChartRef = ref(null);
const pieChartRef = ref(null);
const sankeyChartRef = ref(null);
// --- 模拟数据 ---
// 列表数据
const hotspotList = ref([
{ title: '人工智能领域竞争发展', count: 11 },
{ title: '美元未来与能源出口挂钩', count: 7 },
{ title: '量子领域国家合作', count: 5 },
{ title: '限制中国产燃油进口', count: 5 },
{ title: '禁止政府部门采购受控半导体或服务', count: 4 },
{ title: '禁止向部分中国实体提供资金', count: 3 },
{ title: '中国生产电池', count: 3 },
{ title: '重视新兴中国技术公司威胁', count: 2 },
{ title: '禁止卫星出口至中国', count: 1 },
{ title: '华为设备', count: 1 },
]);
// ECharts 实例
let lineChart = null;
let pieChart = null;
let sankeyChart = null;
// --- 初始化图表 ---
const initLineChart = () => {
if (!lineChartRef.value) return;
lineChart = echarts.init(lineChartRef.value);
const option = {
color: ['#409EFF', '#67C23A', '#909399', '#E6A23C', '#73C0DE', '#F56C6C'],
tooltip: { trigger: 'axis' },
legend: {
data: ['人工智能', '集成电路', '量子科技', '生物科技', '通信网络', '能源'],
bottom: 'top',
icon: 'circle'
},
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
boundaryGap: false,
data: ['2014', '2015', '2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024', '2025']
},
yAxis: { type: 'value' },
series: [
{ name: '人工智能', type: 'line', smooth: true, areaStyle: { opacity: 0.1 }, data: [150, 80, 90, 100, 80, 100, 130, 150, 140, 170, 190, 175] },
{ name: '集成电路', type: 'line', smooth: true, areaStyle: { opacity: 0.1 }, data: [70, 100, 125, 115, 135, 150, 168, 165, 165, 172, 172, 185] },
{ name: '量子科技', type: 'line', smooth: true, data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10] }, // 模拟低数据
{ name: '生物科技', type: 'line', smooth: true, data: [10, 5, 5, 10, 10, 15, 20, 30, 28, 30, 45, 45] },
{ name: '通信网络', type: 'line', smooth: true, areaStyle: { opacity: 0.1 }, data: [40, 35, 45, 50, 50, 48, 55, 75, 65, 65, 68, 60] },
{ name: '能源', type: 'line', smooth: true, areaStyle: { opacity: 0.1 }, data: [100, 75, 65, 85, 65, 75, 85, 120, 118, 150, 140, 110] },
]
};
lineChart.setOption(option);
};
const initPieChart = () => {
if (!pieChartRef.value) return;
pieChart = echarts.init(pieChartRef.value);
const option = {
tooltip: { trigger: 'item' },
legend: {
orient: 'vertical',
left: '5%',
top: 'center',
itemGap: 20,
formatter: (name) => {
// 简单的 mock 数据映射,实际应从 data 动态获取
const dataMap = {
'人工智能': '50项 21%',
'集成电路': '46项 18%',
'通信网络': '40项 16%',
'能源': '32项 14%',
'量子科技': '31项 12%',
'生物科技': '30项 11%',
'其他': '24项 8%'
};
return `{name|${name}}\n{val|${dataMap[name]}}`;
},
textStyle: {
rich: {
name: { fontWeight: 'bold', fontSize: 14, padding: [0, 0, 5, 0] },
val: { color: '#666', fontSize: 12 }
}
}
},
series: [
{
name: '领域分布',
type: 'pie',
radius: ['45%', '70%'],
center: ['65%', '50%'], // 向右偏移,留位置给 Legend
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: { show: false }, // 隐藏连接线上的标签,使用 Legend
data: [
{ value: 50, name: '人工智能', itemStyle: { color: '#409EFF' } },
{ value: 46, name: '集成电路', itemStyle: { color: '#F56C6C' } },
{ value: 40, name: '通信网络', itemStyle: { color: '#a0cfff' } },
{ value: 32, name: '能源', itemStyle: { color: '#E6A23C' } },
{ value: 31, name: '量子科技', itemStyle: { color: '#cceeff' } },
{ value: 30, name: '生物科技', itemStyle: { color: '#d1bdf2' } },
{ value: 24, name: '其他', itemStyle: { color: '#f2f2f2' } }
]
}
]
};
pieChart.setOption(option);
};
const initSankeyChart = () => {
if (!sankeyChartRef.value) return;
sankeyChart = echarts.init(sankeyChartRef.value);
// 简化的桑基图节点和连接
const nodes = [
{ name: '美国', itemStyle: { color: '#1f5cb8' } },
{ name: '英国', itemStyle: { color: '#6f42c1' } },
{ name: '加拿大', itemStyle: { color: '#d9534f' } },
// 中间层
{ name: '美国能源部', itemStyle: { color: '#a4c639' } },
{ name: '美国财政部', itemStyle: { color: '#f06292' } },
{ name: '美国国务院', itemStyle: { color: '#b08d55' } },
{ name: '美国国际开发署', itemStyle: { color: '#4285f4' } },
{ name: '美国内政部', itemStyle: { color: '#8cae9e' } },
{ name: '美国国家民主基金会', itemStyle: { color: '#a1887f' } },
// 右侧层 (部分)
{ name: '麻省理工学院科技评论', itemStyle: { color: '#7cb342' } },
{ name: '麦肯锡全球研究院', itemStyle: { color: '#cddc39' } },
{ name: '卡内基国际和平研究院', itemStyle: { color: '#afb42b' } },
{ name: '战略与国际研究中心', itemStyle: { color: '#827717' } },
{ name: '兰德公司', itemStyle: { color: '#689f38' } },
];
const links = [
{ source: '美国', target: '美国能源部', value: 50 },
{ source: '美国', target: '美国财政部', value: 40 },
{ source: '美国', target: '美国国务院', value: 60 },
{ source: '美国', target: '美国国际开发署', value: 20 },
{ source: '美国', target: '美国内政部', value: 15 },
{ source: '英国', target: '美国国务院', value: 10 },
{ source: '加拿大', target: '美国能源部', value: 5 },
// Flow to right
{ source: '美国能源部', target: '麻省理工学院科技评论', value: 20 },
{ source: '美国能源部', target: '麦肯锡全球研究院', value: 25 },
{ source: '美国财政部', target: '卡内基国际和平研究院', value: 30 },
{ source: '美国国务院', target: '战略与国际研究中心', value: 40 },
{ source: '美国国务院', target: '兰德公司', value: 15 },
];
const option = {
tooltip: { trigger: 'item', triggerOn: 'mousemove' },
series: [
{
type: 'sankey',
layout: 'none',
emphasis: { focus: 'adjacency' },
nodeAlign: 'left',
data: nodes,
links: links,
lineStyle: { color: 'gradient', curveness: 0.5, opacity: 0.3 },
label: { position: 'right', color: '#666', fontSize: 10 }
}
]
};
sankeyChart.setOption(option);
};
// --- 生命周期钩子 ---
onMounted(() => {
nextTick(() => {
initLineChart();
initPieChart();
initSankeyChart();
});
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
lineChart?.dispose();
pieChart?.dispose();
sankeyChart?.dispose();
});
const handleResize = () => {
lineChart?.resize();
pieChart?.resize();
sankeyChart?.resize();
};
</script>
<style scoped>
.dashboard-container {
background-color: #f5f7fa;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
height: 100vh; /* 或根据需要调整高度 */
box-sizing: border-box;
}
/* 通用布局类 */
.top-row, .bottom-row {
display: grid;
grid-template-columns: 1.5fr 1fr; /* 2列等宽 */
gap: 20px;
flex: 1; /* 上下平分高度 */
min-height: 0; /* 防止溢出 */
}
.chart-card {
display: flex;
flex-direction: column;
height: 100%;
border-radius: 8px;
}
/* Element Plus Card 样式覆盖 */
:deep(.el-card__header) {
padding: 15px 20px;
border-bottom: 1px solid #ebeef5;
}
:deep(.el-card__body) {
flex: 1;
padding: 10px;
position: relative;
height: 0; /* 关键:配合 flex:1 让内部图表自适应高度 */
}
/* 头部样式 */
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: bold;
color: #303133;
}
/* 图表容器 */
.chart-container {
width: 100%;
height: 100%;
}
/* 列表样式 */
.list-container {
height: 100%;
overflow-y: auto;
padding: 0 10px;
}
.list-item {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px dashed #eee;
font-size: 14px;
color: #606266;
}
.list-item:last-child {
border-bottom: none;
}
.rank-wrapper {
width: 30px;
text-align: center;
margin-right: 10px;
}
.rank-num {
font-weight: bold;
font-family: Arial, sans-serif;
}
.top-rank {
color: #E6A23C; /* 前三名金色 */
font-size: 16px;
}
.normal-rank {
color: #909399; /* 其他灰色 */
font-size: 14px;
}
.content-text {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #303133;
cursor: pointer;
}
.content-text:hover {
color: #409EFF;
}
.report-count {
color: #909399;
font-size: 12px;
margin-left: 10px;
}
/* 响应式调整 (可选) */
@media (max-width: 1024px) {
.top-row, .bottom-row {
grid-template-columns: 1fr; /* 小屏幕变为单列 */
}
.dashboard-container {
height: auto; /* 允许滚动 */
}
.chart-card {
height: 400px; /* 固定高度 */
}
}
</style>
\ No newline at end of file
<template>
<div class="dashboard-container">
<el-row :gutter="20">
<el-col :span="8" :xs="24">
<el-card shadow="hover" class="dashboard-card">
<template #header>
<div class="card-header">
<div class="title-group">
<el-icon class="icon-blue"><PieChart /></el-icon>
<span class="title">提出建议领域分布</span>
</div>
<span class="link">查看数据源 ></span>
</div>
</template>
<div class="card-body">
<div class="filter-row">
<el-select v-model="yearLeft" size="small" class="year-select">
<el-option label="2025年" value="2025" />
<el-option label="2024年" value="2024" />
</el-select>
</div>
<div ref="leftChartRef" class="chart-container"></div>
</div>
</el-card>
</el-col>
<el-col :span="8" :xs="24">
<el-card shadow="hover" class="dashboard-card">
<template #header>
<div class="card-header">
<div class="title-group">
<el-icon class="icon-orange"><Flag /></el-icon>
<span class="title">相关政策领域分布</span>
</div>
<span class="link">查看数据源 ></span>
</div>
</template>
<div class="card-body">
<div class="filter-row">
<el-select v-model="yearMiddle" size="small" class="year-select">
<el-option label="2025年" value="2025" />
<el-option label="2024年" value="2024" />
</el-select>
</div>
<div class="policy-list">
<div v-for="(item, index) in currentPolicyList" :key="index" class="policy-item">
<div class="item-label">
<span class="dot" :style="{ backgroundColor: item.color }"></span>
<span class="name">{{ item.name }}</span>
</div>
<div class="progress-track">
<div
class="progress-bar"
:style="{ width: item.percent + '%', backgroundColor: item.color }"
></div>
</div>
<div class="item-value">
<span class="count">{{ item.current }}项 / {{ item.total }}项</span>
<span class="percent">{{ item.percent }}%</span>
</div>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8" :xs="24">
<el-card shadow="hover" class="dashboard-card">
<template #header>
<div class="card-header">
<div class="title-group">
<el-icon class="icon-orange"><TrendCharts /></el-icon>
<span class="title">热门研究方向变化趋势</span>
</div>
<span class="link">查看数据源 ></span>
</div>
</template>
<div class="card-body">
<div class="filter-row right-filter">
<el-select v-model="yearRight" size="small" class="year-select">
<el-option label="2025年" value="2025" />
<el-option label="2024年" value="2024" />
</el-select>
</div>
<div ref="rightChartRef" class="chart-container"></div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue'
import { PieChart, Flag, TrendCharts } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
// ==================== 1. 模拟数据源 (Mock Data) ====================
// 视觉颜色常量 (为了还原图片,不同图表用了不同色系)
const COLORS = {
pie: ['#409EFF', '#F56C6C', '#a0cfff', '#E6A23C', '#b3e19d', '#d1edc4', '#909399'],
line: {
ic: '#409EFF', // 集成电路
ai: '#67C23A', // 人工智能 (青绿)
comm: '#8e44ad', // 通信 (紫)
quantum: '#E6A23C', // 量子 (橙)
bio: '#a0cfff', // 生物 (淡蓝)
energy: '#F56C6C' // 能源 (红)
}
}
const mockDatabase = {
'2025': {
// 左侧环形图数据
pieData: [
{ value: 50, name: '集成电路' },
{ value: 46, name: '人工智能' },
{ value: 40, name: '通信网络' },
{ value: 32, name: '量子科技' },
{ value: 31, name: '生物科技' },
{ value: 30, name: '能源' },
{ value: 24, name: '其他' }
],
// 中间列表数据
policyData: [
{ name: '集成电路', current: 2, total: 10, percent: 20, color: '#c0392b' },
{ name: '人工智能', current: 1, total: 6, percent: 17, color: '#2e7d32' },
{ name: '通信网络', current: 2, total: 7, percent: 26, color: '#f39c12' },
{ name: '量子科技', current: 1, total: 2, percent: 50, color: '#c0392b' },
{ name: '生物科技', current: 3, total: 7, percent: 43, color: '#c0392b' },
{ name: '能源', current: 11, total: 20, percent: 55, color: '#c0392b' }
],
// 右侧折线图数据
lineData: {
xAxis: ['2023\nQ1', '2023\nQ3', '2024\nQ1', '2024\nQ3', '2025\nQ1', '2025\nQ3'],
series: {
'集成电路': [32, 13, 20, 28, 37, 40],
'量子科技': [29, 18, 15, 19, 14, 9],
'生物科技': [15, 17, 24, 23, 16, 24],
'能源': [20, 22, 12, 14, 19, 5],
'通信网络': [3, 3, 1, 6, 4, 2],
'人工智能': [5, 15, 25, 10, 15, 18]
}
}
},
'2024': {
pieData: [
{ value: 40, name: '集成电路' },
{ value: 35, name: '人工智能' },
{ value: 45, name: '通信网络' },
{ value: 20, name: '量子科技' },
{ value: 25, name: '生物科技' },
{ value: 35, name: '能源' },
{ value: 15, name: '其他' }
],
policyData: [
{ name: '集成电路', current: 5, total: 8, percent: 62, color: '#c0392b' },
{ name: '人工智能', current: 3, total: 5, percent: 60, color: '#2e7d32' },
{ name: '通信网络', current: 1, total: 10, percent: 10, color: '#f39c12' },
{ name: '量子科技', current: 0, total: 2, percent: 0, color: '#c0392b' },
{ name: '生物科技', current: 4, total: 6, percent: 66, color: '#c0392b' },
{ name: '能源', current: 8, total: 15, percent: 53, color: '#c0392b' }
],
lineData: {
xAxis: ['2022\nQ1', '2022\nQ3', '2023\nQ1', '2023\nQ3', '2024\nQ1', '2024\nQ3'],
series: {
'集成电路': [10, 20, 25, 30, 35, 32],
'量子科技': [5, 8, 12, 15, 20, 29],
'生物科技': [20, 18, 15, 17, 15, 15],
'能源': [15, 14, 18, 20, 22, 12],
'通信网络': [8, 7, 6, 5, 3, 1],
'人工智能': [12, 18, 20, 22, 15, 25]
}
}
}
}
// ==================== 2. 状态管理 ====================
const yearLeft = ref('2025')
const yearMiddle = ref('2025')
const yearRight = ref('2025')
const leftChartRef = ref(null)
const rightChartRef = ref(null)
let leftChart = null
let rightChart = null
// 计算属性:获取当前中间列表的数据
const currentPolicyList = computed(() => {
return mockDatabase[yearMiddle.value]?.policyData || []
})
// ==================== 3. 图表逻辑 ====================
// --- 左侧环形图 ---
const updateLeftChart = () => {
if (!leftChart || !leftChartRef.value) return
const data = mockDatabase[yearLeft.value]?.pieData
if (!data) return
const option = {
color: COLORS.pie,
tooltip: { trigger: 'item' },
legend: { show: false }, // 隐藏图例,直接显示Label
series: [
{
name: '建议领域',
type: 'pie',
radius: ['45%', '70%'],
center: ['50%', '55%'],
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 0,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}\n{c}项 {d}%',
lineHeight: 16,
color: '#606266',
fontSize: 12
},
labelLine: {
show: true,
length: 15,
length2: 10
},
data: data
}
]
}
leftChart.setOption(option)
// 确保图表正确渲染
leftChart.resize()
}
// --- 右侧折线图 ---
const updateRightChart = () => {
if (!rightChart) return
const data = mockDatabase[yearRight.value].lineData
const option = {
tooltip: { trigger: 'axis' },
legend: {
data: ['集成电路', '人工智能', '通信网络', '量子科技', '生物科技', '能源'],
top: 0,
left: 0,
icon: 'circle',
itemWidth: 8,
itemHeight: 8,
textStyle: { fontSize: 10, padding: [0, 2, 0, 0] }
},
grid: {
left: '2%',
right: '3%',
bottom: '3%',
top: '28%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false, // 折线图点在轴线上
data: data.xAxis,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: '#909399', fontSize: 10 }
},
yAxis: {
type: 'value',
splitLine: {
lineStyle: { type: 'dotted', color: '#ebeef5' }
},
axisLabel: { color: '#909399' }
},
series: [
{
name: '集成电路', type: 'line', smooth: false, symbol: 'circle', symbolSize: 6,
itemStyle: { color: COLORS.line.ic },
data: data.series['集成电路']
},
{
name: '量子科技', type: 'line', smooth: false, symbol: 'circle', symbolSize: 6,
itemStyle: { color: COLORS.line.quantum },
data: data.series['量子科技']
},
{
name: '生物科技', type: 'line', smooth: false, symbol: 'circle', symbolSize: 6,
itemStyle: { color: COLORS.line.bio },
data: data.series['生物科技']
},
{
name: '能源', type: 'line', smooth: false, symbol: 'circle', symbolSize: 6,
itemStyle: { color: COLORS.line.energy },
data: data.series['能源']
},
{
name: '通信网络', type: 'line', smooth: false, symbol: 'circle', symbolSize: 6,
itemStyle: { color: COLORS.line.comm },
data: data.series['通信网络']
},
{
name: '人工智能', type: 'line', smooth: false, symbol: 'circle', symbolSize: 6,
itemStyle: { color: COLORS.line.ai },
data: data.series['人工智能']
}
]
}
rightChart.setOption(option, true) // true = 不合并,完全重绘,避免旧线条残留
}
// ==================== 4. 生命周期与监听 ====================
// 监听年份变化,刷新图表
watch(yearLeft, () => updateLeftChart())
watch(yearRight, () => updateRightChart())
// 中间列表由 computed 自动处理,无需 watch
const resizeHandler = () => {
leftChart?.resize()
rightChart?.resize()
}
onMounted(() => {
nextTick(() => {
// 确保 DOM 完全渲染后再初始化
setTimeout(() => {
// 初始化实例
if (leftChartRef.value) {
leftChart = echarts.init(leftChartRef.value)
updateLeftChart()
// 确保图表正确渲染
leftChart.resize()
}
if (rightChartRef.value) {
rightChart = echarts.init(rightChartRef.value)
updateRightChart()
// 确保图表正确渲染
rightChart.resize()
}
window.addEventListener('resize', resizeHandler)
}, 100)
})
})
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler)
leftChart?.dispose()
rightChart?.dispose()
})
</script>
<style lang="scss" scoped>
// 变量定义
$card-height: 360px;
$bg-color: #f5f7fa;
$text-main: #303133;
$text-sub: #909399;
$border-color: #ebeef5;
.dashboard-container {
// background-color: $bg-color;
padding: 20px;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
.dashboard-card {
height: $card-height;
display: flex;
flex-direction: column;
border: none;
border-radius: 8px;
background: #fff;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
margin-bottom: 10px; // 移动端换行间距
:deep(.el-card__header) {
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
}
:deep(.el-card__body) {
flex: 1;
padding: 15px 20px;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
}
// 头部样式
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.title-group {
display: flex;
align-items: center;
gap: 8px;
.title {
font-weight: 700;
font-size: 16px;
color: $text-main;
}
.el-icon { font-size: 18px; }
.icon-blue { color: #409EFF; }
.icon-orange { color: #E6A23C; }
}
.link {
font-size: 12px;
color: #409EFF;
cursor: pointer;
transition: opacity 0.3s;
&:hover { opacity: 0.8; }
}
}
// 过滤器区域
.filter-row {
display: flex;
justify-content: flex-end;
margin-bottom: 10px;
z-index: 5;
.year-select {
width: 90px;
}
&.right-filter {
position: absolute;
right: 20px;
top: 15px; // 调整位置以避开图例
}
}
// ECharts 容器
.chart-container {
width: 100%;
flex: 1;
min-height: 200px; // 确保有最小高度,让图表可以渲染
height: 0; // 配合 flex: 1 使用
}
// 中间进度列表样式
.policy-list {
display: flex;
flex-direction: column;
justify-content: space-between; // 上下均匀分布
height: 100%;
padding-bottom: 5px;
.policy-item {
display: flex;
align-items: center;
font-size: 12px;
color: #606266;
.item-label {
width: 85px; // 固定宽度对齐
display: flex;
align-items: center;
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 8px;
flex-shrink: 0;
}
.name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
}
}
.progress-track {
flex: 1;
height: 6px;
background-color: #f2f3f5;
border-radius: 3px;
margin: 0 15px;
overflow: hidden;
.progress-bar {
height: 100%;
border-radius: 3px;
transition: width 0.5s ease; // 宽度变化动画
}
}
.item-value {
width: 110px;
display: flex;
justify-content: space-between;
color: $text-sub;
.count { font-family: Arial, sans-serif; }
.percent {
width: 35px;
text-align: right;
font-family: Arial, sans-serif;
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="report-page-container">
<!-- 顶部过滤区域 -->
<div class="top-filters">
<div class="filter-left">
<span class="filter-label" :class="{ 'active': activeTab === 'report' }" @click="activeTab = 'report'">智库报告</span>
<span class="filter-label" :class="{ 'active': activeTab === 'policy' }" @click="activeTab = 'policy'">政策建议</span>
</div>
<div class="filter-right">
<el-select v-model="selectedDateRange" placeholder="请选择" class="filter-select">
<el-option label="近一年发布" value="last_year"></el-option>
<el-option label="近三年发布" value="last_three_years"></el-option>
</el-select>
<el-select v-model="selectedSort" placeholder="请选择" class="filter-select">
<el-option label="发布时间" value="publish_time"></el-option>
<el-option label="热度" value="popularity"></el-option>
</el-select>
</div>
</div>
<el-row :gutter="24" v-if="activeTab === 'report'">
<el-col :span="5">
<div class="sidebar-filters">
<div class="filter-group">
<CardTitle title="地区筛选" style="margin-bottom: 10px"/>
<div class="checkbox-group">
<el-checkbox
v-for="region in filters.regions"
:key="region.id"
v-model="activeFilters.regions"
:label="region.id"
class="filter-checkbox"
>
{{ region.name }}
</el-checkbox>
</div>
</div>
<div class="filter-group">
<CardTitle title="智库类型" style="margin-bottom: 10px"/>
<div class="checkbox-group">
<el-checkbox
v-for="type in filters.types"
:key="type.id"
v-model="activeFilters.types"
:label="type.id"
class="filter-checkbox"
>
{{ type.name }}
</el-checkbox>
</div>
</div>
<div class="filter-group">
<CardTitle title="研究类型" style="margin-bottom: 10px"/>
<div class="checkbox-group">
<el-checkbox
v-for="research in filters.researches"
:key="research.id"
v-model="activeFilters.researches"
:label="research.id"
class="filter-checkbox"
>
{{ research.rtypeName }}
</el-checkbox>
</div>
</div>
<div class="subscription-box">
<h3>动态订阅</h3>
<p>选择您感兴趣的研究领域,获取最新报告和相关书目。</p>
<el-button type="primary" style="width: 100%;">保存订阅设置</el-button>
</div>
</div>
</el-col>
<el-col :span="19">
<div class="main-content">
<el-row :gutter="20">
<el-col :span="8" v-for="report in reportList" :key="report.id" class="card-col"
@click="handleReportClick(report)">
<el-card shadow="hover" class="report-card" :body-style="{ padding: '0px' }">
<el-image :src="$withFallbackImage(report.imageUrl, report.id) " fit="cover" class="card-image" lazy></el-image>
<div class="card-content">
<h4 class="card-title">{{ report.name }}</h4>
<div class="card-meta">
<span class="card-date">{{ report.times }}</span>
<span class="card-source">{{ report.thinkTankName }}</span>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
<PolicyTab v-if="activeTab === 'policy'" :showSearch="false" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router'
import PolicyTab from '@/components/PolicyTab.vue'
import { getOverviewReport, getThinkTankResearchType } from '@/api'
import { mockReportList } from '../mockData';
const router = useRouter()
const activeTab = ref('report')
// --- Reactive Data for Filters ---
const filters = reactive({
regions: [
{ id: 'north_america', name: '北美' },
{ id: 'europe', name: '欧洲' },
{ id: 'asia', name: '亚洲' },
{ id: 'other', name: '其他地区' },
],
types: [
{ id: 'gov', name: '政府背景' },
{ id: 'university', name: '高校附庸' },
{ id: 'independent', name: '独立智库' },
{ id: 'corporate', name: '企业视景' },
],
researches: [],
});
const activeFilters = reactive({
regions: ['north_america'],
types: ['gov'],
researches: [],
});
// --- Reactive Data for Right Content ---
const selectedDateRange = ref('last_year');
const selectedSort = ref('publish_time');
const reportList = ref([]);
const getReports = async () => {
const { data } = await getOverviewReport({
areas: activeFilters.regions,
researchTypeIds: activeFilters.researches,
})
// reportList.value = data
reportList.value = mockReportList
}
const handleReportClick = (report) => {
router.push({ name: 'ReportDetail', params: { id: report.id } })
}
const getResearchType = async () => {
const { data } = await getThinkTankResearchType()
console.log('getResearchType', data)
filters.researches = data
}
onMounted(() => {
getReports()
getResearchType()
})
</script>
<style scoped>
.report-page-container {
/* padding: 24px; */
margin-top: 20px;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
}
/* --- Left Sidebar Styles --- */
.sidebar-filters {
background-color: #fff;
padding: 20px;
border-radius: 4px;
}
.filter-group {
margin-bottom: 25px;
}
.filter-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin: 0 0 15px 0;
display: flex;
align-items: center;
}
.filter-title .icon-square {
display: inline-block;
width: 4px;
height: 16px;
background-color: #409EFF;
margin-right: 8px;
border-radius: 2px;
}
/* 复选框组样式 */
.checkbox-group {
display: flex;
flex-direction: column;
gap: 4px;
}
.filter-checkbox {
margin: 0 !important;
border-radius: 4px;
transition: all 0.3s ease;
width: 100%;
display: flex;
align-items: center;
}
.filter-checkbox:hover {
background-color: #f0f2f5;
}
.subscription-box {
background-color: #f5f7fa;
border-radius: 4px;
padding: 15px;
text-align: center;
}
.subscription-box p {
font-size: 13px;
color: #909399;
line-height: 1.6;
margin: 10px 0 15px 0;
}
/* --- Right Content Styles --- */
.main-content {
/* background-color: #fff; */
/* padding: 20px; */
border-radius: 4px;
}
/* 顶部过滤区域样式 */
.top-filters {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
/* background: #fff; */
border-radius: 8px;
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); */
margin-bottom: 10px;
}
.filter-left {
display: flex;
align-items: center;
gap: 32px;
}
.filter-label {
font-size: 16px;
color: #606266;
cursor: pointer;
position: relative;
padding: 8px 0;
transition: color 0.3s ease;
border-radius: 14px;
padding: 4px 10px;
}
.filter-label.active {
color: #fff;
background: #1459BB;
}
.filter-label:hover {
color: #409EFF;
}
.filter-right {
display: flex;
align-items: center;
gap: 12px;
}
.filter-select {
width: 140px;
}
.filter-select :deep(.el-input__wrapper) {
border-radius: 6px;
box-shadow: 0 0 0 1px #dcdfe6;
transition: box-shadow 0.3s ease;
}
.filter-select :deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px #409EFF;
}
.card-col {
margin-bottom: 20px;
}
.report-card {
cursor: pointer;
border-radius: 10px;
padding: 10px;
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.report-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card-image {
width: 100%;
height: 160px;
display: block;
}
.card-content {
padding: 16px;
}
.card-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin: 0 0 10px 0;
line-height: 1.4;
/* For multi-line ellipsis */
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
.card-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #909399;
}
/* 响应式设计 */
@media (max-width: 768px) {
.top-filters {
flex-direction: column;
gap: 16px;
padding: 16px;
}
.filter-left {
width: 100%;
justify-content: center;
gap: 24px;
}
.filter-right {
width: 100%;
justify-content: center;
gap: 8px;
}
.filter-select {
width: 120px;
}
}
@media (max-width: 480px) {
.filter-left {
gap: 16px;
}
.filter-label {
font-size: 14px;
}
.filter-right {
flex-direction: column;
gap: 8px;
}
.filter-select {
width: 100%;
max-width: 200px;
}
}
</style>
\ No newline at end of file
<template>
<div class="think-tank-card" @click="handleClick">
<div class="card-header">
<div class="logo-box">
<el-image
:src="tank.logo || 'https://via.placeholder.com/60'"
:alt="tank.name"
fit="contain"
>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
</div>
<div class="rank-badge" v-if="index !== undefined">
<span class="rank-text">No.{{ index + 1 }}</span>
</div>
</div>
<div class="title-row">
<div class="tank-name" :title="tank.name">{{ tank.name }}</div>
<div class="tank-country">{{ tank.country }}</div>
</div>
<div class="tank-description" :title="tank.describe">
{{ tank.describe }}
</div>
<div class="tank-tags">
<span
v-for="(tag, idx) in tank.tags"
:key="tag"
class="custom-tag"
:class="idx % 2 === 0 ? 'tag-blue' : 'tag-red'"
>
{{ tag }}
</span>
</div>
</div>
</template>
<script setup>
import { Picture } from '@element-plus/icons-vue' // 引入图标用于图片加载失败占位
const props = defineProps({
tank: {
type: Object,
required: true
},
// 新增 index用于显示 No.1, No.2 等排名
index: {
type: Number,
default: 0
}
})
const emit = defineEmits(['click'])
const handleClick = () => {
emit('click', props.tank)
}
</script>
<style scoped lang="scss">
.think-tank-card {
background: #ffffff;
border-radius: 12px; // 更圆润的角
padding: 16px;
width: 280px; // 稍微加宽以匹配设计图比例,可根据父容器自适应
// border: 1px solid #e4e7ed; // 设计图看起来边框很淡,或者主要是阴影
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
cursor: pointer;
position: relative;
transition: all 0.3s ease;
overflow: hidden; // 确保排名背景不溢出
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.1);
}
}
/* --- 顶部区域 --- */
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
position: relative;
}
.logo-box {
width: 48px;
height: 48px;
// 设计图中Logo是自适应显示的,取消了灰底
display: flex;
align-items: center;
justify-content: center;
.el-image {
width: 100%;
height: 100%;
border-radius: 4px;
display: block; // 消除图片底部间隙
}
.image-slot {
width: 100%;
height: 100%;
background: #f5f7fa;
display: flex;
justify-content: center;
align-items: center;
color: #909399;
}
}
/* 排名条样式 */
.rank-badge {
/* 绝对定位到右上角,或者利用Flex布局 */
position: absolute;
right: -16px; /* 抵消父级padding */
top: -6px; /* 微调垂直位置 */
height: 32px;
padding-left: 30px; /* 渐变过度区 */
padding-right: 16px;
display: flex;
align-items: center;
justify-content: flex-end;
/* 核心:背景渐变从透明到淡红色 */
background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(206, 126, 126, 0.15) 40%, rgba(199, 58, 58, 0.15) 100%);
.rank-text {
font-family: 'Impact', sans-serif, 'Arial Black'; /* 选用粗体字体 */
font-size: 20px;
font-style: italic;
color: #b54646; /* 暗红色字体 */
letter-spacing: 1px;
}
}
/* --- 标题行 --- */
.title-row {
display: flex;
justify-content: space-between;
align-items: baseline; /* 文字基线对齐 */
margin-bottom: 12px;
}
.tank-name {
font-size: 16px;
font-weight: 700;
color: #303133;
line-height: 1.4;
margin-right: 8px;
/* 防止名字太长换行导致布局崩坏,可按需开启截断 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tank-country {
font-size: 13px;
color: #909399;
flex-shrink: 0;
}
/* --- 描述文本 --- */
.tank-description {
font-size: 13px;
color: #606266;
line-height: 1.6;
margin-bottom: 16px;
min-height: 63px; /* 预留3行高度,保证卡片高度一致 */
/* 3行省略号 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
/* --- 底部标签 --- */
.tank-tags {
display: flex;
gap: 8px;
margin-top: auto; /* 推到底部 */
}
/* 自定义标签样式,模拟设计图 */
.custom-tag {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
border: 1px solid transparent; // 预留边框位置
&.tag-blue {
background-color: #e6f1fc; /* 淡蓝背景 */
color: #409eff; /* 蓝色文字 */
border-color: #d9ecff;
}
&.tag-red {
background-color: #fef0f0; /* 淡红背景 */
color: #f56c6c; /* 红色文字 */
border-color: #fde2e2;
}
}
</style>
\ No newline at end of file
<template>
<!-- 面包屑导航 -->
<div class="breadcrumb">
<div class="header-item">国家科技安全</div>
<div class="header-item">></div>
<div class="header-item back-item" @click="handleBackHome">中美博弈概览</div>
<div class="header-item">></div>
<div class="header-item">科技智库</div>
</div>
<div class="tech-think-tank">
<!-- 搜索区域 -->
<div class="search-section">
<!-- <el-input v-model="searchText" placeholder="搜索智库、报告或政策建议" class="search-input" size="large">
<template #append>
<el-button type="primary" style="background: #1677ff; color: #fff" :icon="Search">搜索</el-button>
</template>
</el-input> -->
<el-input v-model="searchText" style="width: 800px; height: 100%" placeholder="搜索智库、报告或政策建议" />
<div class="search">
<div class="search-icon">
<img src="@/assets/icons/search-icon.png" alt="" />
</div>
<div class="search-text">搜索</div>
</div>
</div>
<!-- 统计数据 -->
<div class="stats-section">
<div class="stat-item">
<div class="stat-number">128</div>
<div class="stat-label">追踪智库数量</div>
</div>
<div class="stat-item">
<div class="stat-number">42</div>
<div class="stat-label">本月新增报告</div>
</div>
<div class="stat-item">
<div class="stat-number">18</div>
<div class="stat-label">重点政策建议</div>
</div>
<div class="stat-item">
<div class="stat-number">12</div>
<div class="stat-label">热点科技领域</div>
</div>
</div>
<div class="home-main-header-footer-link">
<ClickableCard text="最新动态" @click="scrollToTop('position1')" target="_blank" />
<ClickableCard text="资讯要闻" @click="scrollToTop('position2')" target="_blank" />
<ClickableCard text="数据总览" @click="scrollToTop('position3')" target="_blank" />
<ClickableCard text="资源库" @click="scrollToTop('position4')" target="_blank" />
</div>
<!-- 智库展示区域 -->
<div class="think-tanks-section">
<ThinkTankCard v-for="(tank, index) in thinkTanks" :key="tank.id" :tank="tank" :index="index"
@click="handleClick" />
<div class="view-all">
<span class="view-all-link">查看全部智库 ></span>
</div>
</div>
<DivideHeader id="position1" style="margin: 30px auto" :titleText="'最新动态'"></DivideHeader>
<!-- 主要内容区域 -->
<div class="main-content">
<!-- 左侧数据分布 -->
<div class="left-section">
<div class="section-header">
<el-icon class="location-icon">
<Location />
</el-icon>
<span class="section-title">数据分布</span>
<div class="time-filters">
<span :class="{ active: activeTime === '今天' }" @click="activeTime = '今天'">今天</span>
<span :class="{ active: activeTime === '本周' }" @click="activeTime = '本周'">本周</span>
<span :class="{ active: activeTime === '本月' }" @click="activeTime = '本月'">本月</span>
<span :class="{ active: activeTime === '今年' }" @click="activeTime = '今年'">今年</span>
</div>
</div>
<!-- 地图区域 -->
<div class="map-container">
<div ref="worldMapRef" class="world-map" style="width: 100%; height: 400px"></div>
</div>
</div>
<!-- 右侧网络信号 -->
<div class="right-section">
<div class="signal-header">
<div class="icon">
<img src="@/assets/images/icon-warning.png" alt="" />
</div>
<div class="signal-title-box">
<span class="signal-title">风险信号</span>
<el-badge :value="5" class="signal-badge" />
</div>
</div>
<div class="signal-list">
<div v-for="signal in signals" :key="signal.id" class="signal-item">
<div class="signal-content">
<div class="signal-left">
<div size="small" class="signal-tag" :class="{
tag1: signal.type === 'success',
tag2: signal.type === 'warning',
tag3: signal.type === 'danger'
}">
{{ signal.tag }}
</div>
<div class="signal-text">{{ signal.content }}</div>
</div>
<div class="signal-time">{{ signal.time }}</div>
</div>
</div>
</div>
<div class="box2-footer" @click="handleToMoreRiskSignal">
<div class="icon">
<img src="@/assets/images/more-signal.png" alt="" />
</div>
<div class="text">{{ "查看更多" }}</div>
</div>
<!-- <el-button type="primary" class="process-btn" block> 查看更多 </el-button> -->
</div>
</div>
<DivideHeader id="position2" style="margin: 30px auto" :titleText="'资讯要闻'"></DivideHeader>
<!-- 资讯要闻 -->
<div class="center-center">
<div class="box3">
<div class="box3-header">
<div class="box3-header-left">
<div class="box3-header-icon">
<img src="@/views/bill/billHome/assets/images/box3-header-icon.png" alt="" />
</div>
<div class="box3-header-title">{{ "新闻资讯" }}</div>
<div class="more" @click="handleToMoreNews">{{ "更多 +" }}</div>
</div>
</div>
<div class="box3-main">
<div class="box3-item" v-for="(news, index) in newsList" :key="index">
<div class="left">
<img :src="news.img" alt="" />
</div>
<div class="right">
<div class="right-top">
<div class="title">{{ news.title }}</div>
<div class="time">{{ news.from }}</div>
</div>
<div class="right-footer">{{ news.content }}</div>
</div>
</div>
</div>
</div>
<div class="box4">
<div class="box4-header">
<div class="header-icon">
<img src="@/views/bill/billHome/assets/images/box4-header-icon.png" alt="" />
</div>
<div class="header-title">{{ "社交媒体" }}</div>
</div>
<div class="box4-main">
<div class="box4-main-item" v-for="(item, index) in messageList" :key="index">
<div class="left">
<img :src="item.img" alt="" />
</div>
<div class="right">
<div class="right-top">
<div class="name">{{ item.name }}</div>
<div class="time">{{ item.time }}</div>
</div>
<div class="content">{{ item.content }}</div>
</div>
</div>
</div>
</div>
</div>
<DivideHeader id="position3" style="margin: 30px auto" :titleText="'数据总览'"></DivideHeader>
<DataOverview />
<DivideHeader id="position4" style="margin: 30px auto" :titleText="'资源库'"></DivideHeader>
<ReportList />
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import { Search, Location, Warning, Setting } from "@element-plus/icons-vue";
import * as echarts from "echarts";
import ReportList from "@/views/thinkTank/components/ReportList.vue";
import { useRouter } from "vue-router";
import scrollToTop from "@/utils/scrollToTop";
import { mockThinkTankList } from "./mockData";
import { getThinkTankList } from "@/api";
import ClickableCard from "@/views/finance/components/link.vue";
import DivideHeader from "@/components/DivideHeader.vue";
import DataOverview from "./components/DataOverview.vue";
import ThinkTankCard from "./components/ThinkTankCard.vue";
import News1 from "@/views/bill/billHome/assets/images/news1.png";
import News2 from "@/views/bill/billHome/assets/images/news2.png";
import News3 from "@/views/bill/billHome/assets/images/news3.png";
import News4 from "@/views/bill/billHome/assets/images/news4.png";
import News5 from "@/views/bill/billHome/assets/images/news5.png";
import Message1 from "@/views/bill/billHome/assets/images/message-icon1.png";
import Message2 from "@/views/bill/billHome/assets/images/message-icon2.png";
import Message3 from "@/views/bill/billHome/assets/images/message-icon3.png";
const router = useRouter();
// 返回首页
const handleBackHome = () => {
router.push({
path: "/overview"
});
};
const searchText = ref("");
const activeTime = ref("本月");
const worldMapRef = ref(null);
// 新闻资讯
const newsList = ref([
{
img: News1,
title: "美政府停摆仍持续,拨款法案存缺陷,但两党磋商露曙光",
content: `美国政府停摆已持续34天,距离历史上最长的停摆纪录仅差一天,参议院已先后13次尝试...`,
from: "11-4 · 华盛顿邮报"
},
{
img: News2,
title: "美参议院通过决议,要求终止特朗普全球关税政策",
content: `参议院以51票赞成、47票反对通过一项决议,旨在终止特朗普实施的全面关税政策,四名......`,
from: "11-4 · 纽约时报"
},
{
img: News3,
title: "美众院通过950亿美元对外援助法案,包含对台军援",
content: `国会众议院在4月通过了大规模对外援助法案,其中包括为“印太安全”提供资金的条款,......`,
from: "11-3 · 洛杉矶时报"
},
{
img: News4,
title: "“大而美”法案在激烈争议中通过",
content: `特朗普力推的大规模税收与支出法案在国会以微弱优势通过。该法案因大幅削减医疗补助和......`,
from: "11-3 · 今日美国"
},
{
img: News5,
title: "美政府“停摆”追平历史最长纪录,民生多领域受重创",
content: `联邦政府“停摆”进入第35天,追平历史纪录。食品救济项目资金中断,数百万低收入民......`,
from: "11-2 · ​福克斯新闻网"
}
]);
// 社交媒体
const messageList = ref([
{
img: Message1,
name: "唐纳德·特朗普",
time: "15:23 · 发布于真实社交",
content: `埃隆·马斯克在强力支持我竞选总统之前,早就知道我强烈反对‘电动汽车强制令’。这太荒谬了,这一直是我竞选活动的主要部分。电动汽车没问题,但不应该强迫每个人都拥有一辆。埃隆获得的补贴可能远远超过历史上任何一个人。如果没有补贴,埃隆可能不得不关门大吉,回到南非老家。`
},
{
img: Message2,
name: "埃隆·马斯克",
time: "14:49 · 发布于X",
content: `如果这个疯狂的支出法案获得通过,‘美国党’将在第二天成立。`
},
{
img: Message3,
name: "塞巴斯蒂安·马拉比",
time: "11:05 · 发布于X",
content: `提出特朗普政府的AI政策强调技术开放与快速应用,但可能以牺牲安全防范为代价,开启了“潘多拉魔盒”。`
}
]);
// 世界地图数据 - 各国智库数量统计
const mapData = ref([
{ name: "United States", value: 6, itemStyle: { color: "#ff4757" } },
{ name: "China", value: 3, itemStyle: { color: "#ffa726" } },
{ name: "United Kingdom", value: 2, itemStyle: { color: "#42a5f5" } },
{ name: "Germany", value: 1, itemStyle: { color: "#66bb6a" } },
{ name: "Japan", value: 1, itemStyle: { color: "#66bb6a" } },
{ name: "France", value: 1, itemStyle: { color: "#66bb6a" } }
]);
// 初始化世界地图
const initWorldMap = async () => {
if (!worldMapRef.value) return;
const chart = echarts.init(worldMapRef.value);
// 使用可靠的世界地图数据源
try {
const response = await fetch("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson");
const geoJson = await response.json();
// 注册世界地图
echarts.registerMap("world", geoJson);
const option = {
title: {
text: "全球智库分布",
left: "center",
top: 20,
textStyle: {
color: "#333",
fontSize: 18,
fontWeight: "bold"
}
},
tooltip: {
trigger: "item",
formatter: function (params) {
if (params.seriesType === "map") {
const data = mapData.value.find(item => item.name === params.name);
if (data) {
return `${params.name}<br/>智库数量: ${data.value}个`;
}
return `${params.name}<br/>暂无数据`;
}
return params.name;
}
},
visualMap: {
min: 0,
max: 6,
left: "left",
top: "bottom",
text: ["高", "低"],
calculable: true,
inRange: {
color: ["#e3f2fd", "#1976d2"]
},
textStyle: {
color: "#333"
}
},
series: [
{
name: "智库数量",
type: "map",
map: "world",
roam: true,
emphasis: {
label: {
show: true
}
},
data: [
{ name: "United States of America", value: 6, itemStyle: { color: "#ff4757" } },
{ name: "China", value: 3, itemStyle: { color: "#ffa726" } },
{ name: "United Kingdom", value: 2, itemStyle: { color: "#42a5f5" } },
{ name: "Germany", value: 1, itemStyle: { color: "#66bb6a" } },
{ name: "Japan", value: 1, itemStyle: { color: "#66bb6a" } },
{ name: "France", value: 1, itemStyle: { color: "#66bb6a" } }
],
itemStyle: {
borderColor: "#fff",
borderWidth: 1
}
}
]
};
chart.setOption(option);
} catch (error) {
console.log("使用备用地图方案");
// 如果外部数据源失败,使用内置的简化世界地图
const option = {
title: {
text: "全球智库分布",
left: "center",
top: 20,
textStyle: {
color: "#333",
fontSize: 18,
fontWeight: "bold"
}
},
tooltip: {
trigger: "item",
formatter: function (params) {
return `${params.name}<br/>智库数量: ${params.value}个`;
}
},
geo: {
type: "map",
map: "world",
roam: true,
itemStyle: {
color: "#f0f0f0",
borderColor: "#999",
borderWidth: 0.5
},
emphasis: {
itemStyle: {
color: "#e0e0e0"
}
},
regions: [
{
name: "United States",
itemStyle: {
color: "#ff4757"
}
},
{
name: "China",
itemStyle: {
color: "#ffa726"
}
},
{
name: "United Kingdom",
itemStyle: {
color: "#42a5f5"
}
}
]
},
series: [
{
name: "智库分布",
type: "scatter",
coordinateSystem: "geo",
data: [
{ name: "美国", value: [-95, 37, 6], itemStyle: { color: "#ff4757" } },
{ name: "中国", value: [104, 35, 3], itemStyle: { color: "#ffa726" } },
{ name: "英国", value: [-3, 55, 2], itemStyle: { color: "#42a5f5" } },
{ name: "德国", value: [10, 51, 1], itemStyle: { color: "#66bb6a" } },
{ name: "日本", value: [138, 36, 1], itemStyle: { color: "#66bb6a" } },
{ name: "法国", value: [2, 46, 1], itemStyle: { color: "#66bb6a" } }
],
symbolSize: function (val) {
return Math.max(val[2] * 15, 25);
},
label: {
show: true,
formatter: function (params) {
return params.data.name + "\n" + params.data.value[2] + "个";
},
position: "top",
textStyle: {
color: "#333",
fontSize: 12,
fontWeight: "bold"
}
},
emphasis: {
scale: true,
scaleSize: 10
}
}
]
};
chart.setOption(option);
}
// 响应式处理
window.addEventListener("resize", () => {
chart.resize();
});
};
const signals = ref([
{
id: 1,
type: "danger",
tag: "特别重大",
content: "兰德科技智库发布2025中美年度科技报告",
time: "一天前"
},
{
id: 2,
type: "danger",
tag: "特别重大",
content: "信息技术与创新基金会发布《中国正在迅速成...",
time: "一天前"
},
{
id: 3,
type: "warning",
tag: "重大风险",
content: "战略与国际研究中心发布《DeepSeek、华为、...",
time: "一天前"
},
{
id: 4,
type: "warning",
tag: "重大风险",
content: "兰德科技智库发布《中国对AI的转型产业政策》",
time: "一天前"
},
{
id: 5,
type: "success",
tag: "一般风险",
content: "兰德科技智库发布《中美对抗、竞争和合作...",
time: "一天前"
}
]);
const thinkTanks = ref([]);
const getThinkTanks = async () => {
const { data } = await getThinkTankList();
// thinkTanks.value = data;
thinkTanks.value = [...mockThinkTankList];
};
const handleClick = tank => {
// router.push({ name: "ThinkTankDetail", params: { id: tank.id } });
const curRoute = router.resolve({ name: "ThinkTankDetail", params: { id: tank.id } });
window.open(curRoute.href, "_blank");
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/riskSignal");
window.open(route.href, "_blank");
};
// 查看更多新闻资讯
const handleToMoreNews = () => {
const route = router.resolve("/newsBrief");
window.open(route.href, "_blank");
};
// 组件挂载后初始化地图
onMounted(() => {
getThinkTanks();
nextTick(() => {
initWorldMap();
});
});
</script>
<style scoped lang="scss">
.breadcrumb {
width: 100%;
height: 64px;
background: url("@/assets/images/nav-bg.png") no-repeat;
color: #fff;
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
line-height: 64px;
box-sizing: border-box;
padding-left: 160px;
display: flex;
.header-item {
margin: 0 3px;
}
.back-item {
cursor: pointer;
&:hover {
color: #ccc;
}
}
}
.tech-think-tank {
font-family: Microsoft YaHei;
max-width: 1650px;
margin: 0 auto;
margin-top: 31px;
padding: 20px;
background: url("@/assets/images/background.png") no-repeat;
background-position: center -100px;
background-size: 100% 100%;
min-height: 100vh;
.home-main-header-footer-link {
display: flex;
gap: 30px;
padding: 30px 0;
justify-content: center;
}
.center-center {
margin-top: 21px;
height: 450px;
display: flex;
.box3 {
width: 792px;
height: 450px;
box-shadow: 0px 0px 15px 0px rgba(25, 69, 130, 0.2);
background: rgba(255, 255, 255, 1);
.box3-header {
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 0 20px;
position: relative;
.box3-header-left {
display: flex;
.box3-header-icon {
margin-top: 16px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box3-header-title {
margin-left: 19px;
height: 48px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 48px;
}
.more {
width: 49px;
height: 24px;
position: absolute;
top: 14px;
right: 27px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
}
}
}
.box3-main {
height: 402px;
overflow-y: auto;
overflow-x: hidden;
padding-top: 6px;
.box3-item {
display: flex;
height: 77px;
width: 749px;
margin-left: 21px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
.left {
width: 72px;
height: 48px;
margin-top: 15px;
img {
width: 100%;
height: 100%;
}
}
.right {
width: 657px;
margin-left: 20px;
.right-top {
width: 657px;
display: flex;
justify-content: space-between;
.title {
margin-top: 13px;
width: 520px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.time {
flex: 1;
text-align: right;
height: 22px;
margin-top: 19px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
.right-footer {
width: 657px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
.box4 {
margin-left: 20px;
width: 792px;
height: 450px;
box-shadow: 0px 0px 15px 0px rgba(25, 69, 130, 0.2);
background: rgba(255, 255, 255, 1);
.box4-header {
width: 792px;
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
box-sizing: border-box;
padding-left: 22px;
position: relative;
.header-icon {
margin-top: 15px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.header-title {
height: 48px;
margin-left: 18px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 48px;
}
.more {
width: 49px;
height: 24px;
position: absolute;
top: 14px;
right: 27px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
}
}
.box4-main {
height: 402px;
overflow-y: auto;
box-sizing: border-box;
padding-top: 8px;
.box4-main-item {
margin-top: 16px;
display: flex;
margin-left: 21px;
.left {
margin-top: 5px;
width: 36px;
height: 36px;
img {
width: 100%;
height: 100%;
}
}
.right {
margin-left: 10px;
width: 690px;
box-sizing: border-box;
border: 1px solid rgba(231, 243, 255, 1);
background: rgba(246, 250, 255, 1);
padding: 10px 15px;
.right-top {
display: flex;
justify-content: space-between;
.name {
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
.time {
height: 30px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
}
}
.content {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
}
}
}
}
.search-section {
margin: 0 auto;
margin-bottom: 30px;
width: 800px;
height: 48px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
padding: 1px;
position: relative;
.search {
position: absolute;
right: 1px;
top: 2px;
width: 120px;
height: 44px;
border-radius: 10px;
background: var(--color-main-active);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.search-icon {
width: 18px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
.search-text {
margin-left: 8px;
height: 22px;
color: #fff;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
}
}
}
.search-input {
max-width: 800px;
}
.stats-section {
display: flex;
justify-content: center;
gap: 80px;
margin-bottom: 40px;
}
.stat-item {
text-align: center;
}
.stat-number {
font-size: 48px;
font-weight: bold;
color: #1459bb;
line-height: 1;
margin-bottom: 8px;
}
.stat-label {
font-size: 14px;
color: #666;
}
.main-content {
display: flex;
gap: 10px;
margin-bottom: 40px;
height: 490px;
}
.left-section {
flex: 2;
background: white;
border-radius: 2px;
padding: 20px;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.location-icon {
color: #409eff;
margin-right: 8px;
}
.section-title {
font-weight: 500;
margin-right: auto;
}
.time-filters {
display: flex;
gap: 15px;
}
.time-filters span {
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
color: #666;
}
.time-filters span.active {
background-color: #409eff;
color: white;
}
.map-container {
// height: 300px;
background: #f0f2f5;
border-radius: 8px;
position: relative;
overflow: hidden;
}
.map-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
position: relative;
}
.data-point {
position: absolute;
}
.point-circle {
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
font-weight: bold;
}
.point-circle.yellow {
background-color: #f39c12;
}
.point-circle.red {
background-color: #e74c3c;
}
.point-circle.blue {
background-color: #3498db;
}
.right-section {
flex: 1;
background: white;
border-radius: 2px;
padding: 20px;
padding-top: 0px;
}
.signal-header {
display: flex;
align-items: center;
height: 48px;
box-sizing: border-box;
padding-right: 20px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
.icon {
width: 24px;
height: 22px;
margin-left: 18px;
margin-top: 4px;
img {
width: 100%;
height: 100%;
}
}
}
.signal-title-box {
display: flex;
background-color: #c05052;
padding: 10px;
color: #fff;
margin-left: 20px;
}
.signal-title {
font-weight: 500;
margin-right: 8px;
}
.signal-badge {
margin-right: auto;
}
.signal-list {
height: 370px;
overflow-y: auto;
}
.box2-footer {
margin: 10px auto;
width: 460px;
height: 42px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
border-radius: 6px;
background: var(--color-main-active);
cursor: pointer;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 8px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
.signal-item {
height: 47px;
line-height: 47px;
margin-bottom: 15px;
padding-bottom: 15px;
}
.signal-item:last-child {
border-bottom: none;
}
.signal-content {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
border-bottom: 1px solid #f0f0f0;
}
.signal-left {
flex: 1;
display: flex;
gap: 6px;
height: 47px;
line-height: 47px;
}
.signal-tag {
margin-top: 4px;
margin-left: 3px;
width: 40px;
height: 40px;
border-radius: 20px;
font-family: Microsoft YaHei;
font-size: 12px;
font-weight: 400;
line-height: 14px;
box-sizing: border-box;
padding: 6px 4px;
text-align: center;
}
.tag1 {
color: rgba(82, 196, 26, 1) !important;
background: rgba(246, 255, 237, 1) !important;
}
.tag2 {
color: rgba(250, 140, 22, 1) !important;
background: rgba(255, 247, 230, 1) !important;
}
.tag3 {
background: rgba(255, 241, 240);
color: rgba(245, 34, 45, 1);
}
.signal-text {
font-size: 14px;
color: #333;
line-height: 1.4;
word-break: break-word;
height: 47px;
line-height: 47px;
}
.signal-time {
font-size: 12px;
color: #999;
white-space: nowrap;
flex-shrink: 0;
}
.process-btn {
width: 100%;
}
.think-tanks-section {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: flex-start;
}
.view-all {
text-align: center;
width: 110px;
border-radius: 10px;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
padding: 0 5px;
background: #fff;
display: flex;
align-items: center;
cursor: pointer;
}
.view-all-link {
color: #1459bb;
font-size: 16px;
font-weight: 600;
text-align: center;
margin: 0 auto;
}
.view-all-link:hover {
text-decoration: underline;
}
:deep(.el-input__wrapper) {
box-shadow: none;
}
:deep(.el-input__wrapper) {
box-shadow: none;
}
:deep(.el-input__wrapper:hover) {
box-shadow: none !important;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: none !important;
}
</style>
\ No newline at end of file
<template>
<div class="home-wrapper">
<div class="home-main">
<div class="home-main-header">
<div class="home-main-header-top">
<div class="header-item">国家科技安全</div>
<div class="header-item">></div>
<div class="header-item back-item" @click="handleBackHome">中美博弈概览</div>
<div class="header-item">></div>
<div class="header-item">市场准入限制</div>
</div>
<div class="home-main-header-center">
<el-input v-model="input" style="width: 838px; height: 100%" placeholder="搜索科技人物及观点" />
<div class="search">
<div class="search-icon">
<img src="./assets/images/search-icon.png" alt="" />
</div>
<div class="search-text">搜索</div>
</div>
</div>
<div class="home-main-header-footer">
<div class="home-main-header-footer-item">
<div class="item-top">843</div>
<div class="item-footer">政府官员</div>
</div>
<div class="home-main-header-footer-item">
<div class="item-top">131</div>
<div class="item-footer">科技企业领袖</div>
</div>
<div class="home-main-header-footer-item">
<div class="item-top">633</div>
<div class="item-footer">顶级科学家</div>
</div>
<div class="home-main-header-footer-item">
<div class="item-top">312</div>
<div class="item-footer">国会议员</div>
</div>
</div>
<div class="home-main-header-btn-box">
<div class="btn" @click="scrollToTop('position1')">
<div class="btn-text">{{ "最新动态" }}</div>
<div class="btn-icon">{{ ">" }}</div>
</div>
<div class="btn" @click="scrollToTop('position2')">
<div class="btn-text">{{ "资讯要闻" }}</div>
<div class="btn-icon">{{ ">" }}</div>
</div>
<div class="btn" @click="scrollToTop('position3')">
<div class="btn-text">{{ "统计概览" }}</div>
<div class="btn-icon">{{ ">" }}</div>
</div>
<div class="btn" @click="scrollToTop('position4')">
<div class="btn-text">{{ "资源库" }}</div>
<div class="btn-icon">{{ ">" }}</div>
</div>
</div>
</div>
<div class="home-main-center">
<DivideHeader id="position1" class="divide-header" :titleText="'最新动态'"></DivideHeader>
<div class="center-top">
<div class="box1">
<div class="box1-left">
<img src="./assets/images/box1-left.png" alt="" />
</div>
<div class="box1-right">
<img src="./assets/images/box1-right.png" alt="" />
</div>
<div class="box1-header">
<div class="box1-header-left">
<div class="icon">
<img src="./assets/images/box1-header-icon.png" alt="" />
</div>
<div class="title">{{ "人物新闻动态" }}</div>
</div>
<div class="box1-header-right" @click="handleClickToDetail('337')">
{{ "查看详情 >" }}
</div>
</div>
<div class="box1-main"></div>
</div>
<div class="box2">
<div class="box2-header">
<div class="icon">
<img src="./assets/images/box2-header-icon.png" alt="" />
</div>
<div class="title">
<div class="text">{{ "风险信号" }}</div>
<div class="num">{{ warningList.length }}</div>
</div>
</div>
<div class="box2-main">
<div class="box2-main-item" v-for="(item, index) in warningList" :key="index">
<div class="item-left" :class="{
itemLeftStatus1: item.status === '一般风险',
itemLeftStatus2: item.status === '重大风险'
}">
{{ item.status }}
</div>
<div class="item-right">
<div class="text">
{{ item.title }}
</div>
<div class="time">{{ item.time }}</div>
</div>
</div>
</div>
<div class="box2-footer" @click="handleToMoreRiskSignal">
<div class="icon">
<img src="./assets/images/box2-footer-icon.png" alt="" />
</div>
<div class="text">{{ "查看更多" }}</div>
</div>
</div>
</div>
<DivideHeader id="position2" class="divide-header" :titleText="'言论动态'"></DivideHeader>
<div class="center-center">
<div class="box3">
<div class="box3-header">
<div class="box3-header-left">
<div class="box3-header-icon">
<img src="./assets/images/header-news.png" alt="" />
</div>
<div class="box3-header-title">{{ "人物动向" }}</div>
</div>
</div>
<div class="box3-main"></div>
</div>
<div class="box4">
<div class="box4-header">
<div class="header-icon">
<img src="./assets/images/header-message.png" alt="" />
</div>
<div class="header-title">{{ "重要人物言论及立场" }}</div>
</div>
<div class="box4-main"></div>
</div>
</div>
<DivideHeader id="position3" class="divide-header" :titleText="'数据总览'"></DivideHeader>
<div class="center-footer">
<div class="box5">
<div class="box5-header">
<div class="box5-header-left">
<div class="box5-header-icon">
<img src="./assets/images/box3-header-icon.png" alt="" />
</div>
<div class="box5-header-title">{{ "科技人物观点词云" }}</div>
</div>
</div>
<div class="box5-main" id="box5Chart"></div>
</div>
<div class="box6">
<div class="box6-header">
<div class="header-icon">
<img src="./assets/images/box6-header-icon.png" alt="" />
</div>
<div class="header-title">{{ "科技人物观点涉及领域变化趋势" }}</div>
</div>
<div class="box6-main" id="box6Chart"></div>
</div>
</div>
<div class="center-footer1">
<div class="box7">
<div class="box7-header">
<div class="box7-header-left">
<div class="box7-header-icon">
<img src="./assets/images/box3-header-icon.png" alt="" />
</div>
<div class="box7-header-title">{{ "科技人物类型" }}</div>
</div>
</div>
<div class="box7-main" id="box7Chart"></div>
</div>
<div class="box8">
<div class="box8-header">
<div class="box8-header-left">
<div class="box8-header-icon">
<img src="./assets/images/box6-header-icon.png" alt="" />
</div>
<div class="box8-header-title">{{ "主要人物涉华观点统计" }}</div>
</div>
</div>
<div class="box8-main"></div>
</div>
</div>
</div>
<div class="home-main-footer">
<DivideHeader id="position4" class="divide-header" :titleText="'资源库'"></DivideHeader>
<div class="home-main-footer-header">
<div class="btn-box">
<div class="btn" :class="{ btnActive: activeCate === cate }" v-for="(cate, index) in categoryList"
:key="index" @click="handleClickCate(cate)">
{{ cate }}
</div>
</div>
</div>
<div class="home-main-footer-main"></div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import scrollToTop from "@/utils/scrollToTop";
import router from "@/router";
import DivideHeader from "@/components/DivideHeader.vue";
import setChart from "@/utils/setChart";
import getMultiLineChart from "./utils/multiLineChart";
import getPieChart from "./utils/piechart";
import getRadarChart from "./utils/radarChart";
import getMapChart from "./utils/mapChart";
import getBarChart from "./utils/barChart";
// 返回首页
const handleBackHome = () => {
router.push({
path: "/overview"
});
};
const handleClickToDetail = id => {
const route = router.resolve({
path: "/marketAccessLayout",
query: {
id: id
}
});
window.open(route.href, "_blank");
};
// 风险信号
const warningList = ref([
{
title: "关于对中华人民共和国合成阿片类药物供应链...",
time: "一天前",
status: "特别重大"
},
{
title: "关于调整汽车及汽车零部件进口的公告",
time: "一天前",
status: "特别重大"
},
{
title: "关于调整钢铁进口的公告",
time: "一天前",
status: "重大风险"
},
{
title: "关于使用互惠关税规范进口以纠正导致大规模...",
time: "一天前",
status: "重大风险"
},
{
title: "关于修订对中华人民共和国低价值进口商品适...",
time: "一天前",
status: "一般风险"
}
]);
const categoryList = ref(["全部人物", "国会议员", "行政主官", "科技领袖", "顶尖科学家"]);
const activeCate = ref("全部人物");
const activeHylyId = ref("");
const handleClickCate = cate => {
console.log(cate);
activeCate.value = cate.hylymc;
activeHylyId.value = cate.hylyid;
handleGetBillsByType();
};
const chart1Data = ref({
title: ["2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025"],
data: [
{
name: "337调查",
value: [73, 32, 42, 48, 38, 49, 63, 75, 70, 86, 95, 87]
},
{
name: "301调查",
value: [8, 3, 2, 8, 9, 10, 12, 18, 16, 18, 20, 22]
},
{
name: "232调查",
value: [1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 3]
}
]
});
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/riskSignal");
window.open(route.href, "_blank");
};
const box7Chart1Data = ref([
{
name: "337调查",
data: [
{ name: "北京", value: 10, coord: [115.46, 39.92] },
{ name: "上海", value: 9, coord: [120.48, 31.22] },
{ name: "广东", value: 15, coord: [114.23, 23.16] },
{ name: "江苏", value: 30, coord: [117.78, 32.04] },
{ name: "浙江", value: 20, coord: [121.19, 30.26] },
{ name: "四川", value: 4, coord: [105.06, 30.67] },
{ name: "陕西", value: 1, coord: [106.95, 34.27] },
{ name: "辽宁", value: 3, coord: [122.38, 41.8] }
]
},
{
name: "301调查",
data: [
{ name: "北京", value: 10, coord: [112.48, 38.95] },
{ name: "上海", value: 9, coord: [121.5, 33.25] },
{ name: "广东", value: 15, coord: [118.25, 21.18] },
{ name: "江苏", value: 30, coord: [115.8, 34.06] },
{ name: "浙江", value: 20, coord: [124.21, 31.28] },
{ name: "四川", value: 4, coord: [114.08, 32.69] },
{ name: "陕西", value: 1, coord: [109.97, 30.29] },
{ name: "辽宁", value: 3, coord: [113.4, 40.82] }
]
},
{
name: "232调查",
data: [
{ name: "北京", value: 10, coord: [116.44, 39.9] },
{ name: "上海", value: 9, coord: [121.46, 31.2] },
{ name: "广东", value: 15, coord: [113.21, 23.14] },
{ name: "江苏", value: 30, coord: [118.76, 32.02] },
{ name: "浙江", value: 20, coord: [120.19, 30.24] },
{ name: "四川", value: 4, coord: [104.04, 30.67] },
{ name: "陕西", value: 1, coord: [108.95, 34.25] },
{ name: "辽宁", value: 3, coord: [123.36, 41.8] }
]
}
]);
const box7Chart2Data = ref([
{
name: "广东省",
value: 42
},
{
name: "上海市",
value: 35
},
{
name: "浙江省",
value: 28
},
{
name: "江苏省",
value: 19
},
{
name: "北京市",
value: 15
},
{
name: "四川省",
value: 12
},
{
name: "山东省",
value: 11
},
{
name: "福建省",
value: 8
}
]);
onMounted(async () => {
let chart1 = getMultiLineChart(
chart1Data.value.title,
chart1Data.value.data[0].value,
chart1Data.value.data[1].value,
chart1Data.value.data[2].value
);
setChart(chart1, "chart1");
let chart2 = getRadarChart();
setChart(chart2, "chart2");
let box7Chart1 = getMapChart(box7Chart1Data.value);
setChart(box7Chart1, "box7Chart1");
let box7Chart2 = getBarChart(box7Chart2Data.value);
setChart(box7Chart2, "box7Chart2");
});
</script>
<style lang="scss" scoped>
:deep(.el-input__wrapper) {
box-shadow: none;
}
.home-wrapper {
.home-main {
width: 1920px;
margin: 0 auto;
background: url("./assets/images/background.png");
background-size: 100% 100%;
.home-main-header {
display: flex;
flex-direction: column;
align-items: center;
.home-main-header-top {
box-sizing: border-box;
width: 100%;
height: 64px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 64px;
background: url("./assets/images/header-bg.png");
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
color: #fff;
padding-left: 160px;
display: flex;
.header-item {
margin: 0 3px;
}
.back-item {
cursor: pointer;
&:hover {
color: #ccc;
}
}
}
.home-main-header-center {
margin-top: 48px;
width: 960px;
height: 48px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
padding: 1px;
position: relative;
border: 1px solid transparent;
&:hover {
border: 1px solid var(--color-main-active);
}
.search {
position: absolute;
right: -1px;
top: 0px;
width: 120px;
height: 46px;
border-radius: 10px;
background: var(--color-main-active);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.search-icon {
width: 18px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
.search-text {
margin-left: 8px;
height: 22px;
color: #fff;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
}
}
}
.home-main-header-footer {
margin-top: 38px;
width: 688px;
height: 64px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
.home-main-header-footer-item {
padding: 0 10px;
text-align: center;
.item-top {
height: 22px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 36px;
font-weight: 700;
line-height: 22px;
}
.item-footer {
margin-top: 10px;
height: 30px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
}
}
}
.home-main-header-btn-box {
width: 688px;
margin: 0 auto;
margin-top: 39px;
display: flex;
justify-content: space-between;
.btn {
display: flex;
width: 140px;
height: 36px;
border: 1px solid #aed6ff;
box-sizing: border-box;
border-radius: 18px;
justify-content: center;
background: #e7f3ff;
position: relative;
cursor: pointer;
&:hover {
background: #cae3fc;
}
.btn-text {
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 400;
line-height: 34px;
margin-left: 5px;
}
.btn-icon {
height: 20px;
line-height: 20px;
position: absolute;
top: 6px;
right: 8px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 400;
}
}
}
}
.home-main-center {
margin-top: 34px;
.center-top {
height: 450px;
display: flex;
justify-content: center;
gap: 20px;
.box1 {
width: 1064px;
height: 450px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: #fff;
box-sizing: border-box;
position: relative;
.box1-left {
position: absolute;
left: 0;
top: 220px;
width: 24px;
height: 48px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.box1-right {
position: absolute;
right: 0;
top: 220px;
width: 24px;
height: 48px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.box1-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
justify-content: space-between;
padding-left: 31px;
padding-right: 41px;
.box1-header-left {
display: flex;
.icon {
width: 18px;
height: 18px;
margin-top: 19px;
img {
width: 100%;
height: 100%;
}
}
.title {
width: 152px;
height: 53px;
margin-left: 18px;
color: #fff;
background: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 53px;
text-align: center;
}
}
.box1-header-right {
margin-top: 19px;
height: 16px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 16px;
cursor: pointer;
}
}
.box1-main {
width: 1064px;
height: 354px;
margin-top: 22px;
margin-left: 31px;
}
}
.box2 {
width: 521px;
height: 450px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.box2-header {
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
.icon {
width: 24px;
height: 22px;
margin-left: 33px;
margin-top: 13px;
img {
width: 100%;
height: 100%;
}
}
.title {
display: flex;
width: 148px;
height: 48px;
background: rgba(206, 79, 81, 1);
margin-left: 25px;
.text {
margin-left: 15px;
margin-top: 13px;
height: 22px;
color: #fff;
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 22px;
}
.num {
width: 24px;
height: 20px;
line-height: 20px;
text-align: center;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 12px;
margin-left: 15px;
margin-top: 14px;
border-radius: 100px;
background: rgba(255, 255, 255, 0.3);
}
}
.more {
margin-top: 16px;
margin-left: 200px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 16px;
cursor: pointer;
}
}
.box2-main {
margin-top: 2px;
height: 330px;
overflow-y: auto;
.box2-main-item {
margin-left: 23px;
height: 47px;
width: 464px;
display: flex;
cursor: pointer;
&:hover {
background: var(--color-bg-hover);
}
.itemLeftStatus1 {
color: rgba(82, 196, 26, 1) !important;
background: rgba(246, 255, 237, 1) !important;
}
.itemLeftStatus2 {
color: rgba(250, 140, 22, 1) !important;
background: rgba(255, 247, 230, 1) !important;
}
.item-left {
margin-top: 4px;
margin-left: 2px;
width: 40px;
height: 40px;
border-radius: 20px;
background: rgba(255, 241, 240);
color: rgba(245, 34, 45, 1);
font-family: Microsoft YaHei;
font-size: 12px;
font-weight: 400;
line-height: 14px;
box-sizing: border-box;
padding: 6px 4px;
text-align: center;
}
.item-right {
margin-left: 13px;
width: 408px;
height: 47px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
.text {
width: 348px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 47px;
}
.time {
margin-left: 10px;
line-height: 47px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
}
}
}
}
.box2-footer {
position: absolute;
left: 30px;
bottom: 20px;
width: 460px;
height: 42px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
border-radius: 6px;
background: var(--color-main-active);
gap: 8px;
cursor: pointer;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
}
}
}
}
.center-center {
width: 1600px;
margin: 0 auto;
margin-top: 21px;
.box3 {
width: 1600px;
height: 640px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(25, 69, 130, 0.2);
background: rgba(255, 255, 255, 1);
.box3-header {
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 0 20px;
position: relative;
.box3-header-left {
display: flex;
.box3-header-icon {
margin-top: 16px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box3-header-title {
margin-top: 16px;
margin-left: 19px;
height: 22px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 22px;
}
.more {
width: 49px;
height: 24px;
position: absolute;
top: 14px;
right: 27px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
}
}
}
.box3-main {
height: 402px;
overflow-y: auto;
overflow-x: hidden;
padding-top: 6px;
}
}
.box4 {
margin-top: 16px;
width: 1600px;
height: 460px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(25, 69, 130, 0.2);
background: rgba(255, 255, 255, 1);
.box4-header {
width: 792px;
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
box-sizing: border-box;
padding-left: 22px;
position: relative;
.header-icon {
margin-top: 15px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.header-title {
margin-top: 16px;
margin-left: 18px;
height: 22px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 22px;
}
.more {
width: 49px;
height: 24px;
position: absolute;
top: 14px;
right: 27px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
}
}
.box4-main {
height: 402px;
overflow-y: auto;
box-sizing: border-box;
padding-top: 8px;
}
}
}
.center-footer {
margin-top: 21px;
height: 460px;
display: flex;
justify-content: center;
gap: 15px;
.box5 {
width: 1064px;
height: 460px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.box5-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 0 20px;
position: relative;
.box5-header-left {
display: flex;
.box5-header-icon {
margin-top: 15px;
margin-left: 2px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box5-header-title {
margin-top: 12px;
margin-left: 19px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.box-header-right {
position: absolute;
height: 24px;
top: 12px;
right: 17px;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 8px;
.icon {
width: 14px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
}
.box5-main {
height: 397px;
}
}
.box6 {
width: 521px;
height: 460px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.box6-header {
width: 521px;
height: 53px;
box-sizing: border-box;
padding: 0 20px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
position: relative;
.header-icon {
margin-top: 18px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.header-title {
margin-top: 16px;
margin-left: 15px;
height: 22px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 22px;
}
.box-header-right {
position: absolute;
height: 24px;
top: 12px;
right: 17px;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 8px;
.icon {
width: 14px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
}
.box6-main {
height: 360px;
}
}
}
.center-footer1 {
margin-top: 16px;
display: flex;
justify-content: center;
gap: 15px;
.box7 {
width: 1064px;
height: 460px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.box7-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 0 20px;
position: relative;
.box7-header-left {
display: flex;
.box7-header-icon {
margin-top: 15px;
margin-left: 2px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box7-header-title {
margin-top: 12px;
margin-left: 19px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.box-header-right {
position: absolute;
height: 24px;
top: 12px;
right: 17px;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 8px;
.icon {
width: 14px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
}
.box7-main {
height: 412px;
}
}
.box8 {
width: 521px;
height: 460px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.box8-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 0 20px;
position: relative;
.box8-header-left {
display: flex;
.box8-header-icon {
margin-top: 15px;
margin-left: 2px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box8-header-title {
margin-top: 12px;
margin-left: 19px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.box-header-right {
position: absolute;
height: 24px;
top: 12px;
right: 17px;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 8px;
.icon {
width: 14px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
}
.box8-main {
width: 469px;
height: 360px;
}
}
}
}
.home-main-footer {
height: 1149px;
overflow: hidden;
.home-main-footer-header {
width: 1600px;
height: 42px;
margin: 36px auto;
// background: orange;
display: flex;
justify-content: space-between;
.btn-box {
width: 1000px;
display: flex;
.btn {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 42px;
padding: 0 24px;
border-radius: 21px;
background: rgba(20, 89, 187, 0);
margin-right: 20px;
cursor: pointer;
&:hover {
background: rgba(20, 89, 187, 0.1);
}
}
.btnActive {
padding: 0 24px;
border-radius: 21px;
background: rgba(20, 89, 187, 1);
color: #fff;
&:hover {
color: #fff;
background: rgba(20, 89, 187, 1);
}
}
}
.select-box {
height: 42px;
box-sizing: border-box;
padding: 5px 0;
}
}
.home-main-footer-main {
width: 1600px;
margin-bottom: 20px;
height: 985px;
// box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
// background: rgba(255, 255, 255, 1);
margin: 0 auto;
box-sizing: border-box;
// padding: 20px;
display: flex;
.left {
width: 300px;
height: 560px;
border-radius: 4px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.left-box1 {
margin-top: 17px;
height: 260px;
.left-box1-header {
display: flex;
.icon {
width: 8px;
height: 16px;
margin-top: 4px;
border-radius: 2px 2px 0 0;
background: var(--color-main-active);
}
.title {
height: 2px;
margin-left: 17px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
}
.left-box1-main {
margin-top: 10px;
.time-label {
height: 35px;
margin-left: 25px;
}
}
}
.left-box2 {
margin-top: 17px;
height: 260px;
.left-box2-header {
display: flex;
.icon {
width: 8px;
height: 16px;
margin-top: 4px;
border-radius: 2px 2px 0 0;
background: var(--color-main-active);
}
.title {
height: 2px;
margin-left: 17px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
}
.left-box2-main {
margin-top: 10px;
.area-label {
height: 35px;
margin-left: 25px;
}
}
}
}
.right {
margin-left: 16px;
width: 1284px;
height: 899px;
border-radius: 4px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.right-header {
height: 54px;
background: rgba(59, 65, 75, 1);
display: flex;
.header-item1 {
width: 500px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 54px;
padding-left: 80px;
box-sizing: border-box;
}
.header-item2 {
width: 196px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 54px;
padding-left: 20px;
box-sizing: border-box;
}
.header-item3 {
width: 196px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 54px;
padding-left: 20px;
box-sizing: border-box;
}
.header-item4 {
width: 196px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 54px;
padding-left: 20px;
box-sizing: border-box;
}
.header-item5 {
width: 196px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 54px;
padding-left: 20px;
box-sizing: border-box;
}
}
.right-main {
height: 780px;
// background: orange;
.item {
display: flex;
padding: 16px 0;
// height: 56px;
&:nth-child(2n) {
background: rgba(247, 248, 249, 1);
}
.item-box1 {
width: 500px;
display: flex;
.name {
height: 22px;
padding: 0 8px;
box-sizing: border-box;
border-radius: 4px;
margin-left: 32px;
margin-top: 10px;
// display: flex;
// align-items: center;
}
.name1 {
color: rgba(250, 140, 22, 1);
border: 1px solid rgba(255, 213, 145, 1);
background: rgba(255, 247, 230, 1);
}
.name2 {
color: var(--color-main-active);
border: 1px solid rgba(145, 202, 255, 1);
background: rgba(230, 244, 255, 1);
}
.name3 {
color: rgba(114, 46, 209, 1);
border: 1px solid rgba(211, 173, 247, 1);
background: rgba(249, 240, 255, 1);
}
.title {
margin-left: 12px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
display: flex;
align-items: center;
}
}
.item-box2 {
width: 196px;
padding-left: 20px;
box-sizing: border-box;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
display: flex;
align-items: center;
}
.item-box3 {
width: 196px;
padding-left: 20px;
box-sizing: border-box;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
display: flex;
align-items: center;
}
.item-box4 {
width: 196px;
padding-left: 20px;
box-sizing: border-box;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
display: flex;
align-items: center;
}
.item-box5 {
width: 196px;
padding-left: 20px;
box-sizing: border-box;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
display: flex;
align-items: center;
}
}
}
.right-footer {
display: flex;
// height: 60px;
// background: orange;
justify-content: space-between;
.footer-left {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
margin-left: 24px;
margin-top: 6px;
}
.footer-right {
margin-right: 24px;
}
}
}
}
}
}
}
.divide-header {
margin: 0 auto;
margin-top: 52px;
margin-bottom: 36px;
}
:deep(.el-input__wrapper) {
box-shadow: none;
border-radius: 10px;
}
:deep(.el-input__wrapper:hover) {
box-shadow: none !important;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: none !important;
}
</style>
import rand from "@/assets/images/rand.png";
import brookings from "@/assets/images/brookings.png";
import mit from "@/assets/images/mit.png";
import itif from "@/assets/images/itif.png";
import mckinsley from "@/assets/images/mckinsey.png";
import report1 from "./assets/矩形 196.png";
import report2 from "./assets/矩形 196 (1).png";
import report3 from "./assets/矩形 196 (2).png";
import report4 from "./assets/矩形 196 (3).png";
import report5 from "./assets/矩形 196 (4).png";
import report6 from "./assets/矩形 196 (5).png";
import report7 from "./assets/矩形 196 (6).png";
import report8 from "./assets/矩形 196 (7).png";
import report9 from "./assets/矩形 196 (8).png";
import report10 from "./assets/矩形 196 (9).png";
import report11 from "./assets/矩形 196 (10).png";
import report12 from "./assets/矩形 196 (11).png";
export const mockThinkTankList = [
{
"id": 1,
"name": "兰德科技智库",
"describe": "全球顶尖政策研究机构,专注于国家安全、科技政策、医疗卫生、能源政策、公共安全等领域的研究。",
"country": "美国",
"tags": [
"国家安全",
"科技政策"
],
"logo": rand
},
{
"id": 2,
"name": "布鲁金斯学会",
"describe": "专注于全球经济与发展、外交政策、都市政策、治理研究等领域,是美国最具影响力的智库之一。",
"country": "美国",
"tags": [
"经济政策",
"公共政策"
],
"logo": brookings
},
{
"id": 3,
"name": "麦肯锡全球研究院",
"describe": "专注于国际安全、国防、区域研究、能源与气候变化等领域,以其强硬的安全政策立场著称。",
"country": "美国",
"tags": [
"国际安全",
"国防政策"
],
"logo": mckinsley
},
{
"id": 4,
"name": "麻省理工学院科技评论",
"describe": "全球顶尖政策研究机构,专注于国家安全、科技政策、医疗卫生、能源政策、公共安全等领域的研究。",
"country": "美国",
"tags": [
"公共政策",
"国际关系"
],
"logo": mit
},
{
"id": 5,
"name": "信息技术与创新基金会",
"describe": "专注于国内外公共政策、国际关系等领域,是美国最具影响力的官方智库之一。",
"country": "美国",
"tags": [
"国家安全",
"科技政策"
],
"logo": itif
}
]
export const mockReportList = [
{
"id": 1,
"name": "中国对AI的转型产业政策",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report1
},
{
"id": 2,
"name": "中美对抗、竞争和合作跨越人工智能通用领域...",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report2
},
{
"id": 3,
"name": "中国、智慧城市和中东:地区和美国的选择",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report3
},
{
"id": 4,
"name": "中国对AI的转型产业政策",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report4
},
{
"id": 5,
"name": "中美经济竞争:复杂经济和地缘政治关系中的...",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report5
},
{
"id": 6,
"name": "中国、智慧城市和中东:留给地区和美国的选择",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report6
},
{
"id": 7,
"name": "中国对AI的转型产业政策",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report7
},
{
"id": 8,
"name": "中美对抗、竞争和合作跨越人工智能通用领域...",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report8
},
{
"id": 9,
"name": "中国、智慧城市和中东:地区和美国的选择",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report9
},
{
"id": 10,
"name": "中国对AI的转型产业政策",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report10
},
{
"id": 11,
"name": "中美对抗、竞争和合作跨越人工智能通用领域...",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report11
},
{
"id": 12,
"name": "中国、智慧城市和中东:地区和美国的选择",
"times": "2025-06-26",
"thinkTankName": "兰德科技智库",
imageUrl: report12
}
]
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
}
}
export const mockMainContentList = [
{
"id": 1,
"title": "虽然表面上类似于西方私人公司,但中国私营公司与政府的联系更加紧密...",
"content": "虽然表面上类似于西方私人公司,但中国私营公司与政府的联系更加紧密,并且可以更多地获得政府资源。",
"econtent": "Although superficially resembling Western private companies, Chinese private companies are more tightly bound to...",
"serialNum": 1,
"tags": [
{ "name": "关税", "type": "default" },
{ "name": "跨境电商", "type": "default" }
]
},
{
"id": 2,
"title": "这些概念上的差异意味着,试图改变中国经济将很困难,中国私营企业可能需要与西方公司区别对待。",
"content": "这些概念上的差异意味着,试图改变中国经济将很困难,中国私营企业可能需要与西方公司区别对待。",
"econtent": "These conceptual differences mean that trying to change the Chinese economy will be difficult and that Chinese ...",
"serialNum": 2,
"tags": [
{ "name": "私有经济", "type": "default" }
]
},
{
"id": 3,
"title": "大多数强制性经济措施在实现战略目标方面都取得了有限的成功...",
"content": "大多数强制性经济措施在实现战略目标方面都取得了有限的成功,并影响了美国和盟国经济体。",
"econtent": "Most coercive economic measures have shown limited success in meeting their strategic objectives and have affected...",
"serialNum": 3,
"tags": [
{ "name": "技术封锁", "type": "danger" },
{ "name": "半导体", "type": "default" }
]
},
{
"id": 4,
"title": "合作措施没有改变这种关系,但已经实现了更有限的目标,也可能具有积极的外部性...",
"content": "合作措施没有改变这种关系,但已经实现了更有限的目标,也可能具有积极的外部性、成本更低。",
"econtent": "Cooperative measures have not modified the relationship but have achieved more circumscribed goals and may...",
"serialNum": 4,
"tags": [
{ "name": "关税", "type": "default" },
{ "name": "跨境电商", "type": "default" }
]
},
{
"id": 5,
"title": "自20世纪90年代中期以来,全球经济日益相互关联,这主要是因为越来越依赖中国作为许多国家的投入供应商。",
"content": "自20世纪90年代中期以来,全球经济日益相互关联,这主要是因为越来越依赖中国作为许多国家的投入供应商。",
"econtent": "The global economy has become increasingly interconnected since the mid-1990s, largely because of an increasing...",
"serialNum": 5,
"tags": [
{ "name": "关税", "type": "default" },
{ "name": "跨境电商", "type": "default" }
]
},
{
"id": 6,
"title": "这些概念上的差异意味着,试图改变中国经济将很困难...",
"content": "这些概念上的差异意味着,试图改变中国经济将很困难,中国私营企业可能需要与西方公司区别对待。",
"econtent": "These conceptual differences mean that trying to change the Chinese economy will be difficult and that Chinese ...",
"serialNum": 6,
"tags": [
{ "name": "关税", "type": "default" },
{ "name": "跨境电商", "type": "default" }
]
},
{
"id": 7,
"title": "中国学生返回中国可以通过加强生产力的经济联系来使美国受益...",
"content": "中国学生返回中国可以通过加强生产力的经济联系来使美国受益,但也可能引发对知识产权转让或盗窃的担忧...",
"econtent": "Return migration of Chinese students to China could benefit the United States by strengthening productivity...",
"serialNum": 7,
"tags": [
{ "name": "移民", "type": "default" },
{ "name": "知识产权", "type": "default" }
]
},
{
"id": 8,
"title": "美国和中国经济通过能源交织在一起,两国都寻求能源安全和环境安全。",
"content": "美国和中国经济通过能源交织在一起,两国都寻求能源安全和环境安全。",
"econtent": "The U.S. and Chinese economies are intertwined through energy, and both countries seek energy security and...",
"serialNum": 8,
"tags": [
{ "name": "能源", "type": "default" },
{ "name": "环境安全", "type": "default" }
]
},
{
"id": 9,
"title": "与盟国一起投资技术、降低与盟国的贸易成本...",
"content": "与盟国一起投资技术、降低与盟国的贸易成本,以及开发替代商品,可以与中国分离并确保生产网络安全的...",
"econtent": "Investments in technology with allies, lowering trade costs with allies, and developing alternative goods can be an...",
"serialNum": 9,
"tags": [
{ "name": "产业结构", "type": "default" }
]
},
{
"id": 10,
"title": "美国和中国经济通过能源交织在一起,两国都寻求能源安全和环境安全。",
"content": "美国和中国经济通过能源交织在一起,两国都寻求能源安全和环境安全。",
"econtent": "The U.S. and Chinese economies are intertwined through energy, and both countries seek energy security and...",
"serialNum": 10,
"tags": [
{ "name": "能源", "type": "default" },
{ "name": "环境安全", "type": "default" }
]
}
]
export const mockContent = ` 包括经济竞争在内的美中竞争自2017年以来一直在定义美国外交政策。这两个经济体是世界上第一和第二大国家经济体,并且深深交织在一起。改变关系,无论多么必要,可能是昂贵的。因此,美国面临着一项挑战,确保其经济在耦合的战略竞争条件下满足国家的需求。
为了应对这一挑战,兰德大学的研究人员对美中竞争进行了经济和制度分析,进行了参与式的远见练习,以了解确保美国经济健康的长期路径,并创建了两个经济竞争游戏,探索多个国家在相互交流的同时确保经济健康的动态...`
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论