提交 29e42ed8 authored 作者: 朱政's avatar 朱政

feat:多智库分析界面样式以及功能开发

上级 8055f9f1
...@@ -120,10 +120,44 @@ export function getThinkDynamicsReportType() { ...@@ -120,10 +120,44 @@ export function getThinkDynamicsReportType() {
//智库动态:获取智库报告 //智库动态:获取智库报告
export function getThinkDynamicsReport(params) { export function getThinkDynamicsReport(params) {
const safe = params || {}
// 兼容两种调用方式:
// 1) { id, startDate, authorName, currentPage, pageSize, researchTypeIds, searchText, sortFun, years }
// 2) { id, startDate, parmas: { authorName, currentPage, pageSize, researchTypeIds, searchText, sortFun, years } }
const inner = safe.parmas && typeof safe.parmas === 'object' ? safe.parmas : {}
const id = safe.id
const startDate = safe.startDate
const authorName = inner.authorName ?? safe.authorName ?? ''
const currentPage = inner.currentPage ?? safe.currentPage ?? 1
const pageSize = inner.pageSize ?? safe.pageSize ?? 10
const researchTypeIds = inner.researchTypeIds ?? safe.researchTypeIds ?? ''
const searchText = inner.searchText ?? safe.searchText ?? ''
const sortFun = inner.sortFun ?? safe.sortFun ?? false
const years = inner.years ?? safe.years ?? null
const query = { currentPage, pageSize, sortFun }
// 仅在有值时才传,避免后端按空值筛选
if (authorName) query.authorName = authorName
if (researchTypeIds) query.researchTypeIds = researchTypeIds
if (searchText) query.searchText = searchText
if (years !== null && years !== undefined && years !== '') query.years = years
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/thinkTankInfo/report/${params.id}/${params.startDate}`, url: `/api/thinkTankInfo/report/${id}/${startDate}`,
params: params.parmas params: query
})
}
// 智库领域观点分析(流式)
// [POST] 8.140.26.4:10029/report-domain-view-analysis
export function postReportDomainViewAnalysis(data) {
return request({
method: 'POST',
// 开发环境走 Vite 同源代理,避免浏览器跨域(见 vite.config.js:/intelligent-api -> 8.140.26.4:10029)
url: '/intelligent-api/report-domain-view-analysis',
data
}) })
} }
......
...@@ -4,6 +4,7 @@ const ThinkTankDetail = () => import('@/views/thinkTank/ThinkTankDetail/index.vu ...@@ -4,6 +4,7 @@ const ThinkTankDetail = () => import('@/views/thinkTank/ThinkTankDetail/index.vu
const ReportDetail = () => import('@/views/thinkTank/ReportDetail/index.vue') const ReportDetail = () => import('@/views/thinkTank/ReportDetail/index.vue')
const ReportOriginal = () => import('@/views/thinkTank/reportOriginal/index.vue') const ReportOriginal = () => import('@/views/thinkTank/reportOriginal/index.vue')
const allThinkTank= () => import('@/views/thinkTank/allThinkTank/index.vue') const allThinkTank= () => import('@/views/thinkTank/allThinkTank/index.vue')
const MultiThinkTankViewAnalysis= () => import('@/views/thinkTank/MultiThinkTankViewAnalysis/index.vue')
const thinktankRoutes = [ const thinktankRoutes = [
// 智库系统的主要路由 // 智库系统的主要路由
...@@ -37,17 +38,19 @@ const thinktankRoutes = [ ...@@ -37,17 +38,19 @@ const thinktankRoutes = [
path: "/thinkTank/reportOriginal/:id", path: "/thinkTank/reportOriginal/:id",
name: "ReportOriginal", name: "ReportOriginal",
component: ReportOriginal, component: ReportOriginal,
// meta: {
// title: "报告原文"
// }
}, },
{ {
path: "/thinkTank/allThinkTank", path: "/thinkTank/allThinkTank",
name: "allThinkTank", name: "allThinkTank",
component: allThinkTank, component: allThinkTank,
// meta: {
// title: "报告原文" },
// } {
path: "/thinkTank/MultiThinkTankViewAnalysis/:id",
name: "MultiThinkTankViewAnalysis",
component: MultiThinkTankViewAnalysis,
}, },
] ]
......
<template>
<div class="analysis-box-wrapper" :style="{ width: width ? width : '100%', height: height ? height : '100%' }">
<div class="wrapper-header">
<div class="header-icon"></div>
<div class="header-title">
<div v-if="title">{{ title }}</div>
<slot v-else name="custom-title"></slot>
</div>
<div class="header-btn" v-if="!showAllBtn">
<slot name="header-btn"></slot>
</div>
<div class="header-btn1" v-else>
<slot name="header-btn"></slot>
</div>
<div class="header-right">
<div class="text-one" :class="{ 'is-active': activeView === 'consensus' }"
@click="handleTabChange('consensus')">
{{ "共识观点" }}
</div>
<div class="text-two" :class="{ 'is-active': activeView === 'divergence' }"
@click="handleTabChange('divergence')">
{{ "分歧观点" }}
</div>
<div class="header-right-btn" @click="handleSave" v-if="showAllBtn">
<img src="@/assets/icons/box-header-icon1.png" alt="">
</div>
<div class="header-right-btn" @click="handleCollect">
<img src="@/assets/icons/box-header-icon3.png" alt="">
</div>
</div>
</div>
<div class="wrapper-main">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { ref, computed } from 'vue'
const props = defineProps({
title: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
},
showAllBtn: {
type: Boolean,
default: true
},
// 当业务功能尚未实现时,点击右上角图标仅弹出统一提示
devTip: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['save', 'download', 'collect', 'tab-change'])
// 共识/分歧:单选,默认共识观点
const activeView = ref('consensus')
const handleTabChange = type => {
activeView.value = type
emit('tab-change', type)
}
const handleSave = () => {
if (props.devTip) {
ElMessage.warning('当前功能正在开发中,敬请期待!')
return
}
ElMessage.success('保存当前内容')
// emit('save')
}
const handleDownload = () => {
if (props.devTip) {
ElMessage.warning('当前功能正在开发中,敬请期待!')
return
}
ElMessage.success('下载当前内容')
// emit('download')
}
const handleCollect = () => {
if (props.devTip) {
ElMessage.warning('当前功能正在开发中,敬请期待!')
return
}
ElMessage.success('收藏当前内容')
// emit('collect')
}
</script>
<style lang="scss" scoped>
.analysis-box-wrapper {
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.wrapper-header {
height: 45px;
display: flex;
padding-right: 14px;
align-items: center;
box-sizing: border-box;
.header-icon {
width: 8px;
height: 20px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
margin-right: 14px;
}
.header-title {
flex: auto;
width: 20px;
// color: var(--color-main-active);
// font-family: Source Han Sans CN;
// font-size: 20px;
// font-weight: 700;
// line-height: 26px;
// letter-spacing: 0px;
height: 100%;
&>div {
height: 100%;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
line-height: 45px;
font-weight: 700;
}
}
// .header-btn {
// display: flex;
// justify-content: flex-end;
// gap: 8px;
// }
// .header-btn1 {
// position: absolute;
// top: 14px;
// right: 116px;
// }
.header-right {
height: 28px;
display: flex;
justify-content: flex-end;
gap: 4px;
.text-one {
border: 1px solid rgb(230, 231, 232);
width: 88px;
height: 32px;
border-radius: 4px;
background-color: rgb(255, 255, 255);
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
font-size: 18px;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&.is-active {
background-color: rgb(246, 250, 255);
color: rgb(5, 95, 194);
font-weight: 700;
border: 1px solid rgb(5, 95, 194);
}
}
.text-two {
border: 1px solid rgb(230, 231, 232);
width: 88px;
height: 32px;
border-radius: 4px;
background-color: rgb(255, 255, 255);
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
font-size: 18px;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&.is-active {
background-color: rgb(246, 250, 255);
color: rgb(5, 95, 194);
font-weight: 700;
border: 1px solid rgb(5, 95, 194);
}
}
.header-right-btn {
width: 28px;
height: 28px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
}
.wrapper-main {
height: calc(100% - 45px);
overflow: hidden;
// overflow-y: auto;
padding: 5px auto;
}
}
</style>
<template>
<div class="overview-main-box-wrapper"
:style="{ width: width ? width : '1064px', height: height ? height : '450px' }">
<div class="overview-main-box-header">
<div class="header-left">
<div class="header-icon">
<slot name="header-icon"></slot>
</div>
<div class="header-title">{{ title }}</div>
</div>
<div class="header-right" @click="handleClickToDetail()">
{{ "查看详情 >" }}
</div>
</div>
<div class="wrapper-main">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const emit = defineEmits(['toDetail'])
const props = defineProps({
title: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
}
})
const handleClickToDetail = () => {
emit('toDetail')
}
</script>
<style lang="scss" scoped>
.overview-main-box-wrapper {
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.overview-main-box-header {
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
justify-content: space-between;
box-sizing: border-box;
.header-left {
display: flex;
.header-icon {
width: 24px;
height: 24px;
margin-top: 12px;
margin-left: 17px;
}
.header-title {
margin-left: 19px;
height: 48px;
padding: 0 16px;
background: var(--color-main-active);
color: #fff;
font-family: Source Han Sans CN;
font-size: 20px;
font-weight: 700;
line-height: 48px;
text-align: center;
}
}
.header-right {
margin-right: 27px;
margin-top: 12px;
height: 24px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
}
}
.wrapper-main {
height: calc(100% - 48px);
overflow: hidden;
// position: relative;
}
}
</style>
<template>
<div class="overview-normal-box-wrapper"
:style="{ width: width ? width : '1064px', height: height ? height : '460px' }">
<div class="overview-normal-box-header">
<div class="header-left">
<div class="header-icon">
<slot name="header-icon"></slot>
</div>
<div class="header-title">{{ title }}</div>
</div>
<div class="header-right">
<slot name="header-right"></slot>
</div>
</div>
<div class="wrapper-main">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
title: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
.overview-normal-box-wrapper {
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.overview-normal-box-header {
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
justify-content: space-between;
box-sizing: border-box;
.header-left {
display: flex;
.header-icon {
width: 24px;
height: 24px;
margin-top: 14px;
margin-left: 19px;
}
.header-title {
margin-left: 17px;
height: 48px;
color: var(--color-main-active);
font-family: Source Han Sans CN;
font-size: 20px;
font-weight: 700;
line-height: 48px;
text-align: center;
}
}
.header-right {
height: 48px;
margin-right: 28px;
}
}
.wrapper-main {
height: calc(100% - 48px);
overflow: hidden;
}
}
</style>
...@@ -37,18 +37,23 @@ ...@@ -37,18 +37,23 @@
<div class="author-title">报告作者:</div> <div class="author-title">报告作者:</div>
<div class="author-content"> <div class="author-content">
<template v-if="Array.isArray(reportAuthors) && reportAuthors.length"> <template v-if="Array.isArray(reportAuthors) && reportAuthors.length">
<span v-for="(author, idx) in reportAuthors" :key="idx"> <span v-if="reportAuthors.length === 1">
{{ author.name }} {{ reportAuthors[0].name }}
<span v-if="idx < reportAuthors.length - 1"></span>
</span> </span>
<!-- 多个作者:显示第一个 + 等 -->
<span v-else>
{{ reportAuthors[0].name }}
</span>
</template> </template>
</div> </div>
</div> </div>
</div> </div>
<div class="author-box" v-for="(author, idx) in reportAuthors" :key="idx" <div class="author-box">
<div class="author-item" v-for="(author, idx) in reportAuthors" :key="idx"
v-if="Array.isArray(reportAuthors) && reportAuthors.length"> v-if="Array.isArray(reportAuthors) && reportAuthors.length">
<div class="author-item"> <div class="image"><img :src="author.avatar" alt="" /></div>
<div class="image"><img :src="author.avatar" :alt="reportAuthors[0].name" /></div>
<div class="author-text"> <div class="author-text">
<div class="author-name">{{ author.name }}</div> <div class="author-name">{{ author.name }}</div>
<div class="author-position">{{ author.job }}</div> <div class="author-position">{{ author.job }}</div>
...@@ -58,6 +63,13 @@ ...@@ -58,6 +63,13 @@
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
<div class="box5">
<AnalysisBox title="报告关键词云" :showAllBtn="true">
<div class="box5-main">
<div id="box5Chart"></div>
</div>
</AnalysisBox>
</div>
<div class="box2"> <div class="box2">
<!-- <div class="box-header"> <!-- <div class="box-header">
<div class="header-left"></div> <div class="header-left"></div>
...@@ -94,7 +106,7 @@ ...@@ -94,7 +106,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="box2-btn"> <div class="box2-btn" @click="goToAllThinkTank">
<div class="btn-text"> <div class="btn-text">
多智库报告观点汇聚分析 多智库报告观点汇聚分析
</div> </div>
...@@ -185,7 +197,7 @@ ...@@ -185,7 +197,7 @@
<script setup> <script setup>
import WarningPane from "@/components/base/WarningPane/index.vue" import WarningPane from "@/components/base/WarningPane/index.vue"
import SearchContainer from "@/components/SearchContainer.vue"; import SearchContainer from "@/components/SearchContainer.vue";
import { ref, onMounted, computed, defineProps } from "vue"; import { ref, onMounted, computed, defineProps, nextTick } from "vue";
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
import getWordCloudChart from "./utils/worldCloudChart"; import getWordCloudChart from "./utils/worldCloudChart";
import { import {
...@@ -197,9 +209,20 @@ import { ...@@ -197,9 +209,20 @@ import {
} from "@/api/thinkTank/overview"; } from "@/api/thinkTank/overview";
import { getChartAnalysis } from "@/api/aiAnalysis/index"; import { getChartAnalysis } from "@/api/aiAnalysis/index";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
const router = useRouter();
import "echarts-wordcloud"; import "echarts-wordcloud";
const router = useRouter();
const goToAllThinkTank = () => {
const thinkTankId = props?.thinkInfo?.thinkTankId || props?.thinkInfo?.id;
const route = router.resolve({
name: "MultiThinkTankViewAnalysis",
params: { id: thinkTankId }
});
window.open(route.href, "_blank");
};
const props = defineProps({ const props = defineProps({
thinkInfo: { thinkInfo: {
type: Object, type: Object,
...@@ -321,6 +344,8 @@ const box2Data = ref([ ...@@ -321,6 +344,8 @@ const box2Data = ref([
// value: 89 // value: 89
// } // }
]); ]);
// 报告关键词云
const box5Data = ref([]);
//获取科技领域词云 //获取科技领域词云
const handleGetThinkTankReportIndustryCloud = async () => { const handleGetThinkTankReportIndustryCloud = async () => {
...@@ -332,19 +357,22 @@ const handleGetThinkTankReportIndustryCloud = async () => { ...@@ -332,19 +357,22 @@ const handleGetThinkTankReportIndustryCloud = async () => {
const res = await getThinkTankReportIndustryCloud(params); const res = await getThinkTankReportIndustryCloud(params);
console.log("科技领域词云", res); console.log("科技领域词云", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const data = []; const data = (res.data || []).map(item => ({
res.data.map(item => {
data.push({
name: item.clause, name: item.clause,
value: item.count value: item.count
}); }));
box2Data.value = data; // 该接口数据用于「报告关键词云」
const box2Chart = getWordCloudChart(box2Data.value); box5Data.value = data;
setChart(box2Chart, "box2Chart");
}); await nextTick();
const box5Chart = getWordCloudChart(box5Data.value);
setChart(box5Chart, "box5Chart");
} else {
box5Data.value = [];
} }
} catch (error) { } catch (error) {
console.error("获取科技领域词云error", error); console.error("获取科技领域词云error", error);
box5Data.value = [];
} }
}; };
//涉及科技领域 //涉及科技领域
...@@ -575,7 +603,7 @@ onMounted(() => { ...@@ -575,7 +603,7 @@ onMounted(() => {
} }
.author { .author {
height: 24px;
display: flex; display: flex;
gap: 4px; gap: 4px;
...@@ -607,9 +635,18 @@ onMounted(() => { ...@@ -607,9 +635,18 @@ onMounted(() => {
.author-box { .author-box {
width: 437px; width: 437px;
height: 220px; height: auto;
/* 改为自适应高度,不要固定 220px */
max-height: 220px;
margin-top: 34px; margin-top: 34px;
margin-left: 18px; margin-left: 18px;
display: grid;
grid-template-columns: 1fr 1fr;
/* 两列等宽 */
column-gap: 4px;
/* 左右间距(同一行) */
row-gap: 8px;
/* 上下间距(同一列) */
.author-item { .author-item {
width: 213px; width: 213px;
...@@ -646,6 +683,9 @@ onMounted(() => { ...@@ -646,6 +683,9 @@ onMounted(() => {
letter-spacing: 0; letter-spacing: 0;
text-align: left; text-align: left;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} }
.author-position { .author-position {
...@@ -658,6 +698,9 @@ onMounted(() => { ...@@ -658,6 +698,9 @@ onMounted(() => {
letter-spacing: 0; letter-spacing: 0;
text-align: left; text-align: left;
color: rgb(95, 101, 108); color: rgb(95, 101, 108);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} }
} }
...@@ -669,6 +712,30 @@ onMounted(() => { ...@@ -669,6 +712,30 @@ onMounted(() => {
} }
.box5 {
width: 480px;
height: 415px;
.box5-main {
width: 480px;
height: 361px;
padding-left: 31px;
padding-right: 32px;
padding-top: 26px;
padding-bottom: 43px;
display: flex;
box-sizing: border-box;
overflow: hidden;
#box5Chart {
width: 100%;
height: 100%;
margin: 0 auto;
overflow: hidden;
}
}
}
.box2 { .box2 {
width: 480px; width: 480px;
...@@ -1131,7 +1198,8 @@ onMounted(() => { ...@@ -1131,7 +1198,8 @@ onMounted(() => {
height: 54px !important; height: 54px !important;
display: flex; display: flex;
align-items: center; align-items: center;
.header-title > div {
.header-title>div {
line-height: 54px; line-height: 54px;
} }
} }
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
const getWordCloudChart = (data) => { const getWordCloudChart = (data) => {
const option = { const option = {
width: 417,
height: 292,
grid: { grid: {
left: 0, left: 0,
top: 0, top: 0,
...@@ -11,7 +13,13 @@ const getWordCloudChart = (data) => { ...@@ -11,7 +13,13 @@ const getWordCloudChart = (data) => {
series: [ series: [
{ {
type: "wordCloud", type: "wordCloud",
shape: "circle", // // 让词云渲染区域严格贴合容器
left: "center",
top: "center",
width: "100%",
height: "100%",
// 使用矩形词云更容易铺满容器且减少留白
shape: "rect", // ✅ 矩形 = 文字排版最整齐、最居中
// 其他形状你可以使用形状路径 // 其他形状你可以使用形状路径
// 或者自定义路径 // 或者自定义路径
// shape: 'circle' // 圆形(默认) // shape: 'circle' // 圆形(默认)
...@@ -22,22 +30,16 @@ const getWordCloudChart = (data) => { ...@@ -22,22 +30,16 @@ const getWordCloudChart = (data) => {
// shape: 'pentagon' // 五边形 // shape: 'pentagon' // 五边形
// shape: 'star' // 星形 // shape: 'star' // 星形
// shape: 'cardioid' // 心形 // shape: 'cardioid' // 心形
gridSize: 30, // 网格大小,影响词间距。 // 网格越大越稀疏,越容易产生留白;这里进一步调小以便更贴合容器
sizeRange: [10, 25], // 定义词云中文字大小的范围 gridSize: 5,
// 适当放大最大字号,提升填充度(同时避免太大溢出)
sizeRange: [16, 24],
rotationRange: [0, 0], rotationRange: [0, 0],
rotationStep: 15, rotationStep: 15,
drawOutOfBound: false, // 是否超出画布 drawOutOfBound: false,
layoutAnimation: false,
// 字体 // 字体
textStyle: { textStyle: {
// normal: {
// color: function () {
// return 'rgb(' + [
// Math.round(Math.random() * 160),
// Math.round(Math.random() * 160),
// Math.round(Math.random() * 160)
// ].join(',') + ')';
// }
// },
color: function () { color: function () {
let colors = [ let colors = [
"rgba(189, 33, 33, 1)", "rgba(189, 33, 33, 1)",
...@@ -49,12 +51,12 @@ const getWordCloudChart = (data) => { ...@@ -49,12 +51,12 @@ const getWordCloudChart = (data) => {
]; ];
return colors[parseInt(Math.random() * colors.length)]; return colors[parseInt(Math.random() * colors.length)];
}, },
textAlign: "center", // ✅ 文字自身水平居中
emphasis: { emphasis: {
shadowBlur: 5, shadowBlur: 5,
shadowColor: "#333", shadowColor: "#333",
}, },
}, },
// 设置词云数据
data: data, data: data,
}, },
], ],
......
...@@ -24,9 +24,10 @@ ...@@ -24,9 +24,10 @@
</div> </div>
<div class="select-box"> <div class="select-box">
<div class="search-box"> <div class="search-box">
<el-input placeholder="搜索政策建议" v-model="searchPolicy"> <el-input placeholder="搜索智库" v-model="searchPolicy">
<template #suffix> <template #suffix>
<img src="../assets/images/Line_Search.png" class="search-icon" alt="搜索"> <img src="../assets/images/Line_Search.png" class="search-icon" alt="搜索"
@click="handleGetThinkTankList()">
</template> </template>
</el-input> </el-input>
...@@ -49,13 +50,13 @@ ...@@ -49,13 +50,13 @@
] ]
}"> }">
<template #prefix> <template #prefix>
<img src="../assets/images/sort-asc.png" class="select-prefix-img" alt="" <img src="../assets/images/sort-asc.png" class="select-prefix-img" alt="" @click.stop="toggleSort()"
@click.stop="toggleSortAndFetch()" :key="true" label="正序" :value="true" v-if="sort" /> :key="true" label="正序" :value="true" v-if="sort === true" />
<img src="../assets/images/sort-desc.png" class="select-prefix-img" alt="" <img src="../assets/images/sort-desc.png" class="select-prefix-img" alt="" @click.stop="toggleSort()"
@click.stop="toggleSortAndFetch()" :key="true" label="倒序" :value="true" v-if="!sort" /> :key="false" label="倒序" :value="false" v-if="sort === false" />
</template> </template>
<el-option @click="handleGetThinkDynamicsReport()" :key="true" label="正序" :value="true" /> <el-option :key="true" label="正序" :value="true" />
<el-option @click="handleGetThinkDynamicsReport()" :key="false" label="倒序" :value="false" /> <el-option :key="false" label="倒序" :value="false" />
</el-select> </el-select>
</div> </div>
</div> </div>
...@@ -73,6 +74,8 @@ ...@@ -73,6 +74,8 @@
<div class="all-item"> <div class="all-item">
<div class="item-card" v-for="(item, index) in sortedCardList" :key="item.id || index" <div class="item-card" v-for="(item, index) in sortedCardList" :key="item.id || index"
@click="handleClick(item)"> @click="handleClick(item)">
<div class="red-info" v-if="item.increaseReportNumber != 0 && item.increaseReportNumber != null">{{ "+" }}{{
item.increaseReportNumber }}</div>
<div class="item-header"> <div class="item-header">
<div class="item-header-image"> <div class="item-header-image">
<img :src=item.logo alt="" /> <img :src=item.logo alt="" />
...@@ -114,7 +117,8 @@ const sortedCardList = computed(() => { ...@@ -114,7 +117,8 @@ const sortedCardList = computed(() => {
return [...cardList.value].sort((a, b) => { return [...cardList.value].sort((a, b) => {
const an = Number(a?.reportNumber ?? 0); const an = Number(a?.reportNumber ?? 0);
const bn = Number(b?.reportNumber ?? 0); const bn = Number(b?.reportNumber ?? 0);
return bn - an; // 只有选择“倒序(false)”才反转;初始为 null 时走“正序”规则
return sort.value === false ? an - bn : bn - an;
}); });
}); });
// el-pagination 是 1-based // el-pagination 是 1-based
...@@ -137,9 +141,12 @@ const handleGetThinkTankList = async () => { ...@@ -137,9 +141,12 @@ const handleGetThinkTankList = async () => {
const res = await getAllThinkTankList({ const res = await getAllThinkTankList({
// 后端通常是 0-based,这里做一次转换 // 后端通常是 0-based,这里做一次转换
currentPage: currentPage.value - 1, currentPage: currentPage.value - 1,
pageSize: pageSize.value pageSize: pageSize.value,
keyword: searchPolicy.value
}); });
console.log("智库列表", res); console.log("智库列表", res);
cardList.value = [];
total.value = 0;
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const list = res.data?.content || []; const list = res.data?.content || [];
total.value = res.data.totalElements; total.value = res.data.totalElements;
...@@ -159,9 +166,15 @@ const handleGetThinkTankList = async () => { ...@@ -159,9 +166,15 @@ const handleGetThinkTankList = async () => {
} }
} catch (error) { } catch (error) {
console.error("获取智库列表error", error); console.error("获取智库列表error", error);
cardList.value = [];
total.value = 0;
} }
}; };
// 初始为 null:el-select 显示 placeholder;但排序仍按“正序”规则(见 sortedCardList)
const sort = ref(null); const sort = ref(null);
const toggleSort = () => {
sort.value = sort.value === false ? true : false
};
const searchPolicy = ref(""); const searchPolicy = ref("");
const handleClick = tank => { const handleClick = tank => {
console.log(tank); console.log(tank);
...@@ -427,6 +440,31 @@ onMounted(async () => { ...@@ -427,6 +440,31 @@ onMounted(async () => {
padding-left: 20px; padding-left: 20px;
padding-top: 17px; padding-top: 17px;
padding-bottom: 21px; padding-bottom: 21px;
position: relative;
.red-info {
color: rgb(255, 255, 255);
display: inline-flex;
position: absolute;
left: 277px;
bottom: 208px;
background-color: rgba(255, 77, 79, 1);
align-items: center;
justify-content: center;
border-radius: 50px;
border: 1px solid rgb(255, 255, 255);
width: auto;
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 18px;
line-height: 24px;
letter-spacing: 0px;
padding: 2px 8px;
/* 左右留空隙,更美观 */
white-space: nowrap
}
.item-header { .item-header {
width: 287px; width: 287px;
......
...@@ -54,6 +54,8 @@ ...@@ -54,6 +54,8 @@
</div> </div>
<div class="home-main-header-card-box"> <div class="home-main-header-card-box">
<div class="card" v-for="(item, index) in sortedCardList" :key="index" @click="handleClick(item)"> <div class="card" v-for="(item, index) in sortedCardList" :key="index" @click="handleClick(item)">
<div class="red-info" v-if="item.increaseReportNumber != 0 && item.increaseReportNumber != null">{{ "+" }}{{
item.increaseReportNumber }}</div>
<div class="card-header"> <div class="card-header">
<div class="icon"> <div class="icon">
<img :src="item.logo" alt="" /> <img :src="item.logo" alt="" />
...@@ -1511,7 +1513,7 @@ const handleSurveyCurrentChange = page => { ...@@ -1511,7 +1513,7 @@ const handleSurveyCurrentChange = page => {
const handleGetThinkTankSurvey = async () => { const handleGetThinkTankSurvey = async () => {
const params = { const params = {
currentPage: surveyCurrentPage.value - 1, pageNum: surveyCurrentPage.value,
pageSize: 12, pageSize: 12,
sortFun: surveySort.value, sortFun: surveySort.value,
researchTypeIds: arrayToString(surveySelectedAreaList.value), researchTypeIds: arrayToString(surveySelectedAreaList.value),
...@@ -1655,7 +1657,7 @@ function arrayToString(arr) { ...@@ -1655,7 +1657,7 @@ function arrayToString(arr) {
//获取智库报告 //获取智库报告
const handleGetetThinkTankReport = async () => { const handleGetetThinkTankReport = async () => {
const params = { const params = {
currentPage: currentPage.value - 1, pageNum: currentPage.value,
pageSize: 12, pageSize: 12,
sortFun: sort.value, sortFun: sort.value,
researchTypeIds: arrayToString(selectedAreaList.value), researchTypeIds: arrayToString(selectedAreaList.value),
...@@ -2113,10 +2115,37 @@ onMounted(async () => { ...@@ -2113,10 +2115,37 @@ onMounted(async () => {
background: rgba(255, 255, 255, 0.65); background: rgba(255, 255, 255, 0.65);
transition: all 0.3s; transition: all 0.3s;
cursor: pointer; cursor: pointer;
position: relative; // 让 red-info 按当前 card 自身定位
z-index: 1;
&:hover { &:hover {
transform: translateY(-3px); transform: translateY(-3px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
z-index: 2;
}
.red-info {
color: rgb(255, 255, 255);
display: inline-flex;
position: absolute;
left: 233px;
bottom: 208px;
background-color: rgba(255, 77, 79, 1);
align-items: center;
justify-content: center;
border-radius: 50px;
border: 1px solid rgb(255, 255, 255);
width: auto;
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 18px;
line-height: 24px;
letter-spacing: 0px;
padding: 2px 8px;
/* 左右留空隙,更美观 */
white-space: nowrap
} }
.card-header { .card-header {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论