提交 0204674d authored 作者: 张烨's avatar 张烨

Merge remote-tracking branch 'origin/pre' into zy-dev

流水线 #567 已通过 于阶段
in 1 分 34 秒
// 引入 axios 请求
import axios from 'axios'
// 引入 element-plus 里面的消息提示
import { ElMessage } from 'element-plus'
import { getToken, setToken, removeToken, formatBearerAuthorization } from '@/api/request.js'
export { getToken, setToken, removeToken }
// 【新增】全局 AbortController,用于管理所有通过此 service 发出的请求
let globalAbortController = new AbortController()
// 【新增】暴露一个方法,供外部(如路由守卫)调用以取消所有正在进行的请求
export const cancelAllRequests = () => {
if (globalAbortController) {
globalAbortController.abort()
// 创建一个新的 Controller 以备下次使用
globalAbortController = new AbortController()
}
}
// 创建 axios 实例
const service = axios.create({
timeout: 30 * 1000 // 请求超时时间
})
// request 拦截器:与主 request 一致,就地改 config,避免 mergeConfig 破坏 url
service.interceptors.request.use(config => {
const raw = getToken()
const token = raw ? String(raw).trim() : ""
if (!config.headers) {
config.headers = new axios.AxiosHeaders()
} else if (!(config.headers instanceof axios.AxiosHeaders)) {
config.headers = axios.AxiosHeaders.from(config.headers)
}
if (token) {
config.headers.set('token', token, true)
} else {
config.headers.delete('token')
config.headers.delete('Token')
}
const reqUrl = String(config.url ?? '')
if (reqUrl.includes('aiAnalysis')) {
const aiApiKey = import.meta.env.VITE_AI_ANALYSIS_API_KEY
if (aiApiKey) {
if (!config.headers) {
config.headers = new axios.AxiosHeaders()
} else if (!(config.headers instanceof axios.AxiosHeaders)) {
config.headers = axios.AxiosHeaders.from(config.headers)
}
config.headers.set('X-API-Key', aiApiKey)
}
}
// 【新增】将全局控制器的 signal 注入到当前请求中
// 注意:如果 config 中已经手动传入了 signal(例如组件内单独控制),则优先使用组件内的
if (!config.signal) {
config.signal = globalAbortController.signal
}
return config
}, error => {
console.log(error)
return Promise.reject(error)
})
// response 拦截器
service.interceptors.response.use(
response => {
const res = response?.data
if (!res) {
return Promise.reject(new Error('响应数据为空'))
}
// 根据需求:接口返回 code 不等于 200 的时候报错
if (res.code !== 200) {
ElMessage({
message: res.message || '请求失败',
type: 'error',
duration: 3 * 1000
})
return Promise.reject(res)
}
return res.data
},
error => {
console.log('err' + error)
const isCanceledError =
error?.code === 'ERR_CANCELED' ||
error?.name === 'CanceledError' ||
error?.name === 'AbortError' ||
(typeof error?.message === 'string' && /canceled/i.test(error.message))
if (isCanceledError) return Promise.reject(error)
// 处理 token 过期或无效的情况
const errUrl = String(error.config?.url || '')
const isAiAnalysisRequest = errUrl.includes('aiAnalysis')
if (
error.response &&
(error.response.status === 401 || error.response.status === 403) &&
!isAiAnalysisRequest
) {
ElMessage({
message: 'Token 已过期,请重新登录',
type: 'error',
duration: 3 * 1000
})
const h = error.config?.headers
const hadToken =
h &&
(typeof h.get === 'function'
? Boolean(
h.get('token') || h.get('Token')
)
: Boolean(
h.token || h.Token
))
if (hadToken) removeToken()
} else {
ElMessage({
message: typeof error?.message === 'string' ? error.message : '请求失败',
type: 'error',
duration: 3 * 1000
})
}
return Promise.reject(error)
}
)
// 封装通用请求函数(支持 http(config) 和 http.get/post 等调用方式)
function http(config) {
return service(config)
}
// 为 http 函数添加快捷方法
http.get = function(url, params) {
return service({ url, method: 'get', params })
}
http.post = function(url, data) {
return service({ url, method: 'post', data })
}
http.put = function(url, data) {
return service({ url, method: 'put', data })
}
http.delete = function(url, params) {
return service({ url, method: 'delete', params })
}
export { http }
export default service
\ No newline at end of file
// 引入 axios 请求 // src/api/finance/service.js
import axios from 'axios' import axios from 'axios'
// 引入 element-plus 里面的消息提示
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { getToken, setToken, removeToken, formatBearerAuthorization } from '@/api/request.js' import { getToken, setToken, removeToken } from '@/api/request.js'
export { getToken, setToken, removeToken } export { getToken, setToken, removeToken }
// 定义全局控制器,以便在取消后重新赋值
let currentAbortController = new AbortController()
/**
* 获取当前有效的 AbortSignal
* 供 axios 拦截器和 fetch 请求共同使用
*/
export const getAbortSignal = () => {
return currentAbortController.signal
}
/**
* 取消所有正在进行的请求
* 路由守卫中调用此方法
*/
export const cancelAllRequests = () => {
// 1. 终止当前控制器的所有请求
currentAbortController.abort()
// 2. 创建一个新的控制器,供后续新请求使用
currentAbortController = new AbortController()
}
// 创建 axios 实例 // 创建 axios 实例
const service = axios.create({ const service = axios.create({
timeout: 300 * 1000 // 请求超时时间 timeout: 30 * 1000 // 请求超时时间 30s
}) })
// request 拦截器:与主 request 一致,就地改 config,避免 mergeConfig 破坏 url // request 拦截器
service.interceptors.request.use(config => { service.interceptors.request.use(config => {
const raw = getToken() const raw = getToken()
const token = raw ? String(raw).trim() : "" const token = raw ? String(raw).trim() : ""
// 处理 Headers
if (!config.headers) { if (!config.headers) {
config.headers = new axios.AxiosHeaders() config.headers = new axios.AxiosHeaders()
} else if (!(config.headers instanceof axios.AxiosHeaders)) { } else if (!(config.headers instanceof axios.AxiosHeaders)) {
config.headers = axios.AxiosHeaders.from(config.headers) config.headers = axios.AxiosHeaders.from(config.headers)
} }
// 设置 Token
if (token) { if (token) {
config.headers.set('token', token, true) config.headers.set('token', token, true)
} else { } else {
config.headers.delete('token') config.headers.delete('token')
config.headers.delete('Token') config.headers.delete('Token')
// ===== 旧逻辑保留(勿删):Authorization: Bearer <token> =====
// config.headers.delete('Authorization')
// config.headers.delete('authorization')
} }
// 处理 AI 分析接口的特殊 Header
const reqUrl = String(config.url ?? '') const reqUrl = String(config.url ?? '')
if (reqUrl.includes('aiAnalysis')) { if (reqUrl.includes('aiAnalysis')) {
const aiApiKey = import.meta.env.VITE_AI_ANALYSIS_API_KEY const aiApiKey = import.meta.env.VITE_AI_ANALYSIS_API_KEY
if (aiApiKey) { if (aiApiKey) {
// 确保 headers 存在
if (!config.headers) { if (!config.headers) {
config.headers = new axios.AxiosHeaders() config.headers = new axios.AxiosHeaders()
} else if (!(config.headers instanceof axios.AxiosHeaders)) { } else if (!(config.headers instanceof axios.AxiosHeaders)) {
...@@ -42,6 +67,13 @@ service.interceptors.request.use(config => { ...@@ -42,6 +67,13 @@ service.interceptors.request.use(config => {
config.headers.set('X-API-Key', aiApiKey) config.headers.set('X-API-Key', aiApiKey)
} }
} }
// 将全局控制器的 signal 注入到 axios 请求中
// 如果用户手动传入了 signal,则优先使用用户的(虽然少见)
if (!config.signal) {
config.signal = getAbortSignal()
}
return config return config
}, error => { }, error => {
console.log(error) console.log(error)
...@@ -55,7 +87,6 @@ service.interceptors.response.use( ...@@ -55,7 +87,6 @@ service.interceptors.response.use(
if (!res) { if (!res) {
return Promise.reject(new Error('响应数据为空')) return Promise.reject(new Error('响应数据为空'))
} }
// 根据需求:接口返回 code 不等于 200 的时候报错
if (res.code !== 200) { if (res.code !== 200) {
ElMessage({ ElMessage({
message: res.message || '请求失败', message: res.message || '请求失败',
...@@ -67,19 +98,25 @@ service.interceptors.response.use( ...@@ -67,19 +98,25 @@ service.interceptors.response.use(
return res.data return res.data
}, },
error => { error => {
console.log('err' + error) // 精准识别取消错误,避免弹窗骚扰用户
const isCanceledError = const isCanceledError =
axios.isCancel(error) ||
error?.code === 'ERR_CANCELED' || error?.code === 'ERR_CANCELED' ||
error?.name === 'CanceledError' || error?.name === 'CanceledError' ||
error?.name === 'AbortError' || error?.name === 'AbortError' ||
(typeof error?.message === 'string' && /canceled/i.test(error.message)) (typeof error?.message === 'string' && /canceled/i.test(error.message))
if (isCanceledError) return Promise.reject(error) if (isCanceledError) {
// 静默处理取消错误,不弹窗,不打印.error
return Promise.reject(error)
}
console.log('err' + error)
// 处理 token 过期或无效的情况 // 处理 Token 过期
const errUrl = String(error.config?.url || '') const errUrl = String(error.config?.url || '')
const isAiAnalysisRequest = errUrl.includes('aiAnalysis') const isAiAnalysisRequest = errUrl.includes('aiAnalysis')
if ( if (
error.response && error.response &&
(error.response.status === 401 || error.response.status === 403) && (error.response.status === 401 || error.response.status === 403) &&
...@@ -94,14 +131,11 @@ service.interceptors.response.use( ...@@ -94,14 +131,11 @@ service.interceptors.response.use(
const hadToken = const hadToken =
h && h &&
(typeof h.get === 'function' (typeof h.get === 'function'
? Boolean( ? Boolean(h.get('token') || h.get('Token'))
h.get('token') || h.get('Token') : Boolean(h.token || h.Token))
)
: Boolean(
h.token || h.Token
))
if (hadToken) removeToken() if (hadToken) removeToken()
} else { } else {
// 只有非取消、非 Token 过期的错误才弹出通用提示
ElMessage({ ElMessage({
message: typeof error?.message === 'string' ? error.message : '请求失败', message: typeof error?.message === 'string' ? error.message : '请求失败',
type: 'error', type: 'error',
...@@ -112,12 +146,11 @@ service.interceptors.response.use( ...@@ -112,12 +146,11 @@ service.interceptors.response.use(
} }
) )
// 封装通用请求函数(支持 http(config) 和 http.get/post 等调用方式) // 封装通用请求函数
function http(config) { function http(config) {
return service(config) return service(config)
} }
// 为 http 函数添加快捷方法
http.get = function(url, params) { http.get = function(url, params) {
return service({ url, method: 'get', params }) return service({ url, method: 'get', params })
} }
......
...@@ -17,6 +17,14 @@ export function getLatestRisks() { ...@@ -17,6 +17,14 @@ export function getLatestRisks() {
}); });
} }
// 中美博弈概览V2:首页最新风险动态(10条 + 各类数量/总数)
export function getLatestRisk() {
return request({
method: "GET",
url: `/api/rivalryIndexV2/LatestRisk`
});
}
// 中美博弈概览V2:美对华制裁措施数量趋势 // 中美博弈概览V2:美对华制裁措施数量趋势
export function geDomainContainmentTrend(params) { export function geDomainContainmentTrend(params) {
return request({ return request({
......
...@@ -132,6 +132,28 @@ ...@@ -132,6 +132,28 @@
position: relative; position: relative;
} }
.risk-signal-detail-dialog .el-dialog__headerbtn {
top: 11px !important;
right: 16px !important;
width: 32px !important;
height: 32px !important;
display: inline-flex;
align-items: center;
justify-content: center;
}
.risk-signal-detail-dialog .el-dialog__headerbtn .el-dialog__close {
width: 16px;
height: 16px;
color: rgb(59, 65, 75) !important;
}
.risk-signal-detail-dialog .el-dialog__headerbtn .el-dialog__close svg,
.risk-signal-detail-dialog .el-dialog__headerbtn .el-dialog__close svg path {
fill: rgb(59, 65, 75) !important;
stroke: rgb(59, 65, 75) !important;
}
.risk-signal-detail-dialog .el-dialog__header::after { .risk-signal-detail-dialog .el-dialog__header::after {
content: ""; content: "";
position: absolute; position: absolute;
...@@ -234,7 +256,7 @@ ...@@ -234,7 +256,7 @@
.risk-signal-detail-dialog .risk-signal-detail-dialog__read-indicator { .risk-signal-detail-dialog .risk-signal-detail-dialog__read-indicator {
position: absolute; position: absolute;
right: 115px; right: 61px;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
display: inline-flex; display: inline-flex;
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<el-icon color="var(--color-primary-100)"> <el-icon color="var(--color-primary-100)">
<ArrowRightBold /> <ArrowRightBold />
</el-icon> </el-icon>
<div class="item-dot" v-if="item.delta">+{{ item.delta }}</div> <div class="item-dot" v-if="item.delta">{{ dotPrefix }}{{ item.delta }}</div>
</div> </div>
<div v-if="shouldShowMoreCard" class="summary-item" @click="emit('more-click')"> <div v-if="shouldShowMoreCard" class="summary-item" @click="emit('more-click')">
...@@ -100,6 +100,10 @@ const props = defineProps({ ...@@ -100,6 +100,10 @@ const props = defineProps({
loading: { loading: {
type: Boolean, type: Boolean,
default: false default: false
},
dotPrefix: {
type: String,
default: "+"
} }
}); });
......
<template> <template>
<div class="module-header-wrapper"> <div class="module-header-wrapper" @mouseleave="handleHideUserPanel">
<div class="nav-content" :class="{ 'nav-content-library': isShowDataLibrary }"> <div class="nav-content" :class="{ 'nav-content-library': isShowDataLibrary }">
<div class="nav-left" :class="{ 'flex-start': isShowSearchBar }"> <div class="nav-left" :class="{ 'flex-start': isShowSearchBar }">
<div class="icon"> <div class="icon">
...@@ -24,36 +24,36 @@ ...@@ -24,36 +24,36 @@
<div class="mail" @click="handleClickToolBox"> <div class="mail" @click="handleClickToolBox">
<img src="@/assets/icons/overview/mail.png" alt="" /> <img src="@/assets/icons/overview/mail.png" alt="" />
</div> </div>
<div class="user-trigger"> <div class="user-trigger" @mouseenter="handleShowUserPanel">
<div class="user" @click.stop="handleToggleUserPanel"> <div class="user" @click.stop>
<img src="@/assets/icons/overview/user.png" alt="" /> <img src="@/assets/icons/overview/user.png" alt="" />
</div> </div>
</div> <div v-if="isShowUserPanel" class="user-panel">
<div v-if="isShowUserPanel" class="user-panel">
<div class="user-panel-row">
<div class="user-panel-row"> <div class="blue-solid"></div>
<div class="blue-solid"></div> <span class="user-panel-value user-panel-value--nickname">{{ userNickname }}</span>
<span class="user-panel-value user-panel-value--nickname">{{ userNickname }}</span> <div class="role-box">
<div class="role-box"> <span class="user-panel-value user-panel-value--role">{{ roleName }}</span>
<span class="user-panel-value user-panel-value--role">{{ roleName }}</span> </div>
</div> </div>
</div>
<div class="user-panel-row user-panel-row--single"> <div class="user-panel-row user-panel-row--single">
<span class="user-panel-value user-panel-value--organ">{{ organName }}</span> <span class="user-panel-value user-panel-value--organ">{{ organName }}</span>
</div> </div>
<div class="solid"></div> <div class="solid"></div>
<div class="user-panel-logout" @click.stop="handleUserCommand('logout')"><img src="./back.png" <div class="user-panel-logout" @click.stop="handleUserCommand('logout')"><img src="./back.png"
class="back-image" />{{ "退出登录" }} class="back-image" />{{ "退出登录" }}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="menu-box" v-show="isShowMenu" @mouseenter="handleHoverMenu(true)" <div class="menu-box" v-if="isShowMenu" @mouseenter="handleHoverMenu(true)"
@mouseleave="handleHoverMenu(false)"> @mouseleave="handleHoverMenu(false)">
<div class="menu-content"> <div class="menu-content">
<div class="menu-item" v-for="(item, index) in menuList" :key="index" @click="handleToModule(item, 1)"> <div class="menu-item" v-for="(item, index) in menuList" :key="index" @click="handleToModule(item, 1)">
...@@ -64,9 +64,9 @@ ...@@ -64,9 +64,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="tool-box" v-show="isShowTool" @mouseenter="handleHoverTool(true)" <div class="tool-box" v-if="isShowTool" @mouseenter="handleHoverTool(true)"
@mouseleave="handleHoverTool(false)"> @mouseleave="handleHoverTool(false)">
<div class="menu-content"> <div class="tool-content">
<div class="menu-item" v-for="(item, index) in toolList" :key="index" @click="handleToModule(item, 2)"> <div class="menu-item" v-for="(item, index) in toolList" :key="index" @click="handleToModule(item, 2)">
<div class="icon"> <div class="icon">
<img :src="item.icon" alt="" /> <img :src="item.icon" alt="" />
...@@ -404,8 +404,8 @@ const handleClickToolBox = () => { ...@@ -404,8 +404,8 @@ const handleClickToolBox = () => {
ElMessage.warning("当前功能正在开发中,敬请期待!"); ElMessage.warning("当前功能正在开发中,敬请期待!");
}; };
const handleToggleUserPanel = () => { const handleShowUserPanel = () => {
isShowUserPanel.value = !isShowUserPanel.value; isShowUserPanel.value = true;
}; };
const handleHideUserPanel = () => { const handleHideUserPanel = () => {
...@@ -662,10 +662,13 @@ onMounted(() => { ...@@ -662,10 +662,13 @@ onMounted(() => {
top: calc(32px + 21px); top: calc(32px + 21px);
width: 240px; width: 240px;
height: 141px; height: 141px;
// 与“中美科技博弈”下方菜单框(.menu-box)保持一致的透明/模糊效果
background: rgba(255, 255, 255, 0.8); background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 1); border: 1px solid rgba(255, 255, 255, 1);
border-radius: 10px; border-radius: 10px;
backdrop-filter: blur(30px);
-webkit-backdrop-filter: blur(30px);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -785,6 +788,13 @@ onMounted(() => { ...@@ -785,6 +788,13 @@ onMounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: 8px; margin-left: 8px;
transition: color 0.15s ease, font-size 0.15s ease, font-weight 0.15s ease;
&:hover {
color: var(--color-main-active);
font-size: 18px;
font-weight: 700;
}
.back-image { .back-image {
width: 16px; width: 16px;
...@@ -805,23 +815,6 @@ onMounted(() => { ...@@ -805,23 +815,6 @@ onMounted(() => {
} }
.menu-box { .menu-box {
// position: absolute;
// z-index: 999999999;
// width: 713px;
// height: 413px;
// top: 52px;
// left: 0;
// box-sizing: border-box;
// border-radius: 10px;
// backdrop-filter: blur(10px);
// -webkit-backdrop-filter: blur(10px);
// box-shadow: 0px 8px 32px 0px rgba(31, 38, 135, 0.15);
// background: rgba(255, 255, 255, 0.25);
// backdrop-filter: blur(10px);
// -webkit-backdrop-filter: blur(10px);
// border: 1px solid rgba(255, 255, 255, 0.3);
// background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.2) 100%);
// box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.2);
position: absolute; position: absolute;
z-index: 999999; z-index: 999999;
width: 713px; width: 713px;
...@@ -902,21 +895,17 @@ onMounted(() => { ...@@ -902,21 +895,17 @@ onMounted(() => {
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, 0.8); background: rgba(255, 255, 255, 0.8);
.menu-content { .tool-content {
width: 562px; width: 130px;
height: 348px; height: 120px;
margin-top: 8px; margin-top: 8px;
margin-left: 72px; margin-left: 72px;
.menu-item { .menu-item {
margin-top: 36px; margin-top: 36px;
width: 280px; width: 100%;
height: 24px; height: 24px;
display: flex; display: flex;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
.title { .title {
color: var(--color-main-active); color: var(--color-main-active);
......
...@@ -12,14 +12,14 @@ ...@@ -12,14 +12,14 @@
<div class="box2-main"> <div class="box2-main">
<div class="box2-main-item" v-for="(item, index) in showRiskSignalList" :key="index" @click="handleItemClick(item, index)"> <div class="box2-main-item" v-for="(item, index) in showRiskSignalList" :key="index" @click="handleItemClick(item, index)">
<div :class="{ <div v-if="!isRiskLevelNoData(item?.[props.riskLevel])" :class="{
itemLeftStatus1: item[props.riskLevel] === '特别重大', itemLeftStatus1: item[props.riskLevel] === '特别重大',
itemLeftStatus2: item[props.riskLevel] === '重大风险', itemLeftStatus2: item[props.riskLevel] === '重大风险',
itemLeftStatus3: item[props.riskLevel] === '较大风险', itemLeftStatus3: item[props.riskLevel] === '较大风险',
itemLeftStatus4: item[props.riskLevel] === '一般风险' || !item[props.riskLevel], itemLeftStatus4: item[props.riskLevel] === '一般风险',
itemLeftStatus5: item[props.riskLevel] === '低风险', itemLeftStatus5: item[props.riskLevel] === '低风险',
}"> }">
{{ item[props.riskLevel] || "暂无数据" }} {{ item[props.riskLevel] }}
</div> </div>
<div class="item-right"> <div class="item-right">
<div class="text"> <span class="text-inner">{{ item[props.name] }}</span></div> <div class="text"> <span class="text-inner">{{ item[props.name] }}</span></div>
...@@ -76,6 +76,11 @@ const props = defineProps({ ...@@ -76,6 +76,11 @@ const props = defineProps({
}); });
const isRiskLevelNoData = (level) => {
const t = String(level ?? "").trim();
return !t || t === "暂无数据" || t === "暂无数值";
};
const showRiskSignalList = computed(() => { const showRiskSignalList = computed(() => {
return props.list.slice(0,6) return props.list.slice(0,6)
}) })
......
...@@ -2,6 +2,8 @@ import { createRouter, createWebHistory } from "vue-router"; ...@@ -2,6 +2,8 @@ import { createRouter, createWebHistory } from "vue-router";
import { setToken, removeToken, getToken } from "@/api/request.js"; import { setToken, removeToken, getToken } from "@/api/request.js";
import { AUTH_LOGOUT_CHANNEL } from "@/utils/authCrossTabLogout.js"; import { AUTH_LOGOUT_CHANNEL } from "@/utils/authCrossTabLogout.js";
import { cancelAllRequests } from "@/api/finance/service.js"
/** localStorage:跨标签页记录当前前端的 bootId(与 vite define 的 __APP_BOOT_ID__ 对齐) */ /** localStorage:跨标签页记录当前前端的 bootId(与 vite define 的 __APP_BOOT_ID__ 对齐) */
const VITE_BOOT_STORAGE_KEY = "app_vite_boot_id"; const VITE_BOOT_STORAGE_KEY = "app_vite_boot_id";
/** 退出后强制回登录页(跨标签页/刷新生效) */ /** 退出后强制回登录页(跨标签页/刷新生效) */
...@@ -145,51 +147,9 @@ const router = createRouter({ ...@@ -145,51 +147,9 @@ const router = createRouter({
// 2)登录成功回跳带 ?token=:先 setToken 并同步 bootId,再去掉 URL 中的 token(须先于 clearTokenIfNewDevBoot,避免误清刚写入的登录态) // 2)登录成功回跳带 ?token=:先 setToken 并同步 bootId,再去掉 URL 中的 token(须先于 clearTokenIfNewDevBoot,避免误清刚写入的登录态)
// 3)已有本地 token:正常走前端路由 // 3)已有本地 token:正常走前端路由
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
// ===== SSO/重定向逻辑(切线上接口时停用,保留注释) ===== // 【新增】在每次路由跳转开始前,取消上一个页面所有未完成的请求
// const queryToken = to.query && to.query.token != null && String(to.query.token).trim() !== "" // 这能防止旧页面的数据回来覆盖新页面,也能减少服务器压力
// ? String(to.query.token).trim() cancelAllRequests();
// : "";
//
// if (queryToken) {
// setToken(queryToken);
// // 成功回跳拿到 token,说明统一登录链路已完成,清除强制标记
// try {
// if (typeof window !== "undefined") {
// window.sessionStorage.removeItem(FORCE_SSO_LOGIN_KEY);
// }
// } catch {
// // ignore
// }
// persistViteBootIdOnly();
// const restQuery = { ...to.query };
// delete restQuery.token;
// const isGatewayCallback =
// to.path === "/callback" || to.path.replace(/\/$/, "") === "/callback";
// const targetPath = isGatewayCallback ? SSO_POST_LOGIN_PATH : to.path;
// next({
// path: targetPath,
// query: restQuery,
// hash: to.hash,
// replace: true,
// });
// return;
// }
//
// // 若用户点了“退出登录”,即使本地还有残留 token/或别处写回,也强制先走统一登录链路
// try {
// if (typeof window !== "undefined" && window.sessionStorage.getItem(FORCE_SSO_LOGIN_KEY) === "1") {
// removeToken();
// const targetUrl = `${SSO_GATEWAY_ORIGIN}/api/v2${to.fullPath || "/"}`;
// window.location.replace(targetUrl);
// next(false);
// return;
// }
// } catch {
// // ignore
// }
// 外网/线上版本:不因重启清登录态;仅开发环境需要此逻辑
// clearTokenIfNewDevBoot();
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
clearTokenIfNewDevBoot(); clearTokenIfNewDevBoot();
} }
......
...@@ -35,7 +35,7 @@ const billRoutes = [ ...@@ -35,7 +35,7 @@ const billRoutes = [
component: BillAllCommittee, component: BillAllCommittee,
meta: { meta: {
title: "法案委员会列表", title: "法案委员会列表",
isShowHeader: true isShowHeader: false
} }
}, },
{ {
......
import { useRouter } from "vue-router";
const router = useRouter()
// 跳转法案详情
export const goToBill = (id, tabName) => {
window.sessionStorage.setItem("billId", id);
window.sessionStorage.setItem("curTabName", tabName);
const route = router.resolve({
path: "/billLayout",
query: {
billId: id
}
});
window.open(route.href, "_blank");
};
// 跳转政令详情
export const goToDecree = (id, tabName) => {
window.sessionStorage.setItem("decreeId", id);
window.sessionStorage.setItem("curTabName", tabName);
const route = router.resolve({
path: "/decreeLayout",
query: {
id: id
}
});
window.open(route.href, "_blank");
};
// 跳转智库
export const goToThinkTank = (id, tabName) => {
window.sessionStorage.setItem("curTabName", tabName);
const route = router.resolve({
name: "ReportDetail",
params: {
id: id
}
});
window.open(route.href, "_blank");
}
// 跳转机构详情
export const goToInstitution = (id, tabName) => {
window.sessionStorage.setItem('curTabName', tabName)
const curRoute = router.resolve({
path: "/institution",
query: {
id: id
}
});
window.open(curRoute.href, "_blank");
}
\ No newline at end of file
...@@ -223,14 +223,14 @@ export function getRiskDetailLevelModifier(level) { ...@@ -223,14 +223,14 @@ export function getRiskDetailLevelModifier(level) {
if (t === "低风险") { if (t === "低风险") {
return "lv5"; return "lv5";
} }
if (t === "一般风险" || !t || t === "暂无数据") { if (t === "一般风险" || !t || t === "暂无数据" || t === "风险信号暂无评估") {
return "lv4"; return "lv4";
} }
return "lv4"; return "lv4";
} }
/** 与 `@/components/base/riskSignal` 左侧等级一致:空 / null 等展示「暂无数据」 */ /** 详情弹窗标题区等级文案:空 / null 等展示「风险信号暂无评估」 */
export const RISK_SIGNAL_LIST_LEVEL_EMPTY_TEXT = "暂无数据"; export const RISK_SIGNAL_LIST_LEVEL_EMPTY_TEXT = "风险信号暂无评估";
/** /**
* 列表行风险等级 → 弹窗标题区文案(null、空串与「暂无数值」等与列表「暂无数据」对齐) * 列表行风险等级 → 弹窗标题区文案(null、空串与「暂无数值」等与列表「暂无数据」对齐)
...@@ -241,7 +241,7 @@ export function normalizeRiskSignalListLevelText(raw) { ...@@ -241,7 +241,7 @@ export function normalizeRiskSignalListLevelText(raw) {
return RISK_SIGNAL_LIST_LEVEL_EMPTY_TEXT; return RISK_SIGNAL_LIST_LEVEL_EMPTY_TEXT;
} }
const s = String(raw).trim(); const s = String(raw).trim();
if (s === "" || s === "null" || s === "undefined" || s === "暂无数值") { if (s === "" || s === "null" || s === "undefined" || s === "暂无数值" || s === "暂无数据") {
return RISK_SIGNAL_LIST_LEVEL_EMPTY_TEXT; return RISK_SIGNAL_LIST_LEVEL_EMPTY_TEXT;
} }
return s; return s;
......
...@@ -43,15 +43,10 @@ ...@@ -43,15 +43,10 @@
<div style="display: flex"> <div style="display: flex">
<!-- 风险信号列表 --> <!-- 风险信号列表 -->
<div class="risk-signals" ref="riskSignalsRef"> <div class="risk-signals" ref="riskSignalsRef">
<div <div class="risk-signals-item" v-for="(item, index) in warningList"
class="risk-signals-item"
v-for="(item, index) in warningList"
:key="item.signalId != null ? String(item.signalId) : 'risk-' + index" :key="item.signalId != null ? String(item.signalId) : 'risk-' + index"
@mouseenter="onMouseEnter(item, index)" @mouseenter="onMouseEnter(item, index)" @mouseleave="onMouseLeave" @click.stop
@mouseleave="onMouseLeave" :class="['risk-signals-item', { 'risk-signals-item-hightLight': riskSignalActiveIndex === index }]">
@click.stop
:class="['risk-signals-item', { 'risk-signals-item-hightLight': riskSignalActiveIndex === index }]"
>
<div class="item-left" :class="{ <div class="item-left" :class="{
'item-status-1': item.signalLevel === '特别重大', 'item-status-1': item.signalLevel === '特别重大',
'item-status-2': item.signalLevel === '重大风险', 'item-status-2': item.signalLevel === '重大风险',
...@@ -132,7 +127,8 @@ ...@@ -132,7 +127,8 @@
import { color } from "echarts"; import { color } from "echarts";
import { onMounted, ref, onUnmounted, computed } from "vue"; import { onMounted, ref, onUnmounted, computed } from "vue";
import WaveBall from "./WaveBall.vue"; import WaveBall from "./WaveBall.vue";
import { getLatestRiskUpdates, getLatestRisks } from "@/api/zmOverview/risk/index.js"; import { getLatestRisk } from "@/api/zmOverview/risk/index.js";
import { getRiskSignalInfoById } from "@/api/riskSignal/index.js";
import router from "@/router/index"; import router from "@/router/index";
import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate"; import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate";
import icon1 from "./icon/title-1.svg"; import icon1 from "./icon/title-1.svg";
...@@ -315,12 +311,14 @@ const cardShowIndex4 = ref(0); ...@@ -315,12 +311,14 @@ const cardShowIndex4 = ref(0);
// 最新风险动态统计 // 最新风险动态统计
const handleGetLatestRiskUpdates = async () => { const handleGetLatestRiskUpdates = async () => {
try { try {
const params = { const res = await getLatestRisk();
currentDate: "本周" console.log("最新风险动态", res);
};
const res = await getLatestRiskUpdates(params);
console.log("最新风险动态统计1", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const d = res.data;
const formatChange = (n) => {
const v = Number(n) || 0;
return v === 0 ? "无新增" : String(v);
};
sections.value = [ sections.value = [
{ {
// title: res.data.policiesRegulations.hotspotTitle, // title: res.data.policiesRegulations.hotspotTitle,
...@@ -328,16 +326,16 @@ const handleGetLatestRiskUpdates = async () => { ...@@ -328,16 +326,16 @@ const handleGetLatestRiskUpdates = async () => {
waveBall: [ waveBall: [
{ {
percent: 30, // 估算的百分比 percent: 30, // 估算的百分比
count: res.data.bill.total, count: d.billRiskTotal ?? 0,
change: res.data.bill.dailyIncrement, change: formatChange(d.billRiskNum),
unit: "项", unit: "项",
title: "法案(提出)", title: "法案(提出)",
type: "法案" type: "法案"
}, },
{ {
percent: 20, // 估算的百分比 percent: 20, // 估算的百分比
count: res.data.administrativeOrder.total, count: d.orderRiskTotal ?? 0,
change: res.data.administrativeOrder.dailyIncrement, change: formatChange(d.orderRiskNum),
unit: "个", unit: "个",
title: "政令", title: "政令",
type: "行政令" type: "行政令"
...@@ -350,8 +348,8 @@ const handleGetLatestRiskUpdates = async () => { ...@@ -350,8 +348,8 @@ const handleGetLatestRiskUpdates = async () => {
waveBall: [ waveBall: [
{ {
percent: 10, // 估算的百分比 percent: 10, // 估算的百分比
count: res.data.Entities.total, count: d.entityRiskTotal ?? 0,
change: res.data.Entities.dailyIncrement, change: formatChange(d.entityRiskNum),
unit: "次", unit: "次",
title: "实体清单", title: "实体清单",
...@@ -359,8 +357,8 @@ const handleGetLatestRiskUpdates = async () => { ...@@ -359,8 +357,8 @@ const handleGetLatestRiskUpdates = async () => {
}, },
{ {
percent: 20, // 估算的百分比 percent: 20, // 估算的百分比
count: res.data.CCL.total, count: d.cclRiskTotal ?? 0,
change: res.data.CCL.dailyIncrement, change: formatChange(d.cclRiskNum),
unit: "次", unit: "次",
title: "CCL", title: "CCL",
type: "CCL" type: "CCL"
...@@ -373,16 +371,16 @@ const handleGetLatestRiskUpdates = async () => { ...@@ -373,16 +371,16 @@ const handleGetLatestRiskUpdates = async () => {
waveBall: [ waveBall: [
{ {
percent: 15, // 估算的百分比 percent: 15, // 估算的百分比
count: res.data.SDN.total, count: d.sdnRiskTotal ?? 0,
change: res.data.SDN.dailyIncrement, change: formatChange(d.sdnRiskNum),
unit: "次", unit: "次",
title: "SDN", title: "SDN",
type: "SDN" type: "SDN"
}, },
{ {
percent: 5, // 估算的百分比 percent: 5, // 估算的百分比
count: res.data.militaryInvolvement.total, count: d.armyRiskTotal ?? 0,
change: res.data.militaryInvolvement.dailyIncrement, change: formatChange(d.armyRiskNum),
unit: "家", unit: "家",
title: "涉军企业", title: "涉军企业",
type: "涉军企业" type: "涉军企业"
...@@ -395,24 +393,24 @@ const handleGetLatestRiskUpdates = async () => { ...@@ -395,24 +393,24 @@ const handleGetLatestRiskUpdates = async () => {
waveBall: [ waveBall: [
{ {
percent: 3, // 估算的百分比 percent: 3, // 估算的百分比
count: res.data["337Survey"].total, count: d.m337RiskTotal ?? 0,
change: res.data["337Survey"].dailyIncrement, change: formatChange(d.m337RiskNum),
unit: "次", unit: "次",
title: "337调查", title: "337调查",
type: "337调查" type: "337调查"
}, },
{ {
percent: 3, // 估算的百分比 percent: 3, // 估算的百分比
count: res.data["232Survey"].total, count: d.m232RiskTotal ?? 0,
change: res.data["232Survey"].dailyIncrement, change: formatChange(d.m232RiskNum),
unit: "次", unit: "次",
title: "232调查", title: "232调查",
type: "232调查" type: "232调查"
}, },
{ {
percent: 3, // 估算的百分比 percent: 3, // 估算的百分比
count: res.data["301Survey"].total, count: d.m301RiskTotal ?? 0,
change: res.data["301Survey"].dailyIncrement, change: formatChange(d.m301RiskNum),
unit: "次", unit: "次",
title: "301调查", title: "301调查",
type: "301调查" type: "301调查"
...@@ -454,12 +452,60 @@ const hotNewsList = ref([ ...@@ -454,12 +452,60 @@ const hotNewsList = ref([
//最新风险信号 //最新风险信号
const handleGetLatestRisks = async () => { const handleGetLatestRisks = async () => {
try { try {
const res = await getLatestRisks(); const res = await getLatestRisk();
console.log("最新风险信号", res); console.log("最新风险动态(列表)", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
warningList.value = res.data.riskVOS; const d = res.data;
hotNewsList.value = res.data.hotspotVOS; const list = Array.isArray(d.riskSignals) ? d.riskSignals : [];
riskTotal.value = res.data.riskCount; const getEventTypeFromModule = (code) => {
const c = String(code ?? "").trim();
if (c === "0100") return "法案";
if (c === "0101") return "行政令";
if (c === "0102") return "智库报告";
if (c === "0103") return "出口管制";
if (c === "0104") return "市场准入";
if (c === "0105") return "创新主体";
if (c === "0106") return "合作限制";
if (c === "0107") return "科研资助体系";
if (c === "0108") return "规则限制";
if (c === "0109") return "投融资限制";
if (c === "01031") return "实体清单";
if (c === "01032") return "CCL";
if (c === "01091") return "SDN";
if (c === "01092") return "涉军企业";
if (c === "01041") return "337调查";
if (c === "01042") return "232调查";
if (c === "01043") return "301调查";
return c;
};
warningList.value = list.map((x) => ({
signalId: x.id,
signalTitle: x.title,
signalTime: x.eventTime,
signalLevel: x.riskLevel,
signalCountryId: x.riskCountryId,
dealStatus: x.dealStatus,
eventType: getEventTypeFromModule(x.sanModuleCode)
}));
riskTotal.value = warningList.value.length;
// 右侧轮播:同样使用 LatestRisk 的 riskSignals(字段映射到现有模板使用的热点字段名)
hotNewsList.value = list.map((x) => ({
signalId: x.id,
hotspotID: x.id,
hotspotType: getEventTypeFromModule(x.sanModuleCode),
hotspotTitle: x.title,
// 轮播题目右侧标签:切换轮播时用详情接口 domains 覆盖
hotspotTag: "",
hotspotDesc: x.description || "",
hotspotDate: x.eventTime || "",
domainList: [],
eventTitle: x.issuingOrgId || ""
}));
// 首次加载:主动拉取当前轮播项详情以展示题目右侧标签
await fetchCarouselDetailForIndex(curHotNewsListIndex.value || 0);
cardList1.value = warningList.value cardList1.value = warningList.value
.filter(item => { .filter(item => {
return item.eventType === "法案" || item.eventType === "行政令"; return item.eventType === "法案" || item.eventType === "行政令";
...@@ -520,11 +566,46 @@ const handleGetLatestRisks = async () => { ...@@ -520,11 +566,46 @@ const handleGetLatestRisks = async () => {
const curNews = ref({}); const curNews = ref({});
const carouselRef = ref(null); const carouselRef = ref(null);
const curHotNewsListIndex = ref(0); const curHotNewsListIndex = ref(0);
const carouselDetailCache = ref({});
const applyHotspotTagById = (id, tagText) => {
const sid = String(id ?? "").trim();
if (!sid) return;
hotNewsList.value = (hotNewsList.value || []).map((n) => {
if (String(n.hotspotID ?? "").trim() !== sid) return n;
return { ...n, hotspotTag: tagText };
});
};
const fetchCarouselDetailForIndex = async (index) => {
const item = hotNewsList.value?.[index];
if (!item) return;
const id = String(item.hotspotID ?? "").trim();
if (!id) return;
if (carouselDetailCache.value[id]) {
applyHotspotTagById(id, carouselDetailCache.value[id]);
return;
}
try {
const res = await getRiskSignalInfoById(id);
if (res && res.code === 200 && res.data) {
const tagText = String(res.data.domains ?? "").trim();
if (tagText) {
carouselDetailCache.value = { ...carouselDetailCache.value, [id]: tagText };
applyHotspotTagById(id, tagText);
}
}
} catch (error) {
console.error("轮播风险信号详情获取失败", error);
}
};
const handleCarouselChange = index => { const handleCarouselChange = index => {
curHotNewsListIndex.value = index; curHotNewsListIndex.value = index;
if (hotNewsList.value && hotNewsList.value.length > 0) { if (hotNewsList.value && hotNewsList.value.length > 0) {
curNews.value = hotNewsList.value[index]; curNews.value = hotNewsList.value[index];
} }
fetchCarouselDetailForIndex(index);
}; };
// 查看详情 // 查看详情
......
...@@ -14,6 +14,12 @@ ...@@ -14,6 +14,12 @@
placeholder="搜索委员会" placeholder="搜索委员会"
/> />
</div> </div>
<div class="hard-select">
<el-select v-model="committeeInfo.metricType" @change="onAllCommittee()" placeholder="统计口径" style="width: 160px; margin-left: 8px">
<el-option label="政令数据总量" :value="1" />
<el-option label="政令新增数量" :value="2" />
</el-select>
</div>
</div> </div>
<div class="date-box"> <div class="date-box">
...@@ -30,7 +36,7 @@ ...@@ -30,7 +36,7 @@
<div class="item-name one-line-ellipsis">{{ item.name }}</div> <div class="item-name one-line-ellipsis">{{ item.name }}</div>
<div class="item-chamber one-line-ellipsis">{{ item.chamber }}</div> <div class="item-chamber one-line-ellipsis">{{ item.chamber }}</div>
</div> </div>
<div class="item-total">{{ item.count }}</div> <div class="item-total">{{ getDisplayCount(item) }}</div>
<el-icon color="var(--color-primary-100)"> <el-icon color="var(--color-primary-100)">
<ArrowRightBold /> <ArrowRightBold />
</el-icon> </el-icon>
...@@ -48,6 +54,12 @@ ...@@ -48,6 +54,12 @@
/> />
</div> </div>
</div> </div>
<div class="back-bnt" @click="handleBack">
<el-icon>
<Back />
</el-icon>
<div class="back-text">返回</div>
</div>
</div> </div>
</template> </template>
...@@ -55,6 +67,7 @@ ...@@ -55,6 +67,7 @@
import { onMounted, reactive, ref } from "vue"; import { onMounted, reactive, ref } from "vue";
import { Search } from "@element-plus/icons-vue"; import { Search } from "@element-plus/icons-vue";
import { ArrowRightBold } from "@element-plus/icons-vue"; import { ArrowRightBold } from "@element-plus/icons-vue";
import { Back } from "@element-plus/icons-vue";
import router from "@/router"; import router from "@/router";
import TimeTabPane from "@/components/base/TimeTabPane/index.vue"; import TimeTabPane from "@/components/base/TimeTabPane/index.vue";
import { getStatisticsBillCountByCommittee } from "@/api/bill/billHome"; import { getStatisticsBillCountByCommittee } from "@/api/bill/billHome";
...@@ -66,6 +79,7 @@ const committeeInfo = reactive({ ...@@ -66,6 +79,7 @@ const committeeInfo = reactive({
pageSize: 8, pageSize: 8,
total: 0, total: 0,
keyWord: "", keyWord: "",
metricType: 1,
dateDesc: "近一年", dateDesc: "近一年",
list: [] list: []
}); });
...@@ -89,10 +103,11 @@ const onAllCommittee = async num => { ...@@ -89,10 +103,11 @@ const onAllCommittee = async num => {
id: `${item.orgType || ""}-${item.orgName || ""}`, id: `${item.orgType || ""}-${item.orgName || ""}`,
name: item.orgName, name: item.orgName,
chamber: getChamberLabel(item.orgType), chamber: getChamberLabel(item.orgType),
count: Number(item.count || 0) totalCount: Number(item.count || 0),
recentCount: Number(item.countRecent || item.totalRecent || item.recentCount || item.recent || item.newCount || 0)
})) }))
.filter(item => !committeeInfo.keyWord || item.name?.includes(committeeInfo.keyWord)) .filter(item => !committeeInfo.keyWord || item.name?.includes(committeeInfo.keyWord))
.sort((a, b) => (b.count || 0) - (a.count || 0)); .sort((a, b) => getSortValue(b) - getSortValue(a));
committeeInfo.total = source.length; committeeInfo.total = source.length;
const start = (committeeInfo.pageNum - 1) * committeeInfo.pageSize; const start = (committeeInfo.pageNum - 1) * committeeInfo.pageSize;
...@@ -108,20 +123,36 @@ const onAllCommittee = async num => { ...@@ -108,20 +123,36 @@ const onAllCommittee = async num => {
committeeInfo.loading = false; committeeInfo.loading = false;
}; };
const getSortValue = item => {
if (committeeInfo.metricType === 2) return Number(item?.recentCount || 0);
return Number(item?.totalCount || 0);
};
const getDisplayCount = item => {
return getSortValue(item);
};
const handleDateChange = event => { const handleDateChange = event => {
committeeInfo.dateDesc = event?.time || "近一年"; committeeInfo.dateDesc = event?.time || "近一年";
onAllCommittee(); onAllCommittee();
}; };
const handleToDataLibrary = item => { const handleToDataLibrary = item => {
const route = router.resolve({ router.push({
path: "/dataLibrary/countryBill", path: "/dataLibrary/countryBill",
query: { query: {
selectedOrg: item.name, selectedOrg: item.name,
selectedCongress: item.chamber selectedCongress: item.chamber
} }
}); });
window.open(route.href, "_blank"); };
const handleBack = () => {
if (window.history.length > 1) {
router.back();
return;
}
router.push("/billHome");
}; };
const refCommittee = ref(); const refCommittee = ref();
...@@ -143,6 +174,28 @@ onMounted(() => { ...@@ -143,6 +174,28 @@ onMounted(() => {
background-size: 100% 100%; background-size: 100% 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
position: relative;
.back-bnt {
position: absolute;
top: 16px;
left: 30px;
width: 86px;
height: 38px;
background-color: white;
border-radius: 19px;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-primary-65-color);
font-family: Source Han Sans CN;
font-size: 16px;
cursor: pointer;
}
.back-text {
margin-left: 6px;
}
.container-box { .container-box {
width: 1600px; width: 1600px;
...@@ -180,6 +233,11 @@ onMounted(() => { ...@@ -180,6 +233,11 @@ onMounted(() => {
width: 180px; width: 180px;
height: 32px; height: 32px;
} }
.hard-select {
height: 42px;
padding: 5px 0;
}
} }
.date-box { .date-box {
......
...@@ -69,7 +69,7 @@ const getDoublePieChart = (data1, data2) => { ...@@ -69,7 +69,7 @@ const getDoublePieChart = (data1, data2) => {
const name = truncateLabel(params?.name, 6) const name = truncateLabel(params?.name, 6)
const value = params?.value ?? 0 const value = params?.value ?? 0
const percent = typeof params?.percent === 'number' ? params.percent : 0 const percent = typeof params?.percent === 'number' ? params.percent : 0
return `{name|${name}}\n{time|${value} ${percent}%}` return `{name|${name}}\n{time|${value} ${percent}%}`
}, },
minMargin: 5, minMargin: 5,
edgeDistance: 10, edgeDistance: 10,
......
...@@ -10,6 +10,7 @@ const truncateLabel = (value, maxLen = 6) => { ...@@ -10,6 +10,7 @@ const truncateLabel = (value, maxLen = 6) => {
const getPieChart = (data, colorList, options = {}) => { const getPieChart = (data, colorList, options = {}) => {
const showCount = options.showCount !== false const showCount = options.showCount !== false
const countUnit = options.countUnit || '条'
const chartColors = Array.isArray(colorList) && colorList.length ? colorList : MUTICHARTCOLORS const chartColors = Array.isArray(colorList) && colorList.length ? colorList : MUTICHARTCOLORS
let option = { let option = {
color: chartColors, color: chartColors,
...@@ -38,7 +39,7 @@ const getPieChart = (data, colorList, options = {}) => { ...@@ -38,7 +39,7 @@ const getPieChart = (data, colorList, options = {}) => {
const name = truncateLabel(params?.name, 6) const name = truncateLabel(params?.name, 6)
const value = params?.value ?? 0 const value = params?.value ?? 0
const percent = typeof params?.percent === 'number' ? params.percent : 0 const percent = typeof params?.percent === 'number' ? params.percent : 0
const labelText = showCount ? `${value} ${percent}%` : `${percent}%` const labelText = showCount ? `${value}${countUnit} ${percent}%` : `${percent}%`
return `{name|${name}}\n{time|${labelText}}` return `{name|${name}}\n{time|${labelText}}`
}, },
minMargin: 5, minMargin: 5,
......
...@@ -602,9 +602,7 @@ const initRightChart = () => { ...@@ -602,9 +602,7 @@ const initRightChart = () => {
const domains = Array.from(domainsSet); const domains = Array.from(domainsSet);
const types = Array.from(typesSet); const types = Array.from(typesSet);
const legendSplitAt = Math.ceil(types.length / 2); const legendData = types;
const legendFirstLine = types.slice(0, legendSplitAt);
const legendSecondLine = types.slice(legendSplitAt);
const indicators = domains.map((domain) => { const indicators = domains.map((domain) => {
const domainData = rawData.filter((item) => item.AREA === domain); const domainData = rawData.filter((item) => item.AREA === domain);
...@@ -630,57 +628,56 @@ const initRightChart = () => { ...@@ -630,57 +628,56 @@ const initRightChart = () => {
const option = { const option = {
color: colorMap, color: colorMap,
// 避免自动换行导致“第二行不居中”:拆成两行 legend,每行各自居中 // 图例尽量单行居中;放不下则横向滚动(不换行)
legend: [ legend: {
{ show: true,
show: true, type: "scroll",
type: "plain", orient: "horizontal",
data: legendFirstLine, data: legendData,
top: 8, top: 8,
left: "center", left: "center",
icon: "circle", width: "90%",
itemWidth: 12, height: 24,
itemHeight: 12, icon: "circle",
itemGap: 24, itemWidth: 12,
textStyle: { itemHeight: 12,
color: "rgb(95, 101, 108)", itemGap: 24,
fontSize: 16, pageButtonPosition: "end",
fontFamily: "Microsoft YaHei", pageIconSize: 12,
fontWeight: 400, pageTextStyle: {
lineHeight: 24 color: "rgb(95, 101, 108)",
} fontSize: 12,
fontFamily: "Microsoft YaHei"
}, },
{ textStyle: {
show: legendSecondLine.length > 0, color: "rgb(95, 101, 108)",
type: "plain", fontSize: 16,
data: legendSecondLine, fontFamily: "Microsoft YaHei",
top: 32, fontWeight: 400,
left: "center", lineHeight: 24
icon: "circle",
itemWidth: 12,
itemHeight: 12,
itemGap: 24,
textStyle: {
color: "rgb(95, 101, 108)",
fontSize: 16,
fontFamily: "Microsoft YaHei",
fontWeight: 400,
lineHeight: 24
}
} }
], },
radar: { radar: {
// 对齐左侧折线图(grid top=34%)的“图例到图形”间距:下移雷达中心并略缩半径 // 对齐左侧折线图(grid top=34%)的“图例到图形”间距:下移雷达中心并略缩半径
center: ["50%", "62%"], center: ["50%", "57%"],
radius: "60%", radius: "58%",
indicator: indicators, indicator: indicators,
axisName: { axisName: {
color: "rgba(132, 136, 142, 1)", fontFamily: "Source Han Sans CN",
fontSize: 14, fontWeight: 700,
fontWeight: 400 fontSize: 16,
lineHeight: 24,
letterSpacing: 1,
color: "rgb(59, 65, 75)"
}, },
splitLine: { lineStyle: { color: ["#e6e6e6"] } }, splitLine: { lineStyle: { color: ["#e6e6e6"] } },
splitArea: { show: false } splitArea: {
show: true,
areaStyle: {
// 从最内圈白色开始,向外层交替浅灰
color: ["#ffffff", "rgb(247, 248, 249)"]
}
}
}, },
series: [ series: [
{ {
......
...@@ -7,8 +7,7 @@ ...@@ -7,8 +7,7 @@
</div> </div>
</div> </div>
<el-select v-model="sortModel" placeholder="发布时间" class="select" popper-class="coop-select-dropdown" <el-select v-model="sortModel" placeholder="发布时间" class="select" popper-class="coop-select-dropdown"
:teleported="true" placement="bottom-start" :teleported="true" placement="bottom-start" :popper-options="sortPopperOptions" @change="handleSortChange">
:popper-options="sortPopperOptions" @change="handleSortChange">
<template #prefix> <template #prefix>
<img v-if="sortModel !== true" src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image down.png" <img v-if="sortModel !== true" src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image down.png"
class="select-prefix-img" alt="" @click.stop="toggleSortPrefix" /> class="select-prefix-img" alt="" @click.stop="toggleSortPrefix" />
...@@ -37,35 +36,42 @@ ...@@ -37,35 +36,42 @@
</div> </div>
</div> </div>
</div> </div>
<div class="right"> <div class="right" :class="{ 'right--empty': !(mainDataList && mainDataList.length) }">
<div class="right-title"> <div class="right-title">
<img src="./assets/icon01.png" alt="" /> <img src="./assets/icon01.png" alt="" />
<div>合作限制历程</div> <div>合作限制历程</div>
</div> </div>
<div class="right-main"> <div class="right-main">
<div class="main-content"> <template v-if="mainDataList && mainDataList.length">
<div v-for="item in mainDataList" :key="item.id" class="main-item"> <div class="main-content">
<div class="date">{{ formatDateCn(item.date) }}</div> <div v-for="item in mainDataList" :key="item.id" class="main-item">
<img :src="item.img" alt="" class="img" /> <div class="date">{{ formatDateCn(item.date) }}</div>
<div class="box"> <img :src="item.img" alt="" class="img" />
<div class="title" @click="handleClick(item)">{{ item.title }}</div> <div class="box">
<div class="content" @click="handleClick(item)">{{ item.content }}</div> <div class="title" @click="handleClick(item)">{{ item.title }}</div>
<div class="domain"> <div class="content" @click="handleClick(item)">{{ item.content }}</div>
<AreaTag v-for="(domain, i) in item.domain" :key="i" " :tagName="domain"> <div class="domain">
</AreaTag> <AreaTag v-for="(domain, i) in item.domain" :key="i" " :tagName="domain">
</div> </AreaTag>
</div>
<div class="type" :class="getTypeClass(item.type)">
{{ item.type }} <div class="type" :class="getTypeClass(item.type)">
{{ item.type }}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="page">
<div class="page"> <div class="count">{{ total }} 项调查</div>
<div class="count">共 {{ total }} 项调查</div> <el-pagination v-model:current-page="currentPage" :page-size="pageSize" :total="total"
<el-pagination v-model:current-page="currentPage" :page-size="pageSize" :total="total" layout="prev, pager, next" background @current-change="handlePageChange" />
layout="prev, pager, next" background @current-change="handlePageChange" /> </div>
</div> </template>
<template v-else>
<div class="right-main-empty">
<el-empty class="right-el-empty" description="暂无数据" :image-size="100" />
</div>
</template>
</div> </div>
</div> </div>
</div> </div>
...@@ -533,6 +539,13 @@ watch(currentPage, () => { ...@@ -533,6 +539,13 @@ watch(currentPage, () => {
border-radius: 10px; border-radius: 10px;
background-color: #fff; background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
display: flex;
flex-direction: column;
&.right--empty {
// 与左侧筛选框等高
height: 432px;
}
.right-title { .right-title {
width: 1224px; width: 1224px;
...@@ -563,10 +576,28 @@ watch(currentPage, () => { ...@@ -563,10 +576,28 @@ watch(currentPage, () => {
.right-main { .right-main {
width: 1224px; width: 1224px;
flex: 1;
padding: 12px 0px 80px 0px; padding: 12px 0px 80px 0px;
position: relative; position: relative;
.right-main-empty {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
:deep(.el-empty__image) {
margin-bottom: 0;
}
.right-el-empty {
padding: 0;
margin: 0;
}
}
.main-content { .main-content {
width: 1224px; width: 1224px;
...@@ -594,15 +625,7 @@ watch(currentPage, () => { ...@@ -594,15 +625,7 @@ watch(currentPage, () => {
} }
&:last-child::after { &:last-child::after {
content: ""; content: none;
position: absolute;
top: 37px;
bottom: -37px;
left: 108px;
width: 2px;
background-color: rgb(230, 231, 232);
z-index: 1;
height: calc(100% - 37px);
} }
.date { .date {
...@@ -635,6 +658,7 @@ watch(currentPage, () => { ...@@ -635,6 +658,7 @@ watch(currentPage, () => {
.title { .title {
font-size: 20px; font-size: 20px;
width: 950px;
font-weight: 700; font-weight: 700;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
line-height: 26px; line-height: 26px;
......
...@@ -5,13 +5,13 @@ ...@@ -5,13 +5,13 @@
<img :src="coopData?.IMAGEURL || defaultImg" alt="" /> <img :src="coopData?.IMAGEURL || defaultImg" alt="" />
<div class="content"> <div class="content">
<div class="cl1">{{ coopData?.LIMITNAMEZH }}</div> <div class="cl1">{{ coopData?.LIMITNAMEZH }}</div>
<div class="cl2">{{ coopData?.LIMITNAME }}</div> <div v-if="hasLimitNameEn" class="cl2">{{ coopData?.LIMITNAME }}</div>
<div class="cl3">{{ coopData?.LIMITDATE }} · {{ coopData?.LIMITORGNAME }}</div> <div class="cl3">{{ coopData?.LIMITDATE }} · {{ coopData?.LIMITORGNAME }}</div>
</div> </div>
<div class="btn"> <div class="btn">
<button class="btn1"><img src="./assets/icon01.png" alt="" />查看原文</button>
<!-- <button class="btn1"><img src="./assets/icon02.png" alt="" />查看官网</button> --> <!-- <button class="btn1"><img src="./assets/icon02.png" alt="" />查看官网</button> -->
<button class="btn1 active"><img src="./assets/icon03.png" alt="" />分析报告</button> <button class="btn1 active" @click="handleOpenWrittingAsstaint"><img src="./assets/icon03.png" alt="" />分析报告</button>
</div> </div>
</div> </div>
</div> </div>
...@@ -81,7 +81,8 @@ ...@@ -81,7 +81,8 @@
<img src="./assets/打开按钮.png" alt=""> <img src="./assets/打开按钮.png" alt="">
</div> </div>
<!-- contentList:单条按原样式展示;多条则逐条展示并加 1.2.3. 前缀 --> <!-- contentList:单条按原样式展示;多条则逐条展示并加 1.2.3. 前缀 -->
<div v-if="Array.isArray(item.contentList) && item.contentList.length > 1" class="clause-item-content-list"> <div v-if="Array.isArray(item.contentList) && item.contentList.length > 1"
class="clause-item-content-list">
<div v-for="(row, i) in item.contentList" :key="i" class="clause-item-content-row"> <div v-for="(row, i) in item.contentList" :key="i" class="clause-item-content-row">
<span class="row-index">{{ i + 1 }}.</span> <span class="row-index">{{ i + 1 }}.</span>
<span class="row-text">{{ row.CONTENT }}</span> <span class="row-text">{{ row.CONTENT }}</span>
...@@ -117,6 +118,15 @@ import Rubio from "./assets/卢比奥.png"; ...@@ -117,6 +118,15 @@ import Rubio from "./assets/卢比奥.png";
import Bondi from "./assets/邦迪.png"; import Bondi from "./assets/邦迪.png";
import Nome from "./assets/诺姆.png"; import Nome from "./assets/诺姆.png";
const hasLimitNameEn = computed(() => {
return Boolean(String(coopData.value?.LIMITNAME ?? "").trim());
});
const handleOpenWrittingAsstaint = () => {
const { href } = router.resolve({ path: "/writtingAsstaint" });
window.open(href, "_blank");
};
// 合作限制-查询限制条款 // 合作限制-查询限制条款
const limitClauseData = ref([]); const limitClauseData = ref([]);
const getlimitClauseData = async () => { const getlimitClauseData = async () => {
...@@ -474,9 +484,13 @@ const dataList3 = ref([ ...@@ -474,9 +484,13 @@ const dataList3 = ref([
.btn { .btn {
width: 376px; width: 376px;
height: 36px; height: 48px;
display: flex; display: flex;
justify-content: right; justify-content: right;
position: absolute;
bottom: 0;
margin-left: 1224px;
.btn1 { .btn1 {
border-radius: 6px; border-radius: 6px;
......
...@@ -164,6 +164,7 @@ const handleToPosi = id => { ...@@ -164,6 +164,7 @@ const handleToPosi = id => {
background-size: 100% 100%; background-size: 100% 100%;
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%;
z-index: -100; z-index: -100;
top: -64px; top: -64px;
......
...@@ -124,7 +124,7 @@ ...@@ -124,7 +124,7 @@
<el-table ref="tableRef" :data="tableData" row-key="id" @selection-change="handleSelectionChange" <el-table ref="tableRef" :data="tableData" row-key="id" @selection-change="handleSelectionChange"
@select="handleSelect" @select-all="handleSelectAll" style="width: 100%" :row-style="{ height: '52px' }"> @select="handleSelect" @select-all="handleSelectAll" style="width: 100%" :row-style="{ height: '52px' }">
<el-table-column type="selection" width="40" /> <el-table-column type="selection" width="40" />
<el-table-column label="法案名称" width="455"> <el-table-column label="法案名称" width="600">
<template #default="scope"> <template #default="scope">
<span class="title-item text-compact-bold" @click="handleClickToDetail(scope.row)">{{ <span class="title-item text-compact-bold" @click="handleClickToDetail(scope.row)">{{
scope.row.originalTitle scope.row.originalTitle
...@@ -134,14 +134,14 @@ ...@@ -134,14 +134,14 @@
<el-table-column label="日期" width="120" class-name="date-column"> <el-table-column label="日期" width="120" class-name="date-column">
<template #default="scope">{{ scope.row.date }}</template> <template #default="scope">{{ scope.row.date }}</template>
</el-table-column> </el-table-column>
<el-table-column label="提案人" width="480"> <el-table-column label="提案人" width="300">
<template #default="scope"> <template #default="scope">
<span class="person-item text-compact" @click="handlePerClick(scope.row)">{{ scope.row.sponsorPersonName <span class="person-item text-compact" @click="handlePerClick(scope.row)">{{ scope.row.sponsorPersonName
}}</span> }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column property="affiliation" label="所属党派" width="120" /> <el-table-column property="affiliation" label="所属党派" width="120" />
<el-table-column property="originDepart" label="提出委员会" width="180" /> <el-table-column property="originDepart" label="提出委员会" />
<el-table-column property="status" label="所处阶段" width="120" /> <el-table-column property="status" label="所处阶段" width="120" />
</el-table> </el-table>
</div> </div>
......
...@@ -23,6 +23,7 @@ const getLineChart = (dataX, dataY) => { ...@@ -23,6 +23,7 @@ const getLineChart = (dataX, dataY) => {
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
name: '数量',
splitLine: { splitLine: {
show: true, show: true,
lineStyle: { lineStyle: {
......
...@@ -2,8 +2,8 @@ import { MUTICHARTCOLORS } from "@/common/constant"; ...@@ -2,8 +2,8 @@ import { MUTICHARTCOLORS } from "@/common/constant";
const getPieChart = (data) => { const getPieChart = (data) => {
const colorList = MUTICHARTCOLORS const colorList = MUTICHARTCOLORS
let showData = data let showData = data
if(data.length > 14) { if(data.length > 12) {
showData = data.slice(0,13) showData = data.slice(0,11)
let num = 0 let num = 0
data.slice(13,).forEach(item => { data.slice(13,).forEach(item => {
num = num + item.value num = num + item.value
...@@ -22,8 +22,9 @@ const getPieChart = (data) => { ...@@ -22,8 +22,9 @@ const getPieChart = (data) => {
radius: [150, 180], radius: [150, 180],
// height: '96%', // height: '96%',
left: 'center', left: 'center',
top: 24, top: 60,
width: '98%', width: '98%',
height: '90%',
itemStyle: { itemStyle: {
borderColor: '#fff', borderColor: '#fff',
borderWidth: 1 borderWidth: 1
......
...@@ -134,16 +134,15 @@ const siderList = ref([ ...@@ -134,16 +134,15 @@ const siderList = ref([
active: false active: false
}, },
{ {
name: "实体清单事件", name: "商业管制清单",
path: "/dataLibrary/dataEntityListEvent", path: "/dataLibrary/dataCommerceControlList",
active: false active: false
}, },
{ {
name: "商业管制清单", name: "实体清单事件",
path: "/dataLibrary/dataCommerceControlList", path: "/dataLibrary/dataEntityListEvent",
active: false active: false
}, },
{ {
name: "商业管制清单事件", name: "商业管制清单事件",
path: "/dataLibrary/dataCommerceControlListEvent", path: "/dataLibrary/dataCommerceControlListEvent",
...@@ -257,6 +256,10 @@ const siderList = ref([ ...@@ -257,6 +256,10 @@ const siderList = ref([
]); ]);
const handleSiderItem = item => { const handleSiderItem = item => {
if (item.name === '风险信号' || item.name === '市场准入限制') {
ElMessage.warning('当前模块开发中,敬请期待!')
return
}
siderList.value.forEach(val => { siderList.value.forEach(val => {
val.active = false; val.active = false;
val.isExpanded = false; val.isExpanded = false;
...@@ -282,6 +285,10 @@ const handleSiderItem = item => { ...@@ -282,6 +285,10 @@ const handleSiderItem = item => {
}; };
const handleSiderSecondItem = item => { const handleSiderSecondItem = item => {
if (item.name === '州法案' || item.name === '研究型大学' || item.name === '重点实验室') {
ElMessage.warning('当前模块开发中,敬请期待!')
return
}
siderList.value.forEach(item => { siderList.value.forEach(item => {
if (item.children.length) { if (item.children.length) {
item.children.forEach(val => { item.children.forEach(val => {
...@@ -437,17 +444,16 @@ onMounted(() => { ...@@ -437,17 +444,16 @@ onMounted(() => {
siderList.value[3].isExpanded = true; siderList.value[3].isExpanded = true;
siderList.value[3].children[0].active = true; siderList.value[3].children[0].active = true;
break; break;
case "/dataLibrary/dataEntityListEvent": case "/dataLibrary/dataCommerceControlList":
siderList.value[3].active = true; siderList.value[3].active = true;
siderList.value[3].isExpanded = true; siderList.value[3].isExpanded = true;
siderList.value[3].children[1].active = true; siderList.value[3].children[1].active = true;
break; break;
case "/dataLibrary/dataCommerceControlList": case "/dataLibrary/dataEntityListEvent":
siderList.value[3].active = true; siderList.value[3].active = true;
siderList.value[3].isExpanded = true; siderList.value[3].isExpanded = true;
siderList.value[3].children[2].active = true; siderList.value[3].children[2].active = true;
break; break;
case "/dataLibrary/dataCommerceControlListEvent": case "/dataLibrary/dataCommerceControlListEvent":
siderList.value[3].active = true; siderList.value[3].active = true;
siderList.value[3].isExpanded = true; siderList.value[3].isExpanded = true;
...@@ -559,6 +565,7 @@ onBeforeUnmount(() => { ...@@ -559,6 +565,7 @@ onBeforeUnmount(() => {
height: 100%; height: 100%;
} }
} }
.title { .title {
color: var(--color-primary-100); color: var(--color-primary-100);
} }
...@@ -737,6 +744,7 @@ onBeforeUnmount(() => { ...@@ -737,6 +744,7 @@ onBeforeUnmount(() => {
.tab-item-active { .tab-item-active {
border-bottom: 2px solid var(--color-primary-100) !important; border-bottom: 2px solid var(--color-primary-100) !important;
background: var(--color-primary-2);
} }
} }
......
...@@ -105,7 +105,7 @@ ...@@ -105,7 +105,7 @@
<el-table ref="tableRef" :data="tableData" row-key="id" @selection-change="handleSelectionChange" <el-table ref="tableRef" :data="tableData" row-key="id" @selection-change="handleSelectionChange"
@select="handleSelect" @select-all="handleSelectAll" style="width: 100%" :row-style="{ height: '52px' }"> @select="handleSelect" @select-all="handleSelectAll" style="width: 100%" :row-style="{ height: '52px' }">
<el-table-column type="selection" width="40" /> <el-table-column type="selection" width="40" />
<el-table-column label="新闻标题" width="600"> <el-table-column label="新闻标题" width="420">
<template #default="scope"> <template #default="scope">
<span class="title-item text-compact-bold" @click="handleClickToDetail(scope.row)">{{ <span class="title-item text-compact-bold" @click="handleClickToDetail(scope.row)">{{
scope.row.originalTitle scope.row.originalTitle
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<div class="en-title">{{ institutionInfo.enName }}</div> <div class="en-title">{{ institutionInfo.enName }}</div>
<div class="desc">{{ institutionInfo.desc }}</div> <div class="desc">{{ institutionInfo.desc }}</div>
<div class="tag-box"> <div class="tag-box">
<div class="tag" v-for="(tag, index) in institutionInfo.tagList" :key="index"> <div class="tag" v-for="(tag, index) in showTagList" :key="index">
{{ tag }} {{ tag }}
</div> </div>
</div> </div>
...@@ -22,13 +22,8 @@ ...@@ -22,13 +22,8 @@
</div> --> </div> -->
</div> </div>
<div class="tab-box"> <div class="tab-box">
<div <div class="tab" @click="handleClickTab(item)" :class="{ tabActive: activeTabName == item.name }"
class="tab" v-for="(item, index) in tabList" :key="index">
@click="handleClickTab(item)"
:class="{ tabActive: activeTabName == item.name }"
v-for="(item, index) in tabList"
:key="index"
>
{{ item.name }} {{ item.name }}
</div> </div>
</div> </div>
...@@ -61,6 +56,14 @@ const institutionInfo = ref({ ...@@ -61,6 +56,14 @@ const institutionInfo = ref({
logo: "" logo: ""
}); });
const showTagList = computed(() => {
if(institutionInfo.value.tagList.length > 10) {
return institutionInfo.value.tagList.slice(0,10)
} else {
return institutionInfo.value.tagList
}
})
const handleGetInfo = async () => { const handleGetInfo = async () => {
const params = { const params = {
id: route.query.id id: route.query.id
...@@ -75,7 +78,7 @@ const handleGetInfo = async () => { ...@@ -75,7 +78,7 @@ const handleGetInfo = async () => {
institutionInfo.value.desc = res.data.orgIntroduction; institutionInfo.value.desc = res.data.orgIntroduction;
institutionInfo.value.name = res.data.orgName; institutionInfo.value.name = res.data.orgName;
} }
} catch (error) {} } catch (error) { }
}; };
handleGetInfo(); handleGetInfo();
...@@ -95,7 +98,7 @@ const tabList = ref([ ...@@ -95,7 +98,7 @@ const tabList = ref([
]); ]);
const handleClickTab = val => { const handleClickTab = val => {
if(val.name === '深度挖掘' || val.name === '对华制裁') { if (val.name === '深度挖掘' || val.name === '对华制裁') {
ElMessage.warning('当前功能开发中,敬请期待!') ElMessage.warning('当前功能开发中,敬请期待!')
return return
} }
...@@ -125,6 +128,7 @@ onUnmounted(() => { ...@@ -125,6 +128,7 @@ onUnmounted(() => {
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
padding-top: 16px; padding-top: 16px;
.header { .header {
width: 1600px; width: 1600px;
height: 200px; height: 200px;
...@@ -136,20 +140,23 @@ onUnmounted(() => { ...@@ -136,20 +140,23 @@ onUnmounted(() => {
background: rgba(255, 255, 255, 0.8); background: rgba(255, 255, 255, 0.8);
display: flex; display: flex;
position: relative; position: relative;
.header-left { .header-left {
width: 160px; width: 160px;
height: 160px; height: 160px;
margin: 20px; margin: 20px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.header-right { .header-right {
margin-left: 24px; margin-left: 24px;
width: 1350px; width: 1350px;
overflow: hidden; overflow: hidden;
overflow-y: auto;
.title { .title {
margin-top: 26px; margin-top: 26px;
height: 42px; height: 42px;
...@@ -164,6 +171,7 @@ onUnmounted(() => { ...@@ -164,6 +171,7 @@ onUnmounted(() => {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.en-title { .en-title {
margin-top: 8px; margin-top: 8px;
height: 24px; height: 24px;
...@@ -178,7 +186,9 @@ onUnmounted(() => { ...@@ -178,7 +186,9 @@ onUnmounted(() => {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.desc { .desc {
height: 48px;
margin-top: 6px; margin-top: 6px;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
...@@ -187,11 +197,25 @@ onUnmounted(() => { ...@@ -187,11 +197,25 @@ onUnmounted(() => {
line-height: 24px; line-height: 24px;
letter-spacing: 0px; letter-spacing: 0px;
text-align: justify; text-align: justify;
overflow: hidden;
display: -webkit-box;
/* 3. 限制显示的行数(修改数字即可改变行数) */
-webkit-line-clamp: 2;
/* 4. 设置文字垂直排列方向 */
-webkit-box-orient: vertical;
/* 5. 隐藏超出部分 */
overflow: hidden;
/* 6. 显示省略号 */
text-overflow: ellipsis;
/* 可选:修复文字间距/换行问题 */
word-break: break-all;
} }
.tag-box { .tag-box {
margin-top: 14px; margin-top: 14px;
display: flex; display: flex;
gap: 8px; gap: 8px;
.tag { .tag {
height: 24px; height: 24px;
padding: 0px 8px; padding: 0px 8px;
...@@ -206,6 +230,7 @@ onUnmounted(() => { ...@@ -206,6 +230,7 @@ onUnmounted(() => {
} }
} }
} }
.header-btn { .header-btn {
position: absolute; position: absolute;
top: 26px; top: 26px;
...@@ -219,14 +244,17 @@ onUnmounted(() => { ...@@ -219,14 +244,17 @@ onUnmounted(() => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
.icon { .icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.text { .text {
height: 22px; height: 22px;
color: rgba(255, 255, 255, 1); color: rgba(255, 255, 255, 1);
...@@ -237,6 +265,7 @@ onUnmounted(() => { ...@@ -237,6 +265,7 @@ onUnmounted(() => {
} }
} }
} }
.tab-box { .tab-box {
width: 1600px; width: 1600px;
height: 64px; height: 64px;
...@@ -249,6 +278,7 @@ onUnmounted(() => { ...@@ -249,6 +278,7 @@ onUnmounted(() => {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.tab { .tab {
width: 526px; width: 526px;
height: 54px; height: 54px;
...@@ -263,10 +293,12 @@ onUnmounted(() => { ...@@ -263,10 +293,12 @@ onUnmounted(() => {
font-weight: 400; font-weight: 400;
letter-spacing: 0px; letter-spacing: 0px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: rgba(231, 243, 255, 1); background: rgba(231, 243, 255, 1);
} }
} }
.tabActive { .tabActive {
border: 2px solid rgba(174, 214, 255, 1); border: 2px solid rgba(174, 214, 255, 1);
background: rgba(231, 243, 255, 1); background: rgba(231, 243, 255, 1);
...@@ -275,6 +307,7 @@ onUnmounted(() => { ...@@ -275,6 +307,7 @@ onUnmounted(() => {
font-weight: 700; font-weight: 700;
} }
} }
.main { .main {
height: 800px; height: 800px;
width: 1600px; width: 1600px;
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
<el-carousel <el-carousel
ref="carouselRef" ref="carouselRef"
height="370px" height="370px"
:autoplay="true" :autoplay="false"
:interval="3000" :interval="3000"
arrow="never" arrow="never"
indicator-position="none" indicator-position="none"
...@@ -59,9 +59,7 @@ ...@@ -59,9 +59,7 @@
<el-carousel-item v-for="(item, index) in entitiesDataInfoList" :key="item.id + index"> <el-carousel-item v-for="(item, index) in entitiesDataInfoList" :key="item.id + index">
<div> <div>
<div class="box1-top"> <div class="box1-top">
<div class="box1-top-title"> <div class="box1-top-title">{{ item.postDate }}——{{ item.name }}</div>
{{ item.postDate }}——BIS《实体清单增列与修订条目》
</div>
<div class="box1-top-content"> <div class="box1-top-content">
<div class="box1-top-content-item"> <div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 发布机构:</span> <span class="box1-top-content-item-title">· 发布机构:</span>
...@@ -73,13 +71,7 @@ ...@@ -73,13 +71,7 @@
</div> </div>
<div class="box1-top-content-item"> <div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 涉及领域:</span> <span class="box1-top-content-item-title">· 涉及领域:</span>
<!-- <div
class="box1-top-content-item-tags"
v-for="(domainItem, index) in item.domains"
:key="index"
>
<el-tag :type="getTagType(domainItem)">{{ domainItem }}</el-tag>
</div> -->
<AreaTag <AreaTag
v-for="(domainItem, index) in item.domains" v-for="(domainItem, index) in item.domains"
:key="index" :key="index"
...@@ -146,7 +138,7 @@ ...@@ -146,7 +138,7 @@
}}</span> }}</span>
</div> </div>
<div class="box1-absolute-num"> <div class="box1-absolute-num">
{{ item.cnEntityCount }}{{ item.sanTypeId == allSanTypeIds[0] ? "家" : "" }} {{ item.cnEntityCount }}{{ item.sanTypeId == allSanTypeIds[0] ? "家" : "" }}
</div> </div>
</div> </div>
</div> </div>
...@@ -1961,11 +1953,12 @@ const handleMediaClick = item => { ...@@ -1961,11 +1953,12 @@ const handleMediaClick = item => {
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
position: relative; position: relative;
width: 1036px;
.box1-left-arrow { .box1-left-arrow {
position: absolute; position: absolute;
z-index: 9999; z-index: 9999;
left: -20px; left: -24px;
top: 135px; top: 135px;
width: 24px !important; width: 24px !important;
height: 48px; height: 48px;
...@@ -1989,7 +1982,7 @@ const handleMediaClick = item => { ...@@ -1989,7 +1982,7 @@ const handleMediaClick = item => {
.box1-right-arrow { .box1-right-arrow {
position: absolute; position: absolute;
z-index: 9999; z-index: 9999;
right: -20px; right: 0px;
top: 135px; top: 135px;
width: 24px; width: 24px;
height: 48px; height: 48px;
...@@ -2053,6 +2046,10 @@ const handleMediaClick = item => { ...@@ -2053,6 +2046,10 @@ const handleMediaClick = item => {
color: $base-color; color: $base-color;
margin-top: 10px; margin-top: 10px;
margin-bottom: 15px; margin-bottom: 15px;
max-width: 80%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
&-content { &-content {
...@@ -2079,7 +2076,7 @@ const handleMediaClick = item => { ...@@ -2079,7 +2076,7 @@ const handleMediaClick = item => {
height: 172px; height: 172px;
padding-top: 16px; padding-top: 16px;
box-sizing: border-box; box-sizing: border-box;
padding-right: 24px;
&-title { &-title {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
......
...@@ -60,9 +60,10 @@ ...@@ -60,9 +60,10 @@
<div> <div>
<div class="box1-top"> <div class="box1-top">
<div class="box1-top-title"> <div class="box1-top-title">
{{ item.postDate }}——{{ <!-- {{ item.postDate }}——{{
item.sanTypeId == allSanTypeIds[0] ? "OFAC" : "DoD" item.sanTypeId == allSanTypeIds[0] ? "OFAC" : "DoD"
}}《实体清单增列与修订条目》 }}《实体清单增列与修订条目》 -->
{{ item.postDate }}——{{ item.name }}
</div> </div>
<div class="box1-top-content"> <div class="box1-top-content">
<div class="box1-top-content-item"> <div class="box1-top-content-item">
...@@ -1073,7 +1074,7 @@ const handleToEntityList = item => { ...@@ -1073,7 +1074,7 @@ const handleToEntityList = item => {
console.log("这是什么数据1 =>", item); console.log("这是什么数据1 =>", item);
let id = item?.id; let id = item?.id;
let sanTypeId = item?.sanTypeId || 1; let sanTypeId = item?.sanTypeId || 1;
let date = entitiesDataInfoList.value[currentCarouselIndex.value].postDate let date = entitiesDataInfoList.value[currentCarouselIndex.value].postDate;
if (!id) { if (!id) {
const currentItem = entitiesDataInfoList.value[currentCarouselIndex.value]; const currentItem = entitiesDataInfoList.value[currentCarouselIndex.value];
id = currentItem?.id; id = currentItem?.id;
...@@ -1826,14 +1827,20 @@ const handleToDataLibrary = item => { ...@@ -1826,14 +1827,20 @@ const handleToDataLibrary = item => {
onMounted(async () => { onMounted(async () => {
console.log("finance 页面 mounted"); console.log("finance 页面 mounted");
try { try {
// 获取趋势图数据
fetchTrendData();
fetchRiskSignals("0109");
// 获取社交媒体信息
fetchSocialMediaInfo();
// 获取新闻资讯
fetchNewsInfo();
const [dataCount, entitiesDataInfo, industryCountByYear, cclList] = await Promise.all([ const [dataCount, entitiesDataInfo, industryCountByYear, cclList] = await Promise.all([
getDataCount(), getDataCount(),
getLatestEntityListInfo(), getLatestEntityListInfo(),
getReleaseCount(2) getReleaseCount(2)
// getReleaseCount(3) // getReleaseCount(3)
]); ]);
// 交换第二个和第三个元素
// [dataCount[1], dataCount[2]] = [dataCount[2], dataCount[1]];
console.log("dataCount", dataCount); console.log("dataCount", dataCount);
infoList.value = dataCount.slice(0, 2).map((item, idx) => { infoList.value = dataCount.slice(0, 2).map((item, idx) => {
return { return {
...@@ -1874,6 +1881,11 @@ onMounted(async () => { ...@@ -1874,6 +1881,11 @@ onMounted(async () => {
tags: item.domain tags: item.domain
}; };
}); });
await fetchSanctionProcess(sanctionPage.value, 10);
// 获取雷达图数据
await fetchRadarData(domainChecked.value);
// 获取出口管制制裁措施
await fetchSanctionList();
entityListReleaseFreqChart.interpret({ entityListReleaseFreqChart.interpret({
type: "柱状图", type: "柱状图",
name: "美国商务部发布实体清单的频次", name: "美国商务部发布实体清单的频次",
...@@ -1892,21 +1904,6 @@ onMounted(async () => { ...@@ -1892,21 +1904,6 @@ onMounted(async () => {
name: "美国商务部发布商业管制清单的频次", name: "美国商务部发布商业管制清单的频次",
data: commerceControlListReleaseFreq.value data: commerceControlListReleaseFreq.value
}); });
// 获取趋势图数据
fetchTrendData();
fetchRiskSignals("0109");
// 获取社交媒体信息
fetchSocialMediaInfo();
// 获取新闻资讯
fetchNewsInfo();
// fetchEntitiesList(currentPage.value, pageSize.value);
await fetchSanctionProcess(sanctionPage.value, 10);
// 获取雷达图数据
await fetchRadarData(domainChecked.value);
// 获取出口管制制裁措施
await fetchSanctionList();
} catch (err) { } catch (err) {
console.log("此处报错?"); console.log("此处报错?");
console.log(err); console.log(err);
...@@ -1957,11 +1954,12 @@ const handleMediaClick = item => { ...@@ -1957,11 +1954,12 @@ const handleMediaClick = item => {
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
position: relative; position: relative;
width: 1036px;
.box1-left-arrow { .box1-left-arrow {
position: absolute; position: absolute;
z-index: 9999; z-index: 9999;
left: -20px; left: -24px;
top: 135px; top: 135px;
width: 24px !important; width: 24px !important;
height: 48px; height: 48px;
...@@ -1985,7 +1983,7 @@ const handleMediaClick = item => { ...@@ -1985,7 +1983,7 @@ const handleMediaClick = item => {
.box1-right-arrow { .box1-right-arrow {
position: absolute; position: absolute;
z-index: 9999; z-index: 9999;
right: -20px; right: 0px;
top: 135px; top: 135px;
width: 24px; width: 24px;
height: 48px; height: 48px;
...@@ -2050,6 +2048,10 @@ const handleMediaClick = item => { ...@@ -2050,6 +2048,10 @@ const handleMediaClick = item => {
color: $base-color; color: $base-color;
margin-top: 10px; margin-top: 10px;
margin-bottom: 15px; margin-bottom: 15px;
max-width: 80%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
&-content { &-content {
......
import { ref } from "vue";
export const useChartInterpretation = () => {
const loading = ref(false);
const interpretation = ref("");
const error = ref(null);
const interpret = async text => {
loading.value = true;
error.value = null;
interpretation.value = "";
try {
const response = await fetch("/aiAnalysis/chart_interpretation", {
method: "POST",
headers: {
"X-API-Key": "aircasKEY19491001",
"Content-Type": "application/json"
},
body: JSON.stringify({ text })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (line.startsWith("data: ")) {
const content = line.substring(6);
const textMatch = content.match(/"解读":\s*"([^"]*)"/);
if (textMatch && textMatch[1]) {
interpretation.value = textMatch[1];
}
}
}
}
} catch (err) {
error.value = err.message || "AI 解读失败";
console.error("AI Chart Interpretation Error:", err);
} finally {
loading.value = false;
}
};
return {
loading,
interpretation,
error,
interpret
};
};
// src/views/finance/utils/common.js
import { ref } from "vue"; import { ref } from "vue";
// 【新增】引入获取全局 Signal 的方法
import { getAbortSignal } from "@/api/finance/service.js";
export const useChartInterpretation = () => { export const useChartInterpretation = () => {
const loading = ref(false); const loading = ref(false);
...@@ -10,14 +14,20 @@ export const useChartInterpretation = () => { ...@@ -10,14 +14,20 @@ export const useChartInterpretation = () => {
error.value = null; error.value = null;
interpretation.value = ""; interpretation.value = "";
// 【新增】在请求发起前获取当前的 Signal
// 注意:必须在每次调用 interpret 时重新获取,以确保拿到最新的 controller 的 signal
const signal = getAbortSignal();
try { try {
// 【修改】在 fetch 中传入 signal
const response = await fetch("/aiAnalysis/chart_interpretation", { const response = await fetch("/aiAnalysis/chart_interpretation", {
method: "POST", method: "POST",
headers: { headers: {
"X-API-Key": "aircasKEY19491001", "X-API-Key": "aircasKEY19491001",
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
body: JSON.stringify({ text }) body: JSON.stringify({ text }),
signal: signal // 【关键】绑定取消信号
}); });
if (!response.ok) { if (!response.ok) {
...@@ -29,6 +39,7 @@ export const useChartInterpretation = () => { ...@@ -29,6 +39,7 @@ export const useChartInterpretation = () => {
let buffer = ""; let buffer = "";
while (true) { while (true) {
// reader.read() 会在 signal abort 时抛出 AbortError
const { done, value } = await reader.read(); const { done, value } = await reader.read();
if (done) break; if (done) break;
...@@ -39,18 +50,43 @@ export const useChartInterpretation = () => { ...@@ -39,18 +50,43 @@ export const useChartInterpretation = () => {
for (const line of lines) { for (const line of lines) {
if (line.startsWith("data: ")) { if (line.startsWith("data: ")) {
const content = line.substring(6); const content = line.substring(6);
const textMatch = content.match(/"解读":\s*"([^"]*)"/); // 尝试解析 JSON
if (textMatch && textMatch[1]) { try {
interpretation.value = textMatch[1]; const jsonMatch = content.match(/\{.*\}/);
} if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[0]);
if (parsed["解读"]) {
interpretation.value = parsed["解读"];
}
} else {
// 兼容旧的正则匹配
const textMatch = content.match(/"解读":\s*"([^"]*)"/);
if (textMatch && textMatch[1]) {
interpretation.value = textMatch[1];
}
}
} catch (e) {
// 忽略解析错误
}
} }
} }
} }
} catch (err) { } catch (err) {
// 【关键】判断是否是因路由切换导致的取消
if (err.name === 'AbortError') {
console.log('AI 解读请求已取消');
loading.value = false; // 关闭 loading
return; // 直接返回,不设置 error,不弹窗
}
error.value = err.message || "AI 解读失败"; error.value = err.message || "AI 解读失败";
console.error("AI Chart Interpretation Error:", err); console.error("AI Chart Interpretation Error:", err);
} finally { } finally {
loading.value = false; // 只有在非 AbortError 的情况下,才由 finally 统一关闭 loading
// 如果上面 catch 中已经 return 了,这里不会执行
if (err?.name !== 'AbortError') {
loading.value = false;
}
} }
}; };
...@@ -60,4 +96,4 @@ export const useChartInterpretation = () => { ...@@ -60,4 +96,4 @@ export const useChartInterpretation = () => {
error, error,
interpret interpret
}; };
}; };
\ No newline at end of file
...@@ -2029,6 +2029,7 @@ onMounted(async () => { ...@@ -2029,6 +2029,7 @@ onMounted(async () => {
font-family: Source Han Sans CN; font-family: Source Han Sans CN;
font-size: 16px; font-size: 16px;
color: var(--text-primary-65-color); color: var(--text-primary-65-color);
font-weight: 400;
} }
} }
} }
......
<template> <template>
<el-space direction="vertical" class="full-width news-image-background"> <el-space direction="vertical" class="full-width news-image-background">
<el-button class="float-btn" @click="gotoNewsBrief(false)"><el-icon> <el-button class="float-btn" @click="gotoNewsBrief(false)"
<back /> ><el-icon>
</el-icon> 返回</el-button> <Back />
<el-space style="width: 993px;" direction="vertical" alignment="flex-start"> </el-icon>
<div id="ref-news-list" style="margin-top: 50px; margin-bottom: 24px; margin-left: 24px;"> 返回</el-button
<common-text class="text-title-0-show" color="var(--text-primary-90-color)">{{ >
moduleName <el-space style="width: 993px" direction="vertical" alignment="flex-start">
}}</common-text> <div id="ref-news-list" style="margin-top: 50px; margin-bottom: 24px; margin-left: 24px">
<common-text class="text-regular" <common-text class="text-title-0-show" color="var(--text-primary-90-color)">{{ moduleName }}</common-text>
color="var(--text-primary-65-color)">基于情报价值评估预测算法,掌握全球重要潜在动向</common-text> <common-text class="text-regular" color="var(--text-primary-65-color)"
</div> >基于情报价值评估预测算法,掌握全球重要潜在动向</common-text
<el-space direction="vertical" fill alignment="flex-start" class="background-as-card common-padding"> >
<el-radio-group v-model="currentAreaId" class="radio-group-as-gap-btn"> </div>
<el-space direction="horizontal" wrap alignment="center"> <el-space direction="vertical" fill alignment="flex-start" class="background-as-card common-padding">
<el-radio-button :label="''" @click="changeArea('')">全部</el-radio-button> <el-radio-group v-model="currentAreaId" class="radio-group-as-gap-btn">
<el-radio-button v-for="(t, i) in AreaList" :key="i" :label="t.id" @click="changeArea(t.id)">{{ <el-space direction="horizontal" wrap alignment="center">
t.name }} <el-radio-button :label="''" @click="changeArea('')">全部</el-radio-button>
</el-radio-button> <el-radio-button v-for="(t, i) in AreaList" :key="i" :label="t.id" @click="changeArea(t.id)"
</el-space> >{{ t.name }}
</el-radio-group> </el-radio-button>
<el-divider style="margin: 10px 0px;"></el-divider> </el-space>
<div v-if="NewsData?.content?.length > 0"> </el-radio-group>
<news-list :news="NewsData.content" /> <el-divider style="margin: 10px 0px"></el-divider>
</div> <div v-if="NewsData?.content?.length > 0">
<el-empty v-else></el-empty> <news-list :news="NewsData.content" />
<el-pagination background layout="total, ->, prev, pager, next" :current-page="modulePage" </div>
:total="NewsData?.totalElements ?? 0" v-on:current-change="onCurrentChange" /> <el-empty v-else></el-empty>
</el-space> <el-pagination
</el-space> background
</el-space> layout="total, ->, prev, pager, next"
:current-page="modulePage"
:total="NewsData?.totalElements ?? 0"
v-on:current-change="onCurrentChange"
/>
</el-space>
</el-space>
</el-space>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { useRoute } from 'vue-router'; import { useRoute } from "vue-router";
import '@/styles/container.scss'; import "@/styles/container.scss";
import '@/styles/radio.scss'; import "@/styles/radio.scss";
import NewsList from "./NewsList.vue"; import NewsList from "./NewsList.vue";
import { getAreaList, getHotNewsByArea } from "@/api/news/newsBrief"; import { getAreaList, getHotNewsByArea } from "@/api/news/newsBrief";
import { ElSpace, ElDivider, ElRadioButton, ElRadioGroup, ElButton, ElIcon, ElEmpty, ElPagination } from "element-plus"; import { ElSpace, ElDivider, ElRadioButton, ElRadioGroup, ElButton, ElIcon, ElEmpty, ElPagination } from "element-plus";
import { Back } from "@element-plus/icons-vue";
import CommonText from "@/components/base/texts/CommonText.vue"; import CommonText from "@/components/base/texts/CommonText.vue";
import { useGotoNewsBrief } from "@/router/modules/news"; import { useGotoNewsBrief } from "@/router/modules/news";
import { scrollToElement } from "@/router/common"; import { scrollToElement } from "@/router/common";
import { number } from "echarts";
const route = useRoute(); const route = useRoute();
const gotoNewsBrief = useGotoNewsBrief(); const gotoNewsBrief = useGotoNewsBrief();
const moduleId = ref(route.params.id); const moduleId = ref(route.params.id);
...@@ -54,52 +61,50 @@ const AreaList = ref([]); ...@@ -54,52 +61,50 @@ const AreaList = ref([]);
const currentAreaId = ref(""); const currentAreaId = ref("");
onMounted(async () => { onMounted(async () => {
console.log(route.query.name, moduleId.value, moduleName.value); console.log(route.query.name, moduleId.value, moduleName.value);
const { data: areaList } = await getAreaList(); const { data: areaList } = await getAreaList();
AreaList.value = areaList ?? []; AreaList.value = areaList ?? [];
await updateDate("") await updateDate("");
}); });
const onCurrentChange = async e => { const onCurrentChange = async e => {
await updateDate(currentAreaId.value, e - 1) await updateDate(currentAreaId.value, e - 1);
scrollToElement("ref-news-list"); scrollToElement("ref-news-list");
} };
async function updateDate(id, page = 0) { async function updateDate(id, page = 0) {
const { data } = await getHotNewsByArea({ const { data } = await getHotNewsByArea({
moduleId: moduleId.value, moduleId: moduleId.value,
industryId: id ? id : null, industryId: id ? id : null,
currentPage: page, currentPage: page
}); });
data?.content?.forEach(item => { data?.content?.forEach(item => {
item.newsImage = item.coverUrl ?? "" item.newsImage = item.coverUrl ?? "";
}) });
NewsData.value = data ?? []; NewsData.value = data ?? [];
modulePage.value = (data?.number ?? 0) + 1; modulePage.value = (data?.number ?? 0) + 1;
} }
async function changeArea(id) { async function changeArea(id) {
await updateDate(id, 0) await updateDate(id, 0);
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@use '@/styles/common.scss' as *; @use "@/styles/common.scss" as *;
@import url("./style.css"); @import url("./style.css");
.float-btn { .float-btn {
position: absolute; position: absolute;
top: 24px; top: 24px;
left: 40px; left: 40px;
width: 92px; width: 92px;
height: 40px; height: 40px;
display: flex; display: flex;
gap: 4px; gap: 4px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(255, 255, 255, 1); border: 1px solid rgba(255, 255, 255, 1);
border-radius: 32px; border-radius: 32px;
@extend .text-regular; @extend .text-regular;
color: var(--text-primary-65-color); color: var(--text-primary-65-color);
} }
</style> </style>
\ No newline at end of file
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论