提交 bb99a95a authored 作者: coderBryanFu's avatar coderBryanFu

fix:更新概览页全政府打压遏制时间线组件

---
alwaysApply: true
---
# Overview
Insert overview text here. The agent will only see this should they choose to apply the rule.
```markdown
---
alwaysApply: true
---
# 前端开发(Cursor 专用执行要点)
## 1. HTML / 模板
- 属性命名:统一用小写-中线分割。动态 class 也用这种格式,使用单引号:`:class="{ 'xxx-xxx': isXxx }"`
- v-for 必须有唯一 key,`v-for + :key`(如`:key="item.id"`),禁止仅凭索引。示例:`<li v-for="item in list" :key="item.id"></li>`
- 禁止 v-if 与 v-for 同时在同一节点;如需筛选请用计算属性/方法过滤后再 v-for
- 无内容组件或标签必须自闭合,如 `<MyComp />` `<img />` `<input />`
- HTML 属性用双引号,动态属性外已双引号则用单引号
- 模板表达式保持极简,只写取值或很简单的显示;任何有数据处理(如数组/字符串操作、三元、函数执行等)必须抽到方法或计算属性完成
## 2. CSS
- 样式模块优先通用组件,确需定制严格按设计来写
- 全部样式变量统一来源于全局(如 `:root { --color-main-primary: #055fC2; ... }`)
- `<style scoped>` 默认加 scoped
- 修改子组件样式用 `:deep()`(Vue3 推荐写法),如:`.xxx :deep(.el-xxx) { ... }`
- 严禁页面大量内联 style,样式尽量写到 class 或 :style 绑定表达式
## 3. 文件系统
- 文件夹采用小驼峰,无数字/无关字符
- 每个业务模块 API 独立放模块对应文件夹
- 静态资源分全局 assets 与模块 assets:全局图片 assets/images,icon 建议 SVG,模块区分子文件夹
## 4. 组件
- 组件名大驼峰(PascalCase)。组件类型目录小驼峰,具体组件目录(如 AreaTag)大驼峰
- 单文件组件名 index.vue,多组件文件按具体功能如 LeftBtn.vue
- 优先用通用组件,业务组件只能放业务模块,不得放全局
## 5. JS/TS
- 命名统一规范
- 变量:小驼峰 userName、isVisible
- 常量:大写+下划线,如 MAX_COUNT
- 枚举:枚举名大驼峰,枚举值全大写+下划线 enum Status { SUCCESS = 'SUCCESS' }
- 普通函数:小驼峰+动词前缀 getUser/formatTime
- 事件函数:小驼峰+handle/on handleSubmit/onClose
- 布尔型函数:is/has/should + 大驼峰,如 isValid()
- 异步强制 async/await,catch 错误。禁止 Promise.then 链式嵌套(如遇回调 hell 必须拆分/抽象重写)
- 一律用 const/let 替换 var,优先 const
- 注释:
- 单行注释:后加空格,如 `// 注释内容`
- 方法/复杂模块加 /** JSDoc 注释 */
## 6. 其它
- 所有复杂渲染逻辑逻辑一律抽到计算属性或方法,模板结构保持简洁
...@@ -14,20 +14,20 @@ export function getBillIndustry(params) { ...@@ -14,20 +14,20 @@ export function getBillIndustry(params) {
// 涉华法案统计 // 涉华法案统计
export function getBillCount(params) { export function getBillCount(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/BillOverview/billCount`, url: `/api/BillOverview/billCount`,
params params
}) })
} }
// 获取关键条款 // 获取关键条款
export function getBillOverviewKeyTK() { export function getBillOverviewKeyTK() {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/BillOverview/keyTk`, url: `/api/BillOverview/keyTk`,
}) })
} }
// 获取热门法案列表 // 获取热门法案列表
...@@ -104,22 +104,32 @@ export function getBillPostOrg(params) { ...@@ -104,22 +104,32 @@ export function getBillPostOrg(params) {
}) })
} }
// 获取关键议员提案 // 获取涉华法案进展分布
/** /**
* @param {year} * @param {year}
*/ */
export function getMemberProposal(params) { export function getBillProcess(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/BillOverview/memberProposal/${params.year}`, url: `/bill/BillOverview/billsProcess/${params.year}`,
}) })
} }
// 获取资源库 // 获取资源库法案
export function getBills(params, signal) { export function getBills(params, signal) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/BillOverview/bills`, url: `/bill/BillOverview/bills`,
params,
signal
})
}
// 获取资源库国会议员
export function getBillsPerson(params, signal) {
return request({
method: 'GET',
url: `/bill/BillOverview/billsPerson`,
params, params,
signal signal
}) })
......
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
<!-- <img src="./image1.png" alt="" /> --> <!-- <img src="./image1.png" alt="" /> -->
<img src="./message-icon.svg" alt="" /> <img src="./message-icon.svg" alt="" />
</div> </div>
<div class="header-title">{{ "社交媒体" }}</div> <div class="header-title">
<slot name='title'>{{ "社交媒体" }}</slot>
</div>
<div class="more" @click="handleToMoreNews">{{ "更多 +" }}</div> <div class="more" @click="handleToMoreNews">{{ "更多 +" }}</div>
</div> </div>
<div class="box4-main"> <div class="box4-main">
...@@ -149,7 +151,7 @@ const handleToMoreNews = (item) => { ...@@ -149,7 +151,7 @@ const handleToMoreNews = (item) => {
} }
.header-title { .header-title {
width: 80px; width: 100%;
margin-top: 11px; margin-top: 11px;
margin-left: 18px; margin-left: 18px;
height: 26px; height: 26px;
......
<script setup>
import { ElSpace, ElButton } from 'element-plus';
import "@/styles/main.css"
import { copyToClipboardThenTip } from '@/utils/systemUtils'
// 黑色到白色主色一行
const rowBlackToWhite = [
{ title: "黑色", name: "--text-primary-color" },
{ title: "黑色90% / 主标题文字颜色", name: "--text-primary-90-color" },
{ title: "黑色80% / 小标题文字颜色", name: "--text-primary-80-color" },
{ title: "黑色65% / 正文颜色", name: "--text-primary-65-color" },
{ title: "黑色50%", name: "--text-primary-50-color" },
{ title: "黑色10%", name: "--bg-black-10" },
{ title: "黑色5% / 分割线颜色", name: "--bg-black-5" },
{ title: "黑色2% / 灰色背景色", name: "--bg-black-2" },
{ title: "白色主色", name: "--bg-white-100" },
]
// 主色到主色高亮背景一行(主色高亮背景为浅色,需黑色文字)
const rowPrimaryToHighlight = [
{ title: "主色", name: "--color-primary-100" },
{ title: "主色50%", name: "--color-primary-50", light: true },
{ title: "主色35%", name: "--color-primary-35" },
{ title: "主色10%", name: "--color-primary-10", light: true },
{ title: "主色高亮背景", name: "--color-primary-2", light: true },
]
// 红色到绿色10%一行(*10% 为浅色背景,需黑色文字)
const rowRedToGreen10 = [
{ title: "红色", name: "--color-red-100" },
{ title: "红色10%", name: "--color-red-10", light: true },
{ title: "橙色", name: "--color-orange-100" },
{ title: "橙色10%", name: "--color-orange-10", light: true },
{ title: "黄色", name: "--color-yellow-100" },
{ title: "黄色10%", name: "--color-yellow-10", light: true },
{ title: "绿色", name: "--color-green-100" },
{ title: "绿色10%", name: "--color-green-10", light: true },
]
function copyColorVar(item) {
const color = `var(${item.name})`
copyToClipboardThenTip(color)
}
</script>
<template>
<el-space direction="vertical" alignment="flex-start">
<div class="text-title-2">颜色</div>
<el-space direction="vertical" alignment="flex-start">
<el-space wrap>
<el-button v-for="(item, index) in rowBlackToWhite" :key="'bw-' + index" :color="`var(${item.name})`"
@click="copyColorVar(item)">
{{ item.title }}
</el-button>
</el-space>
<el-space wrap>
<el-button v-for="(item, index) in rowPrimaryToHighlight" :key="'ph-' + index"
:class="{ 'color-btn-text-black': item.light }"
:style="item.light ? { '--btn-bg': `var(${item.name})` } : undefined" :color="`var(${item.name})`"
@click="copyColorVar(item)">
{{ item.title }}
</el-button>
</el-space>
<el-space wrap>
<el-button v-for="(item, index) in rowRedToGreen10" :key="'rg-' + index"
:class="{ 'color-btn-text-black': item.light }"
:style="item.light ? { '--btn-bg': `var(${item.name})` } : undefined" :color="`var(${item.name})`"
@click="copyColorVar(item)">
{{ item.title }}
</el-button>
</el-space>
</el-space>
</el-space>
</template>
<style scoped>
/* 浅色背景按钮:文字黑色 */
:deep(.color-btn-text-black) {
color: var(--el-color-black) !important;
}
</style>
\ No newline at end of file
<script setup lang="ts"> <script setup lang="ts">
import "@/styles/container.scss" import "@/styles/container.scss"
import TextStyle from './textStyle.vue'; import TextStyle from './textStyle.vue';
import { ElScrollbar } from "element-plus"; import ConstStyle from './constStyle.vue';
import { ElScrollbar, ElSpace } from "element-plus";
</script> </script>
<template> <template>
<el-scrollbar> <el-scrollbar>
<div class="common-page"> <div class="common-page">
<text-style></text-style> <el-space direction="vertical" alignment="flex-start">
<div class="text-title-0-show">开发样式</div>
<div class="text-title-1-show">样式变量</div>
<const-style></const-style>
<div class="text-title-1-show">文字样式</div>
<text-style></text-style>
</el-space>
</div> </div>
</el-scrollbar> </el-scrollbar>
</template> </template>
\ No newline at end of file
<template> <template>
<div> <table style="width: 100%; border-collapse: collapse; border: 1px solid #ebeef5;">
<div class="text-title-1-show">文字样式</div> <!-- 表头 -->
<table style="width: 100%; border-collapse: collapse; border: 1px solid #ebeef5;margin-top: 10px;"> <thead>
<!-- 表头 --> <tr class="text-title-2">
<thead> <th> 名称</th>
<tr> <th> 类型名称</th>
<th> 名称</th> <th> 操作</th>
<th> 类型名称</th> </tr>
<th> 操作</th> </thead>
<!-- 表格内容 -->
<tbody>
<template v-for="(row, index) in tableData" :key="index">
<!-- 隔行变色效果 -->
<tr :style="{
height: '60px',
backgroundColor: index % 2 === 1 ? '#fafafa' : 'transparent'
}">
<!-- 名称列 -->
<td style="padding: 12px; border: 1px solid #ebeef5;">
<div :class="row.className">
{{ row.name }}
</div>
</td>
<!-- 类型名称列(带复制功能) -->
<td style="padding: 12px; border: 1px solid #ebeef5;">
<div @click="copyToClipboardThenTip(row.className)" style="cursor: pointer;">
<span>{{ row.className }}</span>
</div>
</td>
<!-- 操作列 -->
<td style="padding: 12px; border: 1px solid #ebeef5; text-align: center;">
<el-button type="primary" link @click="copyToClipboardThenTip(row.className)">
<el-icon>
<DocumentCopy />
</el-icon>
复制
</el-button>
</td>
</tr> </tr>
</thead> </template>
<!-- 表格内容 --> </tbody>
<tbody> </table>
<template v-for="(row, index) in tableData" :key="index">
<!-- 隔行变色效果 -->
<tr :style="{
height: '60px',
backgroundColor: index % 2 === 1 ? '#fafafa' : 'transparent'
}">
<!-- 名称列 -->
<td style="padding: 12px; border: 1px solid #ebeef5;">
<div :class="row.className">
{{ row.name }}
</div>
</td>
<!-- 类型名称列(带复制功能) -->
<td style="padding: 12px; border: 1px solid #ebeef5;">
<div @click="copyClassName(row.className)" style="cursor: pointer;">
<span>{{ row.className }}</span>
</div>
</td>
<!-- 操作列 -->
<td style="padding: 12px; border: 1px solid #ebeef5; text-align: center;">
<el-button type="primary" link @click="copyClassName(row.className)">
<el-icon>
<DocumentCopy />
</el-icon>
复制
</el-button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { ElMessage, } from 'element-plus'
import { DocumentCopy } from '@element-plus/icons-vue' import { DocumentCopy } from '@element-plus/icons-vue'
import "@/styles/common.scss" import "@/styles/common.scss"
import { copyToClipboard } from '@/utils/systemUtils' import { copyToClipboardThenTip } from '@/utils/systemUtils'
// 表格数据 // 表格数据
const tableData = ref([ const tableData = ref([
...@@ -87,18 +84,6 @@ const tableData = ref([ ...@@ -87,18 +84,6 @@ const tableData = ref([
{ name: '3级提示文字', className: 'text-tip-3' }, { name: '3级提示文字', className: 'text-tip-3' },
]) ])
// 复制类型名称到剪贴板
const copyClassName = async (className) => {
const { status, message } = await copyToClipboard(className)
if (status) {
ElMessage.success(message)
} else {
ElMessage.error(message)
}
}
</script> </script>
<style scoped></style> <style scoped></style>
\ No newline at end of file
//企业主页 //企业主页
const companyPages = () => import('@/views/companyPages/index.vue') const companyPages = () => import('@/views/companyPages/index.vue')
const companyPagesRoutes = [ const companyPagesRoutes = [
// 智库系统的主要路由 // 智库系统的主要路由
{ {
...@@ -9,7 +11,7 @@ const companyPagesRoutes = [ ...@@ -9,7 +11,7 @@ const companyPagesRoutes = [
component: companyPages, component: companyPages,
meta: { meta: {
title: "企业主页", title: "企业主页",
dynamicTitle: true dynamicTitle: true
} }
}, },
......
//样式主页 //样式主页
import stylePages from "@/components/devStyle/components/index.vue"; const StylePages = () => import("@/components/devStyle/components/index.vue");
const stylePagesRoutes = [ const stylePagesRoutes = [
// 智库系统的主要路由 // 智库系统的主要路由
{ {
path: "/devStylePages", path: "/devStylePages",
name: "devStylePages", name: "devStylePages",
component: stylePages, component: StylePages,
meta: { meta: {
title: "开发样式", title: "开发样式",
dynamicTitle: true dynamicTitle: true
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
/***没有nav下划线***/ /***没有nav下划线***/
.common-descriptions .el-descriptions__label{ .common-descriptions .el-descriptions__label{
@extend .text-header; @extend .text-tip-1-bold;
} }
.common-descriptions .el-descriptions__content{ .common-descriptions .el-descriptions__content{
@extend .text-base @extend .text-tip-1;
color: var(--text-primary-80-color);
} }
\ No newline at end of file
...@@ -39,10 +39,22 @@ ...@@ -39,10 +39,22 @@
/* 主色 设计定义*/ /* 主色 设计定义*/
--color-primary-100: #055FC2; --color-primary-100: #055FC2;
--color-primary-50: #82AFE0; --color-primary-50: #89C1FF;
--color-primary-35: #AED6FF; --color-primary-35: #B9DCFF;
--color-primary-10: #E7F3FF; --color-primary-10: #E7F3FF;
--color-primary-2: #F6FAFF; --color-primary-2: #F6FAFF;
/* 红色 设计定义 */
--color-red-100:#CE4F51;
--color-red-10:rgba(206, 79, 81, 0.1);
/* 橙色 设计定义 */
--color-orange-100:#FF954D;
--color-orange-10:rgba(255, 149, 77, 0.1);
/* 黄色 设计定义 */
--color-yellow-100:#E8BD0B;
--color-yellow-10:rgba(232, 189, 11, 0.1);
/* 绿色 设计定义 */
--color-green-100:#218139;
--color-green-10:rgba(33, 129, 57, 0.1);
} }
.hover-dialog { .hover-dialog {
......
import { ElMessage } from "element-plus";
interface BaseReturn { interface BaseReturn {
status: boolean; status: boolean;
message: string; message: string;
...@@ -26,3 +28,13 @@ export async function copyToClipboard(txet: string): Promise<BaseReturn> { ...@@ -26,3 +28,13 @@ export async function copyToClipboard(txet: string): Promise<BaseReturn> {
return { status: false, message: "复制失败,请手动复制" }; return { status: false, message: "复制失败,请手动复制" };
} }
} }
// 复制类型名称到剪贴板
export async function copyToClipboardThenTip(text: string): Promise<void> {
const { status, message } = await copyToClipboard(text);
if (status) {
ElMessage.success(message);
} else {
ElMessage.error(message);
}
}
<!--全领域-->
<template>
<div class="content-wrapper">
<div class="btn-wrapper" @mouseenter="stopAutoPlay" @mouseleave="startAutoPlay(true)">
<div class="cards-mask">
<div class="btn-box" :style="{ transform: `translateX(-${currentIndex * (cardWidth + cardGap)}px)` }">
<div class="btn-item-outer" v-for="(item, indexx) in buttonsData" :key="indexx">
<div
v-for="(value, index) in item"
:key="index"
class="btn-item"
:style="{ background: value.background }"
>
<div
:style="{
backgroundImage: 'url(' + `/public/icon/ZM/btn-icon2-${value.originalIndex}.png` + ')'
}"
class="btn-left-text"
>
{{ value.text }}
</div>
<div class="btn-right">
{{ value.count + "次" }}
</div>
</div>
</div>
</div>
</div>
<img :src="leftBtn" alt="" @click="prev" class="left-btn" />
<img :src="rightBtn" alt="" @click="next" class="right-btn" />
</div>
<div class="main-charts">
<div class="charts-title">
<div class="title-left">
<img src="./icon/icon-1.png" alt="" />
<span>美对华领域打压遏制数量趋势</span>
</div>
<div class="title-right">
<el-select
v-model="deptValue"
placeholder="全部部门"
class="custom-select"
@change="handleGetDomainContainmentTrend"
>
<el-option label="全部部门" value="" />
<el-option
v-for="item in departmentList"
:key="item.departId"
:label="item.departName"
:value="item.departId"
/>
</el-select>
<el-select
v-model="methodValue"
placeholder="全部制裁手段"
class="custom-select"
@change="handleGetDomainContainmentTrend"
>
<el-option v-for="item in methodOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
<div class="charts-content">
<div id="chartRef" class="chart-container"></div>
</div>
</div>
<div class="bottom-content">
<div class="news-section main-charts">
<div class="charts-title news-title">
<div class="title-left">
<img src="./icon/icon-2.png" alt="" style="width: 22px; height: 18px" />
<span>美对华领域打压遏制最新动态</span>
</div>
<div class="title-right-select">
<el-select v-model="selectedFieldForLatest" placeholder="全部领域" class="field-select">
<el-option v-for="item in fieldOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
<div class="news-content">
<div v-for="(value, idx) in newsList" :key="idx" class="news-item">
<div class="news-item-title">
<div class="tag-container">
<!-- <div v-for="tag in value.tags" :key="tag" :class="getTagClass(tag)">
{{ tag }}
</div> -->
<AreaTag v-for="(tag, index) in value.tags" :key="index" :tagName="tag"></AreaTag>
</div>
<div class="date">
{{ value.date + " · " + value.type }}
</div>
</div>
<div class="content-title" @click="handleClickTitle(value)">
{{ value.title }}
</div>
<el-tooltip
effect="dark"
:content="value.content"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="content-text">
{{ value.content }}
</div>
</el-tooltip>
</div>
</div>
</div>
<div class="empty-section">
<div class="bottom-item">
<div class="bottom-item-title">
<img :src="icon3" alt="" />
<span>美对华领域打压遏制排行</span>
</div>
</div>
<div class="select-box">
<div class="rank-btns">
<div class="rank-btn" :class="{ active: rankType === 'institution' }" @click="rankType = 'institution'">
对我打压机构
</div>
<div class="rank-btn" :class="{ active: rankType === 'enterprise' }" @click="rankType = 'enterprise'">
受打压企业
</div>
<div class="rank-btn" :class="{ active: rankType === 'school' }" @click="rankType = 'school'">
受打压院校
</div>
</div>
<el-select v-model="selectedField" placeholder="全部领域" class="field-select">
<el-option v-for="item in fieldOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="main-box" v-loading="rankLoading" element-loading-background="rgba(255, 255, 255, 0.5)">
<!-- 机构排行的原有样式 -->
<template v-if="rankType === 'institution'">
<div v-for="(item, index) in rankList" :key="index" class="rank-item">
<div class="rank-num" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<img :src="item.orgPicture ? item.orgPicture : defaultImg" alt="" class="rank-icon" />
<div class="rank-name" :title="item.name">{{ item.name }}</div>
<div class="rank-progress-container">
<div class="rank-progress-bar" :style="{ width: getProgressWidth(item.count) }"></div>
</div>
<div class="rank-count">{{ item.count }}</div>
</div>
</template>
<!-- 企业/院校排行的表格样式 -->
<template v-else>
<div class="table-header">
<div class="col-rank"></div>
<div class="col-name" style="color: rgb(59, 65, 75); font-weight: 700">
{{ rankType === "enterprise" ? "公司名称" : "院校名称" }}
</div>
<div class="col-domain" style="color: rgb(59, 65, 75); font-weight: 700">所属领域</div>
<div class="col-date" style="color: rgb(59, 65, 75); font-weight: 700">制裁时间</div>
<div class="col-member" v-if="rankType !== 'school'" style="color: rgb(59, 65, 75); font-weight: 700">
关键人物
</div>
</div>
<div class="table-list">
<div v-for="(item, index) in rankList" :key="index" class="table-row">
<div class="col-rank rank-num" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="col-name flex-align">
<img :src="item.orgPicture ? item.orgPicture : defaultImg" class="rank-icon" />
<span class="text-ellipsis" :title="item.name">{{ item.name }}</span>
</div>
<div class="col-domain">
<div class="domain-tags">
<span
v-for="(tag, tIndex) in item.domains.slice(0, rankType === 'enterprise' ? 2 : 3)"
:key="tIndex"
class="mini-tag"
:class="getTagClass(tag)"
>
{{ tag }}
</span>
</div>
</div>
<div class="col-date">{{ item.date }}</div>
<div class="col-member" v-if="rankType !== 'school'">{{ item.member }}</div>
</div>
</div>
</template>
</div>
</div>
</div>
<div class="line-time">
<div class="bottom-item">
<div class="bottom-item-title">
<img :src="icon4" alt="" />
<span>美对我领域打压遏制时间线</span>
</div>
<el-select
v-model="selectedFieldTimeline"
placeholder="全部领域"
class="field-select"
@change="handleGetDomainContainmentTimeline"
>
<el-option v-for="item in fieldOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="line-main">
<div class="svg-container">
<svg :viewBox="`0 0 ${svgWidth} ${svgHeight}`" width="100%">
<defs>
<marker
id="arrow"
markerWidth="10"
markerHeight="10"
refX="8"
refY="3"
orient="auto"
markerUnits="strokeWidth"
>
<path d="M0,0 L0,6 L9,3 z" fill="#e8f2ff" />
</marker>
</defs>
<path :d="snakePath" fill="none" stroke="rgba(185, 220, 255, 1)" stroke-width="5" />
<g v-for="(label, index) in axisDates" :key="'date-' + index">
<foreignObject :x="label.x" :y="label.y" width="80" height="60" style="overflow: visible">
<div class="axis-date-label">
<div class="year">{{ label.year }}</div>
<div class="month">{{ label.month }}</div>
</div>
</foreignObject>
</g>
<g v-for="(node, index) in timelineNodes" :key="index" @click="handleTimeLineNode(node)">
<line :x1="node.x" :y1="node.y" :x2="node.x" :y2="node.y + 150" stroke="#1677ff" stroke-width="1" />
<circle :cx="node.x" :cy="node.y" r="4" fill="#fff" stroke="#1677ff" stroke-width="3" />
<foreignObject
:x="node.contentX"
:y="node.contentY"
:width="node.contentWidth"
:height="node.contentHeight"
style="overflow: visible"
>
<div class="timeline-content-item">
<div class="item-tags">
<!-- <span v-for="tag in node.tags" :key="tag" :class="getTagClass(tag)">{{ tag }}</span> -->
<AreaTag v-for="(tag, idx) in node.tags" :key="idx" :tagName="tag"></AreaTag>
</div>
<div class="item-title">
<CommonPrompt :content="node.title" />
</div>
<el-tooltip
effect="dark"
:content="node.content"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="item-desc">{{ node.content }}</div>
</el-tooltip>
<div class="item-footer">{{ node.info }}</div>
</div>
</foreignObject>
</g>
</svg>
</div>
</div>
<div class="left-btn" @click="handleLeft">
<img src="@/assets/icons/card-btn-left.png" alt="" />
</div>
<div class="right-btn" @click="handleRight">
<img src="@/assets/icons/card-btn-right.png" alt="" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch, inject, onUnmounted } from "vue";
import { useRouter } from "vue-router";
import setChart from "@/utils/setChart";
import getMultiLineChart from "./multiLineChart";
import CommonPrompt from "../../../../commonPrompt/index.vue";
import leftBtn from "../../assets/left-btn.png";
import rightBtn from "../../assets/right-btn.png";
import icon3 from "./icon/icon-3.png";
import icon4 from "./icon/icon-4.png";
import defaultImg from "../../../../assets/images/default-icon2.png";
import {
getAllDomainCount,
getDomainContainmentTrend,
getTechnologyGameAnalysis,
getDomainContainmentRanking,
getDomainContainmentTimeline
} from "@/api/zmOverview/allDomains";
import { getUSGovernmentLatestDynamic, getDepartmentList, getSanTypeList } from "@/api/allGovernment.js";
import { ElMessage } from "element-plus";
const router = useRouter();
const activeDate = inject("activeDate");
const deptValue = ref("");
const methodValue = ref("");
const departmentList = ref([]);
const methodOptions = ref([
// { label: "全部制裁手段", value: "" },
// { label: "法案", value: "-1" },
// { label: "政令", value: "-2" },
// { label: "实体清单", value: "1" },
// { label: "特别国民指定清单", value: "2" },
// { label: "涉军企业", value: "3" },
// { label: "行业制裁识别清单", value: "4" },
// { label: "无法核实清单", value: "5" },
// { label: "军事最终用户清单", value: "6" },
// { label: "非SDN中国军工企业名单", value: "7" },
// { label: "拒绝往来人员清单", value: "8" },
// { label: "军事最终用途与最终用户规则", value: "9" },
// { label: "欧盟合并制裁清单", value: "10" },
// { label: "英国制裁清单", value: "11" },
// { label: "加拿大合并自主制裁清单", value: "12" },
// { label: "商业管制清单", value: "13" }
]);
const handleGetSanList = async () => {
const params = {
orgId: deptValue.value
};
try {
const res = await getSanTypeList(params);
console.log("制裁手段列表", res);
if (res.code === 200 && res.data) {
methodOptions.value = res.data.map(item => {
return {
label: item.name,
value: item.id
};
});
}
} catch (error) {}
};
const getDepartmentListData = async () => {
const params = {
sanTypeId: methodValue.value
};
try {
const res = await getDepartmentList(params);
if (res.code === 200 && res.data) {
departmentList.value = res.data;
}
} catch (error) {
console.error("获取部门数据失败:", error);
}
};
watch(
() => methodValue.value,
val => {
getDepartmentListData();
}
);
watch(
() => deptValue.value,
val => {
handleGetSanList();
}
);
const rankType = ref("institution");
const rankLoading = ref(false);
const selectedField = ref("");
const selectedFieldForLatest = ref("");
const selectedFieldTimeline = ref("");
const timelineContainerWidth = 1700;
// 美政府部门打压遏制最新动态
const newsList = ref([]);
const getUSGovernmentLatestDynamicData = async () => {
try {
const res = await getUSGovernmentLatestDynamic();
if (res.code === 200 && res.data) {
// 将接口数据转换为 newsList 需要的格式
newsList.value = res.data.map(item => {
const dateObj = new Date(item.time);
const formattedDate = `${dateObj.getFullYear()}${dateObj.getMonth() + 1}${dateObj.getDate()}日`;
return {
id: item.id,
tags: item.industrylist || [],
date: formattedDate,
type: item.orgName || "未知机构", // 使用 orgName 作为类型信息
title: item.title,
content: item.content || item.title // 如果 content 为空,使用 title 填充
};
});
}
} catch (error) {
console.error("获取美政府部门打压遏制最新动态失败:", error);
}
};
const handleClickTitle = item => {
if (!item || !item.id) return;
// 打开新标签页
const { href } = router.resolve({
path: "/newsAnalysis",
query: {
newsId: item.id
}
});
window.open(href, "_blank");
};
const timelineList = ref([]);
// 处理时间线数据的方法
const processTimelineData = rawData => {
if (!rawData || !Array.isArray(rawData) || rawData.length === 0) {
return [];
}
return rawData.map(item => {
const eventDate = item.eventDate ? new Date(item.eventDate) : null;
const formattedDate = eventDate ? `${eventDate.getFullYear()}${eventDate.getMonth() + 1}月` : "";
let tags = [];
if (item.eventDomainList && Array.isArray(item.eventDomainList)) {
if (!selectedFieldTimeline.value) {
tags = item.eventDomainList.slice(0, 3).map(domain => domain.domainName || "");
} else {
const matchedIndex = item.eventDomainList.findIndex(domain => domain.domainId === selectedFieldTimeline.value);
if (matchedIndex !== -1) {
const matchedItem = item.eventDomainList[matchedIndex];
const remainingItems = item.eventDomainList.filter((_, idx) => idx !== matchedIndex);
const sortedList = [matchedItem, ...remainingItems];
tags = sortedList.slice(0, 3).map(domain => domain.domainName || "");
} else {
tags = item.eventDomainList.slice(0, 3).map(domain => domain.domainName || "");
}
}
}
return {
date: formattedDate,
tags: tags,
title: item.eventName || "",
content: item.eventDesc || "",
info: `${item.eventDate || ""} · ${item.eventType || ""}`,
eventId: item.eventId,
eventOrg: item.eventOrg,
eventType: item.eventType,
eventCountryImg: item.eventCountryImg,
eventDomainList: item.eventDomainList
};
});
};
// 获取领域遏制时间线数据
const handleGetDomainContainmentTimeline = async () => {
try {
const params = {};
if (selectedFieldTimeline.value) {
params.domain = selectedFieldTimeline.value;
}
console.log("美对我领域打压遏制时间线", params);
const res = await getDomainContainmentTimeline(params);
console.log("美对我领域打压遏制时间线", res);
if (res.code === 200 && res.data) {
// 处理返回的数据结构
const processedData = processTimelineData(res.data);
// processedData.forEach(item => {
// if (item.eventDomainList && Array.isArray(item.eventDomainList)) {
// if (!selectedFieldTimeline.value) {
// item.eventDomainList = item.eventDomainList.slice(0, 3);
// } else {
// const matchedIndex = item.eventDomainList.findIndex(
// domain => domain.domainId === selectedFieldTimeline.value
// );
// if (matchedIndex !== -1) {
// const matchedItem = item.eventDomainList[matchedIndex];
// const remainingItems = item.eventDomainList.filter((_, idx) => idx !== matchedIndex);
// item.eventDomainList = [matchedItem, ...remainingItems.slice(0, 2)];
// } else {
// item.eventDomainList = item.eventDomainList.slice(0, 3);
// }
// }
// }
// });
timelineList.value = processedData;
console.log("timelineList", timelineList.value);
}
} catch (error) {
console.error("获取美对我领域打压遏制时间线失败:", error);
// 设置默认空数组
timelineList.value = [];
}
};
const maxPerRow = 3;
const nodeGapX = 420;
const rowHeight = 230;
const startX = 250;
const startY = 45;
const timeLineActiveIndex = ref(0);
const axisDates = computed(() => {
const dates = [];
if (timelineList.value.length > 0) {
dates.push({
year: timelineList.value[0].date.split(" ")[0],
month: timelineList.value[0].date.split(" ")[1],
x: startX - 140,
y: startY - 25
});
const lastNode = timelineNodes.value[timelineNodes.value.length - 1];
const isEvenRow = lastNode.row % 2 === 0;
const endX = isEvenRow ? lastNode.x + 400 : lastNode.x - 140;
dates.push({
year: timelineList.value[timelineList.value.length - 1].date.split(" ")[0],
month: timelineList.value[timelineList.value.length - 1].date.split(" ")[1],
x: endX,
y: lastNode.y - 25
});
}
return dates;
});
const handleLeft = () => {
if (timeLineActiveIndex.value === 0) {
ElMessage.warning("当前已经是第一组数据!");
} else {
timeLineActiveIndex.value--;
}
};
const handleRight = () => {
if (timeLineActiveIndex.value === timelineList.value.length % 9) {
ElMessage.warning("当前已经是最后一组数据!");
} else {
timeLineActiveIndex.value++;
}
console.log("axisDates", axisDates.value);
console.log("timelineNodes", timelineNodes.value);
};
const timelineNodes = computed(() => {
// 计算起始索引:activeIndex * 9
const startIndex = timeLineActiveIndex.value * 9;
// 计算结束索引:起始索引 + 9(因为slice的第二个参数是结束索引,不包含)
const endIndex = timeLineActiveIndex.value + 9;
const showTimeLineList = timelineList.value.slice(startIndex, endIndex);
console.log("时间线相关数据 showTimeLineList =>", showTimeLineList);
console.log("时间线相关数据 timelineList =>", timelineList.value);
return showTimeLineList.map((item, index) => {
const row = Math.floor(index / maxPerRow);
const col = index % maxPerRow;
let x;
if (row % 2 === 0) {
x = startX + col * nodeGapX;
} else {
x = startX + (maxPerRow - 1 - col) * nodeGapX;
}
const y = startY + row * rowHeight;
const contentX = x + 20;
const contentY = y + 10;
return {
...item,
x,
y,
row,
col,
contentX,
contentY,
contentWidth: 320,
contentHeight: 180
};
});
});
const getColorName = tag => {
const tagColorMap = {
航空航天: "blue",
生物科技: "blue",
集成电路: "blue",
能源: "green",
新材料: "green",
人工智能: "red"
};
if (tagColorMap[tag]) return tagColorMap[tag];
const colors = ["blue", "green", "red", "orange", "purple", "cyan"];
let hash = 0;
for (let i = 0; i < tag.length; i++) {
hash = tag.charCodeAt(i) + ((hash << 5) - hash);
}
return colors[Math.abs(hash) % colors.length];
};
const getTagClass = tag => {
return "tag-item " + `tag-${getColorName(tag)}`;
};
const snakePath = computed(() => {
if (timelineNodes.value.length === 0) return "";
let path = `M ${startX - 100} ${startY}`;
path += ` L ${timelineNodes.value[0].x} ${timelineNodes.value[0].y}`;
for (let i = 0; i < timelineNodes.value.length - 1; i++) {
const curr = timelineNodes.value[i];
const next = timelineNodes.value[i + 1];
if (curr.row !== next.row) {
const radius = rowHeight / 2;
if (curr.row % 2 === 0) {
const turnX = curr.x + 400;
path += ` L ${turnX} ${curr.y}`;
path += ` A ${radius} ${radius} 0 0 1 ${turnX} ${next.y}`;
path += ` L ${next.x} ${next.y}`;
} else {
const turnX = curr.x - 100;
path += ` L ${turnX} ${curr.y}`;
path += ` A ${radius} ${radius} 0 0 0 ${turnX} ${next.y}`;
path += ` L ${next.x} ${next.y}`;
}
} else {
path += ` L ${next.x} ${next.y}`;
}
}
const last = timelineNodes.value[timelineNodes.value.length - 1];
if (last.row % 2 === 0) {
path += ` L ${last.x + 400} ${last.y}`;
} else {
path += ` L ${last.x - 100} ${last.y}`;
}
return path;
});
const svgWidth = computed(() => {
return timelineContainerWidth;
});
const svgHeight = computed(() => {
const rows = Math.ceil(timelineList.value.length / maxPerRow);
console.log("看下高度", rows);
return startY + rows * rowHeight + 50;
});
const fieldOptions = [
{ value: "", label: "全部领域" },
{ value: "1", label: "人工智能" },
{ value: "2", label: "生物科技" },
{ value: "3", label: "新一代信息技术" },
{ value: "4", label: "量子科技" },
{ value: "5", label: "新能源" },
{ value: "6", label: "集成电路" },
{ value: "7", label: "海洋" },
{ value: "8", label: "先进制造" },
{ value: "9", label: "新材料" },
{ value: "10", label: "航空航天" },
{ value: "11", label: "深海" },
{ value: "12", label: "极地" },
{ value: "13", label: "太空" },
{ value: "14", label: "核" }
];
// 全领域统计
const buttonsData = ref([]);
const bgList = [
{
text: "集成电路",
count: 101,
background: "linear-gradient(180.00deg, rgba(255, 102, 102, 1),rgba(255, 102, 102, 0.5) 100%)"
},
{
text: "人工智能",
count: 132,
background: "linear-gradient(180.00deg, rgba(51, 153, 255, 1),rgba(51, 153, 255, 0.5) 100%)"
},
{
text: "新一代信息技术",
count: 105,
background: "linear-gradient(180.00deg, rgba(255, 187, 51, 1),rgba(255, 187, 51, 0.5) 100%)"
},
{
text: "量子科技",
count: 97,
background: "linear-gradient(180.00deg, rgba(117, 73, 255, 1),rgba(117, 73, 255, 0.5) 100%)"
},
{
text: "先进制造",
count: 84,
background: "linear-gradient(180.00deg, rgba(102, 102, 102, 1),rgba(102, 102, 102, 0.5) 100%)"
},
{
text: "航空航天",
count: 77,
background: "linear-gradient(180.00deg, rgba(51, 102, 255, 1),rgba(51, 102, 255, 0.5) 100%)"
},
{
text: "生物科技",
count: 62,
background: "linear-gradient(180.00deg, rgba(102, 204, 204, 1),rgba(102, 204, 204, 0.5) 100%)"
},
{
text: "新能源",
count: 60,
background: "linear-gradient(180.00deg, rgba(102, 187, 51, 1),rgba(102, 187, 51, 0.5) 100%)"
},
{
text: "新材料",
count: 43,
background: "linear-gradient(180.00deg, rgba(255, 102, 51, 1),rgba(255, 102, 51, 0.5) 100%)"
},
{
text: "海洋",
count: 25,
background: "linear-gradient(180.00deg, rgba(153, 204, 255, 1),rgba(153, 204, 255, 0.5) 100%)"
},
{
text: "核",
count: 25,
background: "linear-gradient(180.00deg, rgba(255, 102, 102, 1),rgba(255, 102, 102, 0.5) 100%)"
},
{
text: "太空",
count: 25,
background: "linear-gradient(180.00deg, rgba(51, 153, 255, 1),rgba(51, 153, 255, 0.5) 100%)"
},
{
text: "其他",
count: 25,
background: "linear-gradient(180.00deg, rgba(255, 187, 51, 1),rgba(255, 187, 51, 0.5) 100%)"
},
{
text: "深海",
count: 25,
background: "linear-gradient(180.00deg, rgba(117, 73, 255, 1),rgba(117, 73, 255, 0.5) 100%)"
},
{
text: "极地",
count: 25,
background: "linear-gradient(180.00deg, rgba(102, 102, 102, 1),rgba(102, 102, 102, 0.5) 100%)"
},
{
text: "新一代通信网络",
count: 25,
background: "linear-gradient(180.00deg, rgba(153, 204, 255, 1),rgba(153, 204, 255, 0.5) 100%)"
}
];
const getCalculatedDate = type => {
const now = new Date();
const endDate = new Date();
const start = new Date();
switch (type) {
case "week": // 近一个月
start.setMonth(now.getMonth() - 1);
break;
case "three_month": // 近三个月
start.setMonth(now.getMonth() - 3);
break;
case "six_month": // 近半年
start.setMonth(now.getMonth() - 6);
break;
case "year": // 近一年
start.setFullYear(now.getFullYear() - 1);
break;
default:
start.setMonth(now.getMonth() - 1); // 默认近一个月
}
const formatDate = date => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
return {
startDate: formatDate(start),
endDate: formatDate(endDate)
};
};
const handleGetAllDomainCount = async () => {
try {
const { startDate, endDate } = getCalculatedDate(activeDate.value);
const res = await getAllDomainCount({
startDate: startDate,
endDate: endDate
});
console.log("全领域统计", res);
if (res.code === 200 && res.data) {
// 按照 countNum 从大到小排序
res.data.sort((a, b) => b.countNum - a.countNum);
buttonsData.value = res.data.map((item, index) => {
const matchedBg = bgList.find(bg => bg.text === item.countName);
const background = matchedBg ? matchedBg.background : bgList[index % 10].background;
// 映射图标索引
let iconIndex = 0; // 默认
switch (item.countName) {
case "集成电路":
iconIndex = 0;
break;
case "人工智能":
iconIndex = 1;
break;
case "新一代通信网络":
case "新一代信息技术":
iconIndex = 2;
break;
case "量子科技":
iconIndex = 3;
break;
case "先进制造":
iconIndex = 4;
break;
case "航空航天":
iconIndex = 5;
break;
case "生物科技":
iconIndex = 6;
break;
case "新能源":
iconIndex = 7;
break;
case "新材料":
iconIndex = 8;
break;
case "海洋":
iconIndex = 9;
break;
default:
iconIndex = 0;
}
return {
text: item.countName,
count: item.countNum,
background: background,
originalIndex: iconIndex
};
});
const chunkSize = 2;
buttonsData.value = Array.from({ length: Math.ceil(buttonsData.value.length / chunkSize) }, (_, i) =>
buttonsData.value.slice(i * chunkSize, i * chunkSize + chunkSize)
);
console.log("buttonsData", buttonsData.value);
startAutoPlay();
}
} catch (error) {}
};
const box5Data = ref({});
const handleGetDomainContainmentTrend = async () => {
try {
const { startDate, endDate } = getCalculatedDate(activeDate.value);
const params = {
startDate,
endDate
};
if (deptValue.value) {
params.org = deptValue.value;
}
if (methodValue.value) {
params.sanMeasures = methodValue.value;
}
const res = await getDomainContainmentTrend(params);
console.log("美对华领域打压遏制数量趋势", res);
if (res.code === 200 && res.data) {
// 处理返回的数据结构,按照 mockdata.json 中的格式
const processedData = processDomainTrendData(res.data);
box5Data.value = processedData;
// 更新图表
let Chart = getMultiLineChart(box5Data.value);
setChart(Chart, "chartRef");
}
} catch (error) {
console.error("获取美对华领域打压遏制数量趋势失败:", error);
}
};
// 处理领域趋势数据的方法
const processDomainTrendData = rawData => {
// 提取所有的月份作为标题
const titles = rawData.map(item => item.yearOrMonth).reverse();
// 收集所有不重复的领域名称
const domainNamesSet = new Set();
rawData.forEach(item => {
item.domainList.forEach(domain => {
domainNamesSet.add(domain.domainName);
});
});
const domainNames = Array.from(domainNamesSet);
// 定义颜色映射
const colorMap = {
人工智能: "#E34D59",
新一代通信网络: "#FF9F1C",
: "#FFB3B3",
生物科技: "#00A79D",
量子科技: "#7B61FF",
先进制造: "#363B42",
新能源: "#2BA471",
太空: "#3762F0",
集成电路: "#0052D9",
新材料: "#FFD900",
航空航天: "#3762F0",
海洋: "#76D1FF",
深海: "#002060",
其他: "#A6A6A6"
};
// 生成数据系列
const dataSeries = domainNames.map(domainName => {
const values = rawData
.map(monthData => {
const domainItem = monthData.domainList.find(d => d.domainName === domainName);
return domainItem ? domainItem.domainNum : 0;
})
.reverse(); // 数据值也需要跟随标题反转顺序
return {
name: domainName,
color: colorMap[domainName] || `#${Math.floor(Math.random() * 16777215).toString(16)}`, // 如果没有预定义颜色,则随机生成
value: values
};
});
return {
title: titles,
data: dataSeries
};
};
const rankList = ref([]);
const maxCount = computed(() => {
if (!rankList.value || rankList.value.length === 0) return 0;
return Math.max(...rankList.value.map(item => item.count || 0));
});
const getProgressWidth = count => {
if (!maxCount.value) return "0%";
return (count / maxCount.value) * 100 + "%";
};
// 处理排名数据的方法
const processRankingData = rawData => {
if (!rawData || !Array.isArray(rawData) || rawData.length === 0) {
return [];
}
return rawData.map(item => {
// 格式化日期:2025-10-08 -> 2025.10.8
let formattedDate = "";
if (item.sanctionDate) {
const date = new Date(item.sanctionDate);
if (!isNaN(date.getTime())) {
formattedDate = `${date.getFullYear()}.${date.getMonth() + 1}.${date.getDate()}`;
} else {
formattedDate = item.sanctionDate;
}
}
return {
name: item.orgName || "",
count: item.orgCount || 0,
orgPicture: item.orgPicture,
// 新增字段
domains: item.domainList ? item.domainList.map(d => d.name) : [],
date: formattedDate,
member: item.keyMember || "-"
};
});
};
const rankTypeMap = {
institution: "对我打压机构",
enterprise: "受打压企业",
school: "受打压院校"
};
// 获取领域遏制排名数据
const handleGetDomainContainmentRanking = async () => {
rankLoading.value = true;
rankList.value = [];
try {
const res = await getDomainContainmentRanking(
rankTypeMap[rankType.value],
!!selectedField.value ? selectedField.value : ""
);
if (res.code === 200 && res.data) {
// 处理返回的数据结构
const processedData = processRankingData(res.data);
rankList.value = processedData;
}
} catch (error) {
console.error("获取美对华领域打压遏制排行失败:", error);
// 设置默认空数组
rankList.value = [];
} finally {
rankLoading.value = false;
}
};
// 监听rankType变化, 调用获取领域遏制排名数据方法
watch(rankType, handleGetDomainContainmentRanking);
// 监听selectedField变化, 调用获取领域遏制排名数据方法
watch(selectedField, handleGetDomainContainmentRanking);
const currentIndex = ref(0);
let autoTimer = null;
const visibleCount = 7;
const cardWidth = 215; // 卡片宽度
const cardGap = 16; // 卡片间距
const startAutoPlay = isContiune => {
if (!isContiune) {
currentIndex.value = 0;
}
stopAutoPlay();
if (buttonsData.value.length > visibleCount) {
autoTimer = setInterval(() => {
next();
}, 3000);
}
};
const stopAutoPlay = () => {
if (autoTimer) {
clearInterval(autoTimer);
autoTimer = null;
}
};
const next = () => {
let arr = [...buttonsData.value];
if (currentIndex.value < buttonsData.value.length - visibleCount) {
currentIndex.value++;
} else {
// currentIndex.value = 0;
buttonsData.value = [...buttonsData.value, ...arr];
currentIndex.value++;
}
};
const prev = () => {
if (currentIndex.value > 0) {
currentIndex.value--;
} else {
currentIndex.value = Math.max(0, buttonsData.value.length - visibleCount);
}
};
// const next = () => {
// const maxIndex = buttonsData.value.length - visibleCount;
// if (currentIndex.value < maxIndex) {
// currentIndex.value++;
// } else {
// // 滚动到末尾时,重置到开头并复制数组实现无缝滚动
// const arr = [...buttonsData.value];
// buttonsData.value = [...buttonsData.value, ...arr];
// currentIndex.value = buttonsData.value.length - visibleCount - arr.length;
// }
// };
// const prev = () => {
// if (currentIndex.value > 0) {
// currentIndex.value--;
// } else {
// // 滚动到开头时,跳转到末尾
// currentIndex.value = buttonsData.value.length - visibleCount;
// }
// };
const handleTimeLineNode = node => {
// console.log("timeLineNode", node);
if (node.eventType === "科技法案") {
window.sessionStorage.setItem("curTabName", node.title);
const route = router.resolve({
path: "/billLayout",
query: {
billId: node.eventId
}
});
window.open(route.href, "_blank");
}
};
onMounted(() => {
// let Chart = getMultiLineChart(box5Data.value);
// setChart(Chart, "chartRef");
handleGetDomainContainmentTrend();
getDepartmentListData();
handleGetSanList();
handleGetAllDomainCount();
handleGetDomainContainmentRanking();
handleGetDomainContainmentTimeline();
getUSGovernmentLatestDynamicData();
startAutoPlay();
});
onUnmounted(() => {
stopAutoPlay();
});
watch(activeDate, () => {
handleGetAllDomainCount();
handleGetDomainContainmentTrend();
});
</script>
<style lang="scss" scoped>
.content-wrapper {
width: 1666px;
height: 2132px;
}
.btn-wrapper {
position: relative;
width: 1601px;
}
.cards-mask {
width: 100%;
overflow: hidden; // 仅在这里隐藏超出部分,不影响外层的按钮
}
.btn-box {
width: 100%;
width: max-content;
height: 176px;
overflow: hidden;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
align-items: stretch;
gap: 16px;
padding: 0;
transition: transform 0.5s ease; // 平滑过渡动画
}
.left-btn {
width: 24px;
height: 48px;
position: absolute;
top: 50%;
left: -33px;
transform: translateY(-50%);
cursor: pointer;
z-index: 10;
}
.right-btn {
width: 24px;
height: 48px;
position: absolute;
top: 50%;
right: -33px;
transform: translateY(-50%);
cursor: pointer;
z-index: 10;
}
.btn-item {
/* 全领域-总统计 */
// width: 307px;
width: 215px;
height: 80px;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
margin-bottom: 10px;
.btn-left-text {
width: fit-content;
min-width: 60px;
max-width: 97px;
height: 60px;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
font-family: YouSheBiaoTiHei;
font-size: 24px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: left;
color: rgba(255, 255, 255, 1);
//换行
white-space: wrap;
display: flex;
align-items: center;
font-style: italic;
}
.btn-right {
font-family: YouSheBiaoTiHei;
font-size: 30px;
font-weight: 400;
color: rgba(255, 255, 255, 1);
font-style: italic;
}
}
.btn-left {
position: relative;
display: flex;
align-items: center;
}
.btn-left-icon {
margin-right: 8px; // 图标和文本之间的间距
}
.main-charts {
margin-top: 16px;
width: 1601px;
height: 500px;
border-radius: 10px;
background-color: rgba(255, 255, 255, 0.65);
box-shadow: 0 0 20px rgba(25, 69, 130, 0.1);
display: flex;
flex-direction: column;
.charts-title {
width: 100%;
height: 54px;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 17px;
padding-right: 35px;
box-sizing: border-box;
background: linear-gradient(180deg, rgba(231, 243, 255, 1) 0%, rgba(231, 243, 255, 0) 100%);
.title-left {
display: flex;
align-items: center;
img {
width: 18px;
height: 18px;
margin-right: 14px;
}
span {
font-family: YouSheBiaoTiHei;
font-size: 24px;
font-weight: 400;
line-height: 24px;
color: rgb(5, 95, 194);
}
}
.title-right {
display: flex;
height: 48px;
align-items: end;
gap: 12px;
padding-right: 17px;
.custom-select {
width: 160px;
:deep(.el-input) {
height: 32px;
.el-input__wrapper {
background-color: #fff;
box-shadow: 0 0 0 1px #dcdfe6 inset;
border-radius: 4px;
padding: 0 12px;
height: 32px;
&:hover {
box-shadow: 0 0 0 1px #c0c4cc inset;
}
&.is-focus {
box-shadow: 0 0 0 1px #409eff inset;
}
}
.el-input__inner {
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
color: rgb(95, 101, 108);
height: 32px;
line-height: 24px;
&::placeholder {
color: rgb(95, 101, 108);
}
}
}
}
}
.title-right-select {
width: 150px;
}
}
.charts-content {
flex: 1;
width: 100%;
padding: 20px 40px 20px 40px;
box-sizing: border-box;
.chart-container {
width: 100%;
height: 100%;
}
}
}
.bottom-content {
display: flex;
gap: 17px;
background: rgba(255, 255, 255, 0.65);
.news-section {
width: 792px;
height: 700px;
.news-title {
padding: 8px;
}
.tag-container {
display: flex;
align-items: center;
gap: 8px;
.tag-item {
padding: 2px 8px;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
border: 1px solid transparent;
&.tag-blue {
color: rgba(9, 88, 217, 1);
background: rgba(230, 244, 255, 1);
border-color: rgba(186, 224, 255, 1);
}
&.tag-green {
color: rgba(56, 158, 13, 1);
background: rgba(246, 255, 237, 1);
border-color: rgba(217, 247, 190, 1);
}
&.tag-red {
color: rgba(245, 34, 45, 1);
background: rgba(255, 241, 240, 1);
border-color: rgba(255, 163, 158, 1);
}
&.tag-orange {
color: rgba(250, 140, 22, 1);
background: rgba(255, 247, 230, 1);
border-color: rgba(255, 213, 145, 1);
}
&.tag-purple {
color: rgba(114, 46, 209, 1);
background: rgba(249, 240, 255, 1);
border-color: rgba(211, 173, 247, 1);
}
&.tag-cyan {
color: rgba(19, 194, 194, 1);
background: rgba(230, 255, 251, 1);
border-color: rgba(135, 232, 222, 1);
}
}
}
}
.empty-section {
width: 792px;
height: 700px;
margin-top: 16px;
border-radius: 10px;
background-color: rgba(255, 255, 255, 0.65);
box-shadow: 0 0 20px rgba(25, 69, 130, 0.1);
.bottom-item {
width: 100%;
height: 48px;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 17px;
padding-right: 35px;
box-sizing: border-box;
background: linear-gradient(180deg, rgba(231, 243, 255, 1) 0%, rgba(231, 243, 255, 0) 100%);
.bottom-item-title {
display: flex;
align-items: center;
img {
width: 18px;
height: 18px;
margin-right: 14px;
}
span {
font-family: YouSheBiaoTiHei;
font-size: 24px;
font-weight: 400;
line-height: 24px;
color: rgb(5, 95, 194);
}
}
}
.select-box {
width: 691px;
height: 32px;
margin: 10px auto 5px auto;
display: flex;
justify-content: space-between;
align-items: center;
.rank-btns {
display: flex;
gap: 8px;
.rank-btn {
padding: 4px 12px;
border-radius: 4px;
border: 1px solid rgb(230, 231, 232);
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
line-height: 24px;
color: rgb(95, 101, 108);
cursor: pointer;
background-color: #fff;
&.active {
color: rgb(5, 95, 194);
border-color: rgb(5, 95, 194);
background-color: rgba(231, 243, 255, 1);
}
}
}
.field-select {
width: 160px;
:deep(.el-input) {
.el-input__wrapper {
height: 32px;
padding: 0 12px;
box-sizing: border-box;
background-color: #fff;
border-radius: 4px;
.el-input__inner {
height: 32px;
line-height: 32px;
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
color: rgb(95, 101, 108);
&::placeholder {
color: rgb(95, 101, 108);
}
}
}
}
}
}
.main-box {
width: 100%;
height: 577px;
padding: 24px 30px 0px 27px;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 28px;
overflow-y: auto;
.rank-item {
display: flex;
align-items: center;
height: 30px;
flex-shrink: 0;
.rank-num {
width: 24px;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
margin-right: 12px;
text-align: center;
color: #3b414b;
&.rank-1 {
color: #d94b4b;
}
&.rank-2 {
color: #e3935d;
}
&.rank-3 {
color: #ebd348;
}
}
.rank-icon {
width: 30px;
height: 30px;
margin-right: 12px;
}
.rank-name {
width: 180px;
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 700;
color: rgb(59, 65, 75);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.rank-progress-container {
flex: 1;
height: 12px;
margin: 0 20px;
background: transparent;
display: flex;
align-items: center;
.rank-progress-bar {
height: 100%;
border-radius: 6px;
background: linear-gradient(90deg, rgba(55, 98, 240, 0) 0%, rgba(55, 98, 240, 0.8) 100%);
}
}
&:nth-child(1) .rank-progress-bar {
background: linear-gradient(90deg, rgba(217, 75, 75, 0) 0%, rgba(217, 75, 75, 0.8) 100%);
}
&:nth-child(2) .rank-progress-bar {
background: linear-gradient(90deg, rgba(227, 147, 93, 0) 0%, rgba(227, 147, 93, 0.8) 100%);
}
&:nth-child(3) .rank-progress-bar {
background: linear-gradient(90deg, rgba(235, 211, 72, 0) 0%, rgba(235, 211, 72, 0.8) 100%);
}
.rank-count {
width: 60px;
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 700;
color: rgb(59, 65, 75);
text-align: right;
}
}
// 新增表格样式
.table-header {
display: flex;
align-items: center;
padding-bottom: 12px;
// border-bottom: 1px solid #eee;
margin-bottom: 8px;
div {
font-family: "Microsoft YaHei";
font-size: 16px;
color: #5f656c;
font-weight: 400;
}
}
.table-list {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 16px;
}
.table-row {
display: flex;
align-items: center;
height: 40px;
.rank-num {
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
text-align: center;
color: #3b414b;
&.rank-1 {
color: #d94b4b;
}
&.rank-2 {
color: #e3935d;
}
&.rank-3 {
color: #ebd348;
}
}
.flex-align {
display: flex;
align-items: center;
}
.rank-icon {
width: 30px;
height: 30px;
margin-right: 12px;
}
.text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 700;
line-height: 30px;
color: rgb(59, 65, 75);
}
.domain-tags {
display: flex;
gap: 6px;
flex-wrap: wrap;
.mini-tag {
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
font-family: "Microsoft YaHei";
line-height: 1.2;
white-space: nowrap;
// 复用已有的tag颜色逻辑,需要配合 getTagClass
&.tag-item {
// 基础样式
}
&.tag-blue {
color: rgba(9, 88, 217, 1);
background: rgba(230, 244, 255, 1);
border-color: rgba(186, 224, 255, 1);
border: 1px solid rgba(186, 224, 255, 1);
}
&.tag-green {
color: rgba(56, 158, 13, 1);
background: rgba(246, 255, 237, 1);
border-color: rgba(217, 247, 190, 1);
border: 1px solid rgba(217, 247, 190, 1);
}
&.tag-red {
color: rgba(245, 34, 45, 1);
background: rgba(255, 241, 240, 1);
border-color: rgba(255, 163, 158, 1);
border: 1px solid rgba(255, 163, 158, 1);
}
&.tag-orange {
color: rgba(250, 140, 22, 1);
background: rgba(255, 247, 230, 1);
border-color: rgba(255, 213, 145, 1);
border: 1px solid rgba(255, 213, 145, 1);
}
&.tag-purple {
color: rgba(114, 46, 209, 1);
background: rgba(249, 240, 255, 1);
border-color: rgba(211, 173, 247, 1);
border: 1px solid rgba(211, 173, 247, 1);
}
&.tag-cyan {
color: rgba(19, 194, 194, 1);
background: rgba(230, 255, 251, 1);
border-color: rgba(135, 232, 222, 1);
border: 1px solid rgba(135, 232, 222, 1);
}
}
}
}
// 列宽控制
.col-rank {
width: 50px;
text-align: center;
flex-shrink: 0;
}
.col-name {
flex: 1.5;
min-width: 0;
margin-right: 16px;
}
.col-domain {
flex: 1.5;
min-width: 0;
margin-right: 16px;
}
.col-date {
width: 100px;
flex-shrink: 0;
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
line-height: 24px;
color: rgb(59, 65, 75);
text-align: left;
}
.col-member {
width: 80px;
flex-shrink: 0;
font-family: "Microsoft YaHei";
font-size: 16px;
color: rgb(59, 65, 75);
text-align: center;
}
}
}
}
.news-content {
overflow-y: auto;
overflow-x: hidden;
}
.news-item {
/* 全政府-动态 (四全-最新动态) */
width: 737px;
height: 124px;
margin: 0 28px;
/* 自动布局 */
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
padding: 16px 24px 16px 24px;
box-sizing: border-box;
border-bottom: 1px solid rgba(234, 236, 238, 1);
background: transparent;
.news-item-title {
display: flex;
justify-content: space-between;
width: 689px;
.tag {
display: none;
}
.date {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: right;
}
}
.content-title {
cursor: pointer;
width: 689px;
height: 30px;
/* 自动布局 */
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: 12;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 30px;
letter-spacing: 0px;
text-align: left;
}
.content-text {
width: 689px;
height: 30px;
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: left;
color: rgba(59, 65, 75, 1);
margin-top: 8px;
/* 单行省略 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
cursor: pointer;
}
}
.line-time {
margin-top: 16px;
width: 1601px;
height: 700px;
border-radius: 10px;
background-color: rgba(255, 255, 255, 0.65);
box-shadow: 0 0 20px rgba(25, 69, 130, 0.1);
position: relative;
.left-btn {
position: absolute;
z-index: 9999;
left: 0;
top: 345px;
width: 24px;
height: 48px;
img {
width: 100%;
height: 100%;
}
}
.right-btn {
position: absolute;
z-index: 9999;
right: 0;
top: 345px;
width: 24px;
height: 48px;
img {
width: 100%;
height: 100%;
}
}
.bottom-item {
width: 100%;
height: 48px;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 17px;
padding-right: 35px;
box-sizing: border-box;
background: linear-gradient(180deg, rgba(231, 243, 255, 1) 0%, rgba(231, 243, 255, 0) 100%);
.bottom-item-title {
display: flex;
align-items: center;
img {
width: 18px;
height: 18px;
margin-right: 14px;
}
span {
font-family: YouSheBiaoTiHei;
font-size: 24px;
font-weight: 400;
line-height: 24px;
color: rgb(5, 95, 194);
}
}
.field-select {
width: 160px;
:deep(.el-input) {
.el-input__wrapper {
height: 32px;
padding: 0 12px;
box-sizing: border-box;
background-color: #fff;
border-radius: 4px;
.el-input__inner {
height: 32px;
line-height: 32px;
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
color: rgb(95, 101, 108);
&::placeholder {
color: rgb(95, 101, 108);
}
}
}
}
}
}
.line-main {
width: 100%;
height: 652px;
position: relative;
.nav-btn {
position: absolute;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
z-index: 10;
img {
width: 24px;
height: 48px;
}
&.left {
left: 0;
}
&.right {
right: 0;
}
}
.svg-container {
width: 100%;
height: 100%;
overflow: hidden;
display: block;
padding: 0;
box-sizing: border-box;
}
.axis-date-label {
background: rgba(231, 243, 255, 1);
padding: 4px 2px;
border-radius: 8px;
text-align: center;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: auto;
min-width: 60px;
.year,
.month {
font-family: "Microsoft YaHei";
font-size: 18px;
font-weight: 700;
line-height: 24px;
color: rgb(5, 95, 194);
margin-bottom: 0;
}
}
.timeline-content-item {
width: 100%;
height: 160px;
display: flex;
flex-direction: column;
gap: 8px;
.item-tags {
width: 355px;
height: 30px;
display: flex;
gap: 8px;
overflow: auto;
text-overflow: ellipsis;
white-space: nowrap;
.tag-item {
padding: 2px 8px;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
border: 1px solid transparent; // Ensure border width is set for all
&.tag-blue {
color: rgba(9, 88, 217, 1);
background: rgba(230, 244, 255, 1);
border-color: rgba(186, 224, 255, 1);
}
&.tag-green {
color: rgba(56, 158, 13, 1);
background: rgba(246, 255, 237, 1);
border-color: rgba(217, 247, 190, 1);
}
&.tag-red {
color: rgba(245, 34, 45, 1);
background: rgba(255, 241, 240, 1);
border-color: rgba(255, 163, 158, 1);
}
&.tag-orange {
color: rgba(250, 140, 22, 1);
background: rgba(255, 247, 230, 1);
border-color: rgba(255, 213, 145, 1);
}
&.tag-purple {
color: rgba(114, 46, 209, 1);
background: rgba(249, 240, 255, 1);
border-color: rgba(211, 173, 247, 1);
}
&.tag-cyan {
color: rgba(19, 194, 194, 1);
background: rgba(230, 255, 251, 1);
border-color: rgba(135, 232, 222, 1);
}
}
}
.item-title {
font-size: 18px;
font-weight: 700;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
width: 100%;
}
.item-desc {
font-size: 16px;
font-weight: 400;
color: rgb(95, 101, 108);
line-height: 24px;
/* max-height: 48px; 可选:明确限制高度 */
font-family: "Microsoft YaHei";
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
white-space: normal;
}
.el-tooltip__trigger {
height: 48px;
}
.item-footer {
font-size: 16px;
font-weight: 400;
color: rgb(95, 101, 108);
line-height: 24px;
font-family: "Microsoft YaHei";
margin-top: 4px;
}
}
}
}
</style>
<style>
.common-prompt-popper.el-popper {
padding: 8px 16px !important;
border-radius: 10px !important;
background-color: rgb(59, 65, 75) !important;
font-size: 16px !important;
font-weight: 400 !important;
font-family: "Microsoft YaHei" !important;
line-height: 30px !important;
color: #fff !important;
border: none !important;
}
.common-prompt-popper.el-popper .el-popper__arrow::before {
background-color: rgb(59, 65, 75) !important;
border-color: rgb(59, 65, 75) !important;
}
</style>
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<div class="content-wrapper"> <div class="content-wrapper">
<div class="btn-wrapper" @mouseenter="stopAutoPlay" @mouseleave="startAutoPlay(true)"> <div class="btn-wrapper" @mouseenter="stopAutoPlay" @mouseleave="startAutoPlay(true)">
<div class="cards-mask"> <div class="cards-mask">
<div class="btn-box" :style="{ transform: `translateX(-${currentIndex * (307 + 16)}px)` }"> <div class="btn-box" :style="{ transform: `translateX(-${currentIndex * (cardWidth + cardGap)}px)` }">
<div class="btn-item-outer" v-for="(item, indexx) in buttonsData" :key="indexx"> <div class="btn-item-outer" v-for="(item, indexx) in buttonsData" :key="indexx">
<div <div
v-for="(value, index) in item" v-for="(value, index) in item"
...@@ -73,15 +73,20 @@ ...@@ -73,15 +73,20 @@
<img src="./icon/icon-2.png" alt="" style="width: 22px; height: 18px" /> <img src="./icon/icon-2.png" alt="" style="width: 22px; height: 18px" />
<span>美对华领域打压遏制最新动态</span> <span>美对华领域打压遏制最新动态</span>
</div> </div>
<div class="title-right-select">
<el-select v-model="selectedFieldForLatest" placeholder="全部领域" class="field-select">
<el-option v-for="item in fieldOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div> </div>
<div class="news-content"> <div class="news-content">
<div v-for="value,idx in newsList" :key="idx" class="news-item"> <div v-for="(value, idx) in paginatedNewsList" :key="idx" class="news-item">
<div class="news-item-title"> <div class="news-item-title">
<div class="tag-container"> <div class="tag-container">
<!-- <div v-for="tag in value.tags" :key="tag" :class="getTagClass(tag)"> <!-- <div v-for="tag in value.tags" :key="tag" :class="getTagClass(tag)">
{{ tag }} {{ tag }}
</div> --> </div> -->
<AreaTag v-for="tag,index in value.tags" :key="index" :tagName="tag"></AreaTag> <AreaTag v-for="(tag, index) in value.tags" :key="index" :tagName="tag"></AreaTag>
</div> </div>
<div class="date"> <div class="date">
...@@ -104,6 +109,16 @@ ...@@ -104,6 +109,16 @@
</el-tooltip> </el-tooltip>
</div> </div>
</div> </div>
<div class="news-pagination">
<el-pagination
layout="prev, pager, next"
:total="newsList.length"
:page-size="newsPageSize"
v-model:current-page="newsCurrentPage"
size="small"
@current-change="handleNewsPageChange"
/>
</div>
</div> </div>
<div class="empty-section"> <div class="empty-section">
<div class="bottom-item"> <div class="bottom-item">
...@@ -213,7 +228,7 @@ ...@@ -213,7 +228,7 @@
</marker> </marker>
</defs> </defs>
<path :d="snakePath" fill="none" stroke="#e8f2ff" stroke-width="2" /> <path :d="snakePath" fill="none" stroke="rgba(185, 220, 255, 1)" stroke-width="5" />
<g v-for="(label, index) in axisDates" :key="'date-' + index"> <g v-for="(label, index) in axisDates" :key="'date-' + index">
<foreignObject :x="label.x" :y="label.y" width="80" height="60" style="overflow: visible"> <foreignObject :x="label.x" :y="label.y" width="80" height="60" style="overflow: visible">
...@@ -239,7 +254,7 @@ ...@@ -239,7 +254,7 @@
<div class="timeline-content-item"> <div class="timeline-content-item">
<div class="item-tags"> <div class="item-tags">
<!-- <span v-for="tag in node.tags" :key="tag" :class="getTagClass(tag)">{{ tag }}</span> --> <!-- <span v-for="tag in node.tags" :key="tag" :class="getTagClass(tag)">{{ tag }}</span> -->
<AreaTag v-for="tag,idx in node.tags" :key="idx" :tagName="tag"></AreaTag> <AreaTag v-for="(tag, idx) in node.tags" :key="idx" :tagName="tag"></AreaTag>
</div> </div>
<div class="item-title"> <div class="item-title">
<CommonPrompt :content="node.title" /> <CommonPrompt :content="node.title" />
...@@ -261,10 +276,10 @@ ...@@ -261,10 +276,10 @@
</div> </div>
</div> </div>
<div class="left-btn" @click="handleLeft"> <div class="left-btn" @click="handleLeft">
<img src="@/assets/icons/card-btn-left.png" alt=""> <img src="@/assets/icons/card-btn-left.png" alt="" />
</div> </div>
<div class="right-btn" @click="handleRight"> <div class="right-btn" @click="handleRight">
<img src="@/assets/icons/card-btn-right.png" alt=""> <img src="@/assets/icons/card-btn-right.png" alt="" />
</div> </div>
</div> </div>
</div> </div>
...@@ -366,11 +381,25 @@ watch( ...@@ -366,11 +381,25 @@ watch(
const rankType = ref("institution"); const rankType = ref("institution");
const rankLoading = ref(false); const rankLoading = ref(false);
const selectedField = ref(""); const selectedField = ref("");
const selectedFieldForLatest = ref("");
const selectedFieldTimeline = ref(""); const selectedFieldTimeline = ref("");
const timelineContainerWidth = 1700; const timelineContainerWidth = 1700;
// 美政府部门打压遏制最新动态 // 美政府部门打压遏制最新动态
const newsList = ref([]); const newsList = ref([]);
const newsCurrentPage = ref(1);
const newsPageSize = ref(5); // 每页显示 5 条
// 分页后的新闻列表
const paginatedNewsList = computed(() => {
const start = (newsCurrentPage.value - 1) * newsPageSize.value;
const end = start + newsPageSize.value;
return newsList.value.slice(start, end);
});
const handleNewsPageChange = page => {
newsCurrentPage.value = page;
};
const getUSGovernmentLatestDynamicData = async () => { const getUSGovernmentLatestDynamicData = async () => {
try { try {
...@@ -417,18 +446,26 @@ const processTimelineData = rawData => { ...@@ -417,18 +446,26 @@ const processTimelineData = rawData => {
} }
return rawData.map(item => { return rawData.map(item => {
// 将日期格式转换为 "YYYY年 M月" 格式
const eventDate = item.eventDate ? new Date(item.eventDate) : null; const eventDate = item.eventDate ? new Date(item.eventDate) : null;
const formattedDate = eventDate ? `${eventDate.getFullYear()}${eventDate.getMonth() + 1}月` : ""; const formattedDate = eventDate ? `${eventDate.getFullYear()}${eventDate.getMonth() + 1}月` : "";
// 提取领域标签 let tags = [];
const tags = [];
if (item.eventDomainList && Array.isArray(item.eventDomainList)) { if (item.eventDomainList && Array.isArray(item.eventDomainList)) {
item.eventDomainList.forEach(domain => { if (!selectedFieldTimeline.value) {
if (domain.domainName) { tags = item.eventDomainList.slice(0, 3).map(domain => domain.domainName || "");
tags.push(domain.domainName); } else {
const matchedIndex = item.eventDomainList.findIndex(domain => domain.domainId === selectedFieldTimeline.value);
if (matchedIndex !== -1) {
const matchedItem = item.eventDomainList[matchedIndex];
const remainingItems = item.eventDomainList.filter((_, idx) => idx !== matchedIndex);
const sortedList = [matchedItem, ...remainingItems];
tags = sortedList.slice(0, 3).map(domain => domain.domainName || "");
} else {
tags = item.eventDomainList.slice(0, 3).map(domain => domain.domainName || "");
} }
}); }
} }
return { return {
...@@ -437,7 +474,6 @@ const processTimelineData = rawData => { ...@@ -437,7 +474,6 @@ const processTimelineData = rawData => {
title: item.eventName || "", title: item.eventName || "",
content: item.eventDesc || "", content: item.eventDesc || "",
info: `${item.eventDate || ""} · ${item.eventType || ""}`, info: `${item.eventDate || ""} · ${item.eventType || ""}`,
// 保留原始数据字段
eventId: item.eventId, eventId: item.eventId,
eventOrg: item.eventOrg, eventOrg: item.eventOrg,
eventType: item.eventType, eventType: item.eventType,
...@@ -454,15 +490,35 @@ const handleGetDomainContainmentTimeline = async () => { ...@@ -454,15 +490,35 @@ const handleGetDomainContainmentTimeline = async () => {
if (selectedFieldTimeline.value) { if (selectedFieldTimeline.value) {
params.domain = selectedFieldTimeline.value; params.domain = selectedFieldTimeline.value;
} }
console.log("美对我领域打压遏制时间线", params);
const res = await getDomainContainmentTimeline(params); const res = await getDomainContainmentTimeline(params);
console.log("美对我领域打压遏制时间线", res); console.log("美对我领域打压遏制时间线", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
// 处理返回的数据结构 // 处理返回的数据结构
const processedData = processTimelineData(res.data); const processedData = processTimelineData(res.data);
// processedData.forEach(item => {
// if (item.eventDomainList && Array.isArray(item.eventDomainList)) {
// if (!selectedFieldTimeline.value) {
// item.eventDomainList = item.eventDomainList.slice(0, 3);
// } else {
// const matchedIndex = item.eventDomainList.findIndex(
// domain => domain.domainId === selectedFieldTimeline.value
// );
// if (matchedIndex !== -1) {
// const matchedItem = item.eventDomainList[matchedIndex];
// const remainingItems = item.eventDomainList.filter((_, idx) => idx !== matchedIndex);
// item.eventDomainList = [matchedItem, ...remainingItems.slice(0, 2)];
// } else {
// item.eventDomainList = item.eventDomainList.slice(0, 3);
// }
// }
// }
// });
timelineList.value = processedData; timelineList.value = processedData;
console.log('timelineList', timelineList.value); console.log("timelineList", timelineList.value);
} }
} catch (error) { } catch (error) {
console.error("获取美对我领域打压遏制时间线失败:", error); console.error("获取美对我领域打压遏制时间线失败:", error);
...@@ -477,10 +533,9 @@ const rowHeight = 230; ...@@ -477,10 +533,9 @@ const rowHeight = 230;
const startX = 250; const startX = 250;
const startY = 45; const startY = 45;
const timeLineActiveIndex = ref(0) const timeLineActiveIndex = ref(0);
const axisDates = computed(() => { const axisDates = computed(() => {
const dates = []; const dates = [];
if (timelineList.value.length > 0) { if (timelineList.value.length > 0) {
dates.push({ dates.push({
...@@ -500,39 +555,37 @@ const axisDates = computed(() => { ...@@ -500,39 +555,37 @@ const axisDates = computed(() => {
y: lastNode.y - 25 y: lastNode.y - 25
}); });
} }
return dates return dates;
}); });
const handleLeft = () => { const handleLeft = () => {
if(timeLineActiveIndex.value === 0) { if (timeLineActiveIndex.value === 0) {
ElMessage.warning('当前已经是第一组数据!') ElMessage.warning("当前已经是第一组数据!");
} else { } else {
timeLineActiveIndex.value -- timeLineActiveIndex.value--;
} }
} };
const handleRight = () => { const handleRight = () => {
if(timeLineActiveIndex.value === (timelineList.value.length % 9 )) { if (timeLineActiveIndex.value === timelineList.value.length % 9) {
ElMessage.warning('当前已经是最后一组数据!') ElMessage.warning("当前已经是最后一组数据!");
} else { } else {
timeLineActiveIndex.value ++ timeLineActiveIndex.value++;
} }
console.log('axisDates',axisDates.value); console.log("axisDates", axisDates.value);
console.log('timelineNodes',timelineNodes.value); console.log("timelineNodes", timelineNodes.value);
};
}
const timelineNodes = computed(() => { const timelineNodes = computed(() => {
// 计算起始索引:activeIndex * 9 // 计算起始索引:activeIndex * 9
const startIndex = timeLineActiveIndex.value * 9; const startIndex = timeLineActiveIndex.value * 9;
// 计算结束索引:起始索引 + 9(因为slice的第二个参数是结束索引,不包含)
const endIndex = timeLineActiveIndex.value + 9;
const showTimeLineList = timelineList.value.slice(startIndex, endIndex); // 计算结束索引:起始索引 + 9(因为slice的第二个参数是结束索引,不包含)
const endIndex = timeLineActiveIndex.value + 9;
const showTimeLineList = timelineList.value.slice(startIndex, endIndex);
console.log("时间线相关数据 showTimeLineList =>", showTimeLineList);
console.log("时间线相关数据 timelineList =>", timelineList.value);
return showTimeLineList.map((item, index) => { return showTimeLineList.map((item, index) => {
const row = Math.floor(index / maxPerRow); const row = Math.floor(index / maxPerRow);
const col = index % maxPerRow; const col = index % maxPerRow;
...@@ -560,7 +613,7 @@ const timelineNodes = computed(() => { ...@@ -560,7 +613,7 @@ const timelineNodes = computed(() => {
contentWidth: 320, contentWidth: 320,
contentHeight: 180 contentHeight: 180
}; };
}) });
}); });
const getColorName = tag => { const getColorName = tag => {
...@@ -1012,6 +1065,9 @@ watch(selectedField, handleGetDomainContainmentRanking); ...@@ -1012,6 +1065,9 @@ watch(selectedField, handleGetDomainContainmentRanking);
const currentIndex = ref(0); const currentIndex = ref(0);
let autoTimer = null; let autoTimer = null;
const visibleCount = 7;
const cardWidth = 215; // 卡片宽度
const cardGap = 16; // 卡片间距
const startAutoPlay = isContiune => { const startAutoPlay = isContiune => {
if (!isContiune) { if (!isContiune) {
...@@ -1019,7 +1075,7 @@ const startAutoPlay = isContiune => { ...@@ -1019,7 +1075,7 @@ const startAutoPlay = isContiune => {
} }
stopAutoPlay(); stopAutoPlay();
if (buttonsData.value.length > 5) { if (buttonsData.value.length > visibleCount) {
autoTimer = setInterval(() => { autoTimer = setInterval(() => {
next(); next();
}, 3000); }, 3000);
...@@ -1035,7 +1091,7 @@ const stopAutoPlay = () => { ...@@ -1035,7 +1091,7 @@ const stopAutoPlay = () => {
const next = () => { const next = () => {
let arr = [...buttonsData.value]; let arr = [...buttonsData.value];
if (currentIndex.value < buttonsData.value.length - 5) { if (currentIndex.value < buttonsData.value.length - visibleCount) {
currentIndex.value++; currentIndex.value++;
} else { } else {
// currentIndex.value = 0; // currentIndex.value = 0;
...@@ -1048,10 +1104,32 @@ const prev = () => { ...@@ -1048,10 +1104,32 @@ const prev = () => {
if (currentIndex.value > 0) { if (currentIndex.value > 0) {
currentIndex.value--; currentIndex.value--;
} else { } else {
currentIndex.value = Math.max(0, buttonsData.value.length - 5); currentIndex.value = Math.max(0, buttonsData.value.length - visibleCount);
} }
}; };
// const next = () => {
// const maxIndex = buttonsData.value.length - visibleCount;
// if (currentIndex.value < maxIndex) {
// currentIndex.value++;
// } else {
// // 滚动到末尾时,重置到开头并复制数组实现无缝滚动
// const arr = [...buttonsData.value];
// buttonsData.value = [...buttonsData.value, ...arr];
// currentIndex.value = buttonsData.value.length - visibleCount - arr.length;
// }
// };
// const prev = () => {
// if (currentIndex.value > 0) {
// currentIndex.value--;
// } else {
// // 滚动到开头时,跳转到末尾
// currentIndex.value = buttonsData.value.length - visibleCount;
// }
// };
const handleTimeLineNode = node => { const handleTimeLineNode = node => {
// console.log("timeLineNode", node); // console.log("timeLineNode", node);
if (node.eventType === "科技法案") { if (node.eventType === "科技法案") {
...@@ -1144,7 +1222,8 @@ watch(activeDate, () => { ...@@ -1144,7 +1222,8 @@ watch(activeDate, () => {
.btn-item { .btn-item {
/* 全领域-总统计 */ /* 全领域-总统计 */
width: 307px; // width: 307px;
width: 215px;
height: 80px; height: 80px;
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
...@@ -1156,6 +1235,7 @@ watch(activeDate, () => { ...@@ -1156,6 +1235,7 @@ watch(activeDate, () => {
.btn-left-text { .btn-left-text {
width: fit-content; width: fit-content;
min-width: 60px; min-width: 60px;
max-width: 97px;
height: 60px; height: 60px;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
...@@ -1163,19 +1243,19 @@ watch(activeDate, () => { ...@@ -1163,19 +1243,19 @@ watch(activeDate, () => {
font-family: YouSheBiaoTiHei; font-family: YouSheBiaoTiHei;
font-size: 24px; font-size: 24px;
font-weight: 400; font-weight: 400;
line-height: 60px; line-height: 30px;
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
color: rgba(255, 255, 255, 1); color: rgba(255, 255, 255, 1);
//换行 //换行
white-space: nowrap; white-space: wrap;
display: flex; display: flex;
align-items: center; align-items: center;
font-style: italic; font-style: italic;
} }
.btn-right { .btn-right {
font-family: YouSheBiaoTiHei; font-family: YouSheBiaoTiHei;
font-size: 24px; font-size: 30px;
font-weight: 400; font-weight: 400;
color: rgba(255, 255, 255, 1); color: rgba(255, 255, 255, 1);
font-style: italic; font-style: italic;
...@@ -1204,7 +1284,7 @@ watch(activeDate, () => { ...@@ -1204,7 +1284,7 @@ watch(activeDate, () => {
.charts-title { .charts-title {
width: 100%; width: 100%;
height: 48px; height: 54px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
...@@ -1276,6 +1356,10 @@ watch(activeDate, () => { ...@@ -1276,6 +1356,10 @@ watch(activeDate, () => {
} }
} }
} }
.title-right-select {
width: 150px;
}
} }
.charts-content { .charts-content {
...@@ -1298,11 +1382,71 @@ watch(activeDate, () => { ...@@ -1298,11 +1382,71 @@ watch(activeDate, () => {
.news-section { .news-section {
width: 792px; width: 792px;
height: 700px; height: 700px;
display: flex;
flex-direction: column;
justify-content: space-between;
.news-title { .news-title {
padding: 8px; padding: 8px;
} }
.news-pagination {
display: flex;
justify-content: center;
align-items: center;
padding: 16px 0;
:deep(.el-pagination) {
&.is-background .el-pager li {
background-color: #fff;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 14px;
min-width: 32px;
height: 32px;
line-height: 32px;
margin: 0 4px;
&.is-active {
background-color: #055fc2;
color: #fff;
border-color: #055fc2;
}
&:hover {
background-color: #f5f7fa;
}
}
.btn-prev,
.btn-next {
background-color: rgba(231, 243, 255, 1);
// border: 1px solid #dcdfe6;
border: none;
border-radius: 4px;
font-size: 14px;
// min-width: 32px;
// height: 32px;
line-height: 32px;
margin-right: 4px;
.el-icon {
font-size: 16px;
color: #055fc2;
font-weight: 800;
}
&:hover:not(.is-disabled) {
background-color: #f5f7fa;
}
&.is-disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
}
}
.tag-container { .tag-container {
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -1692,11 +1836,12 @@ watch(activeDate, () => { ...@@ -1692,11 +1836,12 @@ watch(activeDate, () => {
.news-content { .news-content {
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
margin-bottom: auto;
} }
.news-item { .news-item {
/* 全政府-动态 (四全-最新动态) */ /* 全政府-动态 (四全-最新动态) */
width: 737px; width: 737px;
height: 124px; height: 116px;
margin: 0 28px; margin: 0 28px;
/* 自动布局 */ /* 自动布局 */
display: flex; display: flex;
...@@ -1764,7 +1909,7 @@ watch(activeDate, () => { ...@@ -1764,7 +1909,7 @@ watch(activeDate, () => {
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
margin-top: 8px; // margin-top: 8px;
/* 单行省略 */ /* 单行省略 */
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
...@@ -1783,27 +1928,27 @@ watch(activeDate, () => { ...@@ -1783,27 +1928,27 @@ watch(activeDate, () => {
box-shadow: 0 0 20px rgba(25, 69, 130, 0.1); box-shadow: 0 0 20px rgba(25, 69, 130, 0.1);
position: relative; position: relative;
.left-btn{ .left-btn {
position: absolute; position: absolute;
z-index: 9999; z-index: 9999;
left: 0; left: 0;
top: 345px; top: 345px;
width: 24px; width: 24px;
height: 48px; height: 48px;
img{ img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.right-btn{ .right-btn {
position: absolute; position: absolute;
z-index: 9999; z-index: 9999;
right: 0; right: 0;
top: 345px; top: 345px;
width: 24px; width: 24px;
height: 48px; height: 48px;
img{ img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
......
{
"code": 200,
"message": "操作成功",
"success": true,
"data": [
{
"eventType": "行政令",
"eventName": "为了美国和委内瑞拉人民的利益保障委内瑞拉的石油收入",
"eventDesc": null,
"eventId": "248914",
"eventDomainList": [
{
"domainId": "1",
"domainName": "人工智能"
},
{
"domainId": "3",
"domainName": "新一代通信网络"
}
],
"eventOrgName": null,
"eventDate": "2026-01-10"
},
{
"eventType": "行政令",
"eventName": null,
"eventDesc": null,
"eventId": "248863",
"eventDomainList": [
{
"domainId": "8",
"domainName": "先进制造"
},
{
"domainId": "9",
"domainName": "新材料"
}
],
"eventOrgName": null,
"eventDate": "2026-01-09"
},
{
"eventType": "行政令",
"eventName": "使美国退出与美国利益相悖的国际组织、公约、条约",
"eventDesc": null,
"eventId": "249004",
"eventDomainList": [
{
"domainId": "2",
"domainName": "生物科技"
},
{
"domainId": "4",
"domainName": "量子科技"
},
{
"domainId": "5",
"domainName": "新能源"
},
{
"domainId": "3",
"domainName": "新一代通信网络"
}
],
"eventOrgName": null,
"eventDate": "2026-01-08"
},
{
"eventType": "行政令",
"eventName": "在国防合同中优先考虑作战人员",
"eventDesc": null,
"eventId": "248915",
"eventDomainList": [
{
"domainId": "99",
"domainName": "其他"
},
{
"domainId": "8",
"domainName": "先进制造"
},
{
"domainId": "10",
"domainName": "航空航天"
}
],
"eventOrgName": null,
"eventDate": "2026-01-08"
},
{
"eventType": "行政令",
"eventName": "大西洋高度洄游物种;商业性大西洋黑鼻鲨和娱乐性大西洋鲨鱼渔业管理措施的修订",
"eventDesc": "NMFS提议对商业和休闲大西洋鲨鱼渔业进行几项改革。具体来说,NMFS正在考虑取消大西洋地区黑鼻鲨管理边界,修改商业保留限额...",
"eventId": "163447",
"eventDomainList": [
{
"domainId": "14",
"domainName": "核"
},
{
"domainId": "7",
"domainName": "海洋"
}
],
"eventOrgName": null,
"eventDate": "2026-01-05"
},
{
"eventType": "行政令",
"eventName": "关于Hiefo Corporation收购Emcore Corporation部分资产",
"eventDesc": null,
"eventId": "248916",
"eventDomainList": [
{
"domainId": "3",
"domainName": "新一代通信网络"
},
{
"domainId": "8",
"domainName": "先进制造"
},
{
"domainId": "6",
"domainName": "集成电路"
}
],
"eventOrgName": null,
"eventDate": "2026-01-03"
},
{
"eventType": "行政令",
"eventName": "汽车贷款利息扣除",
"eventDesc": "本文件包含关于某些纳税人扣除高达10,000美元的合格乘用车贷款利息的拟议法规。本文件还包含关于新信息报告的拟议法规...",
"eventId": "163443",
"eventDomainList": [
{
"domainId": "13",
"domainName": "太空"
},
{
"domainId": "14",
"domainName": "核"
}
],
"eventOrgName": null,
"eventDate": "2026-01-02"
},
{
"eventType": "行政令",
"eventName": "品牌处方药费用法规的法定更新",
"eventDesc": "本文件建议修订有关对从事制造或进口某些品牌处方药业务的相关实体征收年费的法规。为了应对覆盖缺口折扣的替代...",
"eventId": "163444",
"eventDomainList": [
{
"domainId": "2",
"domainName": "生物科技"
},
{
"domainId": "3",
"domainName": "新一代通信网络"
}
],
"eventOrgName": null,
"eventDate": "2026-01-02"
},
{
"eventType": "337",
"eventName": "外国制造的半导体器件及其下游产品和组件",
"eventDesc": "美国ITC发布对外国制造的半导体器件及其下游产品和组件的337部分终裁",
"eventId": "111",
"eventDomainList": [
{
"domainId": "1",
"domainName": "人工智能"
},
{
"domainId": "5",
"domainName": "新能源"
}
],
"eventOrgName": "美国商务部",
"eventDate": "2026-01-01"
},
{
"eventType": "行政令",
"eventName": "美国东北部的渔业;大西洋冲浪蛤和海洋圆蛤渔业管理计划修正案21",
"eventDesc": "NMFS宣布,中大西洋渔业管理委员会已提交大西洋冲浪蛤和海洋圆蛤渔业管理计划第21号修正案,供商务部长审查和批准。我们正在征求意见...",
"eventId": "163426",
"eventDomainList": [
{
"domainId": "7",
"domainName": "海洋"
},
{
"domainId": "3",
"domainName": "新一代通信网络"
}
],
"eventOrgName": null,
"eventDate": "2025-12-29"
}
]
}
\ No newline at end of file
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
<script setup> <script setup>
import { onMounted, ref, computed } from "vue"; import { onMounted, ref, computed } from "vue";
import right from "./assets/right.png"; import right from "./assets/right-white.png";
import background from "./assets/background.png"; import background from "./assets/background.png";
// 组件引入 // 组件引入
...@@ -104,12 +104,13 @@ const handleNavClick = name => { ...@@ -104,12 +104,13 @@ const handleNavClick = name => {
} }
&.active { &.active {
background-color: rgba(246, 250, 255, 1); background-color: rgba(5, 95, 194, 1);
border: 1px solid rgba(174, 214, 255, 1); border: 1px solid rgba(174, 214, 255, 1);
border-radius: 10px; border-radius: 10px;
span { span {
color: rgb(5, 95, 194); // color: rgb(5, 95, 194);
color: #fff;
} }
} }
} }
......
...@@ -21,8 +21,13 @@ ...@@ -21,8 +21,13 @@
<!-- 美对华“四全”打压 --> <!-- 美对华“四全”打压 -->
<div id="us-pressure" class="us-pressure-section"> <div id="us-pressure" class="us-pressure-section">
<div class="data-select"> <div class="data-select">
<div v-for="item in dateList" :key="item.type" class="date-item" :class="{ active: activeDate === item.type }" <div
@click="handleDateClick(item.type)"> v-for="item in dateList"
:key="item.type"
class="date-item"
:class="{ active: activeDate === item.type }"
@click="handleDateClick(item.type)"
>
<!-- <img :src="activeDate === item.type ? item.activeIcon : item.icon" alt="" /> --> <!-- <img :src="activeDate === item.type ? item.activeIcon : item.icon" alt="" /> -->
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
</div> </div>
...@@ -133,8 +138,6 @@ provide("activeDate", activeDate); ...@@ -133,8 +138,6 @@ provide("activeDate", activeDate);
const handleDateClick = type => { const handleDateClick = type => {
activeDate.value = type; activeDate.value = type;
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -250,7 +253,8 @@ const handleDateClick = type => { ...@@ -250,7 +253,8 @@ const handleDateClick = type => {
.home-top-bg { .home-top-bg {
background: background:
url("./assets/images/background.png"), linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%); url("./assets/images/background.png"),
linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%);
background-size: 100% 100%; background-size: 100% 100%;
position: absolute; position: absolute;
width: 100%; width: 100%;
...@@ -266,7 +270,7 @@ const handleDateClick = type => { ...@@ -266,7 +270,7 @@ const handleDateClick = type => {
.data-select { .data-select {
width: 108px; width: 108px;
height: 192px; height: 192px;
position: fixed; position: absolute;
top: 149px; top: 149px;
left: 0; left: 0;
background-color: rgba(255, 255, 255, 0.65); background-color: rgba(255, 255, 255, 0.65);
......
<template>
<div class="overview-card">
<div class="overview-card-header">
<div class="overview-card-header-left">
<div class="overview-card-header-icon">
<img :src="icon" alt="" />
</div>
<div class="overview-card-header-title">{{ title }}</div>
</div>
<div v-if="$slots.right" class="overview-card-header-right">
<slot name="right" />
</div>
</div>
<div class="overview-card-main">
<slot />
</div>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
default: ""
},
icon: {
type: String,
default: ""
}
});
</script>
<style lang="scss" scoped>
.overview-card {
height: 450px;
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);
overflow: hidden;
.overview-card-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 27px 0 22px;
.overview-card-header-left {
display: flex;
align-items: center;
.overview-card-header-icon {
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.overview-card-header-title {
margin-left: 19px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.overview-card-header-right {
display: flex;
align-items: center;
justify-content: flex-end;
height: 100%;
}
}
.overview-card-main {
height: calc(100% - 53px);
}
}
</style>
<template>
<div class="resource-library-section">
<div class="home-content-footer-header">
<div class="btn-box">
<div class="btn" :class="{ btnActive: activeTabName === cate.name, disabled: index > 2 }"
v-for="(cate, index) in tabList" :key="index" @click="index <= 2 && handleClickTab(cate)">
{{ cate.name }}
</div>
</div>
</div>
<div class="home-content-footer-main">
<div class="left" v-if="['国会法案', '国会议员', '议员合作关系'].includes(activeTabName)">
<div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">科技领域</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" v-model="activeAreaList" @change="handleAreaChange">
<el-checkbox class="filter-checkbox" label="全部领域"> 全部领域 </el-checkbox>
<el-checkbox v-for="(area, index) in cateKuList" :key="index" :label="area.id" class="filter-checkbox">
{{ area.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="select-box" v-if="activeTabName !== '议员合作关系'">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">党派</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" v-model="activeDpList" @change="handleDpChange">
<el-checkbox v-for="(dp, index) in dpList" :key="index" :label="dp.id" class="filter-checkbox">
{{ dp.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="select-box" v-if="activeTabName !== '议员合作关系'">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">议院</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" v-model="activeYyList" @change="handleYyChange">
<el-checkbox v-for="(yy, index) in yyList" :key="index" :label="yy.id" class="filter-checkbox">
{{ yy.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="select-box" v-if="activeTabName === '国会法案'">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">发布时间</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" v-model="activePubTime" @change="handlePubTimeChange">
<el-checkbox v-for="(time, index) in pubTime" :key="index" :label="time.id" class="filter-checkbox">
{{ time.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="select-box" v-if="activeTabName === '议员合作关系'">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">合作关系</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" v-model="activeCoopList" @change="handleCoopChange">
<el-checkbox v-for="(coop, index) in coopList" :key="index" :label="coop.id" class="filter-checkbox">
{{ coop.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
<div class="right">
<div class="right-header">
<div class="right-header-box">
<el-select v-model="footerSelect1" placeholder="选择委员会" style="width: 240px" @change="handleFooterSelect1Change">
<el-option v-for="item in postOrgList" :key="item.departmentId" :label="item.departmentName" :value="item.departmentId" />
</el-select>
</div>
<template v-if="activeTabName === '国会法案'">
<div class="right-header-box">
<el-select v-model="footerSelect2" placeholder="选择提出议员" style="width: 240px" @change="handleFooterSelect2Change">
<el-option v-for="item in postMemberList" :key="item.memberId" :label="item.memberName" :value="item.memberId" />
</el-select>
</div>
<div class="right-header-box right-header-sort" style="margin-left: auto">
<el-checkbox v-model="isInvolveCn" true-label="Y" false-label="N" class="involve-checkbox" @change="handleInvolveCnChange">只看涉华法案</el-checkbox>
<el-select v-model="releaseTime" placeholder="选择排序方式" style="width: 120px" @change="handlePxChange">
<template #prefix>
<div style="display: flex; align-items: center; height: 100%">
<img :src="desc" style="width: 14px; height: 14px" />
</div>
</template>
<el-option v-for="item in releaseTimeList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</template>
</div>
<div class="right-main" v-loading="loading">
<template v-if="activeTabName === '国会法案'">
<div class="right-main-box" v-for="(item, index) in bills" :key="index">
<div v-if="item.riskSignal" class="risk-tag" :class="getRiskTagClass(item.riskSignal)">{{ item.riskSignal }}</div>
<div class="header">
<div class="title" @click="onClickToDetail(item)" :title="item.name">{{ item.name }}</div>
<div class="en-title" :title="item.eName">{{ item.eName }}</div>
</div>
<div class="main">
<div class="item"><div class="item-left">提案人:</div><div class="item-right">{{ item.tcr }}</div></div>
<div class="item"><div class="item-left">委员会:</div><div class="item-right">{{ item.wyh }}</div></div>
<div class="item"><div class="item-left">相关领域:</div><div class="item-right1"><div class="tag" v-for="(val, idx) in item.areaList" :key="idx">{{ val }}</div></div></div>
<div class="item"><div class="item-left">最新动议:</div><div class="item-right"><CommonPrompt :content="item.zxdy" /></div></div>
<div class="item">
<div class="item-left">法案进展:</div>
<div class="item-right2">
<div class="tag" v-for="(val, idx) in [...item.progress].reverse()" :key="idx" :style="{ zIndex: item.progress.length - idx }">{{ val }}</div>
</div>
</div>
</div>
</div>
<div class="right-footer">
<div class="footer-left">{{ `共 ${total} 项` }}</div>
<div class="footer-right">
<el-pagination @current-change="handleCurrentChange" :page-size="pageSize" :current-page="currentPage" background layout="prev, pager, next" :total="total" />
</div>
</div>
</template>
<div v-else-if="activeTabName === '国会议员'">
<div class="member-grid">
<div class="member-card" v-for="item in memberList" :key="item.id">
<div class="member-card-top">
<div class="member-avatar-wrap">
<img class="member-avatar" :src="item.avatar || defaultAvatar" alt="avatar" />
<div class="member-icon-row">
<img v-if="item.partyIcon" class="member-mini-icon-img" :src="item.partyIcon" alt="party" />
<img v-if="item.chamberIcon" class="member-mini-icon-img" :src="item.chamberIcon" alt="chamber" />
</div>
</div>
<div class="member-main">
<div class="member-title-row">
<div class="member-name">{{ item.name || '-' }}</div>
<div class="member-link">{{ item.billCountText || '0项提案 >' }}</div>
</div>
<div class="member-meta">{{ item.partyText || '-' }} · {{ item.chamberText || '-' }} · {{ item.termText || '-' }}</div>
<div class="member-committee">{{ item.committeeText || '-' }}</div>
<div class="member-tags">
<div class="member-tag" v-for="(tag, idx) in item.focusTags" :key="idx">{{ tag }}</div>
</div>
</div>
</div>
<div class="member-card-bottom" @click="handleClickLatestProposal(item)">
<div class="member-latest">最新提案:{{ item.latestProposal || '-' }}</div>
<div class="member-arrow">></div>
</div>
</div>
</div>
<div class="right-footer">
<div class="footer-left">{{ `共 ${memberTotal} 项` }}</div>
<div class="footer-right">
<el-pagination @current-change="handleMemberCurrentChange" :page-size="memberPageSize" :current-page="memberCurrentPage" background layout="prev, pager, next" :total="memberTotal" />
</div>
</div>
</div>
<div v-else-if="activeTabName === '议员合作关系'" class="coop-list">
<div class="member-card" v-for="item in memberList" :key="`coop-${item.id}`">
<div class="member-name">{{ item.name || '-' }}</div>
<div class="member-info">党派:{{ item.party || '-' }}</div>
<div class="member-info">议院:{{ item.chamber || '-' }}</div>
<div class="member-info">任期:{{ item.term || '-' }}</div>
<div class="member-info">委员会:{{ item.committee || '-' }}</div>
<div class="member-info">关注领域:{{ item.focus || '-' }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { getHylyList, getPostOrgList, getPostMemberList, getBills, getBillsPerson } from "@/api/bill/billHome";
import CommonPrompt from "../commonPrompt/index.vue";
import desc from "./assets/icons/icon-desc.png";
import defaultAvatar from "./assets/images/user.png";
import zyyIcon from "@/assets/icons/zyy.png";
import cyyIcon from "@/assets/icons/cyy.png";
import ghdIcon from "@/assets/icons/ghd.png";
import mzdIcon from "@/assets/icons/mzd.png";
const props = defineProps({
onClickToDetail: { type: Function, required: true },
onAfterPageChange: { type: Function, default: null }
});
const tabList = ref([
{ name: "国会法案", active: true },
{ name: "国会议员", active: false },
{ name: "议员合作关系", active: false },
{ name: "涉华委员会", active: false }
]);
const activeTabName = ref("国会法案");
const handleClickTab = tab => {
activeTabName.value = tab.name;
if (tab.name === "国会议员") {
memberCurrentPage.value = 1;
handleGetBillsPerson();
return;
}
if (tab.name === "国会法案") {
currentPage.value = 1;
handleGetBills();
}
};
const releaseTime = ref(true);
const releaseTimeList = ref([{ label: "正序", value: true }, { label: "倒序", value: false }]);
const isInvolveCn = ref("Y");
const cateKuList = ref([]);
const activeAreaList = ref(["全部领域"]);
const dpList = ref([{ id: "全部党派", name: "全部党派" }, { id: "Democratic", name: "民主党" }, { id: "Republican", name: "共和党" }]);
const activeDpList = ref(["全部党派"]);
const yyList = ref([{ id: "全部议院", name: "全部议院" }, { id: "S", name: "参议院" }, { id: "H", name: "众议院" }]);
const activeYyList = ref(["全部议院"]);
const pubTime = ref([{ id: "全部时间", name: "全部时间" }, { id: "2025", name: "2025年" }, { id: "2024", name: "2024年" }, { id: "2023", name: "2023年" }, { id: "2022", name: "2022年" }, { id: "2021", name: "2021年" }]);
const activePubTime = ref(["全部时间"]);
const coopList = ref([
{ id: "全部合作关系", name: "全部合作关系" },
{ id: "跨党派合作", name: "跨党派合作" },
{ id: "同党派合作", name: "同党派合作" },
{ id: "地域利益合作", name: "地域利益合作" },
{ id: "委员会内合作", name: "委员会内合作" }
]);
const activeCoopList = ref(["全部合作关系"]);
const footerSelect1 = ref("全部委员会");
const footerSelect2 = ref("全部提出议员");
const postOrgList = ref([{ departmentName: "全部委员会", departmentId: "全部委员会" }]);
const postMemberList = ref([{ memberName: "全部提出议员", memberId: "全部提出议员" }]);
const memberList = ref([]);
const memberTotal = ref(0);
const memberPageSize = ref(15);
const memberCurrentPage = ref(1);
const bills = ref([]);
const total = ref(0);
const pageSize = ref(4);
const currentPage = ref(1);
const loading = ref(false);
const abortController = ref(null);
const getRiskTagClass = riskSignal => {
if (riskSignal === "特别重大风险") return "risk-tag-critical";
if (riskSignal === "重大风险") return "risk-tag-high";
if (riskSignal === "较大风险") return "risk-tag-medium";
return "";
};
const handleGetHylyList = async () => {
try {
const res = await getHylyList();
cateKuList.value = res.data || [];
} catch (error) {}
};
const handleGetPostOrgList = async () => {
try {
const res = await getPostOrgList();
if (res.code === 200) {
const list = (res.data || []).filter(item => item.departmentId);
postOrgList.value = [{ departmentName: "全部委员会", departmentId: "全部委员会" }, ...list];
}
} catch (error) {}
};
const handleGetPostMemberList = async () => {
try {
const res = await getPostMemberList();
if (res.code === 200) {
const list = (res.data || []).filter(item => item.memberId);
postMemberList.value = [{ memberName: "全部提出议员", memberId: "全部提出议员" }, ...list];
}
} catch (error) {}
};
// 获取资源库法案列表
const handleGetBills = async () => {
if (abortController.value) abortController.value.abort();
abortController.value = new AbortController();
loading.value = true;
const params = {
currentPage: currentPage.value,
pageSize: pageSize.value,
isInvolveCn: isInvolveCn.value
};
if (!activeYyList.value.includes("全部议院")) params.congressIds = activeYyList.value.join(",");
if (footerSelect1.value !== "全部委员会") params.departmentId = footerSelect1.value;
if (!activeDpList.value.includes("全部党派")) params.partyIds = activeDpList.value.join(",");
if (footerSelect2.value !== "全部提出议员") params.personId = footerSelect2.value;
if (!activeAreaList.value.includes("全部领域")) params.researchIds = activeAreaList.value.join(",");
if (releaseTime.value !== true) params.sortFun = releaseTime.value;
if (!activePubTime.value.includes("全部时间")) params.years = activePubTime.value.join(",");
try {
const res = await getBills(params, abortController.value.signal);
if (res.code === 200 && res.data && res.data.content) {
bills.value = res.data.content.map(item => ({
billId: item.billId,
name: item.billName,
eName: item.billNameEn,
tcr: item.personName,
wyh: item.congressName,
areaList: item.hylyList || [],
zxdy: item.latestAction,
progress: item.stageList || [],
riskSignal: item.riskSignal || ""
}));
total.value = res.data.totalElements;
} else {
bills.value = [];
total.value = 0;
}
} catch (error) {
if (error.name !== "AbortError") {
bills.value = [];
total.value = 0;
}
} finally {
loading.value = false;
}
};
// 获取资源库国会议员列表
const handleGetBillsPerson = async () => {
if (abortController.value) abortController.value.abort();
abortController.value = new AbortController();
loading.value = true;
const params = {
currentPage: memberCurrentPage.value,
pageSize: memberPageSize.value
};
if (footerSelect1.value !== "全部委员会") params.committeeId = footerSelect1.value;
if (!activeYyList.value.includes("全部议院")) params.congressIds = activeYyList.value;
if (!activeDpList.value.includes("全部党派")) params.partyIds = activeDpList.value;
if (!activeAreaList.value.includes("全部领域")) params.domainIds = activeAreaList.value;
const formatDateYm = dateStr => {
if (!dateStr) return "";
const date = String(dateStr).slice(0, 10);
const parts = date.split("-");
if (parts.length < 2) return dateStr;
return `${parts[0]}.${parts[1]}`;
};
try {
const res = await getBillsPerson(params, abortController.value.signal);
if (res.code === 200 && res.data && res.data.content) {
memberList.value = res.data.content.map(item => {
const partyMap = { Democratic: "民主党", Republican: "共和党" };
const partyText = partyMap[item.partyId] || item.partyId || "-";
const chamberText = item.congressType || "-";
const start = formatDateYm(item.startTime);
const end = formatDateYm(item.endTime);
const termText = start || end ? `${start}${end ? `-${end}` : ""}` : "-";
const focusTags = (item.domainNames || []).slice(0, 3);
const proposalSize = Number(item.proposalSize || 0);
const partyIcon = item.partyId === "Democratic" ? mzdIcon : item.partyId === "Republican" ? ghdIcon : "";
const chamberIcon = chamberText === "众议院" ? zyyIcon : chamberText === "参议院" ? cyyIcon : "";
return {
id: item.id,
name: item.name || "-",
avatar: item.imageUrl || "",
partyIcon,
chamberIcon,
billCountText: `${proposalSize.toLocaleString()}项提案 >`,
partyText,
chamberText,
termText,
committeeText: item.position || "-",
focusTags,
latestProposal: item.latestBillInfo?.billName || "-",
latestProposalBillId: item.latestBillInfo?.billId || "",
party: item.partyId,
chamber: item.congressType,
term: `${item.startTime || ""}${item.endTime ? ` ${item.endTime}` : ""}`,
committee: item.position,
focus: (item.domainNames || []).join("、")
};
});
memberTotal.value = res.data.totalElements || 0;
} else {
memberList.value = [];
memberTotal.value = 0;
}
} catch (error) {
if (error.name !== "AbortError") {
memberList.value = [];
memberTotal.value = 0;
}
} finally {
loading.value = false;
}
};
const normalizeWithAll = (val, allLabel, targetRef) => {
if (val.includes(allLabel) && val.length > 1) {
targetRef.value = val[val.length - 1] === allLabel ? [allLabel] : val.filter(item => item !== allLabel);
} else if (val.length === 0) {
targetRef.value = [allLabel];
} else {
targetRef.value = val;
}
currentPage.value = 1;
memberCurrentPage.value = 1;
if (activeTabName.value === "国会议员") {
handleGetBillsPerson();
return;
}
handleGetBills();
};
const handleAreaChange = val => normalizeWithAll(val, "全部领域", activeAreaList);
const handleDpChange = val => normalizeWithAll(val, "全部党派", activeDpList);
const handleYyChange = val => normalizeWithAll(val, "全部议院", activeYyList);
const handlePubTimeChange = val => normalizeWithAll(val, "全部时间", activePubTime);
const handleCoopChange = val => normalizeWithAll(val, "全部合作关系", activeCoopList);
const handleFooterSelect1Change = val => {
footerSelect1.value = val;
currentPage.value = 1;
memberCurrentPage.value = 1;
if (activeTabName.value === "国会议员") {
handleGetBillsPerson();
return;
}
handleGetBills();
};
const handleFooterSelect2Change = val => {
footerSelect2.value = val;
currentPage.value = 1;
handleGetBills();
};
const handlePxChange = val => {
releaseTime.value = val;
currentPage.value = 1;
handleGetBills();
};
const handleInvolveCnChange = val => {
isInvolveCn.value = val;
currentPage.value = 1;
handleGetBills();
};
const handleCurrentChange = page => {
currentPage.value = page;
handleGetBills();
props.onAfterPageChange && props.onAfterPageChange();
};
const handleClickLatestProposal = item => {
if (!item?.latestProposalBillId) return;
props.onClickToDetail({
billId: item.latestProposalBillId,
name: item.latestProposal
});
};
const handleMemberCurrentChange = page => {
memberCurrentPage.value = page;
handleGetBillsPerson();
props.onAfterPageChange && props.onAfterPageChange();
};
onMounted(() => {
handleGetHylyList();
handleGetPostOrgList();
handleGetPostMemberList();
handleGetBills();
});
</script>
<style lang="scss" scoped>
.home-content-footer-header {
width: 1600px;
margin: 0 auto;
margin-top: 37px;
margin-bottom: 36px;
height: 42px;
display: flex;
justify-content: space-between;
.btn-box {
display: flex;
gap: 24px;
width: 1000px;
.btn {
height: 42px;
line-height: 42px;
padding: 0 20px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 400;
border-radius: 21px;
cursor: pointer;
&:hover {
background: rgba(20, 89, 187, 0.1);
}
}
.btnActive {
background: var(--color-main-active);
color: #fff;
font-weight: 700;
&:hover {
color: #fff;
background: var(--color-main-active);
}
}
.disabled {
cursor: not-allowed;
opacity: 0.5;
&:hover {
background: transparent;
}
}
}
}
.home-content-footer-main {
width: 1600px;
height: 1401px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: flex-start;
.left {
width: 300px;
padding-bottom: 33px;
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);
.select-box {
margin-top: 20px;
.select-box-header {
display: flex;
gap: 17px;
.icon {
margin-top: 4px;
width: 8px;
height: 16px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.title {
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 1px;
text-align: left;
}
}
.select-main {
margin-left: 25px;
margin-top: 16px;
.checkbox-group {
display: flex;
flex-wrap: wrap;
.filter-checkbox {
width: 50%;
margin-right: 0;
margin-bottom: 4px;
:deep(.el-checkbox__label) {
color: rgb(95, 101, 108);
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
}
}
}
}
}
}
.right {
margin-left: 20px;
width: 1280px;
.right-header {
height: 48px;
display: flex;
gap: 18px;
.right-header-sort {
display: flex;
align-items: center;
gap: 20px;
}
.involve-checkbox {
height: 40px;
display: inline-flex;
align-items: center;
margin-right: 0;
:deep(.el-checkbox__inner) {
width: 18px;
height: 18px;
border-radius: 5px;
}
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
background-color: #1677ff;
border-color: #1677ff;
}
:deep(.el-checkbox__inner::after) {
box-sizing: content-box;
}
:deep(.el-checkbox__label) {
color: #5f656c;
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
line-height: 24px;
padding-left: 10px;
}
}
}
.right-main {
height: 1264px;
.member-grid,
.coop-list {
display: grid;
column-gap: 16px;
row-gap: 16px;
.member-card {
height: 200px;
padding: 10px 16px 0;
box-sizing: border-box;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: #fff;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
.member-card-top {
display: flex;
gap: 16px;
}
.member-avatar-wrap {
width: 88px;
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
position: relative;
/* padding-bottom: 22px; */
margin-top: 5px;
}
.member-avatar {
width: 88px;
height: 88px;
border-radius: 50%;
object-fit: cover;
}
.member-icon-row {
position: absolute;
left: 50%;
bottom: 18px;
transform: translateX(-50%);
display: flex;
gap: 2px;
align-items: center;
}
.member-mini-icon-img {
width: 16px;
height: 16px;
padding: 2px;
box-sizing: border-box;
object-fit: contain;
border-radius: 50%;
background: #fff;
}
.member-main {
flex: 1;
min-width: 0;
padding-top: 2px;
}
.member-title-row {
display: flex;
align-items: baseline;
justify-content: space-between;
align-items: center;
gap: 8px;
}
.member-name {
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-size: 18px;
font-weight: 700;
line-height: 24px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.member-link {
flex-shrink: 0;
color: #1459bb;
font-size: 16px;
line-height: 24px;
}
.member-meta,
.member-committee {
margin-top: 6px;
color: #5f656c;
font-size: 16px;
line-height: 24px;
font-weight: 400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.member-tags {
margin-top: 10px;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.member-tag {
height: 24px;
padding: 5px 8px;
border-radius: 4px;
background: rgb(231, 243, 255);
color: rgb(5, 95, 194);
font-size: 14px;
font-weight: 400;
line-height: 14px;
}
.member-card-bottom {
height: 52px;
margin: 10px -16px 0;
padding: 0 16px;
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1px solid #eaeced;
color: #5f656c;
font-size: 16px;
cursor: pointer;
&:hover {
background: rgba(20, 89, 187, 0.04);
}
}
.member-latest {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 8px;
}
.member-arrow {
font-size: 16px;
line-height: 1;
color: #5f656c;
}
.member-info {
color: #5f656c;
font-family: "Microsoft YaHei";
font-size: 15px;
font-weight: 400;
line-height: 28px;
}
}
}
.member-grid {
grid-template-columns: repeat(3, 1fr);
}
.coop-list {
grid-template-columns: 1fr;
}
.right-main-box {
position: relative;
width: 1280px;
height: 300px;
padding-bottom: 24px;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
margin-bottom: 16px;
overflow: hidden;
.risk-tag {
position: absolute;
top: 16px;
right: 40px;
height: 28px;
border-radius: 20px;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 0 10px;
box-sizing: border-box;
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 500;
line-height: 28px;
white-space: nowrap;
&::before {
content: "";
width: 5px;
height: 5px;
border-radius: 50%;
background: currentColor;
flex-shrink: 0;
}
}
.risk-tag-critical {
background: rgba(206, 79, 81, 0.1);
color: rgb(206, 79, 81);
}
.risk-tag-high {
background: rgba(255, 149, 77, 0.1);
color: rgb(255, 149, 77);
}
.risk-tag-medium {
background: rgba(232, 189, 11, 0.1);
color: rgb(232, 189, 11);
}
.header {
height: 91px;
width: 1200px;
margin: 0 auto;
border-bottom: 1px solid rgba(234, 236, 238, 1);
padding-top: 19px;
.title {
cursor: pointer;
height: 26px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.en-title {
margin-top: 8px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.main {
width: 1200px;
margin: 0 auto;
margin-top: 2px;
.item {
margin-top: 12px;
display: flex;
.item-left {
width: 100px;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
.item-right {
max-width: 1000px;
margin-left: 10px;
color: rgba(95, 101, 108, 1);
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.item-right1 {
margin-left: 10px;
display: flex;
gap: 8px;
.tag {
height: 24px;
line-height: 24px;
padding: 0 8px;
border-radius: 4px;
background: rgba(231, 243, 255, 1);
color: var(--color-main-active);
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 400;
}
}
.item-right2 {
margin-left: 10px;
display: flex;
align-items: center;
.tag {
height: 24px;
line-height: 22px;
padding: 0 10px 0 30px;
background: rgba(255, 255, 255, 1);
color: rgb(95, 101, 108);
border-top: 1px solid rgb(234, 236, 238);
border-bottom: 1px solid rgb(234, 236, 238);
position: relative;
margin-left: -10px;
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 400;
&::after {
content: "";
position: absolute;
top: 50%;
right: -8.485px;
width: 16.97px;
height: 16.97px;
background: inherit;
border-top: 1px solid rgb(234, 236, 238);
border-right: 1px solid rgb(234, 236, 238);
transform: translateY(-50%) rotate(45deg);
z-index: 1;
box-shadow: 2px -2px 2px rgba(0, 0, 0, 0.05);
box-sizing: border-box;
}
&:first-child {
margin-left: 0;
padding-left: 10px;
border-left: 1px solid rgb(234, 236, 238);
border-radius: 4px 0 0 4px;
}
&:last-child {
background: rgb(59, 65, 75);
color: rgba(255, 255, 255, 1);
border-color: rgb(59, 65, 75);
padding-right: 10px;
border-radius: 0;
border-right: none;
&::after {
display: block;
border-color: rgb(59, 65, 75);
box-shadow: none;
}
}
&:first-child:last-child {
margin-left: 0;
padding: 0 10px;
border-radius: 4px 0 0 4px;
background: rgb(59, 65, 75);
color: rgba(255, 255, 255, 1);
border: 1px solid rgb(59, 65, 75);
border-right: none;
}
}
}
}
}
}
}
.right-footer {
height: 85px;
display: flex;
justify-content: space-between;
box-sizing: border-box;
padding-top: 12px;
.footer-left {
color: rgba(59, 65, 75, 1);
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
line-height: 32px;
}
}
}
}
</style>
...@@ -13,7 +13,10 @@ ...@@ -13,7 +13,10 @@
<DivideHeader id="position1" class="divide1" :titleText="'最新动态'"></DivideHeader> <DivideHeader id="position1" class="divide1" :titleText="'最新动态'"></DivideHeader>
<div class="home-content-center"> <div class="home-content-center">
<div class="center-top"> <div class="center-top">
<div class="box1"> <overviewMainBox class="box1" title="热门法案" @toDetail="handleClickToDetail">
<template #headerIcon>
<img style="width: 100%; height: 100%" src="./assets/images/box1-header-icon.png" alt="" />
</template>
<div class="box1-left" @click="handleSwithCurBill('left')"> <div class="box1-left" @click="handleSwithCurBill('left')">
<div class="icon"> <div class="icon">
<img src="./assets/images/box1-left.svg" alt="" /> <img src="./assets/images/box1-left.svg" alt="" />
...@@ -24,17 +27,6 @@ ...@@ -24,17 +27,6 @@
<img src="./assets/images/box1-right.svg" alt="" /> <img src="./assets/images/box1-right.svg" alt="" />
</div> </div>
</div> </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()">
{{ "查看详情 >" }}
</div>
</div>
<div class="box1-main" style="display: block"> <div class="box1-main" style="display: block">
<el-carousel ref="carouselRef" height="354px" :autoplay="true" :interval="3000" <el-carousel ref="carouselRef" height="354px" :autoplay="true" :interval="3000"
arrow="never" indicator-position="none" @change="handleCarouselChange"> arrow="never" indicator-position="none" @change="handleCarouselChange">
...@@ -42,7 +34,6 @@ ...@@ -42,7 +34,6 @@
<div class="carousel-content" style="display: flex; height: 100%"> <div class="carousel-content" style="display: flex; height: 100%">
<div class="box1-main-left"> <div class="box1-main-left">
<div class="box1-main-left-title"> <div class="box1-main-left-title">
<!-- "H.R.1(119th)-大而美法案" -->
{{ bill.billName }} {{ bill.billName }}
</div> </div>
<div class="box1-main-left-info"> <div class="box1-main-left-info">
...@@ -102,7 +93,7 @@ ...@@ -102,7 +93,7 @@
</el-carousel-item> </el-carousel-item>
</el-carousel> </el-carousel>
</div> </div>
</div> </overviewMainBox>
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" <RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal"
@item-click="handleClickToDetailO" riskLevel="signalLevel" postDate="signalTime" @item-click="handleClickToDetailO" riskLevel="signalLevel" postDate="signalTime"
name="signalTitle" /> name="signalTitle" />
...@@ -112,320 +103,71 @@ ...@@ -112,320 +103,71 @@
<div class="center-center"> <div class="center-center">
<NewsList :list="newsList" /> <NewsList :list="newsList" />
<MessageBubble :messageList="messageList" imageUrl="personImage" <MessageBubble :messageList="messageList" imageUrl="personImage"
@more-click="handleToSocialDetail" @person-click="handleClcikToCharacter" name="personName" @more-click="handleToSocialDetail" @person-click="handleClickToCharacter" name="personName"
content="remarks" source="orgName" /> content="remarks" source="orgName" />
</div> </div>
<DivideHeader id="position3" class="divide3" :titleText="'数据总览'"></DivideHeader> <DivideHeader id="position3" class="divide3" :titleText="'数据总览'"></DivideHeader>
<div class="center-footer"> <div class="center-footer">
<div class="box5"> <OverviewCard class="overview-card--double box5" title="涉华法案数量" :icon="box5HeaderIcon">
<div class="box5-header"> <template #right>
<div class="box5-header-left"> <el-select v-model="box5Select" placeholder="选择领域" @change="handleBox5Change" style="width: 150px">
<div class="box5-header-icon"> <el-option label="全部领域" value="全部领域" />
<img src="./assets/images/box5-header-icon.png" alt="" /> <el-option v-for="item in categoryList" :key="item.id" :label="item.name" :value="item.id" />
</div> </el-select>
<div class="box5-header-title">{{ "涉华法案数量" }}</div> </template>
</div>
<div class="box5-header-right">
<!-- <div class="header-right-icon">
<img src="./assets/images/tips-icon.png" alt="" />
</div>
<div class="header-right-text">{{ "数据来源:美国国会官方网站" }}</div> -->
<div class="box5-select">
<el-select v-model="box5Select" placeholder="选择领域" @change="handleBox5Change"
style="width: 150px">
<el-option label="全部领域" value="全部领域" />
<el-option v-for="item in categoryList" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</div>
</div>
</div>
<div class="box5-main" :style="getEmptyStateStyle(box5HasData)"> <div class="box5-main" :style="getEmptyStateStyle(box5HasData)">
<el-empty v-if="!box5HasData" description="暂无数据" :image-size="100" /> <el-empty v-if="!box5HasData" description="暂无数据" :image-size="100" />
<div v-else id="box5Chart" style="width: 100%; height: 100%"></div> <div v-else id="box5Chart" style="width: 100%; height: 100%"></div>
</div> </div>
</div> </OverviewCard>
<div class="box6"> <OverviewCard class="overview-card--single box6" title="涉华法案领域分布" :icon="box6HeaderIcon">
<div class="box6-header"> <template #right>
<div class="header-icon"> <el-select v-model="box9selectetedTime" placeholder="选择时间" style="width: 90px">
<img src="./assets/images/box6-header-icon.png" alt="" /> <el-option v-for="item in box9YearList" :key="item.value" :label="item.label" :value="item.value" />
</div> </el-select>
<div class="header-title">{{ "涉华法案领域分布" }}</div> </template>
<div class="box6-header-right">
<el-select v-model="box9selectetedTime" placeholder="选择时间" style="width: 90px">
<el-option v-for="item in box9YearList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
</div>
<div class="box6-main" :style="getEmptyStateStyle(box9HasData)"> <div class="box6-main" :style="getEmptyStateStyle(box9HasData)">
<el-empty v-if="!box9HasData" description="暂无数据" :image-size="100" /> <el-empty v-if="!box9HasData" description="暂无数据" :image-size="100" />
<div v-else id="box9Chart" style="width: 100%; height: 100%"></div> <div v-else id="box9Chart" style="width: 100%; height: 100%"></div>
</div> </div>
</div> </OverviewCard>
</div> </div>
<div class="center-footer1"> <div class="center-footer1">
<div class="box7"> <OverviewCard class="overview-card--single box7" title="法案提出部门" :icon="box7HeaderIcon">
<div class="box7-header"> <template #right>
<div class="box7-header-left">
<div class="box7-header-icon">
<img src="./assets/images/box7-header-icon.png" alt="" />
</div>
<div class="box7-header-title">{{ "法案提出部门" }}</div>
</div>
<div class="box7-header-right">
<!-- <div class="header-right-icon">
<img src="./assets/images/tips-icon.png" alt="" />
</div> -->
<!-- <div class="header-right-text">{{ "数据来源:美国国会官方网站" }}</div> -->
<el-select v-model="box7selectetedTime" placeholder="选择时间" style="width: 90px">
<el-option v-for="item in box7YearList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
</div>
<!-- <div class="box-center">
<el-select v-model="box7selectetedTime" placeholder="选择时间" style="width: 90px"> <el-select v-model="box7selectetedTime" placeholder="选择时间" style="width: 90px">
<el-option <el-option v-for="item in box7YearList" :key="item.value" :label="item.label" :value="item.value" />
v-for="item in box7YearList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</div> --> </template>
<div class="box7-main" :style="getEmptyStateStyle(box7HasData)"> <div class="box7-main" :style="getEmptyStateStyle(box7HasData)">
<el-empty v-if="!box7HasData" description="暂无数据" :image-size="100" /> <el-empty v-if="!box7HasData" description="暂无数据" :image-size="100" />
<div v-else id="box7Chart" style="width: 100%; height: 100%"></div> <div v-else id="box7Chart" style="width: 100%; height: 100%"></div>
</div> </div>
</div> </OverviewCard>
<div class="box8"> <OverviewCard class="overview-card--single box8" title="涉华法案进展分布" :icon="box7HeaderIcon">
<div class="box8-header"> <template #right>
<div class="box8-header-left">
<div class="box8-header-icon">
<img src="./assets/images/box7-header-icon.png" alt="" />
</div>
<div class="box8-header-title">{{ "关键议员提案" }}</div>
</div>
<div class="box8-header-right">
<!-- <div class="header-right-icon">
<img src="./assets/images/tips-icon.png" alt="" />
</div> -->
<!-- <div class="header-right-text">{{ "数据来源:美国国会官方网站" }}</div> -->
<el-select v-model="box8selectetedTime" placeholder="选择时间" style="width: 90px">
<el-option v-for="item in box8YearList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
</div>
<!-- <div class="box-center">
<el-select v-model="box8selectetedTime" placeholder="选择时间" style="width: 90px"> <el-select v-model="box8selectetedTime" placeholder="选择时间" style="width: 90px">
<el-option <el-option v-for="item in box8YearList" :key="item.value" :label="item.label" :value="item.value" />
v-for="item in box8YearList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</div> --> </template>
<div class="box8-main" :style="getEmptyStateStyle(box8Data.length > 0)"> <div class="box8-main" :style="getEmptyStateStyle(box8HasData)">
<el-empty v-if="box8Data.length === 0" description="暂无数据" :image-size="100" /> <el-empty v-if="!box8HasData" description="暂无数据" :image-size="100" />
<div v-else class="box8-main-item" v-for="(item, index) in box8Data" :key="index" <template v-else>
@click="handleClcikToCharacter(item.memberId, item.name)"> <div class="box8-desc">• 通过涉华法案{{ box8Summary }}</div>
<div class="box8-main-item-left"> <div id="box8Chart" class="box8-chart"></div>
<img :src="getProxyUrl(item.img)" alt="" referrerpolicy="no-referrer" </template>
class="left-img" />
<div class="left-icon1">
<img :src="item.dangpai" alt="" />
</div>
<div class="left-icon2">
<img :src="item.yuan" alt="" />
</div>
</div>
<div class="box8-main-item-center">
<div class="box8-main-item-center-top">{{ item.name }}</div>
<div class="box8-main-item-center-footer">{{ item.zhiwei }}</div>
</div>
<div class="box8-main-item-right">
{{ `${item.num}项提案 >` }}
</div>
</div>
</div>
</div>
<div class="box9">
<div class="box9-header">
<div class="box9-header-left">
<div class="box9-header-icon">
<img src="./assets/images/box7-header-icon.png" alt="" />
</div>
<div class="box9-header-title">{{ "关键条款" }}</div>
</div>
</div> </div>
</OverviewCard>
<OverviewCard class="overview-card--single box9" title="关键条款" :icon="box7HeaderIcon">
<div class="box9-main" id="wordCloudChart"></div> <div class="box9-main" id="wordCloudChart"></div>
</div> </OverviewCard>
</div> </div>
</div> </div>
</div> </div>
<div class="home-content-footer"> <div class="home-content-footer">
<DivideHeader id="position4" class="divide4" :titleText="'资源库'"></DivideHeader> <DivideHeader id="position4" class="divide4" :titleText="'资源库'"></DivideHeader>
<div class="home-content-footer-header"> <ResourceLibrarySection :on-click-to-detail="handleClickToDetailO" :on-after-page-change="handlePageChange" />
<div class="btn-box">
<div class="btn" :class="{ btnActive: activeTabName === cate.name, disabled: index !== 0 }"
v-for="(cate, index) in tabList" :key="index" @click="index === 0 && handleClickTab(cate)">
{{ cate.name }}
</div>
</div>
</div>
<div class="home-content-footer-main">
<div class="left">
<div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "科技领域" }}</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" v-model="activeAreaList"
@change="handleAreaChange">
<el-checkbox class="filter-checkbox" label="全部领域"> 全部领域 </el-checkbox>
<el-checkbox v-for="(area, index) in cateKuList" :key="index" :label="area.id"
class="filter-checkbox">
{{ area.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "党派" }}</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" v-model="activeDpList"
@change="handleDpChange">
<el-checkbox v-for="(dp, index) in dpList" :key="index" :label="dp.id"
class="filter-checkbox">
{{ dp.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "议院" }}</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" v-model="activeYyList"
@change="handleYyChange">
<el-checkbox v-for="(yy, index) in yyList" :key="index" :label="yy.id"
class="filter-checkbox">
{{ yy.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "发布时间" }}</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" v-model="activePubTime"
@change="handlePubTimeChange">
<el-checkbox v-for="(time, index) in pubTime" :key="index" :label="time.id"
class="filter-checkbox">
{{ time.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
<div class="right">
<div class="right-header">
<div class="right-header-box">
<el-select v-model="footerSelect1" placeholder="选择委员会" style="width: 240px"
@change="handleFooterSelect1Change">
<el-option v-for="item in postOrgList" :key="item.departmentId"
:label="item.departmentName" :value="item.departmentId" />
</el-select>
</div>
<div class="right-header-box">
<el-select v-model="footerSelect2" placeholder="选择提出议员" style="width: 240px"
@change="handleFooterSelect2Change">
<el-option v-for="item in postMemberList" :key="item.memberId"
:label="item.memberName" :value="item.memberId" />
</el-select>
</div>
<div class="right-header-box" style="margin-left: auto">
<el-select v-model="releaseTime" placeholder="选择排序方式" style="width: 120px"
@change="handlePxChange">
<template #prefix>
<div style="display: flex; align-items: center; height: 100%">
<img :src="desc" style="width: 14px; height: 14px" />
</div>
</template>
<el-option v-for="item in releaseTimeList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
</div>
<div class="right-main" v-loading="loading">
<div class="right-main-box" v-for="(item, index) in bills" :key="index">
<div class="header">
<div class="title" @click="handleClickToDetailO(item)" :title="item.name">
{{ item.name }}
</div>
<div class="en-title" :title="item.eName">{{ item.eName }}</div>
</div>
<div class="main">
<div class="item">
<div class="item-left">{{ "提案人:" }}</div>
<div class="item-right">{{ item.tcr }}</div>
</div>
<div class="item">
<div class="item-left">{{ "委员会:" }}</div>
<div class="item-right">{{ item.wyh }}</div>
</div>
<div class="item">
<div class="item-left">{{ "相关领域:" }}</div>
<div class="item-right1">
<div class="tag" v-for="(val, idx) in item.areaList" :key="idx">{{ val }}
</div>
</div>
</div>
<div class="item">
<div class="item-left">{{ "最新动议:" }}</div>
<div class="item-right">
<CommonPrompt :content="item.zxdy" />
</div>
</div>
<div class="item">
<div class="item-left">{{ "法案进展:" }}</div>
<div class="item-right2">
<div class="tag" v-for="(val, idx) in [...item.progress].reverse()"
:key="idx" :style="{ zIndex: item.progress.length - idx }">
{{ val }}
</div>
</div>
</div>
</div>
</div>
<div class="right-footer">
<div class="footer-left">
{{ `共 ${total} 项` }}
</div>
<div class="footer-right">
<el-pagination @current-change="handleCurrentChange" :page-size="pageSize"
:current-page="currentPage" background layout="prev, pager, next"
:total="total" />
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -444,16 +186,15 @@ import { ...@@ -444,16 +186,15 @@ import {
getBillOverviewKeyTK, getBillOverviewKeyTK,
getBillCount, getBillCount,
getBillPostOrg, getBillPostOrg,
getMemberProposal, getBillProcess,
getNews, getNews,
getRemarks, getRemarks
getPostOrgList,
getPostMemberList,
getBills
} from "@/api/bill/billHome"; } from "@/api/bill/billHome";
import { getPersonSummaryInfo } from "@/api/common/index"; import { getPersonSummaryInfo } from "@/api/common/index";
import DivideHeader from "@/components/DivideHeader.vue"; import DivideHeader from "@/components/DivideHeader.vue";
import CommonPrompt from "../commonPrompt/index.vue"; import overviewMainBox from "@/components/base/boxBackground/overviewMainBox.vue";
import OverviewCard from "./OverviewCard.vue";
import ResourceLibrarySection from "./ResourceLibrarySection.vue";
import { useContainerScroll } from "@/hooks/useScrollShow"; import { useContainerScroll } from "@/hooks/useScrollShow";
import getMultiLineChart from "./utils/multiLineChart"; import getMultiLineChart from "./utils/multiLineChart";
...@@ -461,34 +202,15 @@ import getWordCloudChart from "./utils/worldCloudChart"; ...@@ -461,34 +202,15 @@ import getWordCloudChart from "./utils/worldCloudChart";
import getPieChart from "./utils/piechart"; import getPieChart from "./utils/piechart";
import getDoublePieChart from "./utils/doublePieChart"; import getDoublePieChart from "./utils/doublePieChart";
import desc from "./assets/icons/icon-desc.png"; import box5HeaderIcon from "./assets/images/box5-header-icon.png";
import box6HeaderIcon from "./assets/images/box6-header-icon.png";
import box7HeaderIcon from "./assets/images/box7-header-icon.png";
import Cyy from "@/assets/icons/cyy.png";
import Ghd from "@/assets/icons/ghd.png";
import Mzd from "@/assets/icons/mzd.png";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
// 处理图片代理
const getProxyUrl = url => {
if (!url) return "";
const urlStr = String(url);
// 排除非 http 开头(相对路径)、已经是代理链接、或者是本地链接
if (
!urlStr.startsWith("http") ||
urlStr.includes("images.weserv.nl") ||
urlStr.includes("localhost") ||
urlStr.includes("127.0.0.1")
) {
return url;
}
// 移除协议头 http:// 或 https://
const cleanUrl = urlStr.replace(/^https?:\/\//i, "");
return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`;
};
// 跳转人物主页 // 跳转人物主页
const handleClcikToCharacter = async (id, name) => { const handleClickToCharacter = async (id, name) => {
const personTypeList = JSON.parse(window.sessionStorage.getItem("personTypeList")); const personTypeList = JSON.parse(window.sessionStorage.getItem("personTypeList"));
let type = 0; let type = 0;
...@@ -1143,29 +865,189 @@ watch(box9selectetedTime, () => { ...@@ -1143,29 +865,189 @@ watch(box9selectetedTime, () => {
handleBox9Data(); handleBox9Data();
}); });
const box8Data = ref([]); const box8HasData = ref(true);
const box8Summary = ref(0);
let box8ChartInstance = null;
const box8MockDataByYear = {
"2025": {
passCount: 19,
stages: [
{ name: "已提案", count: 24 },
{ name: "众议院通过", count: 20 },
{ name: "众议院不通过", count: 25 },
{ name: "解决分歧", count: 23 },
{ name: "参议院通过", count: 26 },
{ name: "总统否决", count: 48 },
{ name: "已立法", count: 19 }
]
}
};
const getBox8ChartOption = stageList => {
const axisMax = 100;
const countList = stageList.map(item => item.count || 0);
const totalCount = countList.reduce((sum, cur) => sum + cur, 0);
const safeTotal = totalCount > 0 ? totalCount : 1;
const rawWidths = countList.map(count => (count / safeTotal) * axisMax);
const markerData = rawWidths.map((width, index) => {
if (index === rawWidths.length - 1) {
const used = rawWidths.slice(0, index).reduce((sum, cur) => sum + Number(cur.toFixed(4)), 0);
return Number(Math.max(axisMax - used, 0).toFixed(4));
}
return Number(width.toFixed(4));
});
let cumulative = 0;
const offsetData = markerData.map(width => {
const start = Number(cumulative.toFixed(4));
cumulative += width;
return start;
});
const trackData = stageList.map(() => axisMax);
return {
tooltip: {
trigger: "item",
confine: true,
backgroundColor: "rgba(34, 44, 58, 0.92)",
borderWidth: 0,
textStyle: {
color: "#fff",
fontSize: 13
},
formatter: params => {
const item = stageList[params.dataIndex];
if (!item) return "";
return `${item.name}<br/>${item.count}项`;
}
},
grid: {
left: 118,
right: 60,
top: 6,
bottom: 6
},
xAxis: {
type: "value",
min: 0,
max: axisMax,
show: false
},
yAxis: {
type: "category",
inverse: true,
data: stageList.map(item => item.name),
axisTick: { show: false },
axisLine: { show: false },
axisLabel: {
color: "#3b414b",
fontSize: 16
}
},
series: [
{
type: "bar",
barWidth: 32,
barGap: "-100%",
silent: true,
data: trackData,
itemStyle: {
color: params => (params.dataIndex === stageList.length - 1 ? "rgba(206, 79, 81, 0.1)" : "rgb(246, 250, 255)")
},
label: {
show: true,
position: "right",
formatter: params => `${countList[params.dataIndex]}项`,
color: params => (params.dataIndex === stageList.length - 1 ? "rgb(206, 79, 81)" : "#3b414b"),
fontSize: 36 / 2,
fontWeight: params => (params.dataIndex === stageList.length - 1 ? 700 : 400)
}
},
{
type: "bar",
stack: "progress",
barWidth: 32,
silent: true,
data: offsetData,
itemStyle: { color: "transparent" }
},
{
type: "bar",
stack: "progress",
barWidth: 32,
data: markerData,
itemStyle: {
color: params => (params.dataIndex === stageList.length - 1 ? "rgb(206, 79, 81)" : "rgb(5, 95, 194)")
},
emphasis: {
disabled: false,
itemStyle: {
borderColor: "rgba(255, 255, 255, 0.9)",
borderWidth: 2,
shadowBlur: 10,
shadowColor: "rgba(5, 95, 194, 0.35)"
}
}
}
]
};
};
const handleBox8Data = async () => { const handleBox8Data = async () => {
const stageOrder = ["提案", "众议院通过", "众议院不通过", "分歧已解决", "参议院通过", "总统否决或未签署", "完成立法"];
const stageNameMap = {
提案: "已提案",
众议院通过: "众议院通过",
众议院不通过: "众议院不通过",
分歧已解决: "解决分歧",
参议院通过: "参议院通过",
"总统否决或未签署": "总统否决",
完成立法: "已立法"
};
try { try {
const res = await getMemberProposal({ year: box8selectetedTime.value }); const res = await getBillProcess({ year: box8selectetedTime.value });
console.log("关键议员提案", res); console.log("涉华法案进展分布", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data && res.data.length > 0) {
box8Data.value = res.data.map(item => ({ const countMap = new Map(res.data.map(item => [item.progressName, item.countBill]));
memberId: item.memberId, const stages = stageOrder.map(name => ({
name: item.memberName, name: stageNameMap[name],
zhiwei: item.position, count: countMap.get(name) || 0
img: item.imageUrl,
num: item.countProposal,
dangpai: Cyy,
yuan: item.position === "Democratic" ? Mzd : Ghd
})); }));
box8HasData.value = true;
box8Summary.value = countMap.get("完成立法") || 0;
await nextTick();
const box8Chart = getBox8ChartOption(stages);
box8ChartInstance = setChart(box8Chart, "box8Chart");
} else { } else {
// 接口异常(如500)时,清空图表数据以避免报错或显示错误信息 const data = box8MockDataByYear[box8selectetedTime.value];
box8Data.value = []; if (data && data.stages && data.stages.length > 0) {
box8HasData.value = true;
box8Summary.value = data.passCount || 0;
await nextTick();
const box8Chart = getBox8ChartOption(data.stages);
box8ChartInstance = setChart(box8Chart, "box8Chart");
} else {
box8HasData.value = false;
box8Summary.value = 0;
setChart({}, "box8Chart");
}
} }
} catch (error) { } catch (error) {
console.error("获取关键议员提案失败", error); console.error("获取涉华法案进展分布失败", error);
box8Data.value = []; const data = box8MockDataByYear[box8selectetedTime.value];
if (data && data.stages && data.stages.length > 0) {
box8HasData.value = true;
box8Summary.value = data.passCount || 0;
await nextTick();
const box8Chart = getBox8ChartOption(data.stages);
box8ChartInstance = setChart(box8Chart, "box8Chart");
} else {
box8HasData.value = false;
box8Summary.value = 0;
setChart({}, "box8Chart");
}
} }
}; };
...@@ -1337,6 +1219,7 @@ const footerSelect2 = ref("全部提出议员"); ...@@ -1337,6 +1219,7 @@ const footerSelect2 = ref("全部提出议员");
// ]); // ]);
const handleResize = () => { const handleResize = () => {
box8ChartInstance && box8ChartInstance.resize();
box9ChartInstance && box9ChartInstance.resize(); box9ChartInstance && box9ChartInstance.resize();
}; };
...@@ -2449,510 +2332,65 @@ onUnmounted(() => { ...@@ -2449,510 +2332,65 @@ onUnmounted(() => {
margin-bottom: 36px; margin-bottom: 36px;
} }
$overview-card-gap: 16px;
$overview-single-width: calc((100% - #{$overview-card-gap} * 2) / 3);
.center-footer { .center-footer {
margin-top: 21px; margin-top: 21px;
height: 450px;
display: flex; display: flex;
gap: $overview-card-gap;
.box5 { width: 100%;
width: 1059px;
height: 450px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.box5-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 0 27px 0 22px;
.box5-header-left {
display: flex;
.box5-header-icon {
margin-top: 16px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box5-header-title {
margin-top: 11px;
margin-left: 19px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.box5-header-right {
display: flex;
justify-content: flex-end;
gap: 8px;
height: 24px;
margin-top: 12px;
.header-right-icon {
margin-top: 4px;
width: 14px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.header-right-text {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
.box5-main {
height: 397px;
padding: 8px 16px 8px 16px;
}
}
.box6 {
margin-left: 20px;
width: 521px;
height: 450px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
.box6-header {
width: 521px;
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
box-sizing: border-box;
padding-left: 22px;
.header-icon {
margin-top: 15px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.header-title {
margin-top: 11px;
margin-left: 18px;
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.box6-header-right {
margin-left: 130px;
display: flex;
justify-content: flex-end;
gap: 8px;
height: 24px;
margin-top: 12px;
.header-right-icon {
margin-top: 4px;
width: 14px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.header-right-text {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
.box6-main {
width: 100%;
height: calc(100% - 53px);
}
}
} }
.center-footer1 { .center-footer1 {
display: flex; display: flex;
gap: 16px; gap: $overview-card-gap;
margin-top: 16px; margin-top: 16px;
width: 100%;
}
.box7 { .overview-card--single {
width: 520px; width: $overview-single-width;
height: 450px; }
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 27px 0 22px;
.box7-header-left {
display: flex;
.box7-header-icon {
margin-top: 16px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box7-header-title {
margin-top: 11px;
margin-left: 19px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.box7-header-right {
display: flex;
justify-content: flex-end;
gap: 8px;
height: 24px;
margin-top: 12px;
.header-right-icon {
margin-top: 4px;
width: 14px;
height: 16px;
img { .overview-card--double {
width: 100%; width: calc(#{$overview-single-width} * 2 + #{$overview-card-gap});
height: 100%; }
} .box5-main,
} .box6-main,
.box7-main,
.box9-main {
height: 100%;
box-sizing: border-box;
}
.header-right-text { .box5-main {
color: rgba(132, 136, 142, 1); padding: 8px 16px;
font-family: Microsoft YaHei; }
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
.box-center { .box8-main {
height: 45px; height: 100%;
padding-right: 20px; box-sizing: border-box;
display: flex; padding: 12px 20px 18px;
align-items: center;
justify-content: flex-end;
}
.box7-main { .box8-desc {
height: 390px; height: 24px;
} line-height: 24px;
color: rgb(206, 79, 81);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
} }
.box8 { .box8-chart {
width: 527px; width: 100%;
height: 450px; height: calc(100% - 30px);
box-sizing: border-box; cursor: pointer;
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 27px 0 22px;
.box8-header-left {
display: flex;
.box8-header-icon {
margin-top: 16px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box8-header-title {
margin-top: 11px;
margin-left: 19px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.box8-header-right {
display: flex;
justify-content: flex-end;
gap: 8px;
height: 24px;
margin-top: 12px;
.header-right-icon {
margin-top: 4px;
width: 14px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.header-right-text {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
.box8-main {
height: 380px;
overflow: hidden;
padding: 20px;
.box8-main-item {
// margin: 0 auto;
width: 478px;
height: 51px;
margin-bottom: 20px;
display: flex;
align-items: center;
position: relative;
// padding: 0 10px;
cursor: pointer;
&:hover {
background: var(--color-bg-hover);
}
.box8-main-item-left {
position: relative;
width: 42px;
height: 42px;
.left-img {
width: 42px;
height: 42px;
border-radius: 50%;
}
.left-icon1 {
position: absolute;
left: 2px;
bottom: -6px;
width: 20px;
height: 20px;
border-radius: 10px;
background: (255, 255, 255, 0.8);
box-sizing: border-box;
padding: 2px;
img {
width: 100%;
height: 100%;
}
}
.left-icon2 {
position: absolute;
right: 2px;
bottom: -6px;
width: 20px;
height: 20px;
border-radius: 10px;
background: (255, 255, 255, 0.8);
box-sizing: border-box;
padding: 2px;
img {
width: 100%;
height: 100%;
}
}
}
.box8-main-item-center {
height: 48px;
margin-left: 12px;
.box8-main-item-center-top {
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
.box8-main-item-center-footer {
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
.box8-main-item-right {
position: absolute;
top: 0;
right: 10px;
height: 51px;
text-align: right;
color: rgba(95, 101, 108, 1);
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
line-height: 51px;
}
}
}
} }
}
.box9 { .box9-main {
width: 521px; padding: 10px 20px;
height: 450px;
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);
.box9-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 0 27px 0 22px;
.box9-header-left {
display: flex;
.box9-header-icon {
margin-top: 16px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box9-header-title {
margin-top: 11px;
margin-left: 19px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.box9-header-right {
display: flex;
justify-content: flex-end;
gap: 8px;
height: 24px;
margin-top: 12px;
.header-right-icon {
margin-top: 4px;
width: 14px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.header-right-text {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
.box-center {
height: 45px;
padding-right: 20px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.box9-main {
height: 380px;
padding: 10px 20px;
}
}
} }
} }
} }
......
import * as echarts from 'echarts'; import * as echarts from "echarts";
/** /**
* 计算雷达图最外圈半径(px) * 计算雷达图最外圈半径(px)
...@@ -6,9 +6,9 @@ import * as echarts from 'echarts'; ...@@ -6,9 +6,9 @@ import * as echarts from 'echarts';
* @param {number} radarRadius 配置项里 radar.radius,默认 75% * @param {number} radarRadius 配置项里 radar.radius,默认 75%
*/ */
function getOuterRadius(dom, radarRadius = 75) { function getOuterRadius(dom, radarRadius = 75) {
// 容器短边的一半 * 百分比 // 容器短边的一半 * 百分比
const minSide = Math.min(dom.offsetWidth, dom.offsetHeight); const minSide = Math.min(dom.offsetWidth, dom.offsetHeight);
return (minSide / 2) * (radarRadius / 100); return (minSide / 2) * (radarRadius / 100);
} }
/** /**
...@@ -19,95 +19,121 @@ function getOuterRadius(dom, radarRadius = 75) { ...@@ -19,95 +19,121 @@ function getOuterRadius(dom, radarRadius = 75) {
* @param {HTMLDivElement} dom 图表容器(用于计算半径,必须传) * @param {HTMLDivElement} dom 图表容器(用于计算半径,必须传)
*/ */
const getBarChart = (nameList, valueList, isPer, dom) => { const getBarChart = (nameList, valueList, isPer, dom) => {
// 1. 基础雷达图配置 // 1. 基础雷达图配置
const option = { const option = {
title: { text: '' }, title: { text: "" },
legend: { legend: {
show: false, show: false,
icon: 'circle', icon: "circle",
orient: 'vertical', orient: "vertical",
right: 0, right: 0,
top: 'center', top: "center",
align: 'left', align: "left",
data: ['法案', '行政令', '科技智库', '出口管制', '投融资限制', '市场准入'] data: ["法案", "行政令", "科技智库", "出口管制", "投融资限制", "市场准入"]
}, },
radar: { radar: {
shape: 'circle', shape: "circle",
radius: '75%', // 雷达图本身占容器 75% radius: "75%", // 雷达图本身占容器 75%
indicator: [ indicator: [
{ name: '研究人员总数', max: 6500 }, { name: "研究人员总数", max: 6500 },
{ name: '每万人研究人员数', max: 16000 }, { name: "每万人研究人员数", max: 16000 },
{ name: '每万研究人员研发经费投入额', max: 30000 }, { name: "每万研究人员研发经费投入额", max: 30000 },
{ name: '每万人研发经费投入额', max: 38000 }, { name: "每万人研发经费投入额", max: 38000 },
{ name: '研发经费占GDP的比重', max: 52000 }, { name: "研发经费占GDP的比重", max: 52000 },
{ name: '研发经费投入总额', max: 25000 } { name: "研发经费投入总额", max: 25000 }
], ],
axisName: { axisName: {
// 1. 富文本颜色 // 1. 富文本颜色
color: '#3B414B', color: "#3B414B",
fontSize: 14, fontSize: 14,
fontWeight: 600, fontWeight: 600,
// 2. 白色矩形背景 // 2. 白色矩形背景
backgroundColor: '#fff', // 背景色 backgroundColor: "#fff", // 背景色
borderRadius: 4, // 圆角 borderRadius: 4, // 圆角
padding: [4, 8], // 上下 4px,左右 8px padding: [4, 8], // 上下 4px,左右 8px
// 3. 自动换两行(你原来的逻辑) // 3. 自动换两行(你原来的逻辑)
formatter: function (txt) { formatter: function (txt) {
const len = txt.length; const len = txt.length;
if (len <= 5) return txt; if (len <= 5) return txt;
const br = Math.ceil(len / 2); const br = Math.ceil(len / 2);
return txt.substring(0, br) + '\n' + txt.substring(br); return txt.substring(0, br) + "\n" + txt.substring(br);
} }
}, },
splitLine: { splitLine: {
lineStyle: { color: 'rgba(200,200,200,.6)' } lineStyle: { color: "rgba(200,200,200,.6)" }
}, },
splitArea: { show: false }, splitArea: { show: false },
axisLine: { show: false } axisLine: { show: false }
}, },
series: [{ series: [
name: 'Budget vs spending', {
type: 'radar', name: "Budget vs spending",
data: [ type: "radar",
{ value: [4200, 3000, 20000, 35000, 50000, 18000], name: '法案', areaStyle: { color: 'rgba(10, 87, 166, 0.2)' } }, data: [
{ value: [5000, 14000, 28000, 26000, 42000, 21000], name: '行政令', areaStyle: { color: 'rgba(206, 79, 81, 0.2)' } }, {
{ value: [4000, 14000, 18000, 21000, 32000, 10000], name: '科技智库', areaStyle: { color: 'rgba(250, 140, 22, 0.2)' } }, value: [4200, 3000, 20000, 35000, 50000, 18000],
{ value: [4000, 14000, 18000, 21000, 32000, 10000], name: '出口管制', areaStyle: { color: 'rgba(22, 180, 120, 0.2)' } }, name: "法案",
{ value: [4000, 14000, 18000, 21000, 32000, 10000], name: '投融资限制', areaStyle: { color: 'rgba(120, 100, 200, 0.2)' } }, areaStyle: { color: "rgba(10, 87, 166, 0.2)" }
{ value: [4000, 14000, 18000, 21000, 32000, 10000], name: '市场准入', areaStyle: { color: 'rgba(255, 100, 150, 0.2)' } } },
] {
}] value: [5000, 14000, 28000, 26000, 42000, 21000],
}; name: "行政令",
areaStyle: { color: "rgba(206, 79, 81, 0.2)" }
},
{
value: [4000, 14000, 18000, 21000, 32000, 10000],
name: "科技智库",
areaStyle: { color: "rgba(250, 140, 22, 0.2)" }
},
{
value: [4000, 14000, 18000, 21000, 32000, 10000],
name: "出口管制",
areaStyle: { color: "rgba(22, 180, 120, 0.2)" }
},
{
value: [4000, 14000, 18000, 21000, 32000, 10000],
name: "投融资限制",
areaStyle: { color: "rgba(120, 100, 200, 0.2)" }
},
{
value: [4000, 14000, 18000, 21000, 32000, 10000],
name: "市场准入",
areaStyle: { color: "rgba(255, 100, 150, 0.2)" }
}
]
}
]
};
// 2. 计算文字所在圆半径(单位 px)
const textRadius = getRadarTextRadius(dom, 75) + 30; // 75 与 radar.radius 保持一致
// 2. 计算文字所在圆半径(单位 px) // 3. 用“单圈描边”实现「粗线 + 文字在线上」
const textRadius = getRadarTextRadius(dom, 75) + 30; // 75 与 radar.radius 保持一致 option.graphic = [
{
// 3. 用“单圈描边”实现「粗线 + 文字在线上」 type: "circle",
option.graphic = [{ shape: {
type: 'circle', cx: dom.offsetWidth / 2,
shape: { cy: dom.offsetHeight / 2,
cx: dom.offsetWidth / 2, r: textRadius
cy: dom.offsetHeight / 2, },
r: textRadius style: {
}, stroke: "#EBECEE", // 线的颜色
style: { lineWidth: 6, // 线的粗细(想要再粗就继续加大)
stroke: '#EBECEE', // 线的颜色 fill: "transparent"
lineWidth: 6, // 线的粗细(想要再粗就继续加大) }
fill: 'transparent' // z: 1000 // 保证压在最上层
}, }
// z: 1000 // 保证压在最上层 ];
}]; return option;
return option;
}; };
// 1. 先准备一个“半径换算”小工具 // 1. 先准备一个“半径换算”小工具
function getRadarTextRadius(dom, radiusPercent = 75) { function getRadarTextRadius(dom, radiusPercent = 75) {
// 雷达图中心永远在容器中心 // 雷达图中心永远在容器中心
const minSide = Math.min(dom.offsetWidth, dom.offsetHeight); const minSide = Math.min(dom.offsetWidth, dom.offsetHeight);
return (minSide / 2) * (radiusPercent / 100); return (minSide / 2) * (radiusPercent / 100);
} }
export default getBarChart; export default getBarChart;
\ No newline at end of file
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<div class="btn-icon"> <div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt /> <img src="@/assets/icons/arrow-right-icon.png" alt />
</div> </div>
</div> </div>,
<div class="btn" @click="handleToPosi('position4')"> <div class="btn" @click="handleToPosi('position4')">
<div class="btn-text">资源库</div> <div class="btn-text">资源库</div>
<div class="btn-icon"> <div class="btn-icon">
......
...@@ -6,17 +6,35 @@ ...@@ -6,17 +6,35 @@
<div class="icon"> <div class="icon">
<img src="./images/box-header-icon1.png" alt="" /> <img src="./images/box-header-icon1.png" alt="" />
</div> </div>
<div class="title">{{ "提出建议领域分布" }}</div> <div class="title">{{ "政策建议领域分布" }}</div>
<!-- <div class="box-header-right">{{ "查看数据源 >" }}</div> --> <!-- <div class="box-header-right">{{ "查看数据源 >" }}</div> -->
<div class="select-box">
<el-select v-model="box1SelectYear" placeholder="选择时间" style="width: 100px">
<el-option v-for="(item, index) in box1YearList" :key="index" :label="item.label + '年'"
:value="item.value" @click="handleGetThinkPolicyIndustry()" />
</el-select>
</div>
</div> </div>
<div class="box-main"> <div class="box-main">
<div id="box1Chart"></div>
</div>
</div>
<div class="box1 box">
<div class="box-header">
<div class="icon">
<img src="./images/box-header-icon2.png" alt="" />
</div>
<div class="title">{{ "政策建议涉及部门分布" }}</div>
<!-- <div class="box-header-right">{{ "查看数据源 >" }}</div> -->
<div class="select-box"> <div class="select-box">
<el-select v-model="box1SelectYear" placeholder="选择时间" style="width: 100px"> <el-select v-model="box1SelectYear" placeholder="选择时间" style="width: 100px">
<el-option v-for="(item, index) in box1YearList" :key="index" :label="item.label" :value="item.value" <el-option v-for="(item, index) in box1YearList" :key="index" :label="item.label + '年'"
@click="handleGetThinkPolicyIndustry()" /> :value="item.value" @click="handleGetThinkPolicyIndustry()" />
</el-select> </el-select>
</div> </div>
<div id="box1Chart"></div> </div>
<div class="box-main">
<div id="box2Chart"></div>
</div> </div>
</div> </div>
<!-- <div class="box2 box"> <!-- <div class="box2 box">
...@@ -53,14 +71,14 @@ ...@@ -53,14 +71,14 @@
</div> </div>
<div class="title">{{ "热门研究方向变化趋势" }}</div> <div class="title">{{ "热门研究方向变化趋势" }}</div>
<!-- <div class="box-header-right">{{ "查看数据源 >" }}</div> --> <!-- <div class="box-header-right">{{ "查看数据源 >" }}</div> -->
</div>
<div class="box-main">
<div class="select-box"> <div class="select-box">
<el-select v-model="box3SelectMonths" placeholder="选择时间" style="width: 100px"> <el-select v-model="box3SelectMonths" placeholder="选择时间" style="width: 100px">
<el-option v-for="item in box3MonthsList" :key="item.value" :label="item.label" :value="item.value" <el-option v-for="item in box3MonthsList" :key="item.value" :label="item.label + '年'" :value="item.value"
@click="handleGetThinkPolicyIndustryChange()" /> @click="handleGetThinkPolicyIndustryChange()" />
</el-select> </el-select>
</div> </div>
</div>
<div class="box-main">
<div id="box3Chart"></div> <div id="box3Chart"></div>
</div> </div>
</div> </div>
...@@ -78,43 +96,68 @@ ...@@ -78,43 +96,68 @@
<el-select v-model="selectedYear" placeholder="选择时间" style="width: 120px" @click="handleGetThinkPolicy()"> <el-select v-model="selectedYear" placeholder="选择时间" style="width: 120px" @click="handleGetThinkPolicy()">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
<div class="paixu-btn" @click="handleSwithSort()"> <el-select class="select-box-sort" v-model="sort" placeholder="倒序" style="width: 120px;" :teleported="true"
<div class="icon1"> :placement="'bottom-start'" :popper-options="{
<img v-if="sort" src="@/assets/icons/shengxu1.png" alt="" /> modifiers: [
<img v-else src="@/assets/icons/jiangxu1.png" alt="" /> {
</div> name: 'preventOverflow',
<div class="text">{{ "发布时间" }}</div> options: { mainAxis: false, altAxis: false }
<div class="icon2"> },
<img v-if="sort" src="@/assets/icons/shengxu2.png" alt="" /> {
<img v-else src="@/assets/icons/jiangxu2.png" alt="" /> name: 'flip',
</div> enabled: false
</div> }
<!-- <el-select v-model="sort" placeholder="发布时间" style="width: 120px; margin-left: 8px"> ]
}">
<template #prefix>
<img v-if="!sort" src="../thinkDynamics/images/image down.png" class="select-prefix-img" alt="" />
<img v-else src="../thinkDynamics/images/image up.png" class="select-prefix-img" alt="" />
</template>
<el-option @click="handleGetThinkPolicy()" :key="true" label="正序" :value="true" /> <el-option @click="handleGetThinkPolicy()" :key="true" label="正序" :value="true" />
<el-option @click="handleGetThinkPolicy()" :key="false" label="倒序" :value="false" /> <el-option @click="handleGetThinkPolicy()" :key="false" label="倒序" :value="false" />
</el-select> --> </el-select>
</div> </div>
</div> </div>
<div class="bottom-main"> <div class="bottom-main">
<div class="left"> <div class="left">
<div class="select-box"> <div class="select-box-science">
<div class="select-box-header" style=" display: flex;"> <div class="select-box-header">
<div class="icon"></div> <div class="icon"></div>
<div class="title">{{ "科技领域" }}</div> <div class="title">{{ "科技领域" }}</div>
</div> </div>
<div class="select-main" style="padding: 25px;"> <div class="select-main">
<div class="checkbox-group"> <div class="checkbox-group">
<!-- <el-checkbox v-for="(item, index) in areaList" :key="index" v-model="selectedAreaList" :label="item" <!-- <el-checkbox v-for="(item, index) in areaList" :key="index" v-model="selectedAreaList" :label="item"
class="filter-checkbox"> class="filter-checkbox">
{{ item }} {{ item }}
</el-checkbox> --> </el-checkbox> -->
<el-checkbox style="width: 180px" v-for="research in areaList" :key="research.id" <el-checkbox class="filter-checkbox" label="全部领域"></el-checkbox>
v-model="selectedAreaList" :label="research.id" class="filter-checkbox"> <el-checkbox class="filter-checkbox" v-for="research in areaList" :key="research.id"
v-model="selectedAreaList" :label="research.id">
{{ research.name }} {{ research.name }}
</el-checkbox> </el-checkbox>
</div> </div>
</div> </div>
</div> </div>
<div class="select-box-publish">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "发布时间" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<!-- <el-checkbox v-for="(item, index) in areaList" :key="index" v-model="selectedAreaList" :label="item"
class="filter-checkbox">
{{ item }}
</el-checkbox> -->
<el-checkbox class="filter-checkbox" label="全部领域"></el-checkbox>
<el-checkbox class="filter-checkbox" v-for="year in selectableYears" :key="year"
v-model="selectedAreaList" :label="year">
{{ year }}
</el-checkbox>
</div>
</div>
</div>
</div> </div>
<div class="right"> <div class="right">
<div class="right-main"> <div class="right-main">
...@@ -242,7 +285,7 @@ const box1YearList = ref([ ...@@ -242,7 +285,7 @@ const box1YearList = ref([
value: "2023" value: "2023"
} }
]); ]);
const selectableYears = ref(["2025年", "2024年", "2023年", "2022年", "2021年", "更早"]);
const handleGetThinkPolicyIndustry = async () => { const handleGetThinkPolicyIndustry = async () => {
try { try {
const parmas = { const parmas = {
...@@ -263,6 +306,7 @@ const handleGetThinkPolicyIndustry = async () => { ...@@ -263,6 +306,7 @@ const handleGetThinkPolicyIndustry = async () => {
box1Data.value = data; box1Data.value = data;
const box1Chart = getPieChart(box1Data.value); const box1Chart = getPieChart(box1Data.value);
setChart(box1Chart, "box1Chart"); setChart(box1Chart, "box1Chart");
setChart(box1Chart, "box2Chart");
} }
} catch (error) { } catch (error) {
console.error("获取提出建议领域分布error", error); console.error("获取提出建议领域分布error", error);
...@@ -456,7 +500,7 @@ const selectedAreaList = ref([]); ...@@ -456,7 +500,7 @@ const selectedAreaList = ref([]);
const handleGetHylyList = async () => { const handleGetHylyList = async () => {
try { try {
const res = await getHylyList(); const res = await getHylyList();
console.log("智库研究类型信息", res); console.log("智库研究类型信息", res.data);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
areaList.value = res.data; areaList.value = res.data;
} }
...@@ -616,11 +660,11 @@ onMounted(() => { ...@@ -616,11 +660,11 @@ onMounted(() => {
.top { .top {
height: 420px; height: 420px;
width: 1600px; width: 1600px;
margin: 24px 0; margin: 16px 0;
display: flex; display: flex;
gap: 16px; gap: 16px;
.box { .box {
width: 790px; width: 520px;
height: 420px; height: 420px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1); border: 1px solid rgba(234, 236, 238, 1);
...@@ -629,7 +673,7 @@ onMounted(() => { ...@@ -629,7 +673,7 @@ onMounted(() => {
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
.box-header { .box-header {
width: 790px; width: 520px;
height: 48px; height: 48px;
border-bottom: 1px solid rgba(234, 236, 238, 1); border-bottom: 1px solid rgba(234, 236, 238, 1);
display: flex; display: flex;
...@@ -673,87 +717,34 @@ onMounted(() => { ...@@ -673,87 +717,34 @@ onMounted(() => {
letter-spacing: 0px; letter-spacing: 0px;
text-align: right; text-align: right;
} }
}
.box-main {
height: 360px;
position: relative;
overflow: hidden;
#box1Chart {
height: 360px;
}
#box2Chart {
width: 470px;
margin: 0 auto;
margin-top: 50px;
height: 300px;
overflow: hidden;
overflow-y: auto;
.box2-item {
height: 30px;
margin-top: 20px;
display: flex;
align-items: center;
.icon {
width: 12px;
height: 12px;
border-radius: 6px;
}
.name {
width: 120px;
margin-left: 7px;
margin-right: 23px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.num {
width: 100px;
margin-left: 10px;
height: 22px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
text-align: right;
}
.per {
margin-left: 5px;
height: 22px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
}
#box3Chart {
height: 360px;
}
.select-box { .select-box {
position: absolute; position: absolute;
z-index: 999; z-index: 999;
top: 13px; top: 12px;
right: 33px; right: 43px;
width: 100px; width: 100px;
height: 28px; height: 28px;
.select-box-sort {
background: rgb(255, 255, 255);
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
border: 1px solid rgb(230, 231, 232);
border-radius: 4px;
height: 32px;
}
:deep(.el-select-dropdown) {
left: 0 !important;
/* 强制下拉框左对齐选择框 */
top: 100% !important;
/* 强制下拉框在选择框正下方 */
transform: none !important;
/* 禁用默认的位移变换 */
}
.paixu-btn { .paixu-btn {
display: flex; display: flex;
width: 120px; width: 120px;
...@@ -808,6 +799,26 @@ onMounted(() => { ...@@ -808,6 +799,26 @@ onMounted(() => {
} }
} }
} }
.box-main {
height: 360px;
position: relative;
overflow: hidden;
#box1Chart {
height: 360px;
}
#box2Chart {
height: 360px;
}
#box3Chart {
height: 360px;
}
}
} }
} }
...@@ -821,18 +832,26 @@ onMounted(() => { ...@@ -821,18 +832,26 @@ onMounted(() => {
.search-box { .search-box {
display: flex; display: flex;
width: 300px; width: 360px;
height: 32px; height: 32px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1); border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px; border-radius: 4px;
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
position: relative;
.icon { .icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
margin: 8px 7px;
cursor: pointer; cursor: pointer;
position: absolute;
right: 8px;
top: 8px;
display: flex;
justify-content: flex-end;
img { img {
width: 100%; width: 100%;
...@@ -905,17 +924,165 @@ onMounted(() => { ...@@ -905,17 +924,165 @@ onMounted(() => {
justify-content: space-between; justify-content: space-between;
.left { .left {
width: 300px; width: 360px;
height: 800px; height: 100%;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1); border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
.select-box { .select-box-science {
margin-top: 21px; margin-top: 16px;
.select-box-header {
display: flex;
.title {
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 16px;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
}
.select-main {
margin-top: 12px;
}
.checkbox-group {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px 4px;
margin-left: 24px;
.filter-checkbox {
width: 160px;
margin: 0;
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
}
.paixu-btn {
display: flex;
width: 120px;
height: 32px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
&:hover {
background: var(--color-bg-hover);
}
cursor: pointer;
.icon1 {
width: 11px;
height: 14px;
margin-top: 10px;
margin-left: 9px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 19px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0px;
text-align: left;
margin-top: 7px;
margin-left: 9px;
}
.icon2 {
width: 10px;
height: 5px;
margin-top: 5px;
margin-left: 13px;
img {
width: 100%;
height: 100%;
}
}
}
.icon {
margin-top: 4px;
width: 8px;
height: 16px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.title {
color: rgba(5, 95, 194, 1);
margin-left: 17px;
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: left;
}
}
.select-box-publish {
margin-top: 24px;
margin-bottom: 24px;
.select-box-header {
display: flex;
.title {
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 16px;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
}
.select-main {
margin-top: 12px;
}
.checkbox-group {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px 4px;
margin-left: 24px;
.filter-checkbox {
width: 160px;
margin: 0;
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
}
.paixu-btn { .paixu-btn {
display: flex; display: flex;
...@@ -993,7 +1160,7 @@ onMounted(() => { ...@@ -993,7 +1160,7 @@ onMounted(() => {
} }
.right { .right {
width: 1284px; width: 1224px;
max-height: 1670px; max-height: 1670px;
margin-bottom: 20px; margin-bottom: 20px;
box-sizing: border-box; box-sizing: border-box;
...@@ -1146,4 +1313,10 @@ onMounted(() => { ...@@ -1146,4 +1313,10 @@ onMounted(() => {
} }
} }
} }
:deep(.select-prefix-img) {
width: 8px !important;
height: 8px !important;
margin-right: 4px;
}
</style> </style>
\ No newline at end of file
...@@ -17,7 +17,12 @@ ...@@ -17,7 +17,12 @@
<AreaTag v-for="(tag, index) in thinkTank.tags" :key="index" :tagName="tag.industryName"></AreaTag> <AreaTag v-for="(tag, index) in thinkTank.tags" :key="index" :tagName="tag.industryName"></AreaTag>
</div> </div>
</div> </div>
<!-- <div class="header-top-right">{{ '查看智库官网' }}</div> --> <div class="header-top-right">
<button class="blue-btn">
<img class="btn-img" src="./images/image1.png" alt="" />
<span class="text">{{ '查看智库官网' }}</span>
</button>
</div>
</div> </div>
<div class="header-footer"> <div class="header-footer">
<div class="tab" :class="{ tabActive: tabActiveName === '智库动态' }" @click="switchTab('智库动态')"> <div class="tab" :class="{ tabActive: tabActiveName === '智库动态' }" @click="switchTab('智库动态')">
...@@ -94,19 +99,22 @@ onMounted(async () => { ...@@ -94,19 +99,22 @@ onMounted(async () => {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
overflow-y: auto; overflow-y: auto;
.header { .header {
width: 100%; width: 100%;
height: 188px; height: 188px;
box-sizing: border-box; box-sizing: border-box;
padding: 0 160px;
border-bottom: 1px solid rgba(234, 236, 238, 1); border-bottom: 1px solid rgb(234, 236, 238);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); border-top: 1px solid rgb(234, 236, 238);
box-shadow: 0 0 20px 0 rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
position: relative; position: relative;
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 99999; z-index: 99999;
overflow: hidden; overflow: visible;
.header-top { .header-top {
width: 1600px; width: 1600px;
margin: 0 auto; margin: 0 auto;
...@@ -116,6 +124,8 @@ onMounted(async () => { ...@@ -116,6 +124,8 @@ onMounted(async () => {
.header-top-left { .header-top-left {
width: 88px; width: 88px;
height: 88px; height: 88px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -130,8 +140,8 @@ onMounted(async () => { ...@@ -130,8 +140,8 @@ onMounted(async () => {
.name { .name {
height: 26px; height: 26px;
color: rgba(59, 65, 75, 1); color: rgb(59, 65, 75);
font-family: Microsoft YaHei; font-family: 'Source Han Sans CN';
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
line-height: 26px; line-height: 26px;
...@@ -142,11 +152,11 @@ onMounted(async () => { ...@@ -142,11 +152,11 @@ onMounted(async () => {
.e-name { .e-name {
margin-left: 11px; margin-left: 11px;
height: 26px; height: 26px;
color: rgba(132, 136, 142, 1); color: rgb(132, 136, 142);
font-family: Microsoft YaHei; font-family: 'Source Han Sans CN';
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
line-height: 26px; line-height: 22px;
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
} }
...@@ -156,12 +166,13 @@ onMounted(async () => { ...@@ -156,12 +166,13 @@ onMounted(async () => {
margin-top: 4px; margin-top: 4px;
width: 769px; width: 769px;
height: 24px; height: 24px;
color: rgba(132, 136, 142, 1); color: rgb(132, 136, 142);
font-family: Microsoft YaHei; font-family: 'Source Han Sans CN';
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
line-height: 24px; line-height: 24px;
letter-spacing: 0px; letter-spacing: 0px;
text-align: justify;
} }
.center-footer { .center-footer {
...@@ -171,18 +182,71 @@ onMounted(async () => { ...@@ -171,18 +182,71 @@ onMounted(async () => {
.tag { .tag {
height: 26px; height: 26px;
line-height: 26px;
text-align: center;
padding: 0 8px; padding: 1px 8px 1px 8px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(231, 243, 255, 1); border: 1px solid rgb(231, 243, 255);
border-radius: 4px; border-radius: 4px;
background: rgba(246, 250, 255, 1); background: rgb(246, 250, 255);
color: rgba(5, 95, 194, 1); position: relative;
display: inline-flex;
align-items: center;
.tag-industryName {
color: rgb(5, 95, 194);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 23px;
text-align: left;
letter-spacing: 0px;
}
}
}
}
.header-top-right {
margin-left: auto;
height: 36px;
box-sizing: border-box;
position: relative;
.blue-btn {
height: 100%;
box-sizing: border-box;
border: none;
outline: none;
border-radius: 6px;
background: rgba(5, 95, 194, 1);
padding: 7px 16px;
display: flex;
align-items: center;
&:focus {
outline: none;
}
.btn-img {
width: 16px;
height: 16px;
margin-left: 0px;
margin-top: 3px;
margin-bottom: 3px;
margin-right: 8px;
}
.text {
color: #fff;
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
line-height: 24px; line-height: 22px;
} }
} }
} }
...@@ -191,14 +255,14 @@ onMounted(async () => { ...@@ -191,14 +255,14 @@ onMounted(async () => {
.header-footer { .header-footer {
width: 1600px; width: 1600px;
margin: 0 auto; margin: 0 auto;
margin-top: 25px; margin-top: 28px;
height: 48px; height: 48px;
display: flex; display: flex;
gap: 24px; gap: 24px;
.tab { .tab {
width: 94px;
height: 48px; height: 48px;
box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
...@@ -218,21 +282,21 @@ onMounted(async () => { ...@@ -218,21 +282,21 @@ onMounted(async () => {
.text { .text {
height: 24px; height: 24px;
color: rgba(59, 65, 75, 1); color: rgb(59, 65, 75);
font-family: Microsoft YaHei; font-family: 'Source Han Sans CN';
font-size: 18px; font-size: 18px;
font-weight: 400; font-weight: 400;
line-height: 24px; line-height: 24px;
} }
.textActive { .textActive {
color: rgba(5, 95, 194, 1); color: rgb(5, 95, 194);
font-weight: 700; font-weight: 700;
} }
} }
.tabActive { .tab.tabActive {
border-bottom: 2px solid rgba(5, 95, 194, 1); border-bottom: 2px solid rgb(5, 95, 194);
} }
} }
} }
...@@ -241,167 +305,7 @@ onMounted(async () => { ...@@ -241,167 +305,7 @@ onMounted(async () => {
margin: 0 auto; margin: 0 auto;
width: 1600px; width: 1600px;
.main-header {
height: 64px;
display: flex;
justify-content: space-between;
.search-box {
margin-top: 16px;
display: flex;
width: 300px;
height: 32px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
.icon {
width: 16px;
height: 16px;
margin: 8px 7px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
.select-box {
margin-top: 16px;
margin-right: 5px;
}
}
.main-content {
display: flex;
gap: 16px;
.left {
width: 300px;
height: 806px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.select-box {
margin-top: 21px;
.select-box-header {
display: flex;
gap: 17px;
.icon {
margin-top: 4px;
width: 8px;
height: 16px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.title {
height: 24px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
}
.select-main {
margin-left: 25px;
}
.select-main1 {
width: 100px;
}
}
}
.right {
width: 1284px;
height: 1377px;
.card-box {
width: 1284px;
height: 1248px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.footer-card {
width: 418px;
height: 300px;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.footer-card-top {
width: 384px;
height: 206px;
margin: 0 auto;
margin-top: 15px;
img {
width: 100%;
height: 100%;
}
}
.footer-card-title {
margin: 0 auto;
margin-top: 13px;
width: 376px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
line-height: 24px;
}
.footer-card-footer {
margin: 0 auto;
margin-top: 5px;
width: 376px;
height: 22px;
display: flex;
justify-content: space-between;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
}
.right-footer {
margin-top: 43px;
display: flex;
justify-content: space-between;
.info {
height: 19px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0px;
text-align: left;
}
}
}
}
} }
} }
......
<template>
<div class="main-content">
<div class="left">
<!-- <div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "报告类型" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox v-for="type in reportTypeList" :key="type.id" v-model="selectedReportTypeList"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.typeName }}
</el-checkbox>
</div>
</div>
</div> -->
<div class="select-research-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "研究类型" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox label="全部领域"></el-checkbox>
<el-checkbox v-for="type in researchTypeList" :key="type.id" v-model="selectedResearchTypeList"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.name }}
</el-checkbox>
</div>
</div>
</div>
<div class="select-time-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "发布时间" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox label="全部时间"></el-checkbox>
<el-checkbox v-for="type in researchTimeList" :key="type.id" v-model="selectedResearchTypeList"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.name }}
</el-checkbox>
</div>
</div>
<!-- <div class="input-main">
<el-input placeholder="输入作者名字" v-model="author" @input="handleGetThinkDynamicsReport()" />
</div> -->
</div>
<div class="select-hearing-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "听证会部门" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox label="全部部门"></el-checkbox>
<el-checkbox v-for="type in researchHearingList" :key="type.id" v-model="selectedResearchTypeList"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.name }}
</el-checkbox>
</div>
</div>
<!-- <div class="input-main">
<el-input placeholder="输入作者名字" v-model="author" @input="handleGetThinkDynamicsReport()" />
</div> -->
</div>
</div>
<div class="right">
<div class="card-box">
<div class="card-content">
<div v-for="(item, index) in displayList" :key="item.id">
<div class="card-item">
<img class="card-item-img" src="../images/img congress.png" alt="report image" />
<div class="card-item-text">
<div class="card-item-title">
{{ item.title }}
</div>
<div class="card-item-time">
{{ item.time + ' · ' + item.content }}
<img src="../images/image open.png" alt="open icon" class="card-open-image" />
</div>
<div class="card-item-category"> {{ item.category }}</div>
</div>
</div>
<div class="divider" v-if="index !== displayList.length - 1"></div>
</div>
</div>
</div>
<div class="right-footer">
<div class="info">
{{ hearingData.length }}
</div>
<div class="page-box">
<el-pagination :page-size="10" background layout="prev, pager, next" :total="hearingData.length"
@current-change="handleCurrentChange" :current-page="currentPage" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, toRefs, watch, computed } from "vue";
const props = defineProps({
researchTypeList: {
type: Array,
default: () => []
},
researchTimeList: {
type: Array,
default: () => []
},
researchHearingList: {
type: Array,
default: () => []
},
hearingData: {
type: Array,
default: () => []
},
selectedResearchTypeList: {
type: Array,
default: () => []
},
curFooterList: {
type: Array,
default: () => []
},
total: {
type: Number,
default: 0
},
currentPage: {
type: Number,
default: 1
}
});
const emit = defineEmits([
"update:selectedResearchTypeList",
"filter-change",
"page-change",
"report-click"
]);
// 解构 props,保持模板里变量名不变
const { researchTypeList, researchTimeList, curFooterList, total, currentPage, researchHearingList, hearingData } = toRefs(props);
const pageSize = 10;
// 只展示当前页的 12 条,否则会一页显示全部 20 条
const displayList = computed(() => {
const list = hearingData.value || [];
const start = (currentPage.value - 1) * pageSize;
return list.slice(start, start + pageSize);
});
// 用本地 ref 替代 computed,el-checkbox 会直接修改数组,需要可变的 ref
const selectedResearchTypeList = ref([...(props.selectedResearchTypeList || [])]);
// 父组件更新时同步到子组件
watch(
() => props.selectedResearchTypeList,
val => {
selectedResearchTypeList.value = val ? [...val] : [];
},
{ immediate: true, deep: true }
);
// 子组件勾选变化时通知父组件,flush: 'sync' 确保在 @change 触发前父组件已更新,否则 filter-change 时父组件拿到的还是旧值
watch(
selectedResearchTypeList,
val => {
emit("update:selectedResearchTypeList", val);
},
{ deep: true, flush: "sync" }
);
// 保持模板里的方法名不变,但改成通知父组件,直接传入当前选中值避免时序问题
const handleGetThinkDynamicsReport = () => {
emit("update:selectedResearchTypeList", [...selectedResearchTypeList.value]);
emit("filter-change", [...selectedResearchTypeList.value]);
};
const handleCurrentChange = page => {
emit("page-change", page);
};
const handleToReportDetail = item => {
emit("report-click", item);
};
</script>
<style lang="scss" scoped>
.main-content {
display: flex;
gap: 16px;
.left {
width: 360px;
height: 874px;
padding-bottom: 36px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.select-research-box {
width: 360px;
height: 284px;
margin-top: 21px;
.select-box-header {
display: flex;
gap: 17px;
margin-bottom: 12px;
.icon {
margin-top: 4px;
width: 8px;
height: 16px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.title {
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 1px;
text-align: left;
}
}
.select-main {
margin-left: 25px;
.checkbox-group {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
}
.select-main1 {
width: 100px;
}
.input-main {
margin: 10px auto;
width: 250px;
height: 34px;
border-radius: 10px;
border: 1px solid rgba(230, 231, 232, 1);
}
}
.select-time-box {
margin-top: 21px;
width: 360px;
height: 156px;
.select-box-header {
margin-bottom: 12px;
display: flex;
gap: 17px;
.icon {
margin-top: 4px;
width: 8px;
height: 16px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.title {
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 1px;
text-align: left;
}
}
.select-main {
margin-left: 25px;
.checkbox-group {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
}
.select-main1 {
width: 100px;
}
.input-main {
margin: 10px auto;
width: 250px;
height: 34px;
border-radius: 10px;
border: 1px solid rgba(230, 231, 232, 1);
}
}
.select-hearing-box {
margin-top: 21px;
width: 360px;
.select-box-header {
display: flex;
gap: 17px;
.icon {
margin-top: 4px;
width: 8px;
height: 16px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.title {
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 1px;
text-align: left;
}
}
.select-main {
margin-left: 25px;
.checkbox-group {
display: grid;
}
}
.select-main1 {
width: 100px;
}
.input-main {
margin: 10px auto;
width: 250px;
height: 34px;
border-radius: 10px;
border: 1px solid rgba(230, 231, 232, 1);
}
}
}
.right {
width: 1224px;
height: 1377px;
.card-box {
width: 100%;
height: 1248px;
display: flex;
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
background: rgba(255, 255, 255, 1);
.card-content {
width: 1211px;
height: 1067px;
margin-top: 33px;
margin-left: 37px;
.card-item {
width: 100%;
height: 77px;
display: flex;
align-items: center;
.card-item-img {
width: 56px;
height: 77px;
margin-right: 22px;
flex-shrink: 0;
}
.card-item-text {
flex: 1;
min-width: 0;
flex-direction: column;
justify-content: space-between;
display: flex;
.card-item-title {
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-size: 18px;
font-weight: 700;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
margin-bottom: 2px;
display: inline-flex;
}
.card-item-time {
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN";
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
margin-bottom: 7px;
text-align: left;
display: inline-flex;
.card-open-image {
width: 16px;
height: 16px;
margin-left: 9px;
margin-top: 3px;
}
}
.card-item-category {
padding: 1px 8px;
box-sizing: border-box;
border: 1px solid rgb(231, 243, 255);
border-radius: 4px;
display: inline-flex;
background-color: rgb(247, 248, 249);
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: 0px;
text-align: left;
width: fit-content;
}
}
}
.divider {
height: 1px;
background: rgb(234, 236, 238);
margin: 16px 0;
}
}
}
.right-footer {
margin-top: 43px;
display: flex;
justify-content: space-between;
.info {
height: 19px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0px;
text-align: left;
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="main-content">
<div class="left">
<!-- <div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "报告类型" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox v-for="type in reportTypeList" :key="type.id" v-model="selectedReportTypeList"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.typeName }}
</el-checkbox>
</div>
</div>
</div> -->
<div class="select-research-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "研究类型" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox label="全部领域" class="filter-checkbox"></el-checkbox>
<el-checkbox v-for="type in researchTypeList" :key="type.id" v-model="selectedResearchTypeList"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.name }}
</el-checkbox>
</div>
</div>
</div>
<div class="select-time-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "发布时间" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox label="全部时间"></el-checkbox>
<el-checkbox v-for="type in researchTimeList" :key="type.id" v-model="selectedResearchTypeList"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.name }}
</el-checkbox>
</div>
</div>
<!-- <div class="input-main">
<el-input placeholder="输入作者名字" v-model="author" @input="handleGetThinkDynamicsReport()" />
</div> -->
</div>
</div>
<div class="right">
<div class="card-box">
<div class="footer-card" v-for="(item, index) in curFooterList" :key="index"
@click="handleToReportDetail(item)">
<div class="footer-card-top">
<img :src="item.imageUrl" alt="" />
</div>
<div class="footer-card-title">
{{ item.name }}
</div>
<div class="footer-card-footer">
<div class="time">{{ item.times }}</div>
<div class="from">{{ item.thinkTankName }}</div>
</div>
</div>
</div>
<div class="right-footer">
<div class="info">
{{ total }} 篇智库报告
</div>
<div class="page-box">
<el-pagination :page-size="12" background layout="prev, pager, next" :total="total"
@current-change="handleCurrentChange" :current-page="currentPage" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, toRefs, watch } from "vue";
const props = defineProps({
researchTypeList: {
type: Array,
default: () => []
},
researchTimeList: {
type: Array,
default: () => []
},
selectedResearchTypeList: {
type: Array,
default: () => []
},
curFooterList: {
type: Array,
default: () => []
},
total: {
type: Number,
default: 0
},
currentPage: {
type: Number,
default: 1
}
});
const emit = defineEmits([
"update:selectedResearchTypeList",
"filter-change",
"page-change",
"report-click"
]);
// 解构 props,保持模板里变量名不变
const { researchTypeList, researchTimeList, curFooterList, total, currentPage } = toRefs(props);
// 用本地 ref 替代 computed,el-checkbox 会直接修改数组,需要可变的 ref
const selectedResearchTypeList = ref([...(props.selectedResearchTypeList || [])]);
// 父组件更新时同步到子组件
watch(
() => props.selectedResearchTypeList,
val => {
selectedResearchTypeList.value = val ? [...val] : [];
},
{ immediate: true, deep: true }
);
// 子组件勾选变化时通知父组件,flush: 'sync' 确保在 @change 触发前父组件已更新,否则 filter-change 时父组件拿到的还是旧值
watch(
selectedResearchTypeList,
val => {
emit("update:selectedResearchTypeList", val);
},
{ deep: true, flush: "sync" }
);
// 保持模板里的方法名不变,但改成通知父组件,直接传入当前选中值避免时序问题
const handleGetThinkDynamicsReport = () => {
emit("update:selectedResearchTypeList", [...selectedResearchTypeList.value]);
emit("filter-change", [...selectedResearchTypeList.value]);
};
const handleCurrentChange = page => {
emit("page-change", page);
};
const handleToReportDetail = item => {
emit("report-click", item);
};
</script>
<style lang="scss" scoped>
.main-content {
display: flex;
gap: 16px;
.left {
width: 360px;
height: 541px;
padding-bottom: 36px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.select-research-box {
width: 360px;
height: 284px;
margin-top: 21px;
.select-box-header {
display: flex;
gap: 17px;
margin-bottom: 12px;
.icon {
margin-top: 4px;
width: 8px;
height: 16px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.title {
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 1px;
text-align: left;
}
}
.select-main {
margin-left: 25px;
.checkbox-group {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px 4px;
.filter-checkbox {
width: 160px;
height: 24px;
}
}
}
.select-main1 {
width: 100px;
}
.input-main {
margin: 10px auto;
width: 250px;
height: 34px;
border-radius: 10px;
border: 1px solid rgba(230, 231, 232, 1);
}
}
.select-time-box {
margin-top: 21px;
width: 360px;
height: 156px;
.select-box-header {
display: flex;
gap: 17px;
.icon {
margin-top: 4px;
width: 8px;
height: 16px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.title {
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 1px;
text-align: left;
}
}
.select-main {
margin-left: 25px;
.checkbox-group {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
}
.select-main1 {
width: 100px;
}
.input-main {
margin: 10px auto;
width: 250px;
height: 34px;
border-radius: 10px;
border: 1px solid rgba(230, 231, 232, 1);
}
}
}
.right {
width: 1284px;
height: 1377px;
.card-box {
width: 1226px;
height: 1248px;
display: flex;
flex-wrap: wrap;
gap: 13px;
.footer-card {
width: 398px;
height: 300px;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
position: relative;
&:hover {
transform: translateY(-3px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
cursor: pointer;
.footer-card-top {
width: 364px;
height: 180px;
margin: 0 auto;
margin-top: 15px;
position: relative;
img {
width: 100%;
height: 100%;
}
}
.footer-card-title {
margin: 0 auto;
margin-top: 12px;
width: 360px;
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-size: 18px;
font-weight: 700;
line-height: 24px;
// 多行省略 + 自动换行(兼容写法)
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box; // webkit 私有:开启弹性盒模型
display: box; // 标准写法(兜底)
-webkit-line-clamp: 2; // webkit 私有:限制行数
line-clamp: 2; // 标准属性:解决兼容性提示
-webkit-box-orient: vertical; // webkit 私有:垂直排列
box-orient: vertical; // 标准写法(兜底)
// 基础换行属性(确保文字能换行)
word-wrap: break-word;
word-break: break-all;
white-space: normal;
}
.footer-card-footer {
position: absolute;
left: 50%;
bottom: 15px;
transform: translateX(-50%);
margin: 0;
width: 360px;
height: 22px;
display: flex;
justify-content: space-between;
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN";
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
}
.right-footer {
margin-top: 35px;
display: flex;
justify-content: space-between;
.info {
height: 19px;
color: rgb(132, 136, 142);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0px;
text-align: left;
}
}
}
}
:deep(.el-checkbox) {
margin-right: 0 !important;
}
</style>
\ No newline at end of file
...@@ -3,91 +3,87 @@ ...@@ -3,91 +3,87 @@
<div class="main-header"> <div class="main-header">
<div class="search-box"> <div class="search-box">
<el-input placeholder="搜索智库报告" v-model="searchReport" style="width: 270px" /> <el-input placeholder="搜索智库动态" v-model="searchReport" style="width: 270px" />
<div class="icon"> <div class="icon">
<img src="./images/search-icon.png" alt="" @click="handleGetThinkDynamicsReport()" /> <img src="./images/search-icon.png" alt="" @click="handleGetThinkDynamicsReport()" />
</div> </div>
</div> </div>
<div> <div class="report-btn-group">
<div class="select-box"> <div class="report-btn" v-for="type in choseTypeList" :key="type.id"
<el-select v-model="selectedYear" placeholder="选择时间" style="width: 120px"> :class="{ active: activeTypeId === type.id }" @click="handleChooseType(type)">
<el-option @click="handleGetThinkDynamicsReport()" v-for="item in yearList" :key="item.value" <div class="text"> {{ type.name }}</div>
:label="item.label" :value="item.value" />
</el-select>
<el-select v-model="sort" placeholder="发布时间" style="width: 120px; margin-left: 8px;">
<el-option @click="handleGetThinkDynamicsReport()" :key="true" label="正序" :value="true" />
<el-option @click="handleGetThinkDynamicsReport()" :key="false" label="倒序" :value="false" />
</el-select>
</div> </div>
</div> </div>
</div> <div>
<div class="main-content">
<div class="left">
<!-- <div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "报告类型" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox v-for="type in reportTypeList" :key="type.id" v-model="selectedReportTypeList"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.typeName }}
</el-checkbox>
</div>
</div>
</div> -->
<div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "研究类型" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox v-for="type in researchTypeList" :key="type.id" v-model="selectedResearchTypeList"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.name }}
</el-checkbox>
</div>
</div>
</div>
<div class="select-box"> <div class="select-box">
<div class="select-box-header"> <div class="select-box-time">
<div class="icon"></div>
<div class="title">{{ "作者" }}</div> <el-select v-model="selectedYear" placeholder="近一年以来" style="width: 120px" :teleported="true"
:placement="'bottom-start'" :popper-options="{
modifiers: [
{
name: 'preventOverflow', // 禁用自动翻转逻辑
options: {
mainAxis: false, // 禁用垂直方向的自动调整
altAxis: false, // 禁用水平方向的自动调整
}
},
{
name: 'flip', // 完全禁用翻转功能
enabled: false
}
]
}">
<el-option @click="handleGetThinkDynamicsReport()" v-for="item in yearList" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</div> </div>
<div class="input-main"> <div class="select-box-sort">
<el-input placeholder="输入作者名字" v-model="author" @input="handleGetThinkDynamicsReport()" /> <el-select v-model="sort" placeholder="倒序" style="width: 120px" :teleported="true"
</div> :placement="'bottom-start'" :popper-options="{
</div> modifiers: [
</div> {
<div class="right"> name: 'preventOverflow', // 禁用自动翻转逻辑
<div class="card-box"> options: {
<div class="footer-card" v-for="(item, index) in curFooterList" :key="index" mainAxis: false, // 禁用垂直方向的自动调整
@click="handleToReportDetail(item)"> altAxis: false, // 禁用水平方向的自动调整
<div class="footer-card-top"> }
<img :src="item.imageUrl" alt="" /> },
</div> {
<div class="footer-card-title"> name: 'flip', // 完全禁用翻转功能
{{ item.name }} enabled: false
</div> }
<div class="footer-card-footer"> ]
<div class="time">{{ item.times }}</div> }">
<div class="from">{{ item.thinkTankName }}</div> <template #prefix>
</div> <img src="./images/image down.png" class="select-prefix-img" alt="" @click.stop="toggleSortAndFetch()"
</div> :key="true" label="正序" :value="true" v-if="!sort" />
</div> <img src="./images/image up.png" class="select-prefix-img" alt="" @click.stop="toggleSortAndFetch()"
<div class="right-footer"> :key="true" label="正序" :value="true" v-if="sort" />
<div class="info"> </template>
{{ total }} <el-option @click="handleGetThinkDynamicsReport()" :key="true" label="正序" :value="true" />
</div> <el-option @click="handleGetThinkDynamicsReport()" :key="false" label="倒序" :value="false" />
<div class="page-box"> </el-select>
<el-pagination :page-size="12" background layout="prev, pager, next" :total="total"
@current-change="handleCurrentChange" :current-page="currentPage" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-if="pageChange">
<ThinkTankReport :research-type-list="researchTypeList" :research-time-list="researchTimeList"
:selected-research-type-list="selectedResearchTypeList" :cur-footer-list="curFooterList" :total="total"
:current-page="currentPage" @update:selected-research-type-list="val => (selectedResearchTypeList.value = val)"
@filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange"
@report-click="handleToReportDetail" />
</div>
<div v-if="!pageChange">
<CongressHearing :research-type-list="researchTypeList" :research-time-list="researchTimeList"
:research-hearing-list="researchHearingList" :selected-research-type-list="selectedResearchTypeList"
:cur-footer-list="curFooterList" :total="total" :current-page="currentPage" :hearing-data="hearingData"
@update:selected-research-type-list="val => (selectedResearchTypeList.value = val)"
@filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange"
@report-click="handleToReportDetail" />
</div>
</div> </div>
</template> </template>
...@@ -112,8 +108,10 @@ import { ...@@ -112,8 +108,10 @@ import {
getThinkDynamicsReport getThinkDynamicsReport
} from "@/api/thinkTank/overview"; } from "@/api/thinkTank/overview";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import ThinkTankReport from "./ThinkTankReport/index.vue";
import CongressHearing from "./CongressHearing/index.vue";
const router = useRouter(); const router = useRouter();
const pageChange = ref(true)
const searchReport = ref('') const searchReport = ref('')
const handleToReportDetail = (item) => { const handleToReportDetail = (item) => {
...@@ -126,7 +124,130 @@ const handleToReportDetail = (item) => { ...@@ -126,7 +124,130 @@ const handleToReportDetail = (item) => {
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
} }
const hearingData = ref([
// 军事类(10条)
{
title: "2025财年美国国防部国防授权法案听证会:军事预算与战略部署",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年7月8日"
},
{
title: "美国国会众议院听证会:美军网络安全与军事信息系统防护",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年6月15日"
},
{
title: "俄乌冲突背景下美国对乌军事援助听证会:援助规模与后续规划",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年5月22日"
},
{
title: "美国海军舰艇建造与维护听证会:舰队规模目标与工业基础支撑",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年4月10日"
},
{
title: "美军太空军事能力建设听证会:太空军发展与反卫星武器管控",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年3月5日"
},
{
title: "美国国防工业基础听证会:军工供应链韧性与本土制造能力",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年2月18日"
},
{
title: "美军人工智能军事应用听证会:伦理规范与作战效能评估",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年1月30日"
},
{
title: "美国陆军未来作战系统听证会:下一代战车与单兵装备研发",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年7月2日"
},
{
title: "北约军事合作听证会:美国对北约防务开支承诺与兵力部署",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年6月8日"
},
{
title: "美军核力量现代化听证会:洲际导弹更新与核不扩散政策",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年5月1日"
},
// 先进制造类(10条)
{
title: "美国国会听证会:先进制造业供应链重塑与本土产业回流",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年7月15日"
},
{
title: "先进制造中的人工智能应用听证会:智能制造与产业升级",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年6月20日"
},
{
title: "美国先进制造业研发法案听证会:资金分配与技术突破方向",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年4月25日"
},
{
title: "新能源装备先进制造听证会:动力电池与氢能装备产业化",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年3月12日"
},
{
title: "美国半导体先进制造听证会:芯片制造本土化与技术封锁",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年2月8日"
},
{
title: "先进制造劳动力培训听证会:技能匹配与产业人才供给",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年1月15日"
},
{
title: "航空航天先进制造听证会:民用飞机轻量化材料与智能制造",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年7月20日"
},
{
title: "先进制造中的可持续发展听证会:绿色制造与碳中和目标",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年6月1日"
},
{
title: "美国国防先进制造听证会:军工制造数字化与快速响应能力",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年5月18日"
},
{
title: "先进制造标准制定听证会:美国主导的全球制造标准体系构建",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年4月8日"
}
])
const reportTypeList = ref([ const reportTypeList = ref([
{ {
id: '研究报告', id: '研究报告',
...@@ -165,6 +286,88 @@ const researchTypeList = ref([ ...@@ -165,6 +286,88 @@ const researchTypeList = ref([
name: '期刊文章', name: '期刊文章',
}, },
]) ])
const choseTypeList = ref([
{
id: '智库报告',
name: '智库报告',
},
{
id: '调查项目',
name: '调查项目',
},
{
id: '国会听证会',
name: '国会听证会',
},
])
const activeTypeId = ref(choseTypeList.value[0]?.id || null)
const handleChooseType = (type) => {
activeTypeId.value = type.id
if (type.id === '国会听证会') {
pageChange.value = false
} else {
pageChange.value = true
}
}
const researchTimeList = ref([
{
id: '2025年',
name: '2025年',
},
{
id: '2024年',
name: '2024年',
},
{
id: '2023年',
name: '2023年',
},
{
id: '2022年',
name: '2022年',
},
{
id: '2021年',
name: '2021年',
},
{
id: '更早以前',
name: '更早以前',
}
])
const researchHearingList = ref([
{
id: '美中经济与安全审查委员会',
name: '美中经济与安全审查委员会',
},
{
id: '国会-行政部门中国委员会',
name: '国会-行政部门中国委员会',
},
{
id: '美中战略竞争特设委员会',
name: '美中战略竞争特设委员会',
},
{
id: '众议院小企业委员会',
name: '众议院小企业委员会',
},
{
id: '众议院国土安全委员会',
name: '众议院国土安全委员会',
},
{
id: '参议院能源与自然资源委员会',
name: '参议院能源与自然资源委员会',
},
{
id: '新材料',
name: '新材料',
}
])
const selectedResearchTypeList = ref([]) const selectedResearchTypeList = ref([])
const author = ref('') // 作者 const author = ref('') // 作者
...@@ -260,7 +463,7 @@ const yearList = ref([ ...@@ -260,7 +463,7 @@ const yearList = ref([
]); ]);
const selectedYear = ref(1); const selectedYear = ref(1);
const sort = ref(true) const sort = ref(false);
//获取当前时间x年前的日期 //获取当前时间x年前的日期
function getDateYearsAgo(years) { function getDateYearsAgo(years) {
// 获取当前日期 // 获取当前日期
...@@ -301,7 +504,10 @@ const handleGetHylyList = async () => { ...@@ -301,7 +504,10 @@ const handleGetHylyList = async () => {
} }
}; };
const toggleSortAndFetch = async () => {
sort.value = !sort.value
await handleGetThinkDynamicsReport()
}
const currentPage = ref(1); const currentPage = ref(1);
// 处理页码改变事件 // 处理页码改变事件
const handleCurrentChange = page => { const handleCurrentChange = page => {
...@@ -318,22 +524,23 @@ function arrayToString(arr) { ...@@ -318,22 +524,23 @@ function arrayToString(arr) {
}, ""); }, "");
} }
// 获取智库动态报告 // 获取智库动态报告,payload 为子组件筛选变更时传入的当前选中值,避免时序导致拿到旧值
const handleGetThinkDynamicsReport = async () => { const handleGetThinkDynamicsReport = async (payload) => {
const selectedIds = Array.isArray(payload) ? payload : selectedResearchTypeList.value;
if (Array.isArray(payload)) {
selectedResearchTypeList.value = payload;
}
try { try {
const parmas = { const parmas = {
id: router.currentRoute._value.params.id, id: router.currentRoute._value.params.id,
startDate: getDateYearsAgo(selectedYear.value), startDate: getDateYearsAgo(selectedYear.value ?? 1),
parmas: { parmas: {
searchText: searchReport.value, searchText: searchReport.value,
authorName: author.name,
sortFun: sort.value,
authorName: author.value ? author.value : null, authorName: author.value ? author.value : null,
sortFun: sort.value ?? true,
currentPage: currentPage.value - 1, currentPage: currentPage.value - 1,
pageSize: 12, pageSize: 12,
// reportTypeIds: arrayToString(selectedReportTypeList.value) === '' ? null : arrayToString(selectedResearchTypeList.value), researchTypeIds: arrayToString(selectedIds) === '' ? null : arrayToString(selectedIds)
researchTypeIds: arrayToString(selectedResearchTypeList.value) === '' ? null : arrayToString(selectedResearchTypeList.value)
} }
} }
...@@ -365,22 +572,86 @@ onMounted(async () => { ...@@ -365,22 +572,86 @@ onMounted(async () => {
height: 64px; height: 64px;
width: 1600px; width: 1600px;
display: flex; display: flex;
justify-content: space-between; align-items: center;
background: rgb(255, 255, 255);
// 让右侧下拉框整体靠右
>div:last-child {
margin-left: auto;
display: flex;
align-items: center;
}
.report-btn-group {
width: 384px;
height: 32px;
/* 定位参数:top:268px、left:536px(需基于定位父容器) */
/* 布局分布:集中分布(水平+垂直居中) */
display: flex;
margin-top: 16px;
margin-left: 16px;
/* 图层模式:穿透(事件+视觉穿透) */
background: rgb(255, 255, 255); // 与整体背景一致的纯白色
/* 基础兼容:避免尺寸偏移 */
box-sizing: border-box;
gap: 12px;
.report-btn {
width: 120px; // 宽度:120px
height: 32px; // 高度:32px
border-radius: 21px; // 圆角半径:21px
border: 1px solid rgb(230, 231, 232); // 描边:1px实线,颜色为rgb(230, 231, 232)
background: rgb(255, 255, 255); // 背景色:白色
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
// 布局分布:集中分布(水平+垂直居中)
display: flex;
align-items: center;
justify-content: center;
// 文字样式(匹配你项目的字体风格)
color: rgba(59, 65, 75, 1); // 适配你项目的文字主色
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 1; // 消除行高对垂直居中的影响
// 图层模式:穿透(即允许点击事件穿透,如需的话)
box-sizing: border-box; // 确保描边/内边距不影响尺寸
&.active {
background: rgb(5, 95, 194); // 蓝色背景
color: #fff; // 文字变白
box-shadow: none; // 需要的话可以关掉灰描边
}
}
}
.search-box { .search-box {
margin-top: 16px; margin-top: 16px;
display: flex; display: flex;
width: 300px; width: 360px;
height: 32px; height: 32px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1); border: 1px solid rgb(230, 231, 232);
border-radius: 4px; border-radius: 4px;
background: rgba(255, 255, 255, 1); background: rgb(255, 255, 255);
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
.icon { .icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
margin: 8px 7px; margin-top: 8px;
margin-bottom: 8px;
margin-left: 50px;
cursor: pointer; cursor: pointer;
img { img {
...@@ -392,7 +663,31 @@ onMounted(async () => { ...@@ -392,7 +663,31 @@ onMounted(async () => {
.select-box { .select-box {
margin-top: 16px; margin-top: 16px;
margin-right: 5px; display: flex;
.select-box-time,
.select-box-sort {
background: rgb(255, 255, 255);
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
border: 1px solid rgb(230, 231, 232);
border-radius: 4px;
height: 32px;
}
.select-prefix-img {
width: 8px;
height: 8px;
margin-right: 4px;
}
:deep(.el-select-dropdown) {
left: 0 !important;
/* 强制下拉框左对齐选择框 */
top: 100% !important;
/* 强制下拉框在选择框正下方 */
transform: none !important;
/* 禁用默认的位移变换 */
}
} }
} }
...@@ -402,8 +697,8 @@ onMounted(async () => { ...@@ -402,8 +697,8 @@ onMounted(async () => {
gap: 16px; gap: 16px;
.left { .left {
width: 300px; width: 360px;
height: 100%; height: 541px;
padding-bottom: 36px; padding-bottom: 36px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1); border: 1px solid rgba(234, 236, 238, 1);
...@@ -412,49 +707,7 @@ onMounted(async () => { ...@@ -412,49 +707,7 @@ onMounted(async () => {
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
position: relative; position: relative;
.select-box {
margin-top: 21px;
.select-box-header {
display: flex;
gap: 17px;
.icon {
margin-top: 4px;
width: 8px;
height: 16px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.title {
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 1px;
text-align: left;
}
}
.select-main {
margin-left: 25px;
}
.select-main1 {
width: 100px;
}
.input-main {
margin: 10px auto;
width: 250px;
height: 34px;
border-radius: 10px;
border: 1px solid rgba(230, 231, 232, 1);
}
}
} }
.right { .right {
...@@ -462,14 +715,14 @@ onMounted(async () => { ...@@ -462,14 +715,14 @@ onMounted(async () => {
height: 1377px; height: 1377px;
.card-box { .card-box {
width: 1284px; width: 1226px;
height: 1248px; height: 1248px;
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
flex-wrap: wrap; flex-wrap: wrap;
.footer-card { .footer-card {
width: 418px; width: 398px;
height: 300px; height: 300px;
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
...@@ -484,7 +737,7 @@ onMounted(async () => { ...@@ -484,7 +737,7 @@ onMounted(async () => {
cursor: pointer; cursor: pointer;
.footer-card-top { .footer-card-top {
width: 384px; width: 364px;
height: 206px; height: 206px;
margin: 0 auto; margin: 0 auto;
margin-top: 15px; margin-top: 15px;
...@@ -547,6 +800,7 @@ onMounted(async () => { ...@@ -547,6 +800,7 @@ onMounted(async () => {
.filter-checkbox { .filter-checkbox {
width: 180px; width: 180px;
} }
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
</div> --> </div> -->
<div class="home-main-header-center"> <div class="home-main-header-center">
<SearchContainer style="margin-bottom: 0; margin-top: 48px; height: fit-content" v-if="containerRef" <SearchContainer style="margin-bottom: 0; margin-top: 51px; height: fit-content" v-if="containerRef"
placeholder="搜索智库报告" :containerRef="containerRef" areaName="智库" /> placeholder="搜索智库报告" :containerRef="containerRef" areaName="智库" />
<!-- <el-input v-model="searchThinktankText" @keyup.enter="handleSearch" <!-- <el-input v-model="searchThinktankText" @keyup.enter="handleSearch"
style="width: 838px; height: 100%" placeholder="搜索智库报告" /> style="width: 838px; height: 100%" placeholder="搜索智库报告" />
......
...@@ -88,8 +88,13 @@ export default defineConfig({ ...@@ -88,8 +88,13 @@ export default defineConfig({
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/temporarySearch/, '') rewrite: (path) => path.replace(/^\/temporarySearch/, '')
}, },
'^/bill(?:/|$)': {
target: 'http://172.20.10.3:28080/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/bill/, '')
},
'/pdfSse': { '/pdfSse': {
target: 'http://8.140.26.4:10020/', target: 'http://8.140.26.4:10020/',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/pdfSse/, ''), rewrite: (path) => path.replace(/^\/pdfSse/, ''),
configure: (proxy) => { configure: (proxy) => {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论