提交 1a8ab6e3 authored 作者: yanpeng's avatar yanpeng

merge

...@@ -129,3 +129,42 @@ export function getSupplyList(params) { ...@@ -129,3 +129,42 @@ export function getSupplyList(params) {
params, params,
}) })
} }
// 企业市值:市值变化
export function getMarketCapList(params) {
return request({
method: 'GET',
url: `/api/enterprisePage/marketCap/${params}`,
})
}
// 企业发展:营收折线图
export function getRevenueList(params) {
return request({
method: 'GET',
url: `/api/enterprisePage/revenue/${params}`,
})
}
// 企业发展:净利润折线图
export function getNetProfitList(params) {
return request({
method: 'GET',
url: `/api/enterprisePage/netProfit/${params}`,
})
}
// 企业发展:人员情况折线图
export function getPersonnelList(params) {
return request({
method: 'GET',
url: `/api/enterprisePage/personnel/${params}`,
})
}
// 企业发展: 市场占比折线图
export function getMarketShareList(params) {
return request({
method: 'GET',
url: `/api/enterprisePage/marketShare/${params}`,
})
}
\ No newline at end of file
...@@ -222,9 +222,14 @@ export function getThinkTankReportAbstract(params) { ...@@ -222,9 +222,14 @@ export function getThinkTankReportAbstract(params) {
//获取报告主要观点 //获取报告主要观点
export function getThinkTankReportContent(params) { export function getThinkTankReportContent(params) {
const { id, currentPage, pageSize } = params
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/thinkTankReport/content/${params}`, url: `/api/thinkTankReport/content/${id}`,
params: {
currentPage,
pageSize,
}
}) })
} }
......
...@@ -10,23 +10,20 @@ ...@@ -10,23 +10,20 @@
</div> </div>
</div> </div>
<div class="box3-main"> <div class="box3-main">
<div class="box3-item" v-for="(news, index) in list" :key="index" @click="handleClickToNewsDetail(news)"> <div class="box3-item" v-for="(news, index) in normalizedList" :key="index"
@click="handleClickToNewsDetail(news)">
<div class="left"> <div class="left">
<img <img :src="getProxyUrl(news.newsImage) || defaultImg" alt="" referrerpolicy="no-referrer"
:src="getProxyUrl(news.newsImage) || defaultImg" @error="e => (e.target.src = errImg || News1)" />
alt=""
referrerpolicy="no-referrer"
@error="e => (e.target.src = errImg||News1)"
/>
</div> </div>
<div class="right"> <div class="right">
<div class="right-top"> <div class="right-top">
<div class="title">{{ news.newsTitle||news.title }}</div> <div class="title">{{ news.newsTitle || news.title }}</div>
<div class="time"> <div class="time">
{{ news.newsDate ? news.newsDate.slice(5) : "" }} {{ news.newsOrg ? "-" + news.newsOrg : "" }} {{ news.newsDate ? news.newsDate.slice(5) : "" }} {{ news.newsOrg ? "-" + news.newsOrg : "" }}
</div> </div>
</div> </div>
<div class="right-footer">{{ news.newsContent||news.description }}</div> <div class="right-footer">{{ news.newsContent || news.description }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -34,15 +31,21 @@ ...@@ -34,15 +31,21 @@
</template> </template>
<script setup> <script setup>
import { computed } from "vue";
import router from "@/router/index"; import router from "@/router/index";
import News1 from "@/assets/images/news1.png"; // 错误图片 import News1 from "@/assets/images/news1.png"; // 错误图片
import defaultNew from "@/assets/images/default-icon-news.png"; // 默认图片 import defaultNew from "@/assets/images/default-icon-news.png"; // 默认图片
let { list,errImg,defaultImg } = defineProps({ let { list, newsList, errImg, defaultImg } = defineProps({
list: { list: {
type: Array, type: Array,
default: () => [] default: () => []
}, },
// 兼容历史用法:部分页面使用 newsList 作为入参
newsList: {
type: Array,
default: undefined
},
defaultImg: { defaultImg: {
type: String, type: String,
default: defaultNew default: defaultNew
...@@ -53,6 +56,11 @@ let { list,errImg,defaultImg } = defineProps({ ...@@ -53,6 +56,11 @@ let { list,errImg,defaultImg } = defineProps({
} }
}); });
// 统一对外渲染数据源:优先使用 list,其次兼容 newsList
const normalizedList = computed(() => {
return (Array.isArray(list) && list.length ? list : newsList) || [];
});
// 处理图片代理 // 处理图片代理
const getProxyUrl = url => { const getProxyUrl = url => {
if (!url) return ""; if (!url) return "";
...@@ -74,7 +82,7 @@ const handleClickToNewsDetail = news => { ...@@ -74,7 +82,7 @@ const handleClickToNewsDetail = news => {
const route = router.resolve({ const route = router.resolve({
path: "/newsAnalysis", path: "/newsAnalysis",
query: { query: {
newsId: news.newsId newsId: news.newsId ?? news.id
} }
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
...@@ -183,9 +191,11 @@ const handleToMoreNews = () => { ...@@ -183,9 +191,11 @@ const handleToMoreNews = () => {
width: 657px; width: 657px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
color: var(--color-main-active); color: var(--color-main-active);
.title { .title {
color: var(--color-main-active); color: var(--color-main-active);
} }
......
...@@ -67,7 +67,7 @@ const handleCollect = () => { ...@@ -67,7 +67,7 @@ const handleCollect = () => {
} }
const emit = defineEmits(['save','download','collect']) const emit = defineEmits(['save', 'download', 'collect'])
</script> </script>
......
//样式主页 //样式主页
const StylePages = () => import("@/components/devStyle/components/index.vue"); const StylePages = () => import("@/styles/components/index.vue");
const stylePagesRoutes = [ const stylePagesRoutes = [
// 智库系统的主要路由 // 智库系统的主要路由
......
<script setup lang="ts">
import { ElRow, ElCol } from 'element-plus';
import '@/styles/common.scss'
const span = 12
</script>
<template>
<el-row class="wrapper">
<el-col :span="span">
<pre>
{{ `import '@/styles/common.scss';
<template>
<div class="background-as-card"></div>
</template>
`}}
</pre>
<div class="background-as-card">
<div v-for="item in [1, 2, 3, 4]" :key="item">
{{ item }}
</div>
</div>
</el-col>
</el-row>
</template>
<style lang="scss" scoped>
.wrapper {
background-color: rgba(0, 0, 0, 0.068);
padding: 10px;
}
</style>
\ No newline at end of file
<script setup lang="ts">
import { ElRadioGroup, ElRadioButton, ElRow, ElCol } from 'element-plus';
import { ref } from 'vue';
import '@/styles/radio.scss';
const radio = ref(1)
const span = 12
</script>
<template>
<el-row>
<el-col :span="span">
<pre>
{{ `import '@/styles/radio.scss';
<template>
<el-radio-group class="radio-group-as-gap-btn">
</el-radio-group>
</template>
`}}
</pre>
<el-radio-group v-model="radio" class="radio-group-as-gap-btn">
<el-radio-button :value="1">选项1</el-radio-button>
<el-radio-button :value="2">选项2</el-radio-button>
<el-radio-button :value="3">选项3</el-radio-button>
</el-radio-group>
</el-col>
</el-row>
</template>
<style lang="scss" scoped></style>
\ No newline at end of file
<script setup lang="ts">
import { ElTabs, ElTabPane, ElSpace, ElRow, ElCol } from 'element-plus';
import { ref } from 'vue';
import '@/styles/tabs.scss'
const span = 12
</script>
<template>
<el-row>
<el-col :span="span">
<pre>
{{ `import '@/styles/tabs.scss';
<template>
<el-tabs stretch class="tabs-header-as-card tabs-nav-no-wrap tabs-bar-as-btn">
</el-tabs>
</template>
`}}
</pre>
<el-tabs stretch class="tabs-header-as-card tabs-nav-no-wrap tabs-bar-as-btn">
<el-tab-pane label="tab1">tab1</el-tab-pane>
<el-tab-pane label="tab2">tab2</el-tab-pane>
<el-tab-pane label="tab3">tab3</el-tab-pane>
</el-tabs>
</el-col>
</el-row>
</template>
<style lang="scss" scoped></style>
\ No newline at end of file
<template> <template>
<el-scrollbar> <el-scrollbar>
<div class="common-page"> <div class="common-page">
<el-space direction="vertical" :size="20" alignment="flex-start"> <el-space direction="vertical" :size="20" alignment="flex-start" fill>
<div class="text-title-0-show">开发样式</div> <div class="text-title-0-show">开发样式</div>
<div class="text-title-1-show">样式变量</div> <div class="text-title-1-show">样式变量</div>
<ConstStyle /> <ConstStyle />
<div class="text-title-1-show">文字样式</div> <div class="text-title-1-show">文字样式</div>
<TextStyle /> <TextStyle />
<div class="text-title-1-show">通用样式/组件</div>
<el-tabs tabPosition="left">
<el-tab-pane label="通用" lazy>
<common-page />
</el-tab-pane>
<el-tab-pane label="单选框" lazy>
<radio-page />
</el-tab-pane>
<el-tab-pane label="选项卡" lazy>
<tabs-page />
</el-tab-pane>
</el-tabs>
</el-space> </el-space>
</div> </div>
</el-scrollbar> </el-scrollbar>
...@@ -16,5 +28,10 @@ ...@@ -16,5 +28,10 @@
import "@/styles/container.scss" import "@/styles/container.scss"
import TextStyle from './textStyle.vue'; import TextStyle from './textStyle.vue';
import ConstStyle from './constStyle.vue'; import ConstStyle from './constStyle.vue';
import { ElTabs, ElTabPane, ElSpace } from "element-plus";
import RadioPage from './RadioPage/index.vue';
import TabsPage from './TabsPage/index.vue';
import CommonPage from './CommonPage/index.vue';
</script>
</script> <style lang="scss" scoped></style>
\ No newline at end of file \ No newline at end of file
@use '@/styles/common.scss';
.radio-group-as-gap-btn {
@extend .text-tip-1;
.el-radio-button {
--el-radio-button-checked-bg-color: var(--bg-white-100);
--el-radio-button-checked-border-color: var(--bg-black-10);
border-radius: 4px;
}
.el-radio-button.is-active {
--el-radio-button-checked-text-color: var(--color-primary-100);
--el-radio-button-checked-bg-color: var(--color-primary-10);
--el-radio-button-checked-border-color: var(--color-primary-5);
border-radius: 4px;
}
}
\ No newline at end of file
<template> <template>
<div class="background-wrap"> <div class="background-wrap">
<div class="background-wrap-left"> <div class="background-wrap-left">
<div class="background-wrap-left-box1"> <AnalysisBox class="left-box left-box--background" title="立法背景" :showAllBtn="false">
<!-- <div class="box-header"> <template #header-btn>
<div class="header-left"></div>
<div class="title">立法背景</div>
<div class="header-btn-box"> <div class="header-btn-box">
<div class="btn" @click="handleClickBox1Btn(1)"> <el-button :type="box1Btn1Type" plain @click="handleClickBox1Btn(1)">涉华背景</el-button>
<el-button type="primary" plain v-if="box1BtnActive === 1">涉华背景</el-button> <el-button :type="box1Btn2Type" plain @click="handleClickBox1Btn(2)">全部背景</el-button>
<el-button type="info" plain v-else>涉华背景</el-button>
</div>
<div class="btn" @click="handleClickBox1Btn(2)">
<el-button type="primary" plain v-if="box1BtnActive === 2">全部背景</el-button>
<el-button type="info" plain v-else>全部背景</el-button>
</div>
</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div> </div>
</div> </template>
<div class="box1-main"> <div class="box1-main">
<div class="box1-main-center"> <div class="box1-main-center">
<div class="box1-main-item" v-for="(item, index) in backgroundList" :key="item.id"> <div class="box1-main-item" v-for="item in backgroundDisplayList" :key="item.id">
<div class="id">{{ (currentPage - 1) * 10 + index + 1 }}</div> <div class="id">{{ item.displayIndex }}</div>
<div class="title">{{ item.backgroundTitle }}</div> <div class="title">{{ item.backgroundTitle }}</div>
<div class="share"> <div class="share">
<img src="./assets/icons/open.png" alt="打开" />
</div> </div>
</div> </div>
</div> </div>
<div class="box1-main-footer"> <div class="box1-main-footer">
<div class="info"> <div class="info">
{{ `共 ${total} 项` }} {{ totalText }}
</div>
<div class="page-box">
<el-pagination background layout="prev, pager, next" :total="total" v-model:current-page="currentPage"
@current-change="handleGetBillBackground" />
</div>
</div>
</div> -->
<AnalysisBox title="立法背景" :showAllBtn="false">
<template #header-btn>
<div class="header-btn-box">
<div class="btn" @click="handleClickBox1Btn(1)">
<el-button type="primary" plain v-if="box1BtnActive === 1">涉华背景</el-button>
<el-button type="info" plain v-else>涉华背景</el-button>
</div>
<div class="btn" @click="handleClickBox1Btn(2)">
<el-button type="primary" plain v-if="box1BtnActive === 2">全部背景</el-button>
<el-button type="info" plain v-else>全部背景</el-button>
</div>
</div>
</template>
<div class="box1-main">
<div class="box1-main-center">
<div class="box1-main-item" v-for="(item, index) in backgroundList" :key="item.id">
<div class="id">{{ (currentPage - 1) * 10 + index + 1 }}</div>
<div class="title">{{ item.backgroundTitle }}</div>
<div class="share">
<!-- <img src="./assets/icons/open.png" alt="打开" /> -->
</div>
</div>
</div>
<div class="box1-main-footer">
<div class="info">
{{ `共 ${total} 项` }}
</div>
<div class="page-box">
<el-pagination background layout="prev, pager, next" :total="total" v-model:current-page="currentPage"
@current-change="handleGetBillBackground" />
</div>
</div>
</div>
</AnalysisBox>
</div>
<div class="background-wrap-left-box2">
<!-- <div class="box-header">
<div class="header-left"></div>
<div class="title">相关事件</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div> </div>
<el-pagination background layout="prev, pager, next" :total="total" v-model:current-page="currentPage"
@current-change="handleGetBillBackground" />
</div> </div>
</div> </div>
</AnalysisBox>
<AnalysisBox class="left-box left-box--event" title="相关事件" :showAllBtn="false">
<div class="box2-main"> <div class="box2-main">
<div class="box2-main-item" v-for="(item, index) in eventList" :key="index" @click="handleClickEvent(item)"> <div class="box2-main-item" v-for="item in eventDisplayList" :key="item.id" @click="handleClickEvent(item)">
<div class="left"> <div class="left">
<img :src="item.imageUrl || defaultNew" @error="e => (e.target.src = defaultNew)" alt="" /> <img :src="item.imgSrc" @error="handleNewsImgError" alt="" />
</div> </div>
<div class="center"> <div class="center">
<CommonPrompt :content="item.sjbt"> <CommonPrompt :content="item.sjbt">
...@@ -106,85 +44,46 @@ ...@@ -106,85 +44,46 @@
</div> </div>
<div class="right">{{ item.sjsj }}</div> <div class="right">{{ item.sjsj }}</div>
</div> </div>
</div> --> </div>
<AnalysisBox title="相关事件" :showAllBtn="false"> </AnalysisBox>
<div class="box2-main">
<div class="box2-main-item" v-for="(item, index) in eventList" :key="index" @click="handleClickEvent(item)">
<div class="left">
<img :src="item.imageUrl || defaultNew" @error="e => (e.target.src = defaultNew)" alt="" />
</div>
<div class="center">
<CommonPrompt :content="item.sjbt">
<div class="title">{{ item.sjbt }}</div>
</CommonPrompt>
<CommonPrompt :content="item.sjnr">
<div class="content">{{ item.sjnr }}</div>
</CommonPrompt>
</div>
<div class="right">{{ item.sjsj }}</div>
</div>
</div>
</AnalysisBox>
</div>
</div> </div>
<div class="background-wrap-right"> <AnalysisBox class="right-panel" title="议员相关性分析" :showAllBtn="false">
<!-- <div class="box-header"> <template #header-btn>
<div class="header-left"></div>
<div class="title">议员相关性分析</div>
<div class="header-btn-box"> <div class="header-btn-box">
<div class="btn" @click="handleClickBox2Btn(1)"> <el-button :type="box2Btn1Type" plain @click="handleClickBox2Btn(1)">赞成议员</el-button>
<el-button type="primary" plain v-if="box2BtnActive === 1">赞成议员</el-button> <el-button :type="box2Btn2Type" plain @click="handleClickBox2Btn(2)">反对议员</el-button>
<el-button type="info" plain v-else>赞成议员</el-button>
</div>
<div class="btn" @click="handleClickBox2Btn(2)">
<el-button type="primary" plain v-if="box2BtnActive === 2">反对议员</el-button>
<el-button type="info" plain v-else>反对议员</el-button>
</div>
</div> </div>
<div class="header-right"> </template>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="background-wrap-right-main"> <div class="background-wrap-right-main">
<div class="right-box1"> <div class="right-box1">
<div class="right-box1-main"> <div class="right-box1-main-top">
<div class="right-box1-main-top"> <el-icon class="nav-icon" size="20" :color="prevIconColor" @click="handlePrev">
<el-icon style="margin-top: 20px; cursor: pointer" size="20" <CaretLeft />
:color="currentIndex > 0 ? '#5f656c' : '#ccc'" @click="handlePrev"> </el-icon>
<CaretLeft /> <div class="user-list-container">
</el-icon> <div class="user-list-wrapper" :style="userListWrapperStyle">
<div class="user-list-container"> <div class="user-box" v-for="item in personList" :key="item.id" @click="handleClickUser(item)">
<div class="user-list-wrapper" :style="{ transform: `translateX(-${currentIndex * 110}px)` }"> <div class="img-box">
<div class="user-box" v-for="(item, index) in personList" :key="index" @click="handleClickUser(item)"> <img :src="item.image" alt="" />
<div class="img-box"> <div class="icon1">
<img :src="item.image" alt="" /> <img :src="item.icon" alt="" />
<div class="icon1"> </div>
<img :src="item.icon" alt="" /> <div class="icon2">
</div> <img :src="item.icon1" alt="" />
<div class="icon2">
<img :src="item.icon1" alt="" />
</div>
</div> </div>
<CommonPrompt :content="item.name">
<div class="name">{{ item.name }}</div>
</CommonPrompt>
</div> </div>
<CommonPrompt :content="item.name">
<div class="name">{{ item.name }}</div>
</CommonPrompt>
</div> </div>
</div> </div>
<el-icon style="margin-top: 20px; cursor: pointer" size="20"
:color="currentIndex < personList.length - 4 ? '#5f656c' : '#ccc'" @click="handleNext">
<CaretRight />
</el-icon>
</div>
<div class="right-box1-main-bottom">
<WordCloudMap :data="wordCloudData" :selectedName="selectedIndustryName" shape="circle"
@wordClick="handleWordClick" />
</div> </div>
<el-icon class="nav-icon" size="20" :color="nextIconColor" @click="handleNext">
<CaretRight />
</el-icon>
</div>
<div class="right-box1-main-bottom">
<WordCloudMap :data="wordCloudData" :selectedName="selectedIndustryName" shape="circle" @wordClick="handleWordClick" />
</div> </div>
</div> </div>
<div class="right-box2"> <div class="right-box2">
...@@ -194,7 +93,7 @@ ...@@ -194,7 +93,7 @@
</div> </div>
</div> </div>
<div class="right-box2-center"> <div class="right-box2-center">
<div class="user-box" v-for="(item, index) in aboutUserList" :key="index" @click="handleClickUser(item)"> <div class="user-box" v-for="item in aboutUserList" :key="item.id" @click="handleClickUser(item)">
<div class="user-left"> <div class="user-left">
<div class="img-box"> <div class="img-box">
<img :src="item.img" alt="" /> <img :src="item.img" alt="" />
...@@ -209,111 +108,30 @@ ...@@ -209,111 +108,30 @@
</div> </div>
</div> </div>
</div> </div>
</div> --> </div>
<AnalysisBox title="议员相关性分析" :showAllBtn="false"> </AnalysisBox>
<template #header-btn>
<div class="header-btn-box">
<div class="btn" @click="handleClickBox2Btn(1)">
<el-button type="primary" plain v-if="box2BtnActive === 1">赞成议员</el-button>
<el-button type="info" plain v-else>赞成议员</el-button>
</div>
<div class="btn" @click="handleClickBox2Btn(2)">
<el-button type="primary" plain v-if="box2BtnActive === 2">反对议员</el-button>
<el-button type="info" plain v-else>反对议员</el-button>
</div>
</div>
</template>
<div class="background-wrap-right-main">
<div class="right-box1">
<div class="right-box1-main">
<div class="right-box1-main-top">
<el-icon style="margin-top: 20px; cursor: pointer" size="20"
:color="currentIndex > 0 ? '#5f656c' : '#ccc'" @click="handlePrev">
<CaretLeft />
</el-icon>
<div class="user-list-container">
<div class="user-list-wrapper" :style="{ transform: `translateX(-${currentIndex * 110}px)` }">
<div class="user-box" v-for="(item, index) in personList" :key="index"
@click="handleClickUser(item)">
<div class="img-box">
<img :src="item.image" alt="" />
<div class="icon1">
<img :src="item.icon" alt="" />
</div>
<div class="icon2">
<img :src="item.icon1" alt="" />
</div>
</div>
<CommonPrompt :content="item.name">
<div class="name">{{ item.name }}</div>
</CommonPrompt>
</div>
</div>
</div>
<el-icon style="margin-top: 20px; cursor: pointer" size="20"
:color="currentIndex < personList.length - 4 ? '#5f656c' : '#ccc'" @click="handleNext">
<CaretRight />
</el-icon>
</div>
<div class="right-box1-main-bottom">
<WordCloudMap :data="wordCloudData" :selectedName="selectedIndustryName" shape="circle"
@wordClick="handleWordClick" />
</div>
</div>
</div>
<div class="right-box2">
<div class="right-box2-header">
<div class="title">
<span class="title-active">"{{ selectedIndustryName }}"</span>涉及议员动态 >
</div>
</div>
<div class="right-box2-center">
<div class="user-box" v-for="(item, index) in aboutUserList" :key="index" @click="handleClickUser(item)">
<div class="user-left">
<div class="img-box">
<img :src="item.img" alt="" />
</div>
</div>
<div class="user-right">
<div class="name">{{ item.name }}</div>
<CommonPrompt :content="item.content">
<div class="content">{{ item.content }}</div>
</CommonPrompt>
</div>
</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import WordCloudMap from "./WordCloudMap.vue"; import WordCloudMap from "./WordCloudMap.vue";
import CommonPrompt from "../commonPrompt/index.vue"; import CommonPrompt from "../commonPrompt/index.vue";
const route = useRoute();
const router = useRouter();
import event1 from "./assets/images/event1.png";
import event2 from "./assets/images/event2.png";
import event3 from "./assets/images/event3.png";
import event4 from "./assets/images/event4.png";
import event5 from "./assets/images/event5.png";
import user1 from "./assets/images/user1.png";
import user2 from "./assets/images/user2.png";
import user3 from "./assets/images/user3.png";
import user4 from "./assets/images/user4.png";
import user5 from "./assets/images/user5.png";
import userIcon from "./assets/icons/user-icon.png"; import userIcon from "./assets/icons/user-icon.png";
import userIcon1 from "./assets/icons/user-icon1.png"; import userIcon1 from "./assets/icons/user-icon1.png";
import userIcon2 from "./assets/icons/user-icon2.png"; import userIcon2 from "./assets/icons/user-icon2.png";
import defaultNew from "../assets/images/default-icon-news.png"; import defaultNew from "../assets/images/default-icon-news.png";
import defaultA from "../assets/images/default-icon1.png"; import defaultA from "../assets/images/default-icon1.png";
import { getBillBackground, getBillEvent, getBillPersonAnalyze, getBillInfoEvent, getBillPersonAnalyzeDy } from "@/api/bill"; import { getBillBackground, getBillPersonAnalyze, getBillInfoEvent, getBillPersonAnalyzeDy } from "@/api/bill";
const route = useRoute();
const router = useRouter();
const handleNewsImgError = e => {
e.target.src = defaultNew;
};
// 跳转到相关新闻 // 跳转到相关新闻
const handleClickEvent = item => { const handleClickEvent = item => {
...@@ -397,6 +215,34 @@ const handleWordClick = word => { ...@@ -397,6 +215,34 @@ const handleWordClick = word => {
const wordCloudData = ref([]); const wordCloudData = ref([]);
const totalText = computed(() => `共 ${total.value} 项`);
const backgroundDisplayList = computed(() =>
backgroundList.value.map((item, index) => ({
...item,
displayIndex: (currentPage.value - 1) * 10 + index + 1
}))
);
const eventDisplayList = computed(() =>
eventList.value.map(item => ({
...item,
imgSrc: item.imageUrl || defaultNew
}))
);
const userListWrapperStyle = computed(() => ({
transform: `translateX(-${currentIndex.value * 110}px)`
}));
const box1Btn1Type = computed(() => (box1BtnActive.value === 1 ? "primary" : "info"));
const box1Btn2Type = computed(() => (box1BtnActive.value === 2 ? "primary" : "info"));
const box2Btn1Type = computed(() => (box2BtnActive.value === 1 ? "primary" : "info"));
const box2Btn2Type = computed(() => (box2BtnActive.value === 2 ? "primary" : "info"));
const prevIconColor = computed(() => (currentIndex.value > 0 ? "#5f656c" : "#ccc"));
const nextIconColor = computed(() => (currentIndex.value < personList.value.length - 4 ? "#5f656c" : "#ccc"));
// 获取立法背景内容 // 获取立法背景内容
const handleGetBillBackground = async () => { const handleGetBillBackground = async () => {
const cRelated = box1BtnActive.value === 1 ? "Y" : "N"; const cRelated = box1BtnActive.value === 1 ? "Y" : "N";
...@@ -408,7 +254,6 @@ const handleGetBillBackground = async () => { ...@@ -408,7 +254,6 @@ const handleGetBillBackground = async () => {
}; };
try { try {
const res = await getBillBackground(params); const res = await getBillBackground(params);
console.log("立法背景", res);
backgroundList.value = res.data.content; backgroundList.value = res.data.content;
total.value = res.data.totalElements; // 假设API返回totalElements total.value = res.data.totalElements; // 假设API返回totalElements
} catch (error) { } } catch (error) { }
...@@ -421,21 +266,7 @@ const handleGetRelatedEvent = async () => { ...@@ -421,21 +266,7 @@ const handleGetRelatedEvent = async () => {
}; };
try { try {
const res = await getBillInfoEvent(params); const res = await getBillInfoEvent(params);
console.log("相关事件", res);
eventList.value = res.data; eventList.value = res.data;
eventList.value.forEach((item, index) => {
if (index === 0) {
item.image = event1;
} else if (index === 1) {
item.image = event2;
} else if (index === 2) {
item.image = event3;
} else if (index === 3) {
item.image = event4;
} else {
item.image = event5;
}
});
} catch (error) { } } catch (error) { }
}; };
...@@ -447,7 +278,6 @@ const handleGetBillPersonAnalyze = async isOppose => { ...@@ -447,7 +278,6 @@ const handleGetBillPersonAnalyze = async isOppose => {
}; };
try { try {
const res = await getBillPersonAnalyze(params); const res = await getBillPersonAnalyze(params);
console.log("议员相关性分析", res);
const { members, industryCounts } = res.data; const { members, industryCounts } = res.data;
// 更新人员列表 // 更新人员列表
personList.value = members || []; personList.value = members || [];
...@@ -490,7 +320,6 @@ const handleGetBillPersonAnalyzeDy = async () => { ...@@ -490,7 +320,6 @@ const handleGetBillPersonAnalyzeDy = async () => {
}; };
try { try {
const res = await getBillPersonAnalyzeDy(params); const res = await getBillPersonAnalyzeDy(params);
console.log("议员相关性分析领域人物动态", res);
aboutUserList.value = (res.data || []).map(m => ({ aboutUserList.value = (res.data || []).map(m => ({
id: m.id, id: m.id,
img: m.imageUrl || defaultA, img: m.imageUrl || defaultA,
...@@ -512,10 +341,7 @@ onMounted(() => { ...@@ -512,10 +341,7 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.header-btn-box { .header-btn-box {
display: flex; display: flex;
gap: 8px;
.btn {
margin-left: 8px;
}
:deep(.el-button) { :deep(.el-button) {
height: 28px; height: 28px;
...@@ -562,127 +388,45 @@ onMounted(() => { ...@@ -562,127 +388,45 @@ onMounted(() => {
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
align-items: flex-start;
.box-header {
height: 56px;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
line-height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
}
.header-btn-box {
position: absolute;
top: 14px;
right: 84px;
display: flex;
.btn {
margin-left: 8px;
}
:deep(.el-button) {
height: 28px;
padding: 2px 8px;
border-radius: 4px;
font-size: 16px;
font-weight: 400;
font-family: Microsoft YaHei;
line-height: 24px;
}
:deep(.el-button--primary.is-plain) {
background-color: #f0f7ff;
border-color: rgb(5, 95, 194);
color: rgb(5, 95, 194);
border-width: 1px;
&:hover,
&:focus {
background-color: #f0f7ff;
border-color: rgb(5, 95, 194);
color: rgb(5, 95, 194);
}
}
:deep(.el-button--info.is-plain) {
background-color: #fff;
border-color: rgb(230, 231, 232);
color: rgb(59, 65, 75);
&:hover,
&:focus {
background-color: #fff;
border-color: rgb(230, 231, 232);
color: rgb(59, 65, 75);
}
}
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
display: flex;
justify-content: flex-end;
gap: 4px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
}
}
.background-wrap-left { .background-wrap-left {
width: 1064px; width: 1064px;
margin-top: 16px; margin-top: 16px;
.background-wrap-left-box1 { .left-box {
width: 1064px; width: 1064px;
height: 415px; }
.left-box--background {
height: auto;
.box1-main { .box1-main {
.box1-main-center { .box1-main-center {
margin: 0 auto; margin: 0 auto;
width: 1016px; width: 1016px;
height: 280px; height: auto;
display: flex; display: flex;
flex-wrap: wrap; flex-direction: column;
justify-content: space-between; gap: 8px;
align-content: flex-start; padding: 8px 0;
box-sizing: border-box;
.box1-main-item { .box1-main-item {
width: 500px; width: 100%;
height: 48px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(243, 243, 244, 1); border: none;
border-radius: 2px; background: #fff;
background: rgba(255, 255, 255, 1);
display: flex; display: flex;
margin-top: 8px; align-items: center;
padding: 12px 16px 12px 18px;
&:nth-child(odd) {
background: rgb(247, 248, 249);
}
&:nth-child(even) {
background: #fff;
}
.id { .id {
width: 24px; width: 24px;
...@@ -694,28 +438,34 @@ onMounted(() => { ...@@ -694,28 +438,34 @@ onMounted(() => {
line-height: 24px; line-height: 24px;
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
margin: 12px 16px 12px 18px; margin-right: 16px;
flex: 0 0 auto;
} }
.title { .title {
width: 440px; flex: 1 1 auto;
height: 48px; min-width: 0;
line-height: 48px; line-height: 24px;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
text-align: left; text-align: left;
overflow: hidden; overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
word-break: break-word;
} }
.share { .share {
margin-left: 13px; margin-left: 12px;
margin-top: 16px;
width: 16px; width: 16px;
height: 16px; height: 16px;
padding: 2px; padding: 2px;
cursor: pointer; cursor: pointer;
flex: 0 0 auto;
img { img {
width: 100%; width: 100%;
...@@ -726,7 +476,7 @@ onMounted(() => { ...@@ -726,7 +476,7 @@ onMounted(() => {
} }
.box1-main-footer { .box1-main-footer {
margin: 30px 22px 0 22px; margin: 28px 22px;
height: 22px; height: 22px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
...@@ -744,18 +494,20 @@ onMounted(() => { ...@@ -744,18 +494,20 @@ onMounted(() => {
} }
} }
.background-wrap-left-box2 { .left-box--event {
margin-top: 15px; margin-top: 15px;
width: 1064px; height: auto;
height: 415px;
.box2-main { .box2-main {
width: 1010px; --event-item-height: 60px;
margin: 0 auto;
height: 349px; width: calc(100% - 44px);
margin: 22px 22px;
height: auto;
max-height: calc(var(--event-item-height) * 5);
overflow-y: auto; overflow-y: auto;
.box2-main-item { .box2-main-item {
width: 1005px; width: 100%;
height: 60px; height: 60px;
border-radius: 2px; border-radius: 2px;
box-sizing: border-box; box-sizing: border-box;
...@@ -779,7 +531,8 @@ onMounted(() => { ...@@ -779,7 +531,8 @@ onMounted(() => {
.center { .center {
margin-left: 14px; margin-left: 14px;
width: 805px; flex: 1 1 auto;
min-width: 0;
.title { .title {
height: 22px; height: 22px;
...@@ -813,6 +566,7 @@ onMounted(() => { ...@@ -813,6 +566,7 @@ onMounted(() => {
.right { .right {
margin-left: 25px; margin-left: 25px;
flex: 0 0 auto;
line-height: 60px; line-height: 60px;
color: rgba(132, 136, 142, 1); color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
...@@ -824,233 +578,141 @@ onMounted(() => { ...@@ -824,233 +578,141 @@ onMounted(() => {
} }
} }
} }
.box2-footer {
margin-top: 7px;
display: flex;
justify-content: center;
.btn-more {
width: 108px;
height: 32px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
} }
} }
.background-wrap-right { .right-panel {
margin-right: 18px; margin-right: 18px;
margin-left: 16px; margin-left: 16px;
margin-top: 16px; margin-top: 16px;
width: 520px; width: 520px;
height: 845px; max-height: 824px;
padding-bottom: 30px;
height: auto !important;
display: flex;
flex-direction: column;
overflow: hidden;
:deep(.wrapper-main) {
height: auto !important;
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
}
.background-wrap-right-main { .background-wrap-right-main {
.right-box1 { .right-box1 {
height: 365px; height: auto;
overflow: hidden; overflow: hidden;
.right-box1-header { .nav-icon {
height: 22px; margin-top: 20px;
margin: 0 auto; cursor: pointer;
}
.right-box1-main-top {
margin-top: 17px;
margin-left: 16px;
width: 490px;
display: flex; display: flex;
justify-content: space-between;
.icon { .user-list-container {
margin: 1px 12px 3px 0; width: 440px; // 4个 user-box 的宽度 (110 * 4)
width: 16px; overflow: hidden;
height: 16px;
img { .user-list-wrapper {
width: 100%; display: flex;
height: 100%; transition: transform 0.3s ease;
} }
} }
.title { .user-box {
height: 22px; width: 110px;
color: rgba(95, 101, 108, 1); flex-shrink: 0;
font-family: Microsoft YaHei; height: 80px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
}
.right-box1-main {
width: 502px;
margin-top: 17px;
.right-box1-main-top {
margin-left: 16px;
width: 490px;
display: flex;
justify-content: space-between;
.user-list-container { .img-box {
width: 440px; // 4个 user-box 的宽度 (110 * 4) width: 48px;
overflow: hidden; height: 48px;
position: relative;
margin: 0 auto;
.user-list-wrapper { img {
display: flex; width: 100%;
transition: transform 0.3s ease; height: 100%;
border-radius: 50%; // 圆形头像
object-fit: cover;
} }
}
.user-box {
width: 110px;
flex-shrink: 0;
height: 80px;
.img-box { .icon1 {
width: 48px; position: absolute;
height: 48px; left: 5px;
position: relative; bottom: -8px;
margin: 0 auto; width: 16px;
height: 16px;
border-radius: 10px;
padding: 2px;
background: rgba(255, 255, 255, 0.8);
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: 50%; // 圆形头像
object-fit: cover;
}
.icon1 {
position: absolute;
left: 5px;
bottom: -8px;
width: 16px;
height: 16px;
border-radius: 10px;
padding: 2px;
background: rgba(255, 255, 255, 0.8);
img {
width: 100%;
height: 100%;
}
} }
}
.icon2 { .icon2 {
position: absolute; position: absolute;
right: 5px; right: 5px;
bottom: -8px; bottom: -8px;
width: 16px; width: 16px;
height: 16px; height: 16px;
border-radius: 10px; border-radius: 10px;
padding: 2px; padding: 2px;
background: rgba(255, 255, 255, 0.8); background: rgba(255, 255, 255, 0.8);
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
}
} }
} }
.name {
margin-top: 10px;
height: 14px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 14px;
letter-spacing: 0px;
text-align: center;
// 名字过长处理
width: 100px;
margin-left: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
}
.right-box1-main-bottom {
margin: 17px 16px 0 16px;
border-top: 1px solid rgba(243, 243, 244, 1);
border-bottom: 1px solid rgba(243, 243, 244, 1);
width: 490px;
height: 266px;
background: rgba(249, 250, 252, 1);
border-radius: 5px;
display: flex;
justify-content: space-between;
}
}
.right-box1-footer {
display: flex;
.left {
width: 70px;
height: 18px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
margin-left: 26px;
margin-top: 23px;
}
.right { .name {
flex: 1; margin-top: 10px;
display: flex; height: 14px;
margin-left: 10px; color: rgba(95, 101, 108, 1);
margin-right: 20px;
margin-top: 20px;
justify-content: space-between;
.right-tag {
padding: 1px 8px 1px 8px;
// box-sizing: border-box;
border-radius: 4px;
text-align: center;
height: 20px;
line-height: 20px;
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
} line-height: 14px;
letter-spacing: 0px;
.tag1 { text-align: center;
border: 1px solid rgba(186, 224, 255, 1); // 名字过长处理
background: rgba(230, 244, 255, 1); width: 100px;
color: rgba(22, 119, 255, 1); margin-left: 5px;
} overflow: hidden;
text-overflow: ellipsis;
.tag2 { white-space: nowrap;
border: 1px solid rgba(217, 247, 190, 1);
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
}
.tag3 {
border: 1px solid rgba(255, 204, 199, 1);
background: rgba(255, 241, 240, 1);
color: rgba(255, 77, 79, 1);
}
.tag4 {
border: 1px solid rgba(255, 241, 184, 1);
background: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1);
} }
} }
} }
.right-box1-main-bottom {
margin: 17px 16px 0 16px;
border-top: 1px solid rgba(243, 243, 244, 1);
border-bottom: 1px solid rgba(243, 243, 244, 1);
width: 490px;
height: 266px;
background: rgba(249, 250, 252, 1);
border-radius: 5px;
display: flex;
justify-content: space-between;
}
} }
.right-box2 { .right-box2 {
height: 423px; height: auto;
.right-box2-header { .right-box2-header {
height: 22px; height: 22px;
...@@ -1089,9 +751,8 @@ onMounted(() => { ...@@ -1089,9 +751,8 @@ onMounted(() => {
} }
.right-box2-center { .right-box2-center {
height: 345px; height: auto;
overflow: auto; overflow: visible;
overflow-y: auto;
margin-top: 19px; margin-top: 19px;
margin-left: 16px; margin-left: 16px;
width: 544px; width: 544px;
...@@ -1199,52 +860,15 @@ onMounted(() => { ...@@ -1199,52 +860,15 @@ onMounted(() => {
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
} }
} }
} }
// .right-box2-footer {
// margin-top: 7px;
// display: flex;
// justify-content: center;
// .btn-more {
// width: 108px;
// height: 32px;
// cursor: pointer;
// img {
// width: 100%;
// height: 100%;
// }
// }
// }
} }
} }
} }
} }
:deep(.el-steps--simple) {
padding: 6px 10px;
}
:deep(.el-timeline-item) {
padding-bottom: 5px !important;
}
:deep(.el-timeline-item__timestamp) {
color: rgba(95, 101, 108, 1) !important;
font-family: Microsoft YaHei !important;
font-size: 14px !important;
font-weight: 600 !important;
}
.timeline-content {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
</style> </style>
...@@ -2,76 +2,22 @@ ...@@ -2,76 +2,22 @@
<div class="resource-library-section"> <div class="resource-library-section">
<div class="home-content-footer-header"> <div class="home-content-footer-header">
<div class="btn-box"> <div class="btn-box">
<div class="btn" :class="{ btnActive: activeTabName === cate.name, disabled: cate.active === false}" <div class="btn" :class="{ btnActive: activeTabName === cate.name }" v-for="cate in tabList" :key="cate.name" @click="handleClickTab(cate)">
v-for="(cate, index) in tabList" :key="index" @click="cate.active === true && handleClickTab(cate)">
{{ cate.name }} {{ cate.name }}
</div> </div>
</div> </div>
</div> </div>
<div class="home-content-footer-main" :class="{ 'committee-full-layout': activeTabName === '委员会' }"> <div class="home-content-footer-main" :class="{ 'committee-full-layout': activeTabName === '委员会' }">
<div class="left" v-if="['国会法案', '国会议员', '议员合作关系'].includes(activeTabName)"> <div class="left" v-if="leftFilters.length">
<div class="select-box"> <div class="select-box" v-for="group in leftFilters" :key="group.key">
<div class="select-box-header"> <div class="select-box-header">
<div class="icon"></div> <div class="icon"></div>
<div class="title">科技领域</div> <div class="title">{{ group.title }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group class="checkbox-group" v-model="activeAreaList" @change="handleAreaChange"> <el-checkbox-group class="checkbox-group" v-model="group.model.value" @change="group.onChange">
<el-checkbox class="filter-checkbox" label="全部领域"> 全部领域 </el-checkbox> <el-checkbox v-for="opt in group.options" :key="opt.id" :label="opt.id" class="filter-checkbox">
<el-checkbox v-for="(area, index) in cateKuList" :key="index" :label="area.id" class="filter-checkbox"> {{ opt.name }}
{{ 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>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
...@@ -105,21 +51,27 @@ ...@@ -105,21 +51,27 @@
</div> </div>
<div class="right-main" v-loading="loading"> <div class="right-main" v-loading="loading">
<template v-if="activeTabName === '国会法案'"> <template v-if="activeTabName === '国会法案'">
<div class="right-main-box" v-for="(item, index) in bills" :key="index"> <div class="right-main-box" v-for="item in bills" :key="item.billId">
<div v-if="item.riskSignal" class="risk-tag" :class="getRiskTagClass(item.riskSignal)">{{ item.riskSignal }}</div> <div v-if="item.riskSignal" class="risk-tag" :class="getRiskTagClass(item.riskSignal)">{{ item.riskSignal }}</div>
<div class="header"> <div class="bill-cover">
<div class="title" @click="onClickToDetail(item)" :title="item.name">{{ item.name }}</div> <img class="bill-image" :src="getBillImageUrl(item)" alt="" @error="handleBillImageError" />
<div class="en-title" :title="item.eName">{{ item.eName }}</div> <div class="bill-id" :title="item.billId">{{ item.billId || "-" }}</div>
</div> </div>
<div class="main"> <div class="bill-content">
<div class="item"><div class="item-left">提案人:</div><div class="item-right">{{ item.tcr }}</div></div> <div class="header">
<div class="item"><div class="item-left">委员会:</div><div class="item-right">{{ item.wyh }}</div></div> <div class="title" @click="onClickToDetail(item)" :title="item.name">{{ item.name }}</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="en-title" :title="item.eName">{{ item.eName }}</div>
<div class="item"><div class="item-left">最新动议:</div><div class="item-right"><CommonPrompt :content="item.zxdy" /></div></div> </div>
<div class="item"> <div class="main">
<div class="item-left">法案进展:</div> <div class="item"><div class="item-left">提案人:</div><div class="item-right">{{ item.tcr }}</div></div>
<div class="item-right2"> <div class="item"><div class="item-left">委员会:</div><div class="item-right">{{ item.wyh }}</div></div>
<div class="tag" v-for="(val, idx) in [...item.progress].reverse()" :key="idx" :style="{ zIndex: item.progress.length - idx }">{{ val }}</div> <div class="item"><div class="item-left">相关领域:</div><div class="item-right1"><div class="tag" v-for="(val, idx) in item.areaList" :key="`${item.billId}-${val}-${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 getReversedProgress(item.progress)" :key="`${item.billId}-${val}-${idx}`" :style="{ zIndex: item.progress.length - idx }">{{ val }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -150,7 +102,7 @@ ...@@ -150,7 +102,7 @@
<div class="member-meta">{{ item.partyText || '-' }} · {{ item.chamberText || '-' }} · {{ item.termText || '-' }}</div> <div class="member-meta">{{ item.partyText || '-' }} · {{ item.chamberText || '-' }} · {{ item.termText || '-' }}</div>
<div class="member-committee">{{ item.committeeText || '-' }}</div> <div class="member-committee">{{ item.committeeText || '-' }}</div>
<div class="member-tags"> <div class="member-tags">
<div class="member-tag" v-for="(tag, idx) in item.focusTags" :key="idx">{{ tag }}</div> <div class="member-tag" v-for="(tag, idx) in item.focusTags" :key="`${item.id}-${tag}-${idx}`">{{ tag }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -167,28 +119,91 @@ ...@@ -167,28 +119,91 @@
</div> </div>
</div> </div>
</div> </div>
<div v-else-if="activeTabName === '议员合作关系'" class="coop-list"> <div v-else-if="activeTabName === '议员合作关系'">
<div class="member-card" v-for="item in memberList" :key="`coop-${item.id}`"> <div class="coop-list">
<div class="member-name">{{ item.name || '-' }}</div> <div class="coop-card" v-for="item in coopPagedList" :key="item.id">
<div class="member-info">党派:{{ item.party || '-' }}</div> <div class="coop-card-header">
<div class="member-info">议院:{{ item.chamber || '-' }}</div> <div class="coop-members">
<div class="member-info">任期:{{ item.term || '-' }}</div> <div class="coop-member">
<div class="member-info">委员会:{{ item.committee || '-' }}</div> <img class="coop-avatar" :src="item.left.avatar || defaultAvatar" alt="avatar-left" />
<div class="member-info">关注领域:{{ item.focus || '-' }}</div> <div class="coop-member-name" :title="item.left.name">{{ item.left.name }}</div>
</div>
<div class="coop-dot">·</div>
<div class="coop-member">
<img class="coop-avatar" :src="item.right.avatar || defaultAvatar" alt="avatar-right" />
<div class="coop-member-name" :title="item.right.name">{{ item.right.name }}</div>
</div>
</div>
<div class="coop-summary" :title="item.summary">
{{ item.summary }}
</div>
<div class="coop-count">
{{ item.totalText }}
</div>
<slot name="coop-extra" :relation="item" />
</div>
<div class="coop-proposals">
<div
class="coop-proposal-item"
v-for="proposal in item.proposals"
:key="proposal.billId"
@click="handleClickCoopProposal(proposal)"
>
<div class="coop-proposal-main">
<div class="coop-proposal-title">
<span class="year">{{ proposal.year }}</span>
<span class="code">{{ proposal.code }}</span>
</div>
<div class="coop-proposal-subtitle" :title="proposal.title">
{{ proposal.title }}
</div>
</div>
<div class="coop-proposal-arrow">></div>
</div>
</div>
</div>
</div>
<div class="right-footer">
<div class="footer-left">{{ `共 ${coopTotal} 项` }}</div>
<div class="footer-right">
<el-pagination
@current-change="handleCoopCurrentChange"
:page-size="coopPageSize"
:current-page="coopCurrentPage"
background
layout="prev, pager, next"
:total="coopTotal"
/>
</div>
</div> </div>
</div> </div>
<div v-else-if="activeTabName === '委员会'" class="committee-list"> <div v-else-if="activeTabName === '委员会'">
<div class="committee-card" v-for="item in committeeList" :key="item.id"> <div class="coop-list committee-list">
<div class="committee-info"> <div class="coop-card" v-for="item in committeeList" :key="item.id">
<img class="committee-avatar" :src="item.avatar || defaultAvatar" alt="committee-avatar" /> <div class="coop-card-header">
<div class="committee-text"> <div class="coop-members">
<div class="committee-name">{{ item.name }}</div> <div class="coop-member">
<div class="committee-desc">{{ item.desc }}</div> <img class="coop-avatar" :src="item.avatar || defaultAvatar" alt="committee-avatar" />
<div class="coop-member-name" :title="item.name">{{ item.name }}</div>
</div>
</div>
<div class="coop-summary" :title="item.desc">
{{ item.desc }}
</div>
<div class="coop-count">
{{ `${(item.bills || []).length}项重点法案` }}
</div>
<slot name="committee-extra" :committee="item" />
</div> </div>
</div> <div class="coop-proposals">
<div class="committee-bill-grid"> <div class="coop-proposal-item" v-for="bill in item.bills" :key="`${item.id}-${bill}`">
<div class="committee-bill-item" v-for="(bill, idx) in item.bills" :key="`${item.id}-bill-${idx}`"> <div class="coop-proposal-main">
{{ bill }} <div class="coop-proposal-subtitle" :title="bill">
{{ bill }}
</div>
</div>
<div class="coop-proposal-arrow">></div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -200,11 +215,12 @@ ...@@ -200,11 +215,12 @@
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import { getHylyList, getPostOrgList, getPostMemberList, getBills, getBillsPerson } from "@/api/bill/billHome"; import { getHylyList, getPostOrgList, getPostMemberList, getBills, getBillsPerson } from "@/api/bill/billHome";
import CommonPrompt from "../commonPrompt/index.vue"; import CommonPrompt from "../commonPrompt/index.vue";
import desc from "./assets/icons/icon-desc.png"; import desc from "./assets/icons/icon-desc.png";
import defaultAvatar from "./assets/images/user.png"; import defaultAvatar from "./assets/images/user.png";
import defaultBill from "../assets/images/image1.png";
import zyyIcon from "@/assets/icons/zyy.png"; import zyyIcon from "@/assets/icons/zyy.png";
import cyyIcon from "@/assets/icons/cyy.png"; import cyyIcon from "@/assets/icons/cyy.png";
import ghdIcon from "@/assets/icons/ghd.png"; import ghdIcon from "@/assets/icons/ghd.png";
...@@ -215,12 +231,12 @@ const props = defineProps({ ...@@ -215,12 +231,12 @@ const props = defineProps({
onAfterPageChange: { type: Function, default: null } onAfterPageChange: { type: Function, default: null }
}); });
const tabList = ref([ const tabList = [
{ name: "国会法案", active: true }, { name: "国会法案" },
{ name: "国会议员", active: false }, { name: "国会议员" },
{ name: "议员合作关系", active: false }, { name: "议员合作关系" },
{ name: "委员会", active: false } { name: "委员会" }
]); ];
const activeTabName = ref("国会法案"); const activeTabName = ref("国会法案");
const handleClickTab = tab => { const handleClickTab = tab => {
activeTabName.value = tab.name; activeTabName.value = tab.name;
...@@ -234,6 +250,11 @@ const handleClickTab = tab => { ...@@ -234,6 +250,11 @@ const handleClickTab = tab => {
handleGetBills(); handleGetBills();
return; return;
} }
if (tab.name === "议员合作关系") {
coopCurrentPage.value = 1;
handleGetCoopList();
return;
}
if (tab.name === "委员会") { if (tab.name === "委员会") {
handleGetCommitteeList(); handleGetCommitteeList();
} }
...@@ -251,14 +272,24 @@ const yyList = ref([{ id: "全部议院", name: "全部议院" }, { id: "S", nam ...@@ -251,14 +272,24 @@ const yyList = ref([{ id: "全部议院", name: "全部议院" }, { id: "S", nam
const activeYyList = ref(["全部议院"]); 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 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 activePubTime = ref(["全部时间"]);
const coopList = ref([
{ id: "全部合作关系", name: "全部合作关系" }, const areaOptions = computed(() => [{ id: "全部领域", name: "全部领域" }, ...(cateKuList.value || [])]);
{ id: "跨党派合作", name: "跨党派合作" },
{ id: "同党派合作", name: "同党派合作" }, const leftFilters = computed(() => {
{ id: "地域利益合作", name: "地域利益合作" }, if (activeTabName.value === "委员会") {
{ id: "委员会内合作", name: "委员会内合作" } return [{ key: "chamber", title: "议院", model: activeYyList, options: yyList.value, onChange: handleYyChange }];
]); }
const activeCoopList = ref(["全部合作关系"]);
const filters = [{ key: "area", title: "科技领域", model: activeAreaList, options: areaOptions.value, onChange: handleAreaChange }];
if (activeTabName.value !== "议员合作关系") {
filters.push({ key: "party", title: "党派", model: activeDpList, options: dpList.value, onChange: handleDpChange });
filters.push({ key: "chamber", title: "议院", model: activeYyList, options: yyList.value, onChange: handleYyChange });
}
if (activeTabName.value === "国会法案") {
filters.push({ key: "pubTime", title: "发布时间", model: activePubTime, options: pubTime.value, onChange: handlePubTimeChange });
}
return filters;
});
const footerSelect1 = ref("全部委员会"); const footerSelect1 = ref("全部委员会");
const footerSelect2 = ref("全部提出议员"); const footerSelect2 = ref("全部提出议员");
...@@ -269,6 +300,16 @@ const memberTotal = ref(0); ...@@ -269,6 +300,16 @@ const memberTotal = ref(0);
const memberPageSize = ref(15); const memberPageSize = ref(15);
const memberCurrentPage = ref(1); const memberCurrentPage = ref(1);
// 议员合作关系列表(预留接口插槽,当前为示例数据)
const coopList = ref([]);
const coopTotal = ref(0);
const coopPageSize = ref(6);
const coopCurrentPage = ref(1);
const coopPagedList = computed(() => {
const start = (coopCurrentPage.value - 1) * coopPageSize.value;
return coopList.value.slice(start, start + coopPageSize.value);
});
const committeeList = ref([ const committeeList = ref([
{ {
id: "committee-1", id: "committee-1",
...@@ -299,6 +340,20 @@ const pageSize = ref(4); ...@@ -299,6 +340,20 @@ const pageSize = ref(4);
const currentPage = ref(1); const currentPage = ref(1);
const loading = ref(false); const loading = ref(false);
const abortController = ref(null); const abortController = ref(null);
let requestToken = 0;
const beginAbortableRequest = () => {
if (abortController.value) abortController.value.abort();
abortController.value = new AbortController();
requestToken += 1;
const token = requestToken;
loading.value = true;
return { token, signal: abortController.value.signal };
};
const endAbortableRequest = token => {
if (token === requestToken) loading.value = false;
};
const getRiskTagClass = riskSignal => { const getRiskTagClass = riskSignal => {
if (riskSignal === "特别重大风险") return "risk-tag-critical"; if (riskSignal === "特别重大风险") return "risk-tag-critical";
...@@ -307,6 +362,17 @@ const getRiskTagClass = riskSignal => { ...@@ -307,6 +362,17 @@ const getRiskTagClass = riskSignal => {
return ""; return "";
}; };
const getBillImageUrl = item => item?.imageUrl || defaultBill;
const handleBillImageError = e => {
const img = e?.target;
if (!img) return;
if (img.dataset?.fallbackApplied === "1") return;
img.dataset.fallbackApplied = "1";
img.src = defaultBill;
};
const getReversedProgress = progress => (Array.isArray(progress) ? [...progress].reverse() : []);
// 获取委员会列表(占位) // 获取委员会列表(占位)
const handleGetCommitteeList = async () => { const handleGetCommitteeList = async () => {
loading.value = true; loading.value = true;
...@@ -321,6 +387,64 @@ const handleGetCommitteeList = async () => { ...@@ -321,6 +387,64 @@ const handleGetCommitteeList = async () => {
} }
}; };
// 获取议员合作关系数据(占位)
const handleGetCoopList = async () => {
loading.value = true;
try {
// TODO: 接入“议员合作关系”接口后,在此替换为真实请求并赋值 coopList
// 示例结构:每项包含两名议员及不超过 4 条共同提案
const list = [
{
id: "coop-1",
left: {
name: "查克·舒默",
avatar: ""
},
right: {
name: "林赛·格雷厄姆",
avatar: ""
},
total: 8,
totalText: "8项共同提案",
summary: "跨党派在国防、安全与科技议题上保持长期合作。",
proposals: [
{
billId: "2025-HR-3501",
year: "2025",
code: "H.R.3501",
title: "2026财年国防授权法案"
},
{
billId: "2025-HR-327",
year: "2025",
code: "H.R.327",
title: "汽车零部件25%关税实施规则"
},
{
billId: "2025-HR-1176",
year: "2025",
code: "H.R.1176",
title: "GENIUS稳定币法案"
},
{
billId: "2025-HR-2057",
year: "2025",
code: "H.R.2057",
title: "小额额免包装政策调整"
}
]
}
];
coopList.value = list;
coopTotal.value = list.length;
} catch (error) {
coopList.value = [];
coopTotal.value = 0;
} finally {
loading.value = false;
}
};
const handleGetHylyList = async () => { const handleGetHylyList = async () => {
try { try {
const res = await getHylyList(); const res = await getHylyList();
...@@ -350,9 +474,7 @@ const handleGetPostMemberList = async () => { ...@@ -350,9 +474,7 @@ const handleGetPostMemberList = async () => {
// 获取资源库法案列表 // 获取资源库法案列表
const handleGetBills = async () => { const handleGetBills = async () => {
if (abortController.value) abortController.value.abort(); const { token, signal } = beginAbortableRequest();
abortController.value = new AbortController();
loading.value = true;
const params = { const params = {
currentPage: currentPage.value, currentPage: currentPage.value,
...@@ -369,7 +491,7 @@ const handleGetBills = async () => { ...@@ -369,7 +491,7 @@ const handleGetBills = async () => {
if (!activePubTime.value.includes("全部时间")) params.years = activePubTime.value.join(","); if (!activePubTime.value.includes("全部时间")) params.years = activePubTime.value.join(",");
try { try {
const res = await getBills(params, abortController.value.signal); const res = await getBills(params, signal);
if (res.code === 200 && res.data && res.data.content) { if (res.code === 200 && res.data && res.data.content) {
bills.value = res.data.content.map(item => ({ bills.value = res.data.content.map(item => ({
billId: item.billId, billId: item.billId,
...@@ -380,7 +502,8 @@ const handleGetBills = async () => { ...@@ -380,7 +502,8 @@ const handleGetBills = async () => {
areaList: item.hylyList || [], areaList: item.hylyList || [],
zxdy: item.latestAction, zxdy: item.latestAction,
progress: item.stageList || [], progress: item.stageList || [],
riskSignal: item.riskSignal || "" riskSignal: item.riskSignal || "",
imageUrl: item.imageUrl || ""
})); }));
total.value = res.data.totalElements; total.value = res.data.totalElements;
} else { } else {
...@@ -393,15 +516,13 @@ const handleGetBills = async () => { ...@@ -393,15 +516,13 @@ const handleGetBills = async () => {
total.value = 0; total.value = 0;
} }
} finally { } finally {
loading.value = false; endAbortableRequest(token);
} }
}; };
// 获取资源库国会议员列表 // 获取资源库国会议员列表
const handleGetBillsPerson = async () => { const handleGetBillsPerson = async () => {
if (abortController.value) abortController.value.abort(); const { token, signal } = beginAbortableRequest();
abortController.value = new AbortController();
loading.value = true;
const params = { const params = {
currentPage: memberCurrentPage.value, currentPage: memberCurrentPage.value,
...@@ -422,7 +543,7 @@ const handleGetBillsPerson = async () => { ...@@ -422,7 +543,7 @@ const handleGetBillsPerson = async () => {
}; };
try { try {
const res = await getBillsPerson(params, abortController.value.signal); const res = await getBillsPerson(params, signal);
if (res.code === 200 && res.data && res.data.content) { if (res.code === 200 && res.data && res.data.content) {
memberList.value = res.data.content.map(item => { memberList.value = res.data.content.map(item => {
const partyMap = { Democratic: "民主党", Republican: "共和党" }; const partyMap = { Democratic: "民主党", Republican: "共和党" };
...@@ -467,10 +588,32 @@ const handleGetBillsPerson = async () => { ...@@ -467,10 +588,32 @@ const handleGetBillsPerson = async () => {
memberTotal.value = 0; memberTotal.value = 0;
} }
} finally { } finally {
loading.value = false; endAbortableRequest(token);
} }
}; };
const resetPages = () => {
currentPage.value = 1;
memberCurrentPage.value = 1;
coopCurrentPage.value = 1;
};
const refreshByActiveTab = () => {
if (activeTabName.value === "议员合作关系") {
handleGetCoopList();
return;
}
if (activeTabName.value === "委员会") {
handleGetCommitteeList();
return;
}
if (activeTabName.value === "国会议员") {
handleGetBillsPerson();
return;
}
handleGetBills();
};
const normalizeWithAll = (val, allLabel, targetRef) => { const normalizeWithAll = (val, allLabel, targetRef) => {
if (val.includes(allLabel) && val.length > 1) { if (val.includes(allLabel) && val.length > 1) {
targetRef.value = val[val.length - 1] === allLabel ? [allLabel] : val.filter(item => item !== allLabel); targetRef.value = val[val.length - 1] === allLabel ? [allLabel] : val.filter(item => item !== allLabel);
...@@ -479,30 +622,19 @@ const normalizeWithAll = (val, allLabel, targetRef) => { ...@@ -479,30 +622,19 @@ const normalizeWithAll = (val, allLabel, targetRef) => {
} else { } else {
targetRef.value = val; targetRef.value = val;
} }
currentPage.value = 1; resetPages();
memberCurrentPage.value = 1; refreshByActiveTab();
if (activeTabName.value === "国会议员") {
handleGetBillsPerson();
return;
}
handleGetBills();
}; };
const handleAreaChange = val => normalizeWithAll(val, "全部领域", activeAreaList); const handleAreaChange = val => normalizeWithAll(val, "全部领域", activeAreaList);
const handleDpChange = val => normalizeWithAll(val, "全部党派", activeDpList); const handleDpChange = val => normalizeWithAll(val, "全部党派", activeDpList);
const handleYyChange = val => normalizeWithAll(val, "全部议院", activeYyList); const handleYyChange = val => normalizeWithAll(val, "全部议院", activeYyList);
const handlePubTimeChange = val => normalizeWithAll(val, "全部时间", activePubTime); const handlePubTimeChange = val => normalizeWithAll(val, "全部时间", activePubTime);
const handleCoopChange = val => normalizeWithAll(val, "全部合作关系", activeCoopList);
const handleFooterSelect1Change = val => { const handleFooterSelect1Change = val => {
footerSelect1.value = val; footerSelect1.value = val;
currentPage.value = 1; resetPages();
memberCurrentPage.value = 1; refreshByActiveTab();
if (activeTabName.value === "国会议员") {
handleGetBillsPerson();
return;
}
handleGetBills();
}; };
const handleFooterSelect2Change = val => { const handleFooterSelect2Change = val => {
footerSelect2.value = val; footerSelect2.value = val;
...@@ -534,6 +666,19 @@ const handleClickLatestProposal = item => { ...@@ -534,6 +666,19 @@ const handleClickLatestProposal = item => {
}); });
}; };
const handleClickCoopProposal = proposal => {
if (!proposal?.billId) return;
props.onClickToDetail({
billId: proposal.billId,
name: proposal.title || proposal.code || ""
});
};
const handleCoopCurrentChange = page => {
coopCurrentPage.value = page;
// 如后续改为后端分页,可在此调用 handleGetCoopList();
};
const handleMemberCurrentChange = page => { const handleMemberCurrentChange = page => {
memberCurrentPage.value = page; memberCurrentPage.value = page;
handleGetBillsPerson(); handleGetBillsPerson();
...@@ -589,15 +734,6 @@ onMounted(() => { ...@@ -589,15 +734,6 @@ onMounted(() => {
background: var(--color-main-active); background: var(--color-main-active);
} }
} }
.disabled {
cursor: not-allowed;
opacity: 0.5;
&:hover {
background: transparent;
}
}
} }
} }
...@@ -611,9 +747,6 @@ onMounted(() => { ...@@ -611,9 +747,6 @@ onMounted(() => {
&.committee-full-layout { &.committee-full-layout {
.right { .right {
margin-left: 0;
width: 1600px;
.right-main { .right-main {
height: auto; height: auto;
} }
...@@ -621,7 +754,7 @@ onMounted(() => { ...@@ -621,7 +754,7 @@ onMounted(() => {
} }
.left { .left {
width: 300px; width: 360px;
padding-bottom: 33px; padding-bottom: 33px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1); border: 1px solid rgba(234, 236, 238, 1);
...@@ -647,9 +780,9 @@ onMounted(() => { ...@@ -647,9 +780,9 @@ onMounted(() => {
.title { .title {
color: var(--color-main-active); color: var(--color-main-active);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-size: 20px; font-size: 16px;
font-weight: 700; font-weight: 700;
line-height: 26px; line-height: 24px;
letter-spacing: 1px; letter-spacing: 1px;
text-align: left; text-align: left;
} }
...@@ -683,7 +816,8 @@ onMounted(() => { ...@@ -683,7 +816,8 @@ onMounted(() => {
.right { .right {
margin-left: 20px; margin-left: 20px;
width: 1280px; width: calc(100% - 380px);
min-width: 0;
.right-header { .right-header {
height: 48px; height: 48px;
...@@ -909,6 +1043,165 @@ onMounted(() => { ...@@ -909,6 +1043,165 @@ onMounted(() => {
.coop-list { .coop-list {
grid-template-columns: 1fr; grid-template-columns: 1fr;
.coop-card {
padding: 16px 20px 14px;
box-sizing: border-box;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: #fff;
display: flex;
flex-direction: column;
gap: 12px;
}
.coop-card-header {
display: flex;
align-items: center;
gap: 16px;
}
.coop-members {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.coop-member {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.coop-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.coop-member-name {
max-width: 130px;
color: #3b414b;
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 700;
line-height: 24px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.coop-dot {
color: #9da3ac;
font-size: 18px;
line-height: 1;
}
.coop-summary {
flex: 1;
min-width: 0;
color: #5f656c;
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 400;
line-height: 22px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.coop-count {
flex-shrink: 0;
color: #1459bb;
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 500;
line-height: 22px;
}
.coop-proposals {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #eaeced;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
column-gap: 14px;
row-gap: 12px;
}
.coop-proposal-item {
position: relative;
height: 64px;
padding: 8px 34px 8px 20px;
box-sizing: border-box;
border-radius: 8px;
background: rgb(247, 248, 249);
display: flex;
align-items: center;
cursor: pointer;
transition: background 0.15s ease;
&::before {
content: "";
position: absolute;
left: 0px;
top: 12px;
bottom: 12px;
width: 3px;
background: rgb(5, 95, 194);
}
}
.coop-proposal-item:hover {
background: rgba(20, 89, 187, 0.06);
}
.coop-proposal-main {
flex: 1;
min-width: 0;
}
.coop-proposal-title {
display: flex;
align-items: center;
gap: 8px;
color: #3b414b;
font-family: "Microsoft YaHei";
font-size: 15px;
font-weight: 700;
line-height: 22px;
}
.coop-proposal-title .year {
color: #9da3ac;
font-weight: 400;
}
.coop-proposal-subtitle {
margin-top: 2px;
color: #5f656c;
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 400;
line-height: 22px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.coop-proposal-arrow {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: #c0c4cc;
font-size: 16px;
line-height: 1;
}
} }
.committee-list { .committee-list {
...@@ -979,19 +1272,23 @@ onMounted(() => { ...@@ -979,19 +1272,23 @@ onMounted(() => {
.right-main-box { .right-main-box {
position: relative; position: relative;
width: 1280px; width: 100%;
height: 300px; height: 320px;
padding-bottom: 24px; padding: 12px 16px;
box-sizing: border-box;
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);
margin-bottom: 16px; margin-bottom: 16px;
overflow: hidden; overflow: hidden;
display: flex;
gap: 16px;
align-items: center;
.risk-tag { .risk-tag {
position: absolute; position: absolute;
top: 16px; top: 12px;
right: 40px; right: 16px;
height: 28px; height: 28px;
border-radius: 20px; border-radius: 20px;
display: inline-flex; display: inline-flex;
...@@ -1030,12 +1327,70 @@ onMounted(() => { ...@@ -1030,12 +1327,70 @@ onMounted(() => {
color: rgb(232, 189, 11); color: rgb(232, 189, 11);
} }
.bill-cover {
width: 240px;
flex-shrink: 0;
position: relative;
height: 100%;
margin: 0;
overflow: hidden;
&::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 33.333%;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.92) 60%, rgba(255, 255, 255, 1));
pointer-events: none;
}
}
.bill-image {
width: 240px;
height: 100%;
object-fit: none;
display: block;
}
.bill-id {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 33.333%;
display: flex;
align-items: center;
justify-content: center;
padding: 0 12px;
box-sizing: border-box;
color: rgba(59, 65, 75, 1);
font-family: "Source Han Sans CN";
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: center;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
z-index: 1;
}
.bill-content {
flex: 1;
min-width: 0;
overflow: hidden;
box-sizing: border-box;
}
.header { .header {
height: 91px; width: 100%;
width: 1200px;
margin: 0 auto;
border-bottom: 1px solid rgba(234, 236, 238, 1); border-bottom: 1px solid rgba(234, 236, 238, 1);
padding-top: 19px; padding-bottom: 14px;
padding-right: 120px;
.title { .title {
cursor: pointer; cursor: pointer;
...@@ -1069,16 +1424,16 @@ onMounted(() => { ...@@ -1069,16 +1424,16 @@ onMounted(() => {
} }
.main { .main {
width: 1200px; width: 100%;
margin: 0 auto; margin-top: 10px;
margin-top: 2px;
.item { .item {
margin-top: 12px; margin-top: 12px;
display: flex; display: flex;
align-items: flex-start;
.item-left { .item-left {
width: 100px; width: 88px;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
font-size: 16px; font-size: 16px;
...@@ -1104,6 +1459,7 @@ onMounted(() => { ...@@ -1104,6 +1459,7 @@ onMounted(() => {
margin-left: 10px; margin-left: 10px;
display: flex; display: flex;
gap: 8px; gap: 8px;
flex-wrap: wrap;
.tag { .tag {
height: 24px; height: 24px;
...@@ -1122,6 +1478,7 @@ onMounted(() => { ...@@ -1122,6 +1478,7 @@ onMounted(() => {
margin-left: 10px; margin-left: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap;
.tag { .tag {
height: 24px; height: 24px;
......
...@@ -264,23 +264,13 @@ const handleClickToCharacter = async (id, name) => { ...@@ -264,23 +264,13 @@ const handleClickToCharacter = async (id, name) => {
} catch (error) { } } catch (error) { }
}; };
const currentPage = ref(1);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
handleGetBills();
handleToPosi("position4");
};
const containerRef = ref(null); const containerRef = ref(null);
const { isShow } = useContainerScroll(containerRef); const { isShow } = useContainerScroll(containerRef);
const hotBillList = ref([]); // 热门法案列表 const hotBillList = ref([]); // 热门法案列表
const curHotBillListIndex = ref(0); // 当前热门法案索引
const carouselRef = ref(null); const carouselRef = ref(null);
const handleCarouselChange = index => { const handleCarouselChange = index => {
curHotBillListIndex.value = index;
if (hotBillList.value && hotBillList.value.length > 0) { if (hotBillList.value && hotBillList.value.length > 0) {
curBill.value = hotBillList.value[index]; curBill.value = hotBillList.value[index];
} }
...@@ -380,23 +370,8 @@ const box8YearList = ref([ ...@@ -380,23 +370,8 @@ const box8YearList = ref([
value: "2022" value: "2022"
} }
]); ]);
// 排序方式
const releaseTime = ref(true);
const releaseTimeList = ref([
{
label: "正序",
value: true
},
{
label: "倒序",
value: false
}
]);
// 涉华法案数量使用的领域分类列表 // 涉华法案数量使用的领域分类列表
const categoryList = ref([]); const categoryList = ref([]);
// 资源库使用的领域分类列表
const cateKuList = ref([]);
// 获取领域分类 // 获取领域分类
const handleGetHylyList = async () => { const handleGetHylyList = async () => {
...@@ -404,7 +379,6 @@ const handleGetHylyList = async () => { ...@@ -404,7 +379,6 @@ const handleGetHylyList = async () => {
const res = await getHylyList(); const res = await getHylyList();
console.log("行业领域列表", res); console.log("行业领域列表", res);
categoryList.value = res.data; categoryList.value = res.data;
cateKuList.value = res.data;
} catch (error) { } } catch (error) { }
}; };
// 新闻资讯 // 新闻资讯
...@@ -478,146 +452,6 @@ const handleGetRemarks = async () => { ...@@ -478,146 +452,6 @@ const handleGetRemarks = async () => {
} catch (error) { } } catch (error) { }
}; };
// 获取提出部门列表
const postOrgList = ref([{ departmentName: "全部委员会", departmentId: "全部委员会" }]);
const handleGetPostOrgList = async () => {
try {
const res = await getPostOrgList();
console.log("提出部门列表", res);
if (res.code === 200) {
const list = res.data.filter(item => item.departmentId);
postOrgList.value = [{ departmentName: "全部委员会", departmentId: "全部委员会" }, ...list];
}
} catch (error) { }
};
// 获取提出议员列表
const postMemberList = ref([{ memberName: "全部提出议员", memberId: "全部提出议员" }]);
const handleGetPostMemberList = async () => {
try {
const res = await getPostMemberList();
console.log("提出议员列表", res);
if (res.code === 200) {
const list = res.data.filter(item => item.memberId);
postMemberList.value = [{ memberName: "全部提出议员", memberId: "全部提出议员" }, ...list];
}
} catch (error) { }
};
// 获取资源库
const bills = ref([]);
const total = ref(0);
const pageSize = ref(4);
const loading = ref(false);
const abortController = ref(null);
const handleGetBills = async () => {
// 取消上一次未完成的请求
if (abortController.value) {
abortController.value.abort();
}
// 创建新的 AbortController
abortController.value = new AbortController();
loading.value = true;
const params = {
currentPage: currentPage.value - 1, // Standard Spring Boot page index is 0-based
pageSize: pageSize.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);
console.log("资源库列表", res);
if (res.code === 200) {
if (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 || []
}));
total.value = res.data.totalElements;
} else {
bills.value = [];
total.value = 0;
}
} else {
bills.value = [];
total.value = 0;
}
loading.value = false;
} catch (error) {
if (error.name !== "AbortError") {
console.error(error);
loading.value = false;
}
}
};
// 处理选择委员会变化
const handleFooterSelect1Change = val => {
console.log("选择委员会变化", val);
// 等会再做处理
handleGetBills();
};
// 处理选择议员变化
const handleFooterSelect2Change = val => {
console.log("选择议员变化", val);
// 等会再做处理
handleGetBills();
};
// 处理选择排序方式变化
const handlePxChange = val => {
console.log("选择排序方式变化", val);
// 等会再做处理
handleGetBills();
};
// 根据法案类型获取法案列表
// const handleGetBillsByType = async () => {
// const params = {
// type: activeHylyId.value
// };
// try {
// const res = await getBillsByType(params);
// console.log("根据法案类型获取法案列表", res);
// billList.value = res.data.map(item => {
// return {
// billId: item.billId,
// billName: item.billName,
// introductionDate: item.introductionDate,
// img: bill1
// };
// });
// } catch (error) {}
// };
// 涉华法案数量 // 涉华法案数量
const box5Select = ref("全部领域"); const box5Select = ref("全部领域");
const box5Data = ref({ const box5Data = ref({
...@@ -1078,145 +912,8 @@ const handleToPosi = id => { ...@@ -1078,145 +912,8 @@ const handleToPosi = id => {
} }
}; };
const tabList = ref([ // 资源库分页变更后滚动到资源库区域
{ const handlePageChange = () => handleToPosi("position4");
name: "国会法案",
active: true
},
{
name: "国会议员",
active: false
},
{
name: "议员合作关系",
active: false
},
{
name: "涉华委员会",
active: false
}
]);
const activeTabName = ref("国会法案");
const handleClickTab = tab => {
activeTabName.value = tab.name;
};
const activeAreaList = ref(["全部领域"]);
const handleAreaChange = val => {
if (val.includes("全部领域") && val.length > 1) {
if (val[val.length - 1] === "全部领域") {
activeAreaList.value = ["全部领域"];
} else {
activeAreaList.value = val.filter(item => item !== "全部领域");
}
} else if (val.length === 0) {
activeAreaList.value = ["全部领域"];
}
// 打印 activeAreaList.value
console.log(activeAreaList.value);
handleGetBills();
};
const dpList = ref([
{ id: "全部党派", name: "全部党派" },
{ id: "Democratic", name: "民主党" },
{ id: "Republican", name: "共和党" }
]);
const activeDpList = ref(["全部党派"]);
// 处理选择党派变化
const handleDpChange = val => {
if (val.includes("全部党派") && val.length > 1) {
if (val[val.length - 1] === "全部党派") {
activeDpList.value = ["全部党派"];
} else {
activeDpList.value = val.filter(item => item !== "全部党派");
}
} else if (val.length === 0) {
activeDpList.value = ["全部党派"];
}
console.log("选择党派变化", activeDpList.value);
handleGetBills();
};
const yyList = ref([
{ id: "全部议院", name: "全部议院" },
{ id: "S", name: "参议院" },
{ id: "H", name: "众议院" }
]);
const activeYyList = ref(["全部议院"]);
// 处理选择议院变化
const handleYyChange = val => {
if (val.includes("全部议院") && val.length > 1) {
if (val[val.length - 1] === "全部议院") {
activeYyList.value = ["全部议院"];
} else {
activeYyList.value = val.filter(item => item !== "全部议院");
}
} else if (val.length === 0) {
activeYyList.value = ["全部议院"];
}
console.log("选择议院变化", activeYyList.value);
handleGetBills();
};
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年" }
// { id: "更早时间", name: "更早时间" }
]);
const activePubTime = ref(["全部时间"]);
// 处理选择时间变化
const handlePubTimeChange = val => {
if (val.includes("全部时间") && val.length > 1) {
if (val[val.length - 1] === "全部时间") {
activePubTime.value = ["全部时间"];
} else {
activePubTime.value = val.filter(item => item !== "全部时间");
}
} else if (val.length === 0) {
activePubTime.value = ["全部时间"];
}
console.log("选择时间变化", activePubTime.value);
handleGetBills();
};
const footerSelect1 = ref("全部委员会");
const footerSelect2 = ref("全部提出议员");
// const footerBillList = ref([
// {
// name: "H.R.1-大而美法案",
// eName: "One Big Beautiful Bill Act",
// tcr: "乔迪·阿灵顿等2人",
// wyh: "众议院-预算委员会",
// areaList: ["集成电路", "人工智能"],
// zxdy: "2025.07.04 成为公法 No: 119-21",
// progress: []
// },
// {
// name: "H.R.1-大而美法案",
// eName: "One Big Beautiful Bill Act",
// tcr: "乔迪·阿灵顿等2人",
// wyh: "众议院-预算委员会",
// areaList: ["集成电路", "人工智能"],
// zxdy: "2025.07.04 成为公法 No: 119-21",
// progress: []
// },
// {
// name: "H.R.1-大而美法案",
// eName: "One Big Beautiful Bill Act",
// tcr: "乔迪·阿灵顿等2人",
// wyh: "众议院-预算委员会",
// areaList: ["集成电路", "人工智能"],
// zxdy: "2025.07.04 成为公法 No: 119-21",
// progress: []
// }
// ]);
const handleResize = () => { const handleResize = () => {
box8ChartInstance && box8ChartInstance.resize(); box8ChartInstance && box8ChartInstance.resize();
...@@ -1226,20 +923,9 @@ const handleResize = () => { ...@@ -1226,20 +923,9 @@ const handleResize = () => {
onMounted(async () => { onMounted(async () => {
window.addEventListener("resize", handleResize); window.addEventListener("resize", handleResize);
handleGetHylyList(); handleGetHylyList();
// 获取风险信号
handleGetBillRiskSignal(); handleGetBillRiskSignal();
// 获取新闻资讯
handleGetNews(); handleGetNews();
// 获取社交媒体
handleGetRemarks(); handleGetRemarks();
// 获取提出部门列表
handleGetPostOrgList();
// 获取提出议员列表
handleGetPostMemberList();
// 获取资源库
handleGetBills();
// handleGetBillsByType();
handleBox5(); //涉华法案统计 handleBox5(); //涉华法案统计
handleBox6(); // 关键条款 handleBox6(); // 关键条款
......
...@@ -148,11 +148,12 @@ ...@@ -148,11 +148,12 @@
</div> </div>
</div> --> </div> -->
<AnalysisBox title="政治献金流向"> <AnalysisBox title="政治献金流向">
<div class="box2-main"> <div class="box2-main" :class="{ 'box2-main-no-footer': !showHardcodedTips }">
<el-empty v-if="!fullSourceList.length" description="暂无数据" :image-size="100" /> <el-empty v-if="!fullSourceList.length" description="暂无数据" :image-size="100" />
<div v-else class="chart-box2" id="chart1"></div> <div v-else class="chart-box2" id="chart1"></div>
</div> </div>
<div class="box-footer"> <!-- 该提示文案后续改为接口返回,当前按需求先隐藏展示 -->
<div v-if="showHardcodedTips" class="box-footer">
<div class="box-footer-left"> <div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" /> <img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div> </div>
...@@ -210,7 +211,7 @@ ...@@ -210,7 +211,7 @@
</div> </div>
</div> --> </div> -->
<AnalysisBox title="政治献金领域分布"> <AnalysisBox title="政治献金领域分布">
<div class="box3-main"> <div class="box3-main" :class="{ 'box3-main-no-footer': !showHardcodedTips }">
<div class="box3-main-left" id="chart2"></div> <div class="box3-main-left" id="chart2"></div>
<div class="box3-main-right"> <div class="box3-main-right">
<el-empty v-if="!areaList.length" description="暂无数据" :image-size="100" /> <el-empty v-if="!areaList.length" description="暂无数据" :image-size="100" />
...@@ -225,7 +226,8 @@ ...@@ -225,7 +226,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="box-footer"> <!-- 该提示文案后续改为接口返回,当前按需求先隐藏展示 -->
<div v-if="showHardcodedTips" class="box-footer">
<div class="box-footer-left"> <div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" /> <img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div> </div>
...@@ -271,6 +273,9 @@ import Mzd from "@/assets/icons/mzd.png"; ...@@ -271,6 +273,9 @@ import Mzd from "@/assets/icons/mzd.png";
const activeBtnIndex = ref(0); const activeBtnIndex = ref(0);
const itemActiveIndex = ref(0); const itemActiveIndex = ref(0);
// 写死提示文案先保留逻辑,默认不展示;后续接口完成后改为 true 或替换为接口控制
const showHardcodedTips = ref(false);
const currentPersonName = computed(() => { const currentPersonName = computed(() => {
if (mainPoliContribution.value && mainPoliContribution.value[itemActiveIndex.value]) { if (mainPoliContribution.value && mainPoliContribution.value[itemActiveIndex.value]) {
return mainPoliContribution.value[itemActiveIndex.value].name; return mainPoliContribution.value[itemActiveIndex.value].name;
...@@ -631,7 +636,7 @@ const renderSankeyChart = () => { ...@@ -631,7 +636,7 @@ const renderSankeyChart = () => {
} }
}, },
...sourceList.map((item, index) => ({ ...sourceList.map((item, index) => ({
name: item.orgName, name: item.orgNameZh,
value: item.amount, value: item.amount,
itemStyle: { itemStyle: {
color: sankeyColors[index % sankeyColors.length] color: sankeyColors[index % sankeyColors.length]
...@@ -640,7 +645,7 @@ const renderSankeyChart = () => { ...@@ -640,7 +645,7 @@ const renderSankeyChart = () => {
]; ];
const links = sourceList.map(item => ({ const links = sourceList.map(item => ({
source: item.orgName, source: item.orgNameZhZh,
target: personName, target: personName,
value: item.amount value: item.amount
})); }));
...@@ -1104,6 +1109,14 @@ onMounted(() => { ...@@ -1104,6 +1109,14 @@ onMounted(() => {
margin: 0 auto; margin: 0 auto;
margin-bottom: 9px; margin-bottom: 9px;
&.box2-main-no-footer {
height: 351px;
.chart-box2 {
height: 351px;
}
}
.chart-box2 { .chart-box2 {
width: 1020px; width: 1020px;
height: 302px; height: 302px;
...@@ -1128,6 +1141,10 @@ onMounted(() => { ...@@ -1128,6 +1141,10 @@ onMounted(() => {
margin-bottom: 9px; margin-bottom: 9px;
display: flex; display: flex;
&.box3-main-no-footer {
height: 349px;
}
.box3-main-left { .box3-main-left {
width: 492px; width: 492px;
} }
......
...@@ -32,14 +32,14 @@ ...@@ -32,14 +32,14 @@
</div> </div>
</div> --> </div> -->
<AnalysisBox title="典型阶段耗时"> <AnalysisBox title="典型阶段耗时">
<div class="box1-main"> <div class="box1-main" :class="{ 'box1-main--full': !timeFooterText }">
<div class="box1-main-center" id="chart1"></div> <div class="box1-main-center" id="chart1"></div>
<div class="box1-main-footer"> <div v-if="timeFooterText" class="box1-main-footer">
<div class="box-footer-left"> <div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" /> <img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div> </div>
<div class="box-footer-center"> <div class="box-footer-center">
从立法耗时角度分析,大而美法案从提交到签署仅39天,远快于历史同类法案(通常需6个月以上),立法速度极快。 {{ timeFooterText }}
</div> </div>
<div class="box-footer-right"> <div class="box-footer-right">
<img src="../assets/icons/arrow-right.png" alt="" /> <img src="../assets/icons/arrow-right.png" alt="" />
...@@ -80,15 +80,14 @@ ...@@ -80,15 +80,14 @@
</div> </div>
</div> --> </div> -->
<AnalysisBox title="修正案次数分析"> <AnalysisBox title="修正案次数分析">
<div class="box2-main"> <div class="box2-main" :class="{ 'box2-main--full': !amendFooterText }">
<div class="box2-main-center" id="chart2"></div> <div class="box2-main-center" id="chart2"></div>
<div class="box2-main-footer"> <div v-if="amendFooterText" class="box2-main-footer">
<div class="box-footer-left"> <div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" /> <img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div> </div>
<div class="box-footer-center"> <div class="box-footer-center">
法案本质是共和党与资本集团的深度联盟,共和党获超 {{ amendFooterText }}
​80%利益集团献金,以减税、松监管、军工扩张为核心回报。
</div> </div>
<div class="box-footer-right"> <div class="box-footer-right">
<img src="../assets/icons/arrow-right.png" alt="" /> <img src="../assets/icons/arrow-right.png" alt="" />
...@@ -367,7 +366,17 @@ ...@@ -367,7 +366,17 @@
</div> </div>
</div> --> </div> -->
<AnalysisBox title="投票分析"> <AnalysisBox title="投票分析">
<div class="box3-main"> <div class="vote-legend">
<div class="vote-legend-item">
<span class="vote-legend-dot agree"></span>
<span>赞成票</span>
</div>
<div class="vote-legend-item">
<span class="vote-legend-dot against"></span>
<span>反对票</span>
</div>
</div>
<div class="box3-main" :class="{ 'box3-main--full': !voteFooterText }">
<div class="box3-main-center"> <div class="box3-main-center">
<div class="box3-main-center-header"> <div class="box3-main-center-header">
<div class="box3-main-center-header-box1">立法阶段</div> <div class="box3-main-center-header-box1">立法阶段</div>
...@@ -658,12 +667,12 @@ ...@@ -658,12 +667,12 @@
</div> --> </div> -->
</div> </div>
</div> </div>
<div class="box3-main-footer"> <div v-if="voteFooterText" class="box3-main-footer">
<div class="box-footer-left"> <div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" /> <img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div> </div>
<div class="box-footer-center"> <div class="box-footer-center">
法案以218:214​(众议院)和51:50​(副总统决胜票)微弱优势强行通过,暴露两党极端对立、党内倒戈频发的特点。 {{ voteFooterText }}
</div> </div>
<div class="box-footer-right"> <div class="box-footer-right">
<img src="../assets/icons/arrow-right.png" alt="" /> <img src="../assets/icons/arrow-right.png" alt="" />
...@@ -881,6 +890,11 @@ const voteAnalysisList4 = ref([ ...@@ -881,6 +890,11 @@ const voteAnalysisList4 = ref([
} }
]); ]);
// 底部说明文案(接口预留,默认不展示)
const timeFooterText = ref("");
const amendFooterText = ref("");
const voteFooterText = ref("");
// 绘制echarts图表 // 绘制echarts图表
const setChart = (option, chartId) => { const setChart = (option, chartId) => {
let chartDom = document.getElementById(chartId); let chartDom = document.getElementById(chartId);
...@@ -1091,6 +1105,12 @@ onMounted(async () => { ...@@ -1091,6 +1105,12 @@ onMounted(async () => {
.box1-main { .box1-main {
height: 368px; height: 368px;
&.box1-main--full {
.box1-main-center {
height: 340px;
}
}
.box1-main-center { .box1-main-center {
width: 792px; width: 792px;
height: 300px; height: 300px;
...@@ -1230,6 +1250,12 @@ onMounted(async () => { ...@@ -1230,6 +1250,12 @@ onMounted(async () => {
.box2-main { .box2-main {
height: 359px; height: 359px;
&.box2-main--full {
.box2-main-center {
height: 340px;
}
}
.box2-main-center { .box2-main-center {
height: 300px; height: 300px;
margin: 0 5px; margin: 0 5px;
...@@ -1345,7 +1371,7 @@ onMounted(async () => { ...@@ -1345,7 +1371,7 @@ onMounted(async () => {
#chart2 { #chart2 {
position: relative; position: relative;
width: 330px; width: 330px;
height: 300px; height: 340px;
z-index: 0; z-index: 0;
} }
...@@ -1471,9 +1497,52 @@ onMounted(async () => { ...@@ -1471,9 +1497,52 @@ onMounted(async () => {
.box3 { .box3 {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative;
.vote-legend {
position: absolute;
top: 15px;
left: 50%;
transform: translateX(-50%);
display: inline-flex;
align-items: center;
gap: 20px;
z-index: 2;
.vote-legend-item {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: rgba(95, 101, 108, 1);
line-height: 22px;
}
.vote-legend-dot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
&.agree {
background: rgb(33, 129, 57);
}
&.against {
background: rgb(206, 79, 81);
}
}
}
.box3-main { .box3-main {
height: 791px; height: 791px;
&.box3-main--full {
.box3-main-center {
height: 772px;
}
}
.box3-main-center { .box3-main-center {
height: 732px; height: 732px;
margin: 0 20px; margin: 0 20px;
...@@ -1529,7 +1598,7 @@ onMounted(async () => { ...@@ -1529,7 +1598,7 @@ onMounted(async () => {
} }
.box3-main-center-content { .box3-main-center-content {
height: 682px; height: 722px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
......
...@@ -16,10 +16,10 @@ ...@@ -16,10 +16,10 @@
> >
<div class="item-title">{{ item.actionTitle }}</div> <div class="item-title">{{ item.actionTitle }}</div>
</el-tooltip> </el-tooltip>
<!-- <div class="right"> <div class="right">
<div class="risk-tag" :class="item.riskClass">{{ item.riskText }}</div> <div v-if="item.riskText" class="risk-tag" :class="item.riskClass">{{ item.riskText }}</div>
<div class="arrow">></div> <div class="arrow">></div>
</div> --> </div>
</div> </div>
</div> </div>
...@@ -41,6 +41,27 @@ const RISK_CLASS_MAP = { ...@@ -41,6 +41,27 @@ const RISK_CLASS_MAP = {
"低风险": "risk-low" "低风险": "risk-low"
}; };
const normalizeRiskSignal = riskSignal => {
if (riskSignal === null || riskSignal === undefined) return "";
if (typeof riskSignal === "number" && Number.isFinite(riskSignal)) {
const idx = Math.max(0, Math.min(RISK_LEVELS.length - 1, Math.floor(riskSignal)));
return RISK_LEVELS[idx];
}
const text = String(riskSignal).trim();
if (!text) return "";
if (RISK_CLASS_MAP[text]) return text;
const numeric = Number(text);
if (Number.isFinite(numeric)) {
const idx = Math.max(0, Math.min(RISK_LEVELS.length - 1, Math.floor(numeric)));
return RISK_LEVELS[idx];
}
return text;
};
export default { export default {
name: "SBillProgressList", name: "SBillProgressList",
data() { data() {
...@@ -81,7 +102,7 @@ export default { ...@@ -81,7 +102,7 @@ export default {
formattedDate = `${dateObj.getMonth() + 1}${dateObj.getDate()}日`; formattedDate = `${dateObj.getMonth() + 1}${dateObj.getDate()}日`;
} }
const riskText = RISK_LEVELS[Math.min(index, RISK_LEVELS.length - 1)]; const riskText = normalizeRiskSignal(item?.level);
const riskClass = RISK_CLASS_MAP[riskText] || "risk-low"; const riskClass = RISK_CLASS_MAP[riskText] || "risk-low";
return { return {
......
<template> <template>
<div class="temp-wrap"> <div class="temp-wrap">
<div class="left"> <div class="side">
<div class="side-box side-box-domain">
<AnalysisBox title="涉及领域" width="520px" height="415px">
<div :class="['right-box2-main', { 'right-box-main--full': !domainFooterText }]" id="chart2"></div>
<div v-if="domainFooterText" class="right-box2-footer">
<div class="right-box2-footer-left">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="right-box2-footer-center">
{{ domainFooterText }}
</div>
<div class="right-box2-footer-right">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div>
</AnalysisBox>
</div>
<div class="side-box side-box-limit">
<AnalysisBox title="限制手段" width="520px" height="415px">
<div :class="['right-box1-main', { 'right-box-main--full': !limitFooterText }]" id="chart1"></div>
<div v-if="limitFooterText" class="right-box1-footer">
<div class="right-box1-footer-left">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="right-box1-footer-center">
{{ limitFooterText }}
</div>
<div class="right-box1-footer-right">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div>
</AnalysisBox>
</div>
</div>
<div class="terms">
<!-- <div class="box-header"> <!-- <div class="box-header">
<div class="box-header-left"></div> <div class="box-header-left"></div>
<div class="box-header-title">主要条款</div> <div class="box-header-title">主要条款</div>
...@@ -91,7 +125,7 @@ ...@@ -91,7 +125,7 @@
</div> </div>
</div> </div>
<div class="left-main"> <div class="left-main">
<div class="left-main-item" v-for="(term, index) in mainTermsList" :key="index"> <div class="left-main-item" v-for="(term, index) in mainTermsList" :key="getTermKey(term, index)">
<div class="id">{{ (currentPage - 1) * pageSize + index + 1 }}</div> <div class="id">{{ (currentPage - 1) * pageSize + index + 1 }}</div>
<div class="info"> <div class="info">
<div class="title"> <div class="title">
...@@ -104,7 +138,7 @@ ...@@ -104,7 +138,7 @@
</div> </div>
</div> </div>
<div class="tags-box"> <div class="tags-box">
<div class="tag" v-for="(val, idx) in (term.hylyList || []).slice(0, 2)" :key="idx" :class="{ <div class="tag" v-for="(val, idx) in (term.hylyList || []).slice(0, 2)" :key="getTagKey(val, idx)" :class="{
tag1: val === '人工智能', tag1: val === '人工智能',
tag2: val === '新一代信息技术' || !['人工智能', '政治', '经济', '军事', '科技'].includes(val), tag2: val === '新一代信息技术' || !['人工智能', '政治', '经济', '军事', '科技'].includes(val),
tag3: val === '政治', tag3: val === '政治',
...@@ -131,86 +165,6 @@ ...@@ -131,86 +165,6 @@
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
<div class="right">
<div class="right-box1">
<!-- <div class="box-header">
<div class="box-header-left"></div>
<div class="box-header-title">限制手段</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="right-box1-main" id="chart1"></div>
<div class="right-box1-footer">
<div class="right-box1-footer-left">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="right-box1-footer-center">通过关税壁垒、技术脱钩、新能源打压、地缘捆绑遏制中国产业链发展</div>
<div class="right-box1-footer-right">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div> -->
<AnalysisBox title="限制手段">
<div class="right-box1-main" id="chart1"></div>
<div class="right-box1-footer">
<div class="right-box1-footer-left">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="right-box1-footer-center">通过关税壁垒、技术脱钩、新能源打压、地缘捆绑遏制中国产业链发展</div>
<div class="right-box1-footer-right">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div>
</AnalysisBox>
</div>
<div class="right-box2">
<!-- <div class="box-header">
<div class="box-header-left"></div>
<div class="box-header-title">涉及领域</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="right-box2-main" id="chart2"></div>
<div class="right-box2-footer">
<div class="right-box2-footer-left">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="right-box2-footer-center">系统性挤压中国新能源、跨境电商及高端制造的在美生存空间。</div>
<div class="right-box2-footer-right">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div> -->
<AnalysisBox title="涉及领域">
<div class="right-box2-main" id="chart2"></div>
<div class="right-box2-footer">
<div class="right-box2-footer-left">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="right-box2-footer-center">系统性挤压中国新能源、跨境电商及高端制造的在美生存空间。</div>
<div class="right-box2-footer-right">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div>
</AnalysisBox>
</div>
</div>
</div> </div>
</template> </template>
...@@ -234,11 +188,21 @@ const pageSize = ref(10); ...@@ -234,11 +188,21 @@ const pageSize = ref(10);
const total = ref(0); const total = ref(0);
const mainTermsList = ref([]); const mainTermsList = ref([]);
const domainFooterText = ref("");
const limitFooterText = ref("");
const btnActiveIndex = ref(1); const btnActiveIndex = ref(1);
const handleSelectBtn = index => { const handleSelectBtn = index => {
btnActiveIndex.value = index; btnActiveIndex.value = index;
}; };
const getTermKey = (term, index) => {
return term?.ywid ?? term?.id ?? term?.tkxh ?? index;
};
const getTagKey = (val, idx) => {
return `${val}-${idx}`;
};
const chart1Data = ref([]); const chart1Data = ref([]);
const chart1ColorList = ref(["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"]); const chart1ColorList = ref(["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"]);
...@@ -478,8 +442,9 @@ onMounted(async () => { ...@@ -478,8 +442,9 @@ onMounted(async () => {
} }
} }
.left { .terms {
margin-top: 16px; margin-top: 16px;
margin-left: 16px;
width: 1064px; width: 1064px;
height: 845px; height: 845px;
.left-top { .left-top {
...@@ -668,19 +633,27 @@ onMounted(async () => { ...@@ -668,19 +633,27 @@ onMounted(async () => {
} }
} }
.right { .side {
width: 520px; width: 520px;
margin-top: 16px; margin-top: 16px;
margin-left: 16px;
.right-box1 { .side-box {
width: 520px; width: 520px;
height: 415px; }
.right-box1-main {
.side-box-limit {
margin-top: 15px;
width: 520px; width: 520px;
height: 315px; height: 415px;
padding: 16px; .right-box1-main {
} width: 520px;
height: 315px;
padding: 16px;
}
.right-box-main--full {
height: 375px;
}
.right-box1-footer { .right-box1-footer {
width: 493px; width: 493px;
...@@ -735,15 +708,18 @@ onMounted(async () => { ...@@ -735,15 +708,18 @@ onMounted(async () => {
} }
} }
.right-box2 { .side-box-domain {
margin-top: 15px;
width: 520px;
height: 415px;
.right-box2-main {
width: 520px; width: 520px;
height: 315px; height: 415px;
padding: 16px; .right-box2-main {
} width: 520px;
height: 315px;
padding: 16px;
}
.right-box-main--full {
height: 375px;
}
.right-box2-footer { .right-box2-footer {
width: 493px; width: 493px;
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'; import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { getEnterpriseBranch, getEnterpriseKeyPerson } from '@/api/companyPages'; import { getEnterpriseBranch, getEnterpriseKeyPerson } from '@/api/companyPages';
import PersonAvatar from '@/components/base/people/personAvatar.vue'; import PersonAvatar from '@/components/base/people/PersonAvatar.vue';
import { ElDescriptions, ElDescriptionsItem, ElDivider, ElImage, ElSpace } from 'element-plus'; import { ElDescriptions, ElDescriptionsItem, ElDivider, ElImage, ElSpace } from 'element-plus';
import '@/styles/descriptions.scss' import '@/styles/descriptions.scss'
......
<template>
<sanctions-situation :enterprise-info="enterpriseInfo" :line-data="lineData"></sanctions-situation>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getNetProfitList, getPersonnelList, getRevenueList } from '@/api/companyPages';
import SanctionsSituation, { LineDataItem } from './SanctionsSituation.vue';
// 定义组件属性
const props = defineProps({
enterpriseInfo: {
type: Object,
default: {}
}
});
const lineData = ref<LineDataItem[]>([])
onMounted(async () => {
await intData()
})
async function intData() {
let { data: revenue } = await getRevenueList(props.enterpriseInfo.id)
revenue = revenue?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.value,
type: '营收',
unit: item.unit ?? '亿元'
})) ?? []
let { data: netProfit } = await getNetProfitList(props.enterpriseInfo.id)
netProfit = netProfit?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.value,
type: '净利润',
unit: item.unit ?? '亿元'
})) ?? []
let { data: personnel } = await getPersonnelList(props.enterpriseInfo.id)
personnel = personnel?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.value,
type: '人员',
unit: item.unit ?? '亿元'
})) ?? []
lineData.value = [...revenue, ...netProfit, ...personnel]
}
</script>
\ No newline at end of file
<template>
<sanctions-situation :enterprise-info="enterpriseInfo" :line-data="lineData"></sanctions-situation>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getMarketShareList } from '@/api/companyPages';
import SanctionsSituation, { LineDataItem } from './SanctionsSituation.vue';
// 定义组件属性
const props = defineProps({
enterpriseInfo: {
type: Object,
default: {}
}
});
const lineData = ref<LineDataItem[]>([])
onMounted(async () => {
await intData()
})
async function intData() {
const { data } = await getMarketShareList(props.enterpriseInfo.id)
lineData.value = data?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.value,
unit: item.unit ?? '亿元'
})) ?? []
}
</script>
\ No newline at end of file
<template>
<sanctions-situation :enterprise-info="enterpriseInfo" :line-data="lineData"></sanctions-situation>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getMarketCapList } from '@/api/companyPages';
import SanctionsSituation, { LineDataItem } from './SanctionsSituation.vue';
// 定义组件属性
const props = defineProps({
enterpriseInfo: {
type: Object,
default: {}
}
});
const lineData = ref<LineDataItem[]>([])
onMounted(async () => {
await intData()
})
async function intData() {
const { data } = await getMarketCapList(props.enterpriseInfo.id)
lineData.value = data?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.value,
type: '市值变化',
unit: item.unit ?? '亿元'
})) ?? []
}
</script>
\ No newline at end of file
<template>
<sanctions-situation :enterprise-info="enterpriseInfo" :line-data="lineData"></sanctions-situation>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getMarketCapList, getStudyList } from '@/api/companyPages';
import SanctionsSituation, { LineDataItem } from './SanctionsSituation.vue';
// 定义组件属性
const props = defineProps({
enterpriseInfo: {
type: Object,
default: {}
}
});
const lineData = ref<LineDataItem[]>([])
onMounted(async () => {
await intData()
})
async function intData() {
const { data } = await getStudyList(props.enterpriseInfo.id)
lineData.value = data?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.currentValue,
type: item.type,
unit: item.unit ?? '亿元'
})) ?? []
}
</script>
\ No newline at end of file
<script setup> <script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'; import { ref, onMounted, onUnmounted, watch } from 'vue';
import { ElSpace, ElRadioGroup, ElRadio, ElRadioButton } from 'element-plus'; import { ElRadioGroup, ElRadioButton } from 'element-plus';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { getStudyList, getSanctionList } from '@/api/companyPages'; import { getSanctionList } from '@/api/companyPages';
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'; import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue';
import AiTipPane from '@/components/base/panes/AiTipPane.vue' import AiTipPane from '@/components/base/panes/AiTipPane.vue'
export interface LineDataItem {
time: Date;
value: number;
type: string;
unit: string;
}
// 定义组件属性 // 定义组件属性
const props = defineProps({ const props = defineProps({
enterpriseInfo: { enterpriseInfo: {
type: Object, type: Object,
default: {} default: {}
},
lineData: {
type: Array<LineDataItem>,
default: () => []
} }
}); });
const studyList = ref([])
const sanctionList = ref([]) const sanctionList = ref([])
const studyTypes = ref([]) const lineTypes = ref([])
const studyType = ref() const lineType = ref()
const chartDom = ref() const chartDom = ref()
let myChart = null let myChart = null
...@@ -28,24 +38,26 @@ onMounted(async () => { ...@@ -28,24 +38,26 @@ onMounted(async () => {
}) })
onUnmounted(() => myChart?.dispose()) onUnmounted(() => myChart?.dispose())
watch(() => [props.lineData, sanctionList.value], async () => {
lineTypes.value = [...new Set(props.lineData.map(t => t.type))]
if (lineTypes.value.length > 0) {
lineType.value = lineTypes.value[0]
}
updateCharts(lineType.value, props.lineData, sanctionList.value)
})
async function intData() { async function intData() {
const { data } = await getStudyList(props.enterpriseInfo.id) const { data } = await getSanctionList(props.enterpriseInfo.id)
studyList.value = data ?? [] sanctionList.value = data ?? []
data.forEach(t => t.time = new Date(t.year, 1, 1))
studyTypes.value = [...new Set(data.map(t => t.type))]
const { data: sanctionData } = await getSanctionList(props.enterpriseInfo.id)
sanctionList.value = sanctionData ?? []
sanctionList.value.forEach(t => t.time = new Date(t.sanctionDate)) sanctionList.value.forEach(t => t.time = new Date(t.sanctionDate))
if (studyTypes.value.length > 0) {
studyType.value = studyTypes.value[0]
}
updateCharts(studyType.value, studyList.value, sanctionList.value)
} }
// 辅助函数:获取制裁年份对应的Y值 // 辅助函数:获取制裁年份对应的Y值
function getSanctionYValue(sanctionDate, filteredList, defaultYValue) { function getSanctionYValue(sanctionDate: Date, filteredList: LineDataItem[], defaultYValue: number) {
const year = sanctionDate.getFullYear() const year = sanctionDate.getFullYear()
const yearData = filteredList.find(d => d.year === year) const yearData = filteredList.find(d => d.time.getFullYear() === year)
return yearData ? yearData.currentValue : defaultYValue return yearData ? yearData.value : defaultYValue
} }
// 辅助函数:格式化文本内容,实现智能换行(考虑中英文混合) // 辅助函数:格式化文本内容,实现智能换行(考虑中英文混合)
...@@ -77,7 +89,95 @@ function formatContent(content, maxWidth = 26) { ...@@ -77,7 +89,95 @@ function formatContent(content, maxWidth = 26) {
return lines.join('\n') return lines.join('\n')
} }
function updateCharts(type, dataStudy, dataSanction) {
// 辅助函数:获取折线图数据
function getLineSeries(filteredList: LineDataItem[]) {
return {
type: 'line',
//从上到下填充颜色
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(128, 181, 255, 0.8)'
},
{
offset: 1,
color: 'rgba(128, 181, 255, 0)'
}
])
},
color: '#80B5FF',
data: filteredList.map(t => [t.time, t.value])
}
}
// 辅助函数:获取散点图数据
function getScatterSeries(filteredList: LineDataItem[], dataSanction: LineDataItem[], yValue: number) {
// 计算散点图数据
const xyData = dataSanction.map(t => {
const currentYValue = getSanctionYValue(t.time, filteredList, yValue) + yValue / 3
return [t.time, currentYValue, t]
})
return {
type: 'scatter',
tooltip: { show: false },
data: xyData,
// 绘制垂直虚线
markLine: {
lineStyle: {
color: '#ff4d4f',
type: 'dashed',
width: 1
},
data: xyData.map(t => {
return [{
coord: [t[0], 0],
symbol: 'none'
}, {
coord: [t[0], t[1]],
symbol: 'none',
}]
})
},
coordinateSystem: 'cartesian2d',
symbolSize: 1,
// 散点图标签
label: {
show: true,
position: 'insideBottomLeft',
formatter: function (params) {
const title = params.data[2].sanctionDate;
const content = params.data[2].content;
const formattedContent = formatContent(content);
return `{title|${title}}\n{content|${formattedContent}}`;
},
rich: {
title: {
fontSize: 16,
lineHeight: 22,
color: '#CE4F51'
},
content: {
fontSize: 15,
fontWeight: 'bold',
width: 200, // 限制宽度
color: '#CE4F51',
lineHeight: 22,
overflow: 'break' // 超出宽度自动换行
}
},
backgroundColor: 'rgba(255, 241, 240, 1)',
borderColor: 'rgba(255, 204, 199, 1)',
borderRadius: 4,
padding: [8, 12],
distance: -1
}
}
}
// 辅助函数:更新图表数据
function updateCharts(type: string, dataStudy: LineDataItem[], dataSanction: LineDataItem[]) {
const filteredList = dataStudy.filter(t => t.type === type) const filteredList = dataStudy.filter(t => t.type === type)
if (!filteredList.length) return if (!filteredList.length) return
...@@ -91,13 +191,15 @@ function updateCharts(type, dataStudy, dataSanction) { ...@@ -91,13 +191,15 @@ function updateCharts(type, dataStudy, dataSanction) {
// y轴单位 // y轴单位
const unit = filteredList[0].unit const unit = filteredList[0].unit
const yValue = Math.max(...filteredList.map(d => d.currentValue)) const yValue = Math.max(...filteredList.map(d => d.value))
// 计算x轴范围,扩大活动空间 // 计算x轴范围,扩大活动空间
const allDates = [ const allDates = [
...filteredList.map(t => t.time), ...filteredList.map(t => t.time.getTime()),
...dataSanction.map(t => t.time) ...dataSanction.map(t => t.time.getTime())
] ]
// 计算x轴范围,扩大活动空间
const minDate = new Date(Math.min(...allDates)) const minDate = new Date(Math.min(...allDates))
const maxDate = new Date(Math.max(...allDates)) const maxDate = new Date(Math.max(...allDates))
...@@ -144,104 +246,38 @@ function updateCharts(type, dataStudy, dataSanction) { ...@@ -144,104 +246,38 @@ function updateCharts(type, dataStudy, dataSanction) {
nameLocation: 'end' nameLocation: 'end'
}, },
series: [ series: [
{ getScatterSeries(filteredList, dataSanction, yValue),
type: 'line', getLineSeries(filteredList),
//从上到下填充颜色 ]
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(128, 181, 255, 0.8)'
},
{
offset: 1,
color: 'rgba(128, 181, 255, 0)'
}
])
},
data: filteredList.map(t => [t.time, t.currentValue])
},
{
type: 'scatter',
tooltip: { show: false },
data: dataSanction.map(t => {
const currentYValue = getSanctionYValue(t.time, filteredList, yValue) + yValue / 3
return [t.time, currentYValue, t]
}),
markLine: {
lineStyle: {
color: '#ff4d4f',
type: 'dashed',
width: 1
},
data: dataSanction.map(t => {
const currentYValue = getSanctionYValue(t.time, filteredList, yValue) + yValue / 3
return [{
coord: [t.time, 0],
symbol: 'none'
}, {
coord: [t.time, currentYValue],
symbol: 'none',
}]
})
},
coordinateSystem: 'cartesian2d',
symbolSize: 1,
label: {
show: true,
position: 'insideBottomLeft',
formatter: function (params) {
const title = params.data[2].sanctionDate;
const content = params.data[2].content;
const formattedContent = formatContent(content);
return `{title|${title}}\n{content|${formattedContent}}`;
},
rich: {
title: {
fontSize: 16,
lineHeight: 22,
color: '#CE4F51'
},
content: {
fontSize: 15,
fontWeight: 'bold',
width: 200, // 限制宽度
color: '#CE4F51',
lineHeight: 22,
overflow: 'break' // 超出宽度自动换行
}
},
backgroundColor: 'rgba(255, 241, 240, 1)',
borderColor: 'rgba(255, 204, 199, 1)',
borderRadius: 4,
padding: [8, 12],
distance: -1
}
}
],
}) })
} }
// 辅助函数:处理研究类型改变事件
function handleStudyTypesChange() { function handleStudyTypesChange() {
updateCharts(studyType.value, studyList.value, sanctionList.value) updateCharts(lineType.value, props.lineData, sanctionList.value)
} }
</script> </script>
<template> <template>
<analysis-box title="被制裁时间轴"> <analysis-box title="被制裁时间轴">
<template v-slot:header-btn> <template v-if="lineTypes.length > 1" v-slot:header-btn>
<el-radio-group v-model="studyType" @change="handleStudyTypesChange"> <el-radio-group class="radio-group-as-gap-btn" v-model="lineType" @change="handleStudyTypesChange">
<el-radio-button v-for="item in studyTypes" :key="item" :label="item">{{ item }}</el-radio-button> <el-radio-button v-for="item in lineTypes" :key="item" :label="item">{{ item }}</el-radio-button>
</el-radio-group> </el-radio-group>
</template> </template>
<div class="flex-display content-box"> <div class="flex-display content-box">
<div ref="chartDom" class="chart-container"></div> <div ref="chartDom" class="chart-container"></div>
<ai-tip-pane>123</ai-tip-pane> <ai-tip-pane
v-if="enterpriseInfo.id === '914403001922038216'">近年来,华为遭遇了严峻的外部技术封锁,其核心源于某些国家的持续制裁。这主要包括被列入“实体清单”,禁止其未经许可从美国公司获取芯片等关键技术;以及遭受更严格的“外国直接产品规则”打击,旨在切断其利用美国工具、软件和技术设计或制造先进芯片的渠道。这些措施不仅限制了华为自身产品的设计与生产,也影响了其全球供应链,使其在获取先进半导体、移动操作系统生态(如GMS)及基础软件工具等方面面临巨大挑战,意图从根本上遏制其技术发展。</ai-tip-pane>
</div> </div>
</analysis-box> </analysis-box>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@use '@/styles/radio.scss';
.content-box { .content-box {
padding: 10px; padding: 10px;
gap: 10px; gap: 10px;
......
<script setup lang="ts"> <script setup lang="ts">
import '@/styles/tabs.scss' import '@/styles/tabs.scss'
import { ElTabPane, ElTabs } from 'element-plus'; import { ElTabPane, ElTabs } from 'element-plus';
import SanctionsSituation from './SanctionsSituation.vue'; import EnterpriseScale from './EnterpriseScale.vue';
import MarketShare from './MarketShare.vue';
import MarketValue from './MarketValue.vue';
import ResearchAndDevelopment from './ResearchAndDevelopment.vue';
// 定义组件属性 // 定义组件属性
const props = defineProps({ const props = defineProps({
...@@ -15,12 +18,18 @@ const props = defineProps({ ...@@ -15,12 +18,18 @@ const props = defineProps({
<template> <template>
<div style="overflow: visible;"> <div style="overflow: visible;">
<el-tabs tabPosition="left" class="disinheritance tabs-nav-no-wrap left-float-nav-tabs"> <el-tabs tabPosition="left" class="disinheritance tabs-nav-no-wrap left-float-nav-tabs">
<el-tab-pane label="企业规模"> <el-tab-pane label="企业规模" lazy>
<sanctions-situation :enterprise-info="enterpriseInfo"></sanctions-situation> <enterprise-scale :enterprise-info="enterpriseInfo"></enterprise-scale>
</el-tab-pane>
<el-tab-pane label="市值变化" lazy>
<market-value :enterprise-info="enterpriseInfo"></market-value>
</el-tab-pane>
<el-tab-pane label="研发投入" lazy>
<research-and-development :enterprise-info="enterpriseInfo"></research-and-development>
</el-tab-pane>
<el-tab-pane label="市场占比" lazy>
<market-share :enterprise-info="enterpriseInfo"></market-share>
</el-tab-pane> </el-tab-pane>
<!-- <el-tab-pane label="市值变化"></el-tab-pane>
<el-tab-pane label="研发投入"></el-tab-pane>
<el-tab-pane label="市场占比"></el-tab-pane> -->
</el-tabs> </el-tabs>
</div> </div>
</template> </template>
\ No newline at end of file
...@@ -30,7 +30,7 @@ import { getSupplyAreaList } from '@/api/companyPages'; ...@@ -30,7 +30,7 @@ import { getSupplyAreaList } from '@/api/companyPages';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { ElSpace } from 'element-plus'; import { ElSpace } from 'element-plus';
// 导入自定义组件 // 导入自定义组件
import CapitalScale from './capitalScale.vue' import CapitalScale from './CapitalScale.vue'
// 响应式数据 // 响应式数据
const areas = ref([]) // 供应区域列表 const areas = ref([]) // 供应区域列表
......
...@@ -29,10 +29,10 @@ ...@@ -29,10 +29,10 @@
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { getEnterprisePageInfo } from '@/api/companyPages'; import { getEnterprisePageInfo } from '@/api/companyPages';
import TitlePane from './component/titlePane.vue'; import TitlePane from './component/TitlePane.vue';
import NewsPane from './component/detailsPages/newsPane.vue'; import NewsPane from './component/DetailsPages/NewsPane.vue';
import BaseInfo from './component/detailsPages/baseInfo.vue'; import BaseInfo from './component/DetailsPages/BaseInfo.vue';
import OperatingPages from './component/operatingPages/index.vue'; import OperatingPages from './component/OperatingPages/index.vue';
import '@/styles/tabs.scss' import '@/styles/tabs.scss'
import '@/styles/container.scss' import '@/styles/container.scss'
import { ElScrollbar, ElSpace, ElTabs, ElTabPane } from 'element-plus'; import { ElScrollbar, ElSpace, ElTabs, ElTabPane } from 'element-plus';
......
...@@ -50,14 +50,10 @@ ...@@ -50,14 +50,10 @@
</template> </template>
<script setup> <script setup>
import NewsList from "@/components/base/NewsList/index.vue"; import NewsList from "@/components/NewsList.vue";
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import router from '@/router' import router from '@/router'
import { getCoopRestrictionNews, getCoopRestrictionSocial } from '@/api/coopRestriction/coopRestriction' import { getCoopRestrictionNews, getCoopRestrictionSocial } from '@/api/coopRestriction/coopRestriction'
import CommonPrompt from "../../commonPrompt/index.vue";
import defaultNews from "../../assets/images/default-icon-news.png"
import defaultAvatar from "../../assets/images/default-icon1.png"
import title01 from './assets/title01.png' import title01 from './assets/title01.png'
import title02 from './assets/title02.png' import title02 from './assets/title02.png'
import title03 from './assets/title03.png' import title03 from './assets/title03.png'
...@@ -92,7 +88,7 @@ const getCoopRestrictionSocialData = async () => { ...@@ -92,7 +88,7 @@ const getCoopRestrictionSocialData = async () => {
console.error("获取合作限制社交媒体数据失败:", error); console.error("获取合作限制社交媒体数据失败:", error);
} }
}; };
const leftList = ref([])
// 合作限制-查询新闻资讯接口 // 合作限制-查询新闻资讯接口
const getCoopRestrictionNewsData = async () => { const getCoopRestrictionNewsData = async () => {
...@@ -114,7 +110,7 @@ const getCoopRestrictionNewsData = async () => { ...@@ -114,7 +110,7 @@ const getCoopRestrictionNewsData = async () => {
}; };
const leftList = ref([])
const rightList = ref([]) const rightList = ref([])
......
...@@ -673,7 +673,7 @@ const newsList = ref([ ...@@ -673,7 +673,7 @@ const newsList = ref([
]); ]);
const handleGetNews = async () => { const handleGetNews = async () => {
const params = { const params = {
moduleId: "0100" moduleId: "0101"
}; };
try { try {
const res = await getNews(params); const res = await getNews(params);
...@@ -723,7 +723,7 @@ const messageList = ref([ ...@@ -723,7 +723,7 @@ const messageList = ref([
]); ]);
const handleGetMessage = async () => { const handleGetMessage = async () => {
const params = { const params = {
moduleId: "0100" moduleId: "0101"
}; };
try { try {
const res = await getSocialMedia(params); const res = await getSocialMedia(params);
......
...@@ -701,7 +701,7 @@ ...@@ -701,7 +701,7 @@
</template> </template>
<script setup> <script setup>
import NewsList from "@/components/base/newsList/index.vue"; import NewsList from "@/components/NewsList.vue";
import RiskSignal from "@/components/RiskSignal/RiskSignal.vue"; import RiskSignal from "@/components/RiskSignal/RiskSignal.vue";
import { onMounted, ref, computed, reactive, shallowRef, watch, nextTick } from "vue"; import { onMounted, ref, computed, reactive, shallowRef, watch, nextTick } from "vue";
import { useContainerScroll } from "@/hooks/useScrollShow"; import { useContainerScroll } from "@/hooks/useScrollShow";
......
...@@ -934,7 +934,7 @@ onMounted(() => { ...@@ -934,7 +934,7 @@ onMounted(() => {
:deep(.el-table__header-wrapper) { :deep(.el-table__header-wrapper) {
th { th {
background-color: rgb(59, 65, 75) !important; background-color: var(--color-primary-100) !important;
height: 48px; height: 48px;
padding: 0; padding: 0;
color: #fff; color: #fff;
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
</div> </div>
</div> --> </div> -->
<div class="home-main" ref="containerRef"> <div class="home-main" ref="containerRef">
<div class="home-top-bg"></div> <div class="home-top-bg"></div>
<div class="home-main-header"> <div class="home-main-header">
<div class="home-main-header-center"> <div class="home-main-header-center">
<SearchContainer style="margin-bottom: 0; height: fit-content" v-if="containerRef" placeholder="搜索投融资限制政策" <SearchContainer style="margin-bottom: 0; height: fit-content" v-if="containerRef" placeholder="搜索投融资限制政策"
...@@ -477,7 +477,7 @@ import CustomContainer from "@/components/Container/index.vue"; ...@@ -477,7 +477,7 @@ import CustomContainer from "@/components/Container/index.vue";
import ClickableCard from "./components/link.vue"; import ClickableCard from "./components/link.vue";
import InfoCard from "./components/info.vue"; import InfoCard from "./components/info.vue";
import CustomTitle from "./components/title.vue"; import CustomTitle from "./components/title.vue";
import NewsList from "@/components/base/NewsList/index.vue"; import NewsList from "@/components/NewsList.vue";
import trumpAvatar from "@/assets/images/icon-trump.png"; import trumpAvatar from "@/assets/images/icon-trump.png";
import elongAvatar from "@/assets/images/icon-elong.png"; import elongAvatar from "@/assets/images/icon-elong.png";
...@@ -1719,7 +1719,7 @@ onMounted(async () => { ...@@ -1719,7 +1719,7 @@ onMounted(async () => {
// background: url("./assets/images/background.png"); // background: url("./assets/images/background.png");
// background-size: 100% 100%; // background-size: 100% 100%;
.home-top-bg { .home-top-bg {
background: background:
url("./assets/images/background.png"), url("./assets/images/background.png"),
linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%); linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%);
......
...@@ -374,18 +374,13 @@ ...@@ -374,18 +374,13 @@
<script setup> <script setup>
import RiskSignal from "@/components/base/RiskSignal/index.vue"; import RiskSignal from "@/components/RiskSignal/RiskSignal.vue";
import NewsList from "@/components/base/NewsList/index.vue"; import NewsList from "@/components/NewsList.vue";
import { onMounted, ref, computed } from "vue"; import { onMounted, ref, computed } from "vue";
import * as echarts from "echarts";
import router from "@/router"; import router from "@/router";
import DivideHeader from "@/components/DivideHeader.vue"; import DivideHeader from "@/components/DivideHeader.vue";
import scrollToTop from "@/utils/scrollToTop";
import { useContainerScroll } from "@/hooks/useScrollShow"; import { useContainerScroll } from "@/hooks/useScrollShow";
import getBarChart from "./utils/barChart";
import getPieChart from "./utils/piechart"; import getPieChart from "./utils/piechart";
import getCalendarHeatChart from "./utils/cleandarHeat";
import EChart from "@/components/Chart/index.vue"; import EChart from "@/components/Chart/index.vue";
import { pieOption, raderOption } from "./utils/charts"; import { pieOption, raderOption } from "./utils/charts";
import { import {
......
...@@ -436,9 +436,9 @@ ...@@ -436,9 +436,9 @@
</template> </template>
<script setup> <script setup>
import RiskSignal from "@/components/RiskSignal/RiskSignal.vue";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import NewsList from "@/components/base/NewsList/index.vue"; import NewsList from "@/components/base/NewsList/index.vue";
import RiskSignal from "@/components/base/RiskSignal/index.vue";
import DivideHeader from "@/components/DivideHeader.vue"; import DivideHeader from "@/components/DivideHeader.vue";
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
import router from "@/router"; import router from "@/router";
...@@ -1802,6 +1802,7 @@ onMounted(async () => { ...@@ -1802,6 +1802,7 @@ onMounted(async () => {
.box1 { .box1 {
width: 1064px; width: 1064px;
height: 450px; height: 450px;
.box1-left { .box1-left {
position: absolute; position: absolute;
left: 0; left: 0;
......
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
</template> </template>
<script setup> <script setup>
import NewsList from "@/components/base/NewsList/index.vue"; import NewsList from "@/components/NewsList.vue";
import { ref, onBeforeMount } from "vue"; import { ref, onBeforeMount } from "vue";
import router from "@/router" import router from "@/router"
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
</template> </template>
<script setup> <script setup>
import NewsList from "@/components/base/NewsList/index.vue"; import NewsList from "@/components/NewsList.vue";
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { import {
......
...@@ -95,6 +95,7 @@ const handleGetThinkTankReportSummary = async () => { ...@@ -95,6 +95,7 @@ const handleGetThinkTankReportSummary = async () => {
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
reportUrl.value = res.data.reportUrl; reportUrl.value = res.data.reportUrl;
thinkInfo.value = res.data; thinkInfo.value = res.data;
console.log(reportUrl.value, 'reportUrl.value')
} }
} catch (error) { } catch (error) {
console.error("获取报告全局信息error", error); console.error("获取报告全局信息error", error);
......
...@@ -57,24 +57,32 @@ ...@@ -57,24 +57,32 @@
</div> </div>
<div class="center"> <div class="center">
<div class="title">{{ item.content }}</div> <div class="title">{{ item.content }}</div>
<div>
<img
src="../images/image-open.png"
alt=""
class="center-image"
@click="handleOpenReportOriginal(item)"
/>
</div>
<!-- <div class="desc">{{ item.econtent }}</div> --> <!-- <div class="desc">{{ item.econtent }}</div> -->
</div> </div>
<div class="right"> <!-- <div class="right"> -->
<!-- <div class="tag" v-for="(val, idx) in item.hylyList" :key="idx"> <!-- <div class="tag" v-for="(val, idx) in item.hylyList" :key="idx">
{{ val }} {{ val }}
</div> </div>
<div class="tag" v-for="(val, idx) in item.serialNum" :key="idx"> <div class="tag" v-for="(val, idx) in item.serialNum" :key="idx">
{{ val }} {{ val }}
</div> --> </div> -->
<AreaTag v-for="(val, idx) in item.hylyList" :key="idx" :tagName="val"></AreaTag> <!-- <AreaTag v-for="(val, idx) in item.hylyList" :key="idx" :tagName="val"></AreaTag>
</div> </div> -->
<!-- <div class="more"> <!-- <div class="more">
<img src="@/assets/icons/open.png" alt="" /> <img src="@/assets/icons/open.png" alt="" />
</div> --> </div> -->
</div> </div>
</div> </div>
<div class="box3-main-footer"> <div class="box3-main-footer">
<div class="info"> {{ total }}</div> <div class="info">{{ total }}条核心论点</div>
<div class="page-box"> <div class="page-box">
<el-pagination <el-pagination
:page-size="12" :page-size="12"
...@@ -87,19 +95,6 @@ ...@@ -87,19 +95,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="box3-footer">
<div class="footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="footer-center">
{{
`中美经济深度交织,全面脱钩成本高昂且不现实。其核心揭示了三大纽带:生产网络相互依存使强行分离代价巨大;人才双向流动推动创新却成政策博弈焦点;能源领域合作与竞争并存,关乎全球气候治理与经济博弈。报告主张理性竞合,在竞争中找到合作路径。`
}}
</div>
<div class="footer-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="" />
</div>
</div>
</AnalysisBox> </AnalysisBox>
</div> </div>
</div> </div>
...@@ -247,22 +242,49 @@ const majorOpinions = ref([ ...@@ -247,22 +242,49 @@ const majorOpinions = ref([
] ]
} }
]); ]);
//处理点击详情页事件
const handleOpenReportOriginal = item => {
const route = router.resolve({
name: "ReportOriginal",
params: {
id: router.currentRoute._value.params.id
},
query: {
currentPage: currentPage.value,
pageSize: pageSize.value,
opinionId: item?.id ?? "",
opinionContent: item?.content ?? ""
}
});
window.open(route.href, "_blank");
};
const tabActiveName = ref("报告分析");
const switchTab = name => {
tabActiveName.value = name;
};
// 处理页码改变事件 // 处理页码改变事件
const currentPage = ref(1); const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0); const total = ref(0);
const handleCurrentChange = page => { const handleCurrentChange = page => {
currentPage.value = page; currentPage.value = page;
handleGetThinkDynamicsReport(); handleGetThinkTankReportContent();
}; };
//获取报告主要观点 //获取报告主要观点
const handleGetThinkTankReportContent = async () => { const handleGetThinkTankReportContent = async () => {
try { try {
const res = await getThinkTankReportContent(router.currentRoute._value.params.id); const params = {
id: router.currentRoute._value.params.id,
currentPage: currentPage.value - 1,
pageSize: pageSize.value
};
const res = await getThinkTankReportContent(params);
console.log("主要观点", res.data); console.log("主要观点", res.data);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
majorOpinions.value = res.data.content; majorOpinions.value = res.data.content || [];
handleGetBox3AnalysisContent(majorOpinions.value); handleGetBox3AnalysisContent(majorOpinions.value);
total.value = res.data.totalElements; total.value = res.data.totalElements || 0;
} }
} catch (error) { } catch (error) {
console.error("获取主要观点error", error); console.error("获取主要观点error", error);
...@@ -495,7 +517,7 @@ onMounted(() => { ...@@ -495,7 +517,7 @@ onMounted(() => {
.box3 { .box3 {
width: 1103px; width: 1103px;
height: 946px; height: 965px;
// 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);
...@@ -504,11 +526,11 @@ onMounted(() => { ...@@ -504,11 +526,11 @@ onMounted(() => {
.box3-main { .box3-main {
width: 1057px; width: 1057px;
height: 800px;
margin: 0 auto; margin: 0 auto;
.box3-main-main { .box3-main-main {
height: 720px; height: 767px;
overflow: hidden; overflow: hidden;
.box3-item { .box3-item {
...@@ -547,6 +569,7 @@ onMounted(() => { ...@@ -547,6 +569,7 @@ onMounted(() => {
.title { .title {
margin-top: 12px; margin-top: 12px;
width: 918px;
// height: 55px; // height: 55px;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
...@@ -564,6 +587,13 @@ onMounted(() => { ...@@ -564,6 +587,13 @@ onMounted(() => {
overflow: hidden; overflow: hidden;
} }
.center-image {
width: 16px;
height: 24px;
margin-top: 12px;
margin-left: 18px;
}
.desc { .desc {
height: 22px; height: 22px;
color: rgba(95, 101, 108, 1); color: rgba(95, 101, 108, 1);
......
...@@ -200,7 +200,15 @@ ...@@ -200,7 +200,15 @@
<div class="item-right"> <div class="item-right">
<div> <div>
<div class="title">{{ item.content }}</div> <div class="title">{{ item.content }}</div>
<div class="info">{{ item.times }} · {{ item.name }}</div> <div class="info">
{{ item.times }} · {{ item.name }}
<div class="more" @click="toDetail(item)">
<img
src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image open.png"
alt=""
/>
</div>
</div>
<div class="tag-box"> <div class="tag-box">
<AreaTag v-for="(tag, idx) in item.tagList" :key="idx" :tagName="tag"></AreaTag> <AreaTag v-for="(tag, idx) in item.tagList" :key="idx" :tagName="tag"></AreaTag>
</div> </div>
...@@ -221,16 +229,12 @@ ...@@ -221,16 +229,12 @@
</div> </div>
</div> </div>
</div> </div>
<div> <div></div>
<div class="more" @click="toDetail(item)">
<img src="@/assets/icons/open.png" alt="" />
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="right-footer"> <div class="right-footer">
<div class="info"> {{ total }} 项</div> <div class="info">{{ total }}条政策建议</div>
<div class="page-box"> <div class="page-box">
<el-pagination <el-pagination
:page-size="12" :page-size="12"
...@@ -307,7 +311,8 @@ const box1Data = ref([ ...@@ -307,7 +311,8 @@ const box1Data = ref([
// color: "#D6E4FF" // color: "#D6E4FF"
// } // }
]); ]);
const relationBillsList = ref([{ billName: "2025《人工智能安全与评估法案》" }]);
const relationAdList = ref([{ adName: "2025《人工智能安全与评估法案》" }]);
const box1SelectYear = ref("2025"); const box1SelectYear = ref("2025");
const box1YearList = ref([ const box1YearList = ref([
{ {
...@@ -621,7 +626,11 @@ const handleGetThinkPolicy = async () => { ...@@ -621,7 +626,11 @@ const handleGetThinkPolicy = async () => {
const res = await getThinkPolicy(parmas); const res = await getThinkPolicy(parmas);
console.log("智库政策", res); console.log("智库政策", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
policyList.value = res.data.content; policyList.value = res.data.content.map(item => ({
...item,
relationBillsList: relationBillsList.value,
relationAdList: relationAdList.value
}));
total.value = res.data.totalElements; total.value = res.data.totalElements;
} }
} catch (error) { } catch (error) {
...@@ -656,6 +665,7 @@ onMounted(() => { ...@@ -656,6 +665,7 @@ onMounted(() => {
margin: 16px 0; margin: 16px 0;
display: flex; display: flex;
gap: 16px; gap: 16px;
.box { .box {
width: 520px; width: 520px;
height: 420px; height: 420px;
...@@ -1157,21 +1167,29 @@ onMounted(() => { ...@@ -1157,21 +1167,29 @@ onMounted(() => {
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
.right-main { .right-main {
margin: 17px auto; margin-top: 17px;
width: 1209px; margin-left: 0;
width: 100%;
box-sizing: border-box;
padding-left: 37px;
padding-right: 0;
max-height: 1540px; max-height: 1540px;
.right-main-item { .right-main-item {
// height: 154px; // height: 154px;
box-sizing: border-box; box-sizing: border-box;
padding-top: 8px; padding-top: 16px;
padding-bottom: 16px;
margin-left: -37px;
padding-left: 37px;
padding-right: 36px;
width: calc(100% + 37px - 36px);
border-bottom: 1px solid rgba(234, 236, 238, 1); border-bottom: 1px solid rgba(234, 236, 238, 1);
display: flex; display: flex;
.item-left { .item-left {
width: 57px; width: 57px;
height: 77px; height: 77px;
margin-top: 3px;
img { img {
width: 100%; width: 100%;
...@@ -1188,7 +1206,7 @@ onMounted(() => { ...@@ -1188,7 +1206,7 @@ onMounted(() => {
.title { .title {
// height: 24px; // height: 24px;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei; font-family: "Source Han Sans CN";
font-size: 18px; font-size: 18px;
font-weight: 700; font-weight: 700;
line-height: 24px; line-height: 24px;
...@@ -1200,12 +1218,25 @@ onMounted(() => { ...@@ -1200,12 +1218,25 @@ onMounted(() => {
margin-top: 7px; margin-top: 7px;
height: 22px; height: 22px;
color: rgba(95, 101, 108, 1); color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei; font-family: "Source Han Sans CN";
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
line-height: 22px; line-height: 22px;
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
display: flex;
.more {
width: 16px;
height: 16px;
margin-top: 3px;
margin-left: 9px;
img {
width: 100%;
height: 100%;
}
}
} }
.tag-box { .tag-box {
...@@ -1235,30 +1266,30 @@ onMounted(() => { ...@@ -1235,30 +1266,30 @@ onMounted(() => {
.file { .file {
height: 32px; height: 32px;
padding: 0 8px; padding: 4px 8px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: 8px; gap: 12px;
border-radius: 4px; border-radius: 4px;
background: rgba(246, 250, 255, 1); background: rgba(246, 250, 255, 1);
.type { .type {
height: 22px; height: 22px;
margin-top: 1px;
padding: 0 4px; padding: 0 4px;
border-radius: 4px; border-radius: 4px;
background: rgba(231, 243, 255, 1); background: rgba(231, 243, 255, 1);
color: rgba(5, 95, 194, 1); color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei; font-family: "Source Han Sans CN";
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
line-height: 22px; line-height: 22px;
} }
.title { .title {
height: 24px;
color: rgba(5, 95, 194, 1); color: rgba(5, 95, 194, 1);
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;
...@@ -1267,8 +1298,10 @@ onMounted(() => { ...@@ -1267,8 +1298,10 @@ onMounted(() => {
.more { .more {
width: 20px; width: 20px;
height: 20px; height: 20px;
display: flex;
margin-top: 2px;
img { .img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
...@@ -1280,21 +1313,30 @@ onMounted(() => { ...@@ -1280,21 +1313,30 @@ onMounted(() => {
} }
.right-footer { .right-footer {
margin: 0 auto; height: 86px;
width: 1209px;
height: 96px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-top: 17px;
.info { .info {
margin-top: 29px;
margin-left: 39px;
color: rgba(132, 136, 142, 1); color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-size: 14px; font-size: 14px;
height: 19px;
font-weight: 400; font-weight: 400;
line-height: 18px; line-height: 18px;
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
} }
.page-box {
margin-top: 23px;
margin-right: 36px;
height: 32px;
}
} }
} }
} }
......
...@@ -22,9 +22,12 @@ ...@@ -22,9 +22,12 @@
</div> </div>
<div class="select-main"> <div class="select-main">
<div class="checkbox-group"> <div class="checkbox-group">
<el-checkbox label="全部领域"></el-checkbox> <el-checkbox class="filter-checkbox" :model-value="isGroupAllSelected(researchTypeIds)"
<el-checkbox v-for="type in researchTypeList" :key="type.id" v-model="selectedResearchTypeList" @change="val => handleToggleAll(val, researchTypeIds)">
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()"> 全部领域
</el-checkbox>
<el-checkbox v-for="type in researchTypeList" :key="type.id" v-model="selectedResearchIds" :label="type.id"
class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.name }} {{ type.name }}
</el-checkbox> </el-checkbox>
</div> </div>
...@@ -37,8 +40,11 @@ ...@@ -37,8 +40,11 @@
</div> </div>
<div class="select-main"> <div class="select-main">
<div class="checkbox-group"> <div class="checkbox-group">
<el-checkbox label="全部时间"></el-checkbox> <el-checkbox class="filter-checkbox" :model-value="isGroupAllSelected(researchTimeIds)"
<el-checkbox v-for="type in researchTimeList" :key="type.id" v-model="selectedResearchTypeList" @change="val => handleToggleAll(val, researchTimeIds)">
全部时间
</el-checkbox>
<el-checkbox v-for="type in researchTimeList" :key="type.id" v-model="selectedResearchTimeIds"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()"> :label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.name }} {{ type.name }}
</el-checkbox> </el-checkbox>
...@@ -55,8 +61,11 @@ ...@@ -55,8 +61,11 @@
</div> </div>
<div class="select-main"> <div class="select-main">
<div class="checkbox-group"> <div class="checkbox-group">
<el-checkbox label="全部部门"></el-checkbox> <el-checkbox class="filter-checkbox" :model-value="isGroupAllSelected(researchHearingIds)"
<el-checkbox v-for="type in researchHearingList" :key="type.id" v-model="selectedResearchTypeList" @change="val => handleToggleAll(val, researchHearingIds)">
全部部门
</el-checkbox>
<el-checkbox v-for="type in researchHearingList" :key="type.id" v-model="selectedResearchHearingIds"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()"> :label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.name }} {{ type.name }}
</el-checkbox> </el-checkbox>
...@@ -92,10 +101,10 @@ ...@@ -92,10 +101,10 @@
</div> </div>
<div class="right-footer"> <div class="right-footer">
<div class="info"> <div class="info">
{{ hearingData.length }} {{ filteredHearingData.length }} 篇智库报告
</div> </div>
<div class="page-box"> <div class="page-box">
<el-pagination :page-size="10" background layout="prev, pager, next" :total="hearingData.length" <el-pagination :page-size="10" background layout="prev, pager, next" :total="filteredHearingData.length"
@current-change="handleCurrentChange" :current-page="currentPage" /> @current-change="handleCurrentChange" :current-page="currentPage" />
</div> </div>
</div> </div>
...@@ -122,9 +131,17 @@ const props = defineProps({ ...@@ -122,9 +131,17 @@ const props = defineProps({
type: Array, type: Array,
default: () => [] default: () => []
}, },
selectedResearchTypeList: { selectedFilters: {
type: Array, type: Object,
default: () => [] default: () => ({
researchTypeIds: [],
researchTimeIds: [],
researchHearingIds: []
})
},
selectedYear: {
type: Number,
default: 1
}, },
curFooterList: { curFooterList: {
type: Array, type: Array,
...@@ -141,48 +158,108 @@ const props = defineProps({ ...@@ -141,48 +158,108 @@ const props = defineProps({
}); });
const emit = defineEmits([ const emit = defineEmits([
"update:selectedResearchTypeList", "update:selectedFilters",
"filter-change", "filter-change",
"page-change", "page-change",
"report-click" "report-click"
]); ]);
// 解构 props,保持模板里变量名不变 // 解构 props,保持模板里变量名不变
const { researchTypeList, researchTimeList, curFooterList, total, currentPage, researchHearingList, hearingData } = toRefs(props); const { researchTypeList, researchTimeList, curFooterList, total, currentPage, researchHearingList, hearingData, selectedYear } = toRefs(props);
const pageSize = 10; const pageSize = 10;
// 只展示当前页的 12 条,否则会一页显示全部 20 条 function getDateYearsAgo(years) {
const displayList = computed(() => { const currentDate = new Date();
const list = hearingData.value || []; return new Date(currentDate.getFullYear() - years, currentDate.getMonth(), currentDate.getDate());
const start = (currentPage.value - 1) * pageSize; }
return list.slice(start, start + pageSize);
}); function parseChineseDate(dateStr) {
if (!dateStr) return null;
const match = String(dateStr).match(/(\d{4})(\d{1,2})(\d{1,2})日/);
if (!match) return null;
return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
}
// 用本地 ref 替代 computed,el-checkbox 会直接修改数组,需要可变的 ref const selectedResearchIds = ref([]);
const selectedResearchTypeList = ref([...(props.selectedResearchTypeList || [])]); const selectedResearchTimeIds = ref([]);
const selectedResearchHearingIds = ref([]);
// 父组件更新时同步到子组件
watch( watch(
() => props.selectedResearchTypeList, () => props.selectedFilters,
val => { val => {
selectedResearchTypeList.value = val ? [...val] : []; selectedResearchIds.value = val?.researchTypeIds ? [...val.researchTypeIds] : [];
selectedResearchTimeIds.value = val?.researchTimeIds ? [...val.researchTimeIds] : [];
selectedResearchHearingIds.value = val?.researchHearingIds ? [...val.researchHearingIds] : [];
}, },
{ immediate: true, deep: true } { immediate: true, deep: true }
); );
// 子组件勾选变化时通知父组件,flush: 'sync' 确保在 @change 触发前父组件已更新,否则 filter-change 时父组件拿到的还是旧值 const buildSelectedFiltersPayload = () => ({
watch( researchTypeIds: [...selectedResearchIds.value],
selectedResearchTypeList, researchTimeIds: [...selectedResearchTimeIds.value],
val => { researchHearingIds: [...selectedResearchHearingIds.value]
emit("update:selectedResearchTypeList", val); });
},
{ deep: true, flush: "sync" } const researchTypeIds = computed(() => (researchTypeList.value || []).map(item => item.id));
); const researchTimeIds = computed(() => (researchTimeList.value || []).map(item => item.id));
const researchHearingIds = computed(() => (researchHearingList.value || []).map(item => item.id));
const getTargetSelection = ids => {
if (ids === researchTypeIds.value) return selectedResearchIds;
if (ids === researchTimeIds.value) return selectedResearchTimeIds;
return selectedResearchHearingIds;
};
const isGroupAllSelected = ids =>
ids.length > 0 && ids.every(id => getTargetSelection(ids).value.includes(id));
const handleToggleAll = (checked, ids) => {
if (!ids.length) return;
const targetSelection = getTargetSelection(ids);
const nextSelected = new Set(targetSelection.value);
if (checked) {
ids.forEach(id => nextSelected.add(id));
} else {
ids.forEach(id => nextSelected.delete(id));
}
targetSelection.value = [...nextSelected];
handleGetThinkDynamicsReport();
};
const filteredHearingData = computed(() => {
const rangeStart = getDateYearsAgo(selectedYear.value || 1);
return (hearingData.value || []).filter(item => {
const itemDate = parseChineseDate(item.time);
const matchTopRange = itemDate ? itemDate >= rangeStart : true;
const matchYear =
selectedResearchTimeIds.value.length === 0 ||
selectedResearchTimeIds.value.some(year => String(item.time || "").startsWith(year));
const matchDepartment =
selectedResearchHearingIds.value.length === 0 ||
selectedResearchHearingIds.value.some(department =>
String(item.content || "").includes(department) || String(item.title || "").includes(department)
);
const matchType =
selectedResearchIds.value.length === 0 ||
selectedResearchIds.value.some(typeId =>
String(item.category || "").includes(typeId) || String(item.title || "").includes(typeId)
);
return matchTopRange && matchYear && matchDepartment && matchType;
});
});
// 只展示当前页的数据
const displayList = computed(() => {
const list = filteredHearingData.value || [];
const start = (currentPage.value - 1) * pageSize;
return list.slice(start, start + pageSize);
});
// 保持模板里的方法名不变,但改成通知父组件,直接传入当前选中值避免时序问题 // 保持模板里的方法名不变,但改成通知父组件,直接传入当前选中值避免时序问题
const handleGetThinkDynamicsReport = () => { const handleGetThinkDynamicsReport = () => {
emit("update:selectedResearchTypeList", [...selectedResearchTypeList.value]); const payload = buildSelectedFiltersPayload();
emit("filter-change", [...selectedResearchTypeList.value]); emit("update:selectedFilters", payload);
emit("filter-change", payload);
}; };
const handleCurrentChange = page => { const handleCurrentChange = page => {
...@@ -214,7 +291,7 @@ const handleToReportDetail = item => { ...@@ -214,7 +291,7 @@ const handleToReportDetail = item => {
.select-research-box { .select-research-box {
width: 360px; width: 360px;
height: 284px; height: 284px;
margin-top: 21px; margin-top: 19px;
.select-box-header { .select-box-header {
display: flex; display: flex;
...@@ -243,31 +320,30 @@ const handleToReportDetail = item => { ...@@ -243,31 +320,30 @@ const handleToReportDetail = item => {
.select-main { .select-main {
margin-left: 25px; margin-left: 24px;
.checkbox-group { .checkbox-group {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); 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 { .select-time-box {
margin-top: 21px; margin-top: 44px;
width: 360px; width: 360px;
height: 156px; height: 156px;
...@@ -298,11 +374,18 @@ const handleToReportDetail = item => { ...@@ -298,11 +374,18 @@ const handleToReportDetail = item => {
.select-main { .select-main {
margin-left: 25px; margin-left: 24px;
.checkbox-group { .checkbox-group {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 8px 4px;
.filter-checkbox {
width: 160px;
height: 24px;
}
} }
...@@ -322,12 +405,13 @@ const handleToReportDetail = item => { ...@@ -322,12 +405,13 @@ const handleToReportDetail = item => {
} }
.select-hearing-box { .select-hearing-box {
margin-top: 21px; margin-top: 36px;
width: 360px; width: 360px;
.select-box-header { .select-box-header {
display: flex; display: flex;
gap: 17px; gap: 17px;
margin-bottom: 12px;
.icon { .icon {
margin-top: 4px; margin-top: 4px;
...@@ -350,13 +434,16 @@ const handleToReportDetail = item => { ...@@ -350,13 +434,16 @@ const handleToReportDetail = item => {
} }
.select-main { .select-main {
margin-left: 24px;
margin-left: 25px;
.checkbox-group { .checkbox-group {
display: grid; display: grid;
width: 259px;
gap: 4px;
.filter-checkbox {
height: 24px;
}
} }
...@@ -509,4 +596,13 @@ const handleToReportDetail = item => { ...@@ -509,4 +596,13 @@ const handleToReportDetail = item => {
} }
} }
} }
:deep(.el-checkbox) {
margin-right: 0 !important;
}
:deep(.el-checkbox__inner) {
border-radius: 4px !important;
}
</style> </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 class="filter-checkbox" :model-value="isGroupAllSelected(researchTypeIds)"
@change="val => handleToggleAll(val, researchTypeIds)">
全部领域
</el-checkbox>
<el-checkbox v-for="type in researchTypeList" :key="type.id" v-model="selectedResearchIds" :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 class="filter-checkbox" :model-value="isGroupAllSelected(researchTimeIds)"
@change="val => handleToggleAll(val, researchTimeIds)">
全部时间
</el-checkbox>
<el-checkbox v-for="type in researchTimeList" :key="type.id" v-model="selectedResearchTimeIds"
: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 { computed, ref, toRefs, watch } from "vue";
const props = defineProps({
researchTypeList: {
type: Array,
default: () => []
},
researchTimeList: {
type: Array,
default: () => []
},
selectedFilters: {
type: Object,
default: () => ({
researchTypeIds: [],
researchTimeIds: [],
researchHearingIds: []
})
},
curFooterList: {
type: Array,
default: () => []
},
total: {
type: Number,
default: 0
},
currentPage: {
type: Number,
default: 1
}
});
const emit = defineEmits([
"update:selectedFilters",
"filter-change",
"page-change",
"report-click"
]);
// 解构 props,保持模板里变量名不变
const { researchTypeList, researchTimeList, curFooterList, total, currentPage } = toRefs(props);
const selectedResearchIds = ref([]);
const selectedResearchTimeIds = ref([]);
// 父组件更新时同步到子组件
watch(
() => props.selectedFilters,
val => {
selectedResearchIds.value = val?.researchTypeIds ? [...val.researchTypeIds] : [];
selectedResearchTimeIds.value = val?.researchTimeIds ? [...val.researchTimeIds] : [];
},
{ immediate: true, deep: true }
);
const buildSelectedFiltersPayload = () => ({
researchTypeIds: [...selectedResearchIds.value],
researchTimeIds: [...selectedResearchTimeIds.value],
researchHearingIds: []
});
const researchTypeIds = computed(() => (researchTypeList.value || []).map(item => item.id));
const researchTimeIds = computed(() => (researchTimeList.value || []).map(item => item.id));
const getTargetSelection = ids => (ids === researchTypeIds.value ? selectedResearchIds : selectedResearchTimeIds);
const isGroupAllSelected = ids =>
ids.length > 0 && ids.every(id => getTargetSelection(ids).value.includes(id));
const handleToggleAll = (checked, ids) => {
if (!ids.length) return;
const targetSelection = getTargetSelection(ids);
const nextSelected = new Set(targetSelection.value);
if (checked) {
ids.forEach(id => nextSelected.add(id));
} else {
ids.forEach(id => nextSelected.delete(id));
}
targetSelection.value = [...nextSelected];
handleGetThinkDynamicsReport();
};
// 保持模板里的方法名不变,但改成通知父组件,直接传入当前选中值避免时序问题
const handleGetThinkDynamicsReport = () => {
const payload = buildSelectedFiltersPayload();
emit("update:selectedFilters", payload);
emit("filter-change", payload);
};
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: 19px;
.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: 24px;
.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: 44px;
width: 360px;
height: 156px;
.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: 24px;
.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);
}
}
}
.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;
}
/* PolicyTracking 分页按钮样式:1px 描边 + 白底 + 6px 圆角 */
:deep(.right-footer .el-pagination.is-background .btn-prev),
:deep(.right-footer .el-pagination.is-background .btn-next),
:deep(.right-footer .el-pagination.is-background .el-pager li) {
border: 1px solid rgba(0, 0, 0, 0.15) !important;
background-color: rgba(255, 255, 255, 1) !important;
border-radius: 6px !important;
box-sizing: border-box;
}
// 选中状态的页码样式(描边+文字颜色改为指定蓝色)
:deep(.right-footer .el-pagination.is-background .el-pager li.is-active) {
border-color: rgba(22, 119, 255, 1) !important; // 选中后描边颜色
color: rgba(22, 119, 255, 1) !important; // 选中后页码文字颜色
background-color: rgba(255, 255, 255, 1) !important; // 保持白色背景
font-weight: 400;
}
:deep(.el-checkbox__inner) {
border-radius: 4px !important;
}
</style>
\ No newline at end of file
...@@ -22,9 +22,12 @@ ...@@ -22,9 +22,12 @@
</div> </div>
<div class="select-main"> <div class="select-main">
<div class="checkbox-group"> <div class="checkbox-group">
<el-checkbox label="全部领域" class="filter-checkbox"></el-checkbox> <el-checkbox class="filter-checkbox" :model-value="isGroupAllSelected(researchTypeIds)"
<el-checkbox v-for="type in researchTypeList" :key="type.id" v-model="selectedResearchTypeList" @change="val => handleToggleAll(val, researchTypeIds)">
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()"> 全部领域
</el-checkbox>
<el-checkbox v-for="type in researchTypeList" :key="type.id" v-model="selectedResearchIds" :label="type.id"
class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.name }} {{ type.name }}
</el-checkbox> </el-checkbox>
</div> </div>
...@@ -37,8 +40,11 @@ ...@@ -37,8 +40,11 @@
</div> </div>
<div class="select-main"> <div class="select-main">
<div class="checkbox-group"> <div class="checkbox-group">
<el-checkbox label="全部时间"></el-checkbox> <el-checkbox class="filter-checkbox" :model-value="isGroupAllSelected(researchTimeIds)"
<el-checkbox v-for="type in researchTimeList" :key="type.id" v-model="selectedResearchTypeList" @change="val => handleToggleAll(val, researchTimeIds)">
全部时间
</el-checkbox>
<el-checkbox v-for="type in researchTimeList" :key="type.id" v-model="selectedResearchTimeIds"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()"> :label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
{{ type.name }} {{ type.name }}
</el-checkbox> </el-checkbox>
...@@ -78,7 +84,7 @@ ...@@ -78,7 +84,7 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, toRefs, watch } from "vue"; import { computed, ref, toRefs, watch } from "vue";
const props = defineProps({ const props = defineProps({
researchTypeList: { researchTypeList: {
...@@ -89,9 +95,13 @@ const props = defineProps({ ...@@ -89,9 +95,13 @@ const props = defineProps({
type: Array, type: Array,
default: () => [] default: () => []
}, },
selectedResearchTypeList: { selectedFilters: {
type: Array, type: Object,
default: () => [] default: () => ({
researchTypeIds: [],
researchTimeIds: [],
researchHearingIds: []
})
}, },
curFooterList: { curFooterList: {
type: Array, type: Array,
...@@ -108,7 +118,7 @@ const props = defineProps({ ...@@ -108,7 +118,7 @@ const props = defineProps({
}); });
const emit = defineEmits([ const emit = defineEmits([
"update:selectedResearchTypeList", "update:selectedFilters",
"filter-change", "filter-change",
"page-change", "page-change",
"report-click" "report-click"
...@@ -117,31 +127,51 @@ const emit = defineEmits([ ...@@ -117,31 +127,51 @@ const emit = defineEmits([
// 解构 props,保持模板里变量名不变 // 解构 props,保持模板里变量名不变
const { researchTypeList, researchTimeList, curFooterList, total, currentPage } = toRefs(props); const { researchTypeList, researchTimeList, curFooterList, total, currentPage } = toRefs(props);
// 用本地 ref 替代 computed,el-checkbox 会直接修改数组,需要可变的 ref const selectedResearchIds = ref([]);
const selectedResearchTypeList = ref([...(props.selectedResearchTypeList || [])]); const selectedResearchTimeIds = ref([]);
// 父组件更新时同步到子组件 // 父组件更新时同步到子组件
watch( watch(
() => props.selectedResearchTypeList, () => props.selectedFilters,
val => { val => {
selectedResearchTypeList.value = val ? [...val] : []; selectedResearchIds.value = val?.researchTypeIds ? [...val.researchTypeIds] : [];
selectedResearchTimeIds.value = val?.researchTimeIds ? [...val.researchTimeIds] : [];
}, },
{ immediate: true, deep: true } { immediate: true, deep: true }
); );
// 子组件勾选变化时通知父组件,flush: 'sync' 确保在 @change 触发前父组件已更新,否则 filter-change 时父组件拿到的还是旧值 const buildSelectedFiltersPayload = () => ({
watch( researchTypeIds: [...selectedResearchIds.value],
selectedResearchTypeList, researchTimeIds: [...selectedResearchTimeIds.value],
val => { researchHearingIds: []
emit("update:selectedResearchTypeList", val); });
},
{ deep: true, flush: "sync" } const researchTypeIds = computed(() => (researchTypeList.value || []).map(item => item.id));
); const researchTimeIds = computed(() => (researchTimeList.value || []).map(item => item.id));
const getTargetSelection = ids => (ids === researchTypeIds.value ? selectedResearchIds : selectedResearchTimeIds);
const isGroupAllSelected = ids =>
ids.length > 0 && ids.every(id => getTargetSelection(ids).value.includes(id));
const handleToggleAll = (checked, ids) => {
if (!ids.length) return;
const targetSelection = getTargetSelection(ids);
const nextSelected = new Set(targetSelection.value);
if (checked) {
ids.forEach(id => nextSelected.add(id));
} else {
ids.forEach(id => nextSelected.delete(id));
}
targetSelection.value = [...nextSelected];
handleGetThinkDynamicsReport();
};
// 保持模板里的方法名不变,但改成通知父组件,直接传入当前选中值避免时序问题 // 保持模板里的方法名不变,但改成通知父组件,直接传入当前选中值避免时序问题
const handleGetThinkDynamicsReport = () => { const handleGetThinkDynamicsReport = () => {
emit("update:selectedResearchTypeList", [...selectedResearchTypeList.value]); const payload = buildSelectedFiltersPayload();
emit("filter-change", [...selectedResearchTypeList.value]); emit("update:selectedFilters", payload);
emit("filter-change", payload);
}; };
const handleCurrentChange = page => { const handleCurrentChange = page => {
...@@ -171,7 +201,7 @@ const handleToReportDetail = item => { ...@@ -171,7 +201,7 @@ const handleToReportDetail = item => {
.select-research-box { .select-research-box {
width: 360px; width: 360px;
height: 284px; height: 284px;
margin-top: 21px; margin-top: 19px;
.select-box-header { .select-box-header {
display: flex; display: flex;
...@@ -200,7 +230,7 @@ const handleToReportDetail = item => { ...@@ -200,7 +230,7 @@ const handleToReportDetail = item => {
.select-main { .select-main {
margin-left: 25px; margin-left: 24px;
.checkbox-group { .checkbox-group {
display: grid; display: grid;
...@@ -230,13 +260,14 @@ const handleToReportDetail = item => { ...@@ -230,13 +260,14 @@ const handleToReportDetail = item => {
} }
.select-time-box { .select-time-box {
margin-top: 21px; margin-top: 44px;
width: 360px; width: 360px;
height: 156px; height: 156px;
.select-box-header { .select-box-header {
display: flex; display: flex;
gap: 17px; gap: 17px;
margin-bottom: 12px;
.icon { .icon {
margin-top: 4px; margin-top: 4px;
...@@ -260,11 +291,17 @@ const handleToReportDetail = item => { ...@@ -260,11 +291,17 @@ const handleToReportDetail = item => {
.select-main { .select-main {
margin-left: 25px; margin-left: 24px;
.checkbox-group { .checkbox-group {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 8px 4px;
.filter-checkbox {
width: 160px;
height: 24px;
}
} }
...@@ -394,4 +431,26 @@ const handleToReportDetail = item => { ...@@ -394,4 +431,26 @@ const handleToReportDetail = item => {
margin-right: 0 !important; margin-right: 0 !important;
} }
/* PolicyTracking 分页按钮样式:1px 描边 + 白底 + 6px 圆角 */
:deep(.right-footer .el-pagination.is-background .btn-prev),
:deep(.right-footer .el-pagination.is-background .btn-next),
:deep(.right-footer .el-pagination.is-background .el-pager li) {
border: 1px solid rgba(0, 0, 0, 0.15) !important;
background-color: rgba(255, 255, 255, 1) !important;
border-radius: 6px !important;
box-sizing: border-box;
}
// 选中状态的页码样式(描边+文字颜色改为指定蓝色)
:deep(.right-footer .el-pagination.is-background .el-pager li.is-active) {
border-color: rgba(22, 119, 255, 1) !important; // 选中后描边颜色
color: rgba(22, 119, 255, 1) !important; // 选中后页码文字颜色
background-color: rgba(255, 255, 255, 1) !important; // 保持白色背景
font-weight: 400;
}
:deep(.el-checkbox__inner) {
border-radius: 4px !important;
}
</style> </style>
\ No newline at end of file
...@@ -69,18 +69,28 @@ ...@@ -69,18 +69,28 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="pageChange"> <div v-if="isThinkTankReport">
<ThinkTankReport :research-type-list="researchTypeList" :research-time-list="researchTimeList" <ThinkTankReport :research-type-list="researchTypeList" :research-time-list="researchTimeList"
:selected-research-type-list="selectedResearchTypeList" :cur-footer-list="curFooterList" :total="total" :key="`智库报告-${tabResetKey}`" :selected-filters="selectedFilters" :cur-footer-list="curFooterList" :total="total"
:current-page="currentPage" @update:selected-research-type-list="val => (selectedResearchTypeList.value = val)" :current-page="currentPage"
@update:selected-filters="handleSelectedFiltersUpdate"
@filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange" @filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange"
@report-click="handleToReportDetail" /> @report-click="handleToReportDetail" />
</div> </div>
<div v-if="!pageChange"> <div v-if="isCongressHearing">
<CongressHearing :research-type-list="researchTypeList" :research-time-list="researchTimeList" <CongressHearing :research-type-list="researchTypeList" :research-time-list="researchTimeList"
:research-hearing-list="researchHearingList" :selected-research-type-list="selectedResearchTypeList" :key="`国会听证会-${tabResetKey}`" :research-hearing-list="researchHearingList"
:cur-footer-list="curFooterList" :total="total" :current-page="currentPage" :hearing-data="hearingData" :selected-filters="selectedFilters" :selected-year="selectedYear" :cur-footer-list="curFooterList" :total="total"
@update:selected-research-type-list="val => (selectedResearchTypeList.value = val)" :current-page="currentPage" :hearing-data="hearingData"
@update:selected-filters="handleSelectedFiltersUpdate"
@filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange"
@report-click="handleToReportDetail" />
</div>
<div>
<SurveyForm v-if="isSurveyForm" :research-type-list="researchTypeList" :research-time-list="researchTimeList"
:key="`调查项目-${tabResetKey}`" :selected-filters="selectedFilters" :cur-footer-list="curFooterList" :total="total"
:current-page="currentPage"
@update:selected-filters="handleSelectedFiltersUpdate"
@filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange" @filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange"
@report-click="handleToReportDetail" /> @report-click="handleToReportDetail" />
</div> </div>
...@@ -89,6 +99,7 @@ ...@@ -89,6 +99,7 @@
<script setup> <script setup>
import { ref, reactive, onMounted } from "vue"; import { ref, reactive, onMounted } from "vue";
import SurveyForm from "./SurveyForm/index.vue"
// import Img1 from "./images/img1.png"; // import Img1 from "./images/img1.png";
// import Img2 from "./images/img2.png"; // import Img2 from "./images/img2.png";
// import Img3 from "./images/img3.png"; // import Img3 from "./images/img3.png";
...@@ -111,7 +122,9 @@ import { useRouter } from "vue-router"; ...@@ -111,7 +122,9 @@ import { useRouter } from "vue-router";
import ThinkTankReport from "./ThinkTankReport/index.vue"; import ThinkTankReport from "./ThinkTankReport/index.vue";
import CongressHearing from "./CongressHearing/index.vue"; import CongressHearing from "./CongressHearing/index.vue";
const router = useRouter(); const router = useRouter();
const pageChange = ref(true) const isThinkTankReport = ref(true);
const isSurveyForm = ref(false);
const isCongressHearing = ref(false);
const searchReport = ref('') const searchReport = ref('')
const handleToReportDetail = (item) => { const handleToReportDetail = (item) => {
...@@ -302,15 +315,46 @@ const choseTypeList = ref([ ...@@ -302,15 +315,46 @@ const choseTypeList = ref([
]) ])
const activeTypeId = ref(choseTypeList.value[0]?.id || null) const activeTypeId = ref(choseTypeList.value[0]?.id || null)
const tabResetKey = ref(0)
const createDefaultSelectedFilters = () => ({
researchTypeIds: [],
researchTimeIds: [],
researchHearingIds: []
})
const resetThinkDynamicsState = () => {
searchReport.value = ''
selectedYear.value = 1
sort.value = false
author.value = ''
currentPage.value = 1
selectedFilters.value = createDefaultSelectedFilters()
curFooterList.value = []
total.value = 0
tabResetKey.value += 1
}
const handleChooseType = (type) => { const handleChooseType = (type) => {
if (activeTypeId.value === type.id) {
return
}
activeTypeId.value = type.id activeTypeId.value = type.id
if (type.id === '国会听证会') { if (type.id === '国会听证会') {
pageChange.value = false isThinkTankReport.value = false
isCongressHearing.value = true
isSurveyForm.value = false
} else if (type.id === "智库报告") {
isThinkTankReport.value = true
isCongressHearing.value = false
isSurveyForm.value = false
} else { } else {
pageChange.value = true isThinkTankReport.value = false
isCongressHearing.value = false
isSurveyForm.value = true
} }
resetThinkDynamicsState()
handleGetThinkDynamicsReport()
} }
const researchTimeList = ref([ const researchTimeList = ref([
{ {
...@@ -368,7 +412,11 @@ const researchHearingList = ref([ ...@@ -368,7 +412,11 @@ const researchHearingList = ref([
name: '新材料', name: '新材料',
} }
]) ])
const selectedResearchTypeList = ref([]) const selectedFilters = ref(createDefaultSelectedFilters())
const handleSelectedFiltersUpdate = val => {
selectedFilters.value = val
}
const author = ref('') // 作者 const author = ref('') // 作者
...@@ -524,11 +572,26 @@ function arrayToString(arr) { ...@@ -524,11 +572,26 @@ function arrayToString(arr) {
}, ""); }, "");
} }
function buildThinkDynamicsYears(selectedYears) {
const allYearIds = researchTimeList.value.map(item => item.id)
if (!selectedYears?.length || selectedYears.length === allYearIds.length) {
return null
}
return arrayToString(selectedYears)
}
// 获取智库动态报告,payload 为子组件筛选变更时传入的当前选中值,避免时序导致拿到旧值 // 获取智库动态报告,payload 为子组件筛选变更时传入的当前选中值,避免时序导致拿到旧值
const handleGetThinkDynamicsReport = async (payload) => { const handleGetThinkDynamicsReport = async (payload) => {
const selectedIds = Array.isArray(payload) ? payload : selectedResearchTypeList.value; const nextFilters = payload && typeof payload === 'object'
if (Array.isArray(payload)) { ? {
selectedResearchTypeList.value = payload; researchTypeIds: payload.researchTypeIds ? [...payload.researchTypeIds] : [],
researchTimeIds: payload.researchTimeIds ? [...payload.researchTimeIds] : [],
researchHearingIds: payload.researchHearingIds ? [...payload.researchHearingIds] : []
}
: selectedFilters.value;
if (payload && typeof payload === 'object') {
selectedFilters.value = nextFilters;
currentPage.value = 1;
} }
try { try {
const parmas = { const parmas = {
...@@ -540,7 +603,8 @@ const handleGetThinkDynamicsReport = async (payload) => { ...@@ -540,7 +603,8 @@ const handleGetThinkDynamicsReport = async (payload) => {
sortFun: sort.value ?? true, sortFun: sort.value ?? true,
currentPage: currentPage.value - 1, currentPage: currentPage.value - 1,
pageSize: 12, pageSize: 12,
researchTypeIds: arrayToString(selectedIds) === '' ? null : arrayToString(selectedIds) researchTypeIds: arrayToString(nextFilters.researchTypeIds) === '' ? null : arrayToString(nextFilters.researchTypeIds),
years: buildThinkDynamicsYears(nextFilters.researchTimeIds)
} }
} }
......
...@@ -202,7 +202,7 @@ ...@@ -202,7 +202,7 @@
<div class="info"> <div class="info">
<div class="info-header"> <div class="info-header">
<div class="name">{{ item.name }}</div> <div class="name">{{ item.name }}</div>
<div class="position">{{ item.positionTitle }}</div> <div class="position">{{ item.positionTitle ?? '全球研究人才' }}</div>
</div> </div>
<div class="info-footer">{{ item.describe }}</div> <div class="info-footer">{{ item.describe }}</div>
</div> </div>
...@@ -220,7 +220,7 @@ ...@@ -220,7 +220,7 @@
<div class="info"> <div class="info">
<div class="info-header"> <div class="info-header">
<div class="name">{{ item.name }}</div> <div class="name">{{ item.name }}</div>
<div class="position">{{ item.positionTitle }}</div> <div class="position">{{ item.positionTitle ?? '全球研究人才' }}</div>
</div> </div>
<div class="info-footer">{{ item.describe }}</div> <div class="info-footer">{{ item.describe }}</div>
</div> </div>
...@@ -401,6 +401,97 @@ const handleGetThinkTankFundsSource = async () => { ...@@ -401,6 +401,97 @@ const handleGetThinkTankFundsSource = async () => {
}) })
box1ChartData.value = data box1ChartData.value = data
const box1Chart = getPieChart(box1ChartData.value); const box1Chart = getPieChart(box1ChartData.value);
// 图表整体宽度 830px,中间圆形 194*194(半径 97)
const CHART_WIDTH = 830
const PIE_OUTER_RADIUS = 97
const PIE_INNER_RADIUS = Math.round((80 / 110) * PIE_OUTER_RADIUS)
box1Chart.series[0].radius = [PIE_INNER_RADIUS, PIE_OUTER_RADIUS]
// 仅覆盖 label:左侧标注 部门左+经费比例右,右侧标注 经费比例左+部门右
const LABEL_BOX_HEIGHT = 21.41
const LABEL_OFFSET_UP = 3 + LABEL_BOX_HEIGHT / 2
const dataList = box1ChartData.value
const total = dataList.reduce((s, d) => s + (d.value || 0), 0) || 1
box1Chart.series[0].label = {
...box1Chart.series[0].label,
alignTo: 'edge',
offset: [0, -LABEL_OFFSET_UP],
formatter(params) {
const valueYi = (params.data.value || 0) / 1000000
const percent = Math.round(params.percent || 0)
const valueStr = `${Math.round(valueYi)}亿 ${percent}%`
let cumulative = 0
for (let i = 0; i < params.dataIndex; i++) cumulative += dataList[i].value || 0
const centerAngle = 90 + ((cumulative + (params.data.value || 0) / 2) / total) * 360
const angleNorm = ((centerAngle % 360) + 360) % 360
// 左侧仅 270–360° 及跨圈:部门左、经费右;其余(含 180–270° 右下两条):经费左、部门右
const isLeftLabel = (angleNorm >= 270 && angleNorm < 360) || centerAngle >= 360
if (isLeftLabel) {
return `{nameL|${params.data.name}}{valueR|${valueStr}}`
}
return `{valueL|${valueStr}}{nameR|${params.data.name}}`
},
lineHeight: LABEL_BOX_HEIGHT,
rich: {
nameL: {
fontFamily: 'Source Han Sans CN',
fontSize: 16,
fontWeight: 700,
lineHeight: 24,
color: 'rgba(59, 65, 75, 1)',
width: 180,
align: 'left',
overflow: 'truncate',
ellipsis: '...',
letterSpacing: 1
},
nameR: {
fontFamily: 'Source Han Sans CN',
fontSize: 16,
fontWeight: 700,
lineHeight: 24,
color: 'rgba(59, 65, 75, 1)',
width: 180,
align: 'right',
overflow: 'truncate',
ellipsis: '...',
letterSpacing: 1
},
valueL: {
fontFamily: 'Microsoft YaHei',
fontSize: 14,
fontWeight: 400,
lineHeight: 22,
color: 'rgba(59, 65, 75, 1)',
align: 'left',
width: 88,
padding: [0, 8, 0, 0]
},
valueR: {
fontFamily: 'Microsoft YaHei',
fontSize: 14,
fontWeight: 400,
lineHeight: 22,
color: 'rgba(59, 65, 75, 1)',
align: 'left',
width: 88,
padding: [0, 0, 0, 8]
}
}
}
// 标线末端 x 对齐(左右贴齐),图表宽度 830
box1Chart.series[0].labelLayout = function (params) {
const isLeft = params.labelRect.x < CHART_WIDTH / 2
const points = params.labelLinePoints
points[2][0] = isLeft
? params.labelRect.x
: params.labelRect.x + params.labelRect.width
return {
labelLinePoints: points
}
}
setChart(box1Chart, "box1Chart"); setChart(box1Chart, "box1Chart");
} }
} catch (error) { } catch (error) {
...@@ -445,8 +536,14 @@ const handleGetThinkTankResearchAreae = async () => { ...@@ -445,8 +536,14 @@ const handleGetThinkTankResearchAreae = async () => {
}; };
// 核心研究人员 // 核心研究人员
const handleBox3Chart = () => { const handleBox3Chart = () => {
// 将分类数据转换为树状图格式 // 四大区域:顺序与比例 9:6:6:6 → 左侧3×3、中间3×2、右侧3×2、底4–9列一行
const treemapData = []; const REGION_ORDER = ['政府部门及国家实验室', '领先科技企业', '顶尖大学与研究机构', '国际人才'];
const REGION_COLORS = {
政府部门及国家实验室: 'rgb(80,112,221)',
领先科技企业: 'rgb(182,214,52)',
顶尖大学与研究机构: 'rgb(80,83,114)',
国际人才: 'rgb(255,153,77)'
};
const mockRandResearcherCategories = { const mockRandResearcherCategories = {
政府部门及国家实验室: { 政府部门及国家实验室: {
商务部: 12, 商务部: 12,
...@@ -478,19 +575,45 @@ const handleBox3Chart = () => { ...@@ -478,19 +575,45 @@ const handleBox3Chart = () => {
其他: 6 其他: 6
} }
}; };
Object.keys(mockRandResearcherCategories).forEach(category => { // 政府部门占整块左侧;领先科技企业、顶尖大学各加长 0.5 行,国际人才缩小 0.5 行(右三区比例约 50:50:35)
const children = Object.keys(mockRandResearcherCategories[category]).map(item => ({ const REGION_VALUES = { 政府部门及国家实验室: 135, 领先科技企业: 50, 顶尖大学与研究机构: 50, 国际人才: 35 };
const treemapData = REGION_ORDER.map(name => {
const children = Object.keys(mockRandResearcherCategories[name]).map(item => ({
name: item, name: item,
value: mockRandResearcherCategories[category][item] value: mockRandResearcherCategories[name][item]
})); }));
treemapData.push({ return {
name: category, name,
value: children.reduce((sum, item) => sum + item.value, 0), value: REGION_VALUES[name],
children: children itemStyle: { color: REGION_COLORS[name] },
}); children
};
}); });
const box3Chart = getTreeMapChart(treemapData); const box3Chart = getTreeMapChart(treemapData);
delete box3Chart.series[0].itemStyle;
box3Chart.series[0].sort = false;
// 图表充满 box3-main-left,左右各留 12px
box3Chart.series[0].left = 12;
box3Chart.series[0].right = 12;
box3Chart.series[0].top = 0;
box3Chart.series[0].bottom = 0;
// 方块内文字与区域标题统一为黑色,方块内文字加 1px 白色描边(等效 CSS text-stroke: 1px #fff)
box3Chart.series[0].label = {
...box3Chart.series[0].label,
color: 'rgb(51,51,51)',
textBorderColor: '#fff',
textBorderWidth: 0.7,
// 可选:白色阴影兜底
textShadowColor: '#fff',
textShadowBlur: 0,
textShadowOffsetX: 0,
textShadowOffsetY: 0
};
box3Chart.series[0].upperLabel = {
...box3Chart.series[0].upperLabel,
color: 'rgb(51,51,51)'
};
setChart(box3Chart, "box3Chart"); setChart(box3Chart, "box3Chart");
}; };
...@@ -591,12 +714,13 @@ onMounted(() => { ...@@ -591,12 +714,13 @@ onMounted(() => {
height: 16px; height: 16px;
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
background: var(--color-main-active); background: var(--color-main-active);
} }
.title { .title {
margin-left: 14px; margin-left: 14px;
color: var(--color-main-active); color: var(--color-main-active);
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;
...@@ -656,7 +780,7 @@ onMounted(() => { ...@@ -656,7 +780,7 @@ onMounted(() => {
} }
.left-bottom { .left-bottom {
padding-bottom: 16px; margin-bottom: 69px;
.left-bottom-main { .left-bottom-main {
margin-top: 4px; margin-top: 4px;
...@@ -770,13 +894,15 @@ onMounted(() => { ...@@ -770,13 +894,15 @@ onMounted(() => {
height: 72px; height: 72px;
border-radius: 4px; border-radius: 4px;
background: linear-gradient(180deg, rgba(230, 244, 255, 1), rgba(230, 244, 255, 0) 100%); background: linear-gradient(180deg, rgba(230, 244, 255, 1), rgba(230, 244, 255, 0) 100%);
display: flex;
flex-direction: column;
.card-title { .card-title {
height: 24px; height: 24px;
margin-top: 8px; margin-top: 8px;
margin-left: 19px; margin-left: 19px;
color: var(--color-main-active); color: var(--color-main-active);
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;
...@@ -804,13 +930,15 @@ onMounted(() => { ...@@ -804,13 +930,15 @@ onMounted(() => {
height: 72px; height: 72px;
border-radius: 4px; border-radius: 4px;
background: linear-gradient(180deg, rgba(255, 241, 240, 1), rgba(255, 241, 240, 0) 100%); background: linear-gradient(180deg, rgba(255, 241, 240, 1), rgba(255, 241, 240, 0) 100%);
display: flex;
flex-direction: column;
.card-title { .card-title {
height: 24px; height: 24px;
margin-top: 8px; margin-top: 8px;
margin-left: 19px; margin-left: 19px;
color: rgba(206, 79, 81, 1); color: rgba(206, 79, 81, 1);
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;
...@@ -838,13 +966,15 @@ onMounted(() => { ...@@ -838,13 +966,15 @@ onMounted(() => {
height: 72px; height: 72px;
border-radius: 4px; border-radius: 4px;
background: linear-gradient(180deg, rgba(230, 255, 251, 1), rgba(230, 255, 251, 0) 100%); background: linear-gradient(180deg, rgba(230, 255, 251, 1), rgba(230, 255, 251, 0) 100%);
display: flex;
flex-direction: column;
.card-title { .card-title {
height: 24px; height: 24px;
margin-top: 8px; margin-top: 8px;
margin-left: 19px; margin-left: 19px;
color: rgba(19, 168, 168, 1); color: rgba(19, 168, 168, 1);
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;
...@@ -870,7 +1000,7 @@ onMounted(() => { ...@@ -870,7 +1000,7 @@ onMounted(() => {
.box1-main-right { .box1-main-right {
margin-left: 38px; margin-left: 38px;
margin-top: 6px; margin-top: 6px;
width: 800px; width: 830px;
height: 300px; height: 300px;
} }
} }
...@@ -879,7 +1009,8 @@ onMounted(() => { ...@@ -879,7 +1009,8 @@ onMounted(() => {
height: 320px; height: 320px;
display: flex; display: flex;
position: relative; position: relative;
padding-left: 150px; padding-left: 111px;
margin-top: 4px;
.box2-arrow-left { .box2-arrow-left {
width: 24px; width: 24px;
...@@ -919,13 +1050,13 @@ onMounted(() => { ...@@ -919,13 +1050,13 @@ onMounted(() => {
} }
.box2-item { .box2-item {
margin-top: 4px; margin-top: 19px;
width: 300px; width: 221px;
height: 150px; height: 134px;
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
padding-left: 13px; padding-left: 13px;
margin-left: -100px; margin-left: 0;
border-left: 1px solid #0a57a6; border-left: 1px solid #0a57a6;
.box2-item-header { .box2-item-header {
...@@ -935,7 +1066,7 @@ onMounted(() => { ...@@ -935,7 +1066,7 @@ onMounted(() => {
.title { .title {
color: var(--color-main-active); color: var(--color-main-active);
height: 26px; height: 26px;
font-family: Microsoft YaHei; font-family: "Source Han Sans CN";
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
} }
...@@ -946,7 +1077,7 @@ onMounted(() => { ...@@ -946,7 +1077,7 @@ onMounted(() => {
min-height: 48px; min-height: 48px;
max-height: 96px; max-height: 96px;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
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;
...@@ -1008,13 +1139,17 @@ onMounted(() => { ...@@ -1008,13 +1139,17 @@ onMounted(() => {
.box3-main { .box3-main {
display: flex; display: flex;
.box3-main-left { .box3-main-left {
width: 536px; width: 536px;
height: 345px; height: 326px;
margin-left: 9px;
} }
.box3-main-right { .box3-main-right {
margin-left: 10px; margin-left: 28px;
margin-top: 12px;
width: 536px; width: 536px;
height: 326px; height: 326px;
...@@ -1027,8 +1162,10 @@ onMounted(() => { ...@@ -1027,8 +1162,10 @@ onMounted(() => {
.icon { .icon {
width: 48px; width: 48px;
height: 48px; height: 48px;
margin-left: 8px; border-radius: 50% !important;
cursor: pointer; cursor: pointer;
overflow: hidden;
flex-shrink: 0;
img { img {
width: 100%; width: 100%;
...@@ -1047,7 +1184,7 @@ onMounted(() => { ...@@ -1047,7 +1184,7 @@ onMounted(() => {
.name { .name {
height: 24px; height: 24px;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei; font-family: "Source Han Sans CN";
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
line-height: 24px; line-height: 24px;
...@@ -1057,7 +1194,7 @@ onMounted(() => { ...@@ -1057,7 +1194,7 @@ onMounted(() => {
.position { .position {
height: 22px; height: 22px;
color: var(--color-main-active); color: var(--color-main-active);
font-family: Microsoft YaHei; font-family: "Source Han Sans CN";
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
line-height: 22px; line-height: 22px;
...@@ -1069,7 +1206,7 @@ onMounted(() => { ...@@ -1069,7 +1206,7 @@ onMounted(() => {
.info-footer { .info-footer {
height: 22px; height: 22px;
color: rgba(95, 101, 108, 1); color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei; font-family: "Source Han Sans CN";
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
line-height: 22px; line-height: 22px;
......
...@@ -33,10 +33,10 @@ ...@@ -33,10 +33,10 @@
</div> </div>
<div class="report-box"> <div class="report-box">
<iframe :src="reportUrl" width="50%" height="100%"> <iframe :src="reportUrlWithPage" width="50%" height="100%">
</iframe> </iframe>
<iframe :src="reportUrlEn" width="50%" height="100%"> <iframe :src="reportUrlEnWithPage" width="50%" height="100%">
</iframe> </iframe>
<!-- <pdf :pdfUrl="reportUrl" style="width: 48%;" /> <!-- <pdf :pdfUrl="reportUrl" style="width: 48%;" />
...@@ -49,21 +49,35 @@ ...@@ -49,21 +49,35 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { computed, ref, onMounted } from "vue";
import pdf from "./pdf.vue"; import pdf from "./pdf.vue";
import { import {
getThinkTankReportSummary, getThinkTankReportSummary,
getThinkTankReportcontentUrl getThinkTankReportcontentUrl
} from "@/api/thinkTank/overview"; } from "@/api/thinkTank/overview";
import { useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
const router = useRouter(); const router = useRouter();
const route = useRoute();
const reportUrl = ref('') const reportUrl = ref('')
const reportUrlEn = ref('') const reportUrlEn = ref('')
const thinkInfo = ref({}) const thinkInfo = ref({})
const defaultPdfPage = ref(1)
const sourceCurrentPage = ref(Number(route.query.currentPage) || 1)
const sourcePageSize = ref(Number(route.query.pageSize) || 12)
const opinionId = ref(route.query.opinionId || "")
const opinionContent = ref(route.query.opinionContent || "")
const buildPdfPageUrl = url => {
if (!url) return ''
return `${url}#page=${defaultPdfPage.value}`
}
const reportUrlWithPage = computed(() => buildPdfPageUrl(reportUrl.value))
const reportUrlEnWithPage = computed(() => buildPdfPageUrl(reportUrlEn.value))
// 获取报告全局信息 // 获取报告全局信息
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论