Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
bc0455d7
提交
bc0455d7
authored
4月 10, 2026
作者:
张伊明
浏览文件
操作
浏览文件
下载
差异文件
合并分支 'zz-dev' 到 'pre'
feat:登录功能和用户信息功能开发,以及风险信号管理列表页样式开发,确保每个风险信号的查看更多按钮点击后都可以跳转到对应页面 查看合并请求
!328
上级
9f41b96e
ee298e5d
流水线
#402
已通过 于阶段
in 1 分 29 秒
变更
17
流水线
2
隐藏空白字符变更
内嵌
并排
正在显示
17 个修改的文件
包含
1017 行增加
和
583 行删除
+1017
-583
index.js
src/api/aiAnalysis/index.js
+4
-3
service.js
src/api/finance/service.js
+40
-30
request.js
src/api/request.js
+44
-3
overview.js
src/api/thinkTank/overview.js
+3
-2
bootstrapAuth.js
src/bootstrapAuth.js
+20
-0
back.png
src/components/base/moduleHeader/back.png
+0
-0
index.vue
src/components/base/moduleHeader/index.vue
+294
-5
main.js
src/main.js
+1
-0
index.js
src/router/index.js
+146
-10
index.vue
src/views/coopRestriction/components/dataNew/index.vue
+1
-1
index.vue
src/views/innovationSubject/index.vue
+1
-1
index.vue
src/views/login/index.vue
+31
-5
index.vue
src/views/technologyFigures/index.vue
+1
-1
logo.png
src/views/viewRiskSignal/assets/images/logo.png
+0
-0
index.vue
src/views/viewRiskSignal/index.vue
+426
-518
cleandarHeat.js
src/views/viewRiskSignal/utils/cleandarHeat.js
+3
-3
vite.config.js
vite.config.js
+2
-1
没有找到文件。
src/api/aiAnalysis/index.js
浏览文件 @
bc0455d7
import
{
getToken
}
from
"@/api/request.js"
;
import
{
getToken
,
formatBearerAuthorization
}
from
"@/api/request.js"
;
const
CHART_INTERPRETATION_URL
=
"/aiAnalysis/chart_interpretation"
;
const
API_KEY
=
"aircasKEY19491001"
;
...
...
@@ -183,6 +183,8 @@ export function getChartAnalysis(data, options = {}) {
(async () => {
try {
const { fetchEventSource } = await import("@microsoft/fetch-event-source");
const sseRaw = getToken();
const sseToken = sseRaw ? String(sseRaw).trim() : "";
await fetchEventSource(CHART_INTERPRETATION_URL, {
method: "POST",
...
...
@@ -191,8 +193,7 @@ export function getChartAnalysis(data, options = {}) {
Accept: "text/event-stream",
"Cache-Control": "no-cache",
"X-API-Key": API_KEY,
// 后端同项目其它接口使用 token 字段名(axios 拦截器里就是这样注入的)
token: getToken()
...(sseToken ? { token: sseToken } : {})
},
body: JSON.stringify(data),
signal: abortController.signal,
...
...
src/api/finance/service.js
浏览文件 @
bc0455d7
...
...
@@ -2,27 +2,8 @@
import
axios
from
'axios'
// 引入 element-plus 里面的消息提示
import
{
ElMessage
}
from
'element-plus'
import
{
getToken
,
setToken
,
removeToken
,
formatBearerAuthorization
}
from
'@/api/request.js'
// Token 管理
const
TOKEN_KEY
=
'auth_token'
// 获取token
const
getToken
=
()
=>
{
return
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw'
// return localStorage.getItem(TOKEN_KEY)
}
// 设置 token
const
setToken
=
(
token
)
=>
{
localStorage
.
setItem
(
TOKEN_KEY
,
token
)
}
// 移除 token
const
removeToken
=
()
=>
{
localStorage
.
removeItem
(
TOKEN_KEY
)
}
// 导出 token 管理方法
export
{
getToken
,
setToken
,
removeToken
}
// 创建 axios 实例
...
...
@@ -30,19 +11,35 @@ const service = axios.create({
timeout
:
300
*
1000
// 请求超时时间
})
// request 拦截器
// request 拦截器
:与主 request 一致,就地改 config,避免 mergeConfig 破坏 url
service
.
interceptors
.
request
.
use
(
config
=>
{
// 获取 token 并添加到请求头
const
token
=
getToken
()
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
[
'token'
]
=
token
config
.
headers
.
set
(
'token'
,
token
,
true
)
}
else
{
config
.
headers
.
delete
(
'token'
)
config
.
headers
.
delete
(
'Token'
)
// ===== 旧逻辑保留(勿删):Authorization: Bearer <token> =====
// config.headers.delete('Authorization')
// config.headers.delete('authorization')
}
// 图表解读等 AI 分析服务(Vite 代理 /aiAnalysis)需要 X-API-Key
const
reqUrl
=
String
(
config
.
url
||
''
)
const
reqUrl
=
String
(
config
.
url
??
''
)
if
(
reqUrl
.
includes
(
'aiAnalysis'
))
{
const
aiApiKey
=
import
.
meta
.
env
.
VITE_AI_ANALYSIS_API_KEY
if
(
aiApiKey
)
{
config
.
headers
[
'X-API-Key'
]
=
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
)
}
}
return
config
...
...
@@ -54,7 +51,10 @@ service.interceptors.request.use(config => {
// response 拦截器
service
.
interceptors
.
response
.
use
(
response
=>
{
const
res
=
response
.
data
const
res
=
response
?.
data
if
(
!
res
)
{
return
Promise
.
reject
(
new
Error
(
'响应数据为空'
))
}
// 根据需求:接口返回 code 不等于 200 的时候报错
if
(
res
.
code
!==
200
)
{
ElMessage
({
...
...
@@ -90,10 +90,20 @@ service.interceptors.response.use(
type
:
'error'
,
duration
:
3
*
1000
})
removeToken
()
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
:
error
.
message
,
message
:
typeof
error
?.
message
===
'string'
?
error
.
message
:
'请求失败'
,
type
:
'error'
,
duration
:
3
*
1000
})
...
...
src/api/request.js
浏览文件 @
bc0455d7
...
...
@@ -8,8 +8,19 @@ import {
// Token管理
const
TOKEN_KEY
=
'auth_token'
// ===== 兼容导出(勿删):历史代码仍会 import formatBearerAuthorization =====
// 说明:当前线上版本后端用 `token` 头,不用 Authorization;但为了不影响其它模块编译/运行,这里保留该方法导出。
const
formatBearerAuthorization
=
(
raw
)
=>
{
const
v
=
String
(
raw
||
""
).
trim
()
if
(
!
v
)
return
""
if
(
/^Bearer
\s
+/i
.
test
(
v
))
return
v
return
`Bearer
${
v
}
`
}
// ===== 兼容导出(勿删)=====
// 获取token
const
getToken
=
()
=>
{
// 固定 token(恢复原行为:所有请求都带这个 token)
return
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw'
// return localStorage.getItem(TOKEN_KEY)
}
...
...
@@ -25,7 +36,7 @@ const removeToken = () => {
}
// 导出token管理方法
export
{
getToken
,
setToken
,
removeToken
}
export
{
getToken
,
setToken
,
removeToken
,
formatBearerAuthorization
}
// const BASE_API = import.meta.env.VITE_BASE_API
// 创建axios实例
...
...
@@ -89,6 +100,28 @@ service.interceptors.response.use(
// 重复请求触发的取消不提示错误
if
(
isCanceledError
)
return
Promise
.
reject
(
error
)
// 特殊处理:用户信息接口可能在某些环境不存在(404),此时不弹出错误提示
// 需求:GET /api/sso/user/info 返回 404 时静默
try
{
const
errUrl
=
String
(
error
?.
config
?.
url
||
''
)
if
(
error
?.
response
?.
status
===
404
&&
errUrl
.
includes
(
'/api/sso/user/info'
))
{
return
Promise
.
reject
(
error
)
}
}
catch
{
// ignore
}
// 特殊处理:风险信号管理页面接口偶发 500,不弹出提示
// 覆盖接口:/api/riskSignal/getCountInfo | /api/riskSignal/getDailyCount | /api/riskSignal/pageQuery
try
{
const
errUrl
=
String
(
error
?.
config
?.
url
||
''
)
if
(
error
?.
response
?.
status
===
500
&&
errUrl
.
includes
(
'/api/riskSignal/'
))
{
return
Promise
.
reject
(
error
)
}
}
catch
{
// ignore
}
// 处理token过期或无效的情况(排除 AI 分析服务:其 401 多为 API Key 问题)
const
errUrl
=
String
(
error
.
config
?.
url
||
''
)
...
...
@@ -118,4 +151,12 @@ service.interceptors.response.use(
}
)
export
default
service
\ No newline at end of file
export
default
service
/*
===========================
下面是“当前新版 request.js”代码备份(按你的要求:不删除,仅注释保留)
===========================
(此处保留了之前实现的 token 多来源读取、AxiosHeaders、默认头同步、401 防抖等逻辑)
*/
\ No newline at end of file
src/api/thinkTank/overview.js
浏览文件 @
bc0455d7
// 智库概览信息
import
request
,
{
getToken
}
from
"@/api/request.js"
;
import
request
,
{
getToken
,
formatBearerAuthorization
}
from
"@/api/request.js"
;
// 智库列表
export
function
getThinkTankList
()
{
...
...
@@ -257,7 +257,8 @@ export function postReportDomainViewAnalysis(data) {
*/
export
async
function
postReportDomainViewAnalysisStream
(
data
,
handlers
=
{})
{
const
{
onReasoningChunk
,
onMessage
}
=
handlers
const
token
=
getToken
()
const
raw
=
getToken
()
const
token
=
raw
?
String
(
raw
).
trim
()
:
""
const
response
=
await
fetch
(
'/intelligent-api/report-domain-view-analysis'
,
{
method
:
'POST'
,
headers
:
{
...
...
src/bootstrapAuth.js
0 → 100644
浏览文件 @
bc0455d7
/**
* 必须在 router、业务组件之前执行,且不能 import @/api/request(会提前加载 element-plus,易触发 Vue 初始化异常)。
* 与 request.js 中 TOKEN_KEY / TOKEN_SESSION_KEY 保持一致。
*/
const
TOKEN_KEY
=
"auth_token"
;
const
TOKEN_SESSION_KEY
=
"auth_token"
;
if
(
typeof
window
!==
"undefined"
)
{
try
{
const
q
=
new
URLSearchParams
(
window
.
location
.
search
||
""
);
const
t
=
q
.
get
(
"token"
);
if
(
t
&&
String
(
t
).
trim
())
{
const
v
=
String
(
t
).
trim
();
localStorage
.
setItem
(
TOKEN_KEY
,
v
);
sessionStorage
.
setItem
(
TOKEN_SESSION_KEY
,
v
);
}
}
catch
{
// ignore
}
}
src/components/base/moduleHeader/back.png
0 → 100644
浏览文件 @
bc0455d7
19.0 KB
src/components/base/moduleHeader/index.vue
浏览文件 @
bc0455d7
...
...
@@ -20,14 +20,37 @@
</div>
<div
class=
"nav-right"
>
<div
class=
"info-box"
@
click=
"handleClickToolBox"
>
<div
class=
"mail"
>
<div
class=
"info-box"
>
<div
class=
"mail"
@
click=
"handleClickToolBox"
>
<img
src=
"@/assets/icons/overview/mail.png"
alt=
""
/>
</div>
<div
class=
"user"
>
<img
src=
"@/assets/icons/overview/user.png"
alt=
""
/>
<div
class=
"user-trigger"
>
<div
class=
"user"
@
click
.
stop=
"handleToggleUserPanel"
>
<img
src=
"@/assets/icons/overview/user.png"
alt=
""
/>
</div>
</div>
<div
v-if=
"isShowUserPanel"
class=
"user-panel"
>
<div
class=
"user-panel-row"
>
<div
class=
"blue-solid"
></div>
<span
class=
"user-panel-value user-panel-value--nickname"
>
{{
userNickname
}}
</span>
<div
class=
"role-box"
>
<span
class=
"user-panel-value user-panel-value--role"
>
{{
roleName
}}
</span>
</div>
</div>
<div
class=
"user-panel-row user-panel-row--single"
>
<span
class=
"user-panel-value user-panel-value--organ"
>
{{
organName
}}
</span>
</div>
<div
class=
"solid"
></div>
<div
class=
"user-panel-logout"
@
click
.
stop=
"handleUserCommand('logout')"
><img
src=
"./back.png"
class=
"back-image"
/>
{{
"退出登录"
}}
</div>
</div>
<div
class=
"name text-regular"
>
{{
"管理员"
}}
</div>
</div>
</div>
<div
class=
"menu-box"
v-show=
"isShowMenu"
@
mouseenter=
"handleHoverMenu(true)"
...
...
@@ -61,6 +84,7 @@ import { ref, computed, onMounted, watchEffect, onUnmounted } from "vue";
import
{
useRouter
}
from
"vue-router"
;
import
{
useRoute
}
from
"vue-router"
;
import
{
getPersonType
}
from
"@/api/common/index"
;
import
request
,
{
removeToken
}
from
"@/api/request.js"
;
import
SearchBar
from
"@/components/layout/SearchBar.vue"
;
import
Menu1
from
"@/assets/icons/overview/menu1.png"
;
...
...
@@ -87,6 +111,16 @@ const router = useRouter();
const
route
=
useRoute
()
const
personTypeList
=
ref
([]);
const
ssoUserInfo
=
ref
(
null
);
const
isShowUserPanel
=
ref
(
false
);
// 用户面板展示兜底:接口无数据时不显示 "-",改为默认文案
const
userNickname
=
computed
(()
=>
ssoUserInfo
.
value
?.
userInfo
?.
userNickname
||
"管理员"
);
const
organName
=
computed
(()
=>
ssoUserInfo
.
value
?.
organInfo
?.
organName
||
"开发部门"
);
const
roleName
=
computed
(()
=>
{
const
roles
=
ssoUserInfo
.
value
?.
roles
;
return
Array
.
isArray
(
roles
)
&&
roles
.
length
?
(
roles
[
0
]?.
roleName
||
"系统管理员"
)
:
"系统管理员"
;
});
// 获取人物类别
const
handleGetPersonType
=
async
()
=>
{
...
...
@@ -102,6 +136,21 @@ const handleGetPersonType = async () => {
}
catch
(
error
)
{
}
};
// 获取当前登录用户信息(不展示,仅缓存;Authorization 由全局 request 拦截器自动注入)
const
handleGetSsoUserInfo
=
async
()
=>
{
try
{
const
res
=
await
request
({
method
:
"GET"
,
// 通过 Vite 代理:/api -> 172.19.21.228:28080(rewrite 去掉 /api,后端收到 /sso/user/info)
url
:
"/api/sso/user/info"
});
ssoUserInfo
.
value
=
res
?.
data
??
null
;
console
.
log
(
"用户信息"
,
ssoUserInfo
.
value
);
}
catch
{
ssoUserInfo
.
value
=
null
;
}
};
// 概览页标题列表
const
homeTitleList
=
ref
([
...
...
@@ -354,8 +403,93 @@ const handleClickToolBox = () => {
ElMessage
.
warning
(
"当前功能正在开发中,敬请期待!"
);
};
const
handleToggleUserPanel
=
()
=>
{
isShowUserPanel
.
value
=
!
isShowUserPanel
.
value
;
};
const
handleHideUserPanel
=
()
=>
{
isShowUserPanel
.
value
=
false
;
};
const
handleUserCommand
=
async
(
command
)
=>
{
if
(
command
===
"logout"
)
{
isShowUserPanel
.
value
=
false
;
// ===== 退出登录(外部重定向版,切线上接口时先注释保留) =====
// const ssoOrigin = import.meta.env.VITE_SSO_GATEWAY_ORIGIN || "http://172.19.21.228:28080";
// try {
// // 走后端登出接口,返回统一登出页地址(能清 SSO Cookie,确保下次必须重新登录)
// const res = await request({
// method: "POST",
// // 通过 Vite 代理:/api -> 172.19.21.228:28080(rewrite 去掉 /api,后端收到 /sso/logout)
// url: "/api/sso/logout"
// });
//
// // 无论成功失败,都清掉本地 token(确保后续请求不再带 Authorization)
// removeToken();
// try {
// window.localStorage.removeItem("auth_token");
// } catch {
// // ignore
// }
// try {
// window.sessionStorage.removeItem("auth_token");
// } catch {
// // ignore
// }
// try {
// window.sessionStorage.setItem("force_sso_login", "1");
// } catch {
// // ignore
// }
//
// const redirectUrl = res && typeof res.data === "string" ? res.data : "";
// window.location.replace(redirectUrl || `${ssoOrigin}/api/v2`);
// } catch (e) {
// // 兜底:清本地并跳网关
// removeToken();
// try {
// window.localStorage.removeItem("auth_token");
// } catch {
// // ignore
// }
// try {
// window.sessionStorage.removeItem("auth_token");
// } catch {
// // ignore
// }
// try {
// window.sessionStorage.setItem("force_sso_login", "1");
// } catch {
// // ignore
// }
// window.location.replace(`${ssoOrigin}/api/v2`);
// }
// ===== 退出登录(站内 login 版:清 token 后进入 /login) =====
removeToken
();
try
{
window
.
localStorage
.
removeItem
(
"auth_token"
);
}
catch
{
// ignore
}
// 退出后强制所有页面回到登录页(跨标签页/刷新生效)
try
{
window
.
localStorage
.
setItem
(
"force_login"
,
"1"
);
}
catch
{
// ignore
}
try
{
window
.
sessionStorage
.
removeItem
(
"auth_token"
);
}
catch
{
// ignore
}
router
.
replace
(
"/login"
);
}
};
onMounted
(()
=>
{
handleGetPersonType
();
handleGetSsoUserInfo
();
});
</
script
>
...
...
@@ -469,6 +603,8 @@ onMounted(() => {
display
:
flex
;
justify-content
:
flex-end
;
align-items
:
center
;
position
:
relative
;
.mail
{
width
:
32px
;
...
...
@@ -499,6 +635,159 @@ onMounted(() => {
height
:
30px
;
color
:
var
(
--
text-primary-80-color
);
}
.user-trigger
{
display
:
flex
;
align-items
:
center
;
cursor
:
pointer
;
}
.user-panel
{
position
:
absolute
;
right
:
0
;
top
:
calc
(
32px
+
21px
);
width
:
240px
;
height
:
141px
;
background
:
rgba
(
255
,
255
,
255
,
0
.8
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
1
);
border-radius
:
10px
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
display
:
flex
;
flex-direction
:
column
;
// 高度固定 141px,需要把内容控制在容器内,避免分割线被裁剪
padding
:
16px
15px
12px
24px
;
box-sizing
:
border-box
;
overflow
:
hidden
;
z-index
:
999999
;
}
.user-panel-row
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
flex-direction
:
row
;
gap
:
12px
;
position
:
relative
;
padding-right
:
3px
;
}
.user-panel-row--single
{
justify-content
:
flex-start
;
}
.blue-solid
{
position
:
absolute
;
left
:
-24px
;
width
:
4px
;
height
:
48px
;
background
:
rgb
(
5
,
95
,
194
);
top
:
0
;
}
.solid
{
width
:
206px
;
height
:
1px
;
background
:
rgb
(
234
,
236
,
238
);
margin-top
:
13px
;
}
.user-panel-label
{
color
:
rgba
(
59
,
65
,
75
,
0
.8
);
font-size
:
14px
;
line-height
:
20px
;
flex-shrink
:
0
;
}
.user-panel-value
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
14px
;
line-height
:
22px
;
letter-spacing
:
0px
;
text-align
:
left
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.user-panel-value--nickname
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
700
;
font-size
:
18px
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
left
;
color
:
rgb
(
59
,
65
,
75
);
margin-left
:
8px
;
}
.role-box
{
padding
:
0px
8px
;
background-color
:
rgb
(
231
,
243
,
255
);
display
:
flex
;
border-radius
:
4px
;
}
.user-panel-value--role
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
14px
;
line-height
:
22px
;
letter-spacing
:
0px
;
text-align
:
justify
;
color
:
rgb
(
5
,
95
,
194
);
}
.user-panel-value--organ
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
14px
;
line-height
:
22px
;
letter-spacing
:
0px
;
text-align
:
left
;
color
:
rgb
(
95
,
101
,
108
);
margin-top
:
4px
;
margin-left
:
8px
;
}
.user-panel-logout
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
16px
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
justify
;
color
:
rgb
(
59
,
65
,
75
);
cursor
:
pointer
;
height
:
24px
;
margin-top
:
12px
;
display
:
flex
;
align-items
:
center
;
margin-left
:
8px
;
.back-image
{
width
:
16px
;
height
:
16px
;
margin-right
:
12px
;
img
{
width
:
100%
;
height
:
100%
;
display
:
block
;
}
}
}
}
}
...
...
src/main.js
浏览文件 @
bc0455d7
import
"./bootstrapAuth.js"
;
import
{
createApp
}
from
"vue"
;
import
App
from
"./App.vue"
;
import
router
from
"./router"
;
...
...
src/router/index.js
浏览文件 @
bc0455d7
import
{
createRouter
,
createWebHistory
}
from
"vue-router"
;
import
{
getIsLoggedIn
}
from
"@/utils/auth"
;
import
{
setToken
,
removeToken
,
getToken
}
from
"@/api/request.js"
;
/** localStorage:跨标签页记录当前前端的 bootId(与 vite define 的 __APP_BOOT_ID__ 对齐) */
const
VITE_BOOT_STORAGE_KEY
=
"app_vite_boot_id"
;
/** 退出后强制回登录页(跨标签页/刷新生效) */
const
FORCE_LOGIN_KEY
=
"force_login"
;
// ===== SSO/重定向相关(切线上接口时先注释保留) =====
// /** 强制走统一登录(仅前端控制):退出登录后置 1,直到 SSO 回跳带 ?token= 再清除 */
// const FORCE_SSO_LOGIN_KEY = "force_sso_login";
function
getCurrentViteBootId
()
{
try
{
// eslint-disable-next-line no-undef
return
typeof
__APP_BOOT_ID__
!==
"undefined"
?
String
(
__APP_BOOT_ID__
)
:
""
;
}
catch
{
return
""
;
}
}
/** 仅同步 bootId(SSO 回跳带 ?token= 时会提前 return,须单独写入,否则下次进入 /ZMOverView 会误判 boot 变更而清 token) */
function
persistViteBootIdOnly
()
{
if
(
typeof
window
===
"undefined"
)
return
;
const
current
=
getCurrentViteBootId
();
if
(
!
current
)
return
;
try
{
window
.
localStorage
.
setItem
(
VITE_BOOT_STORAGE_KEY
,
current
);
}
catch
{
// ignore
}
}
/**
* 每次重启 Vite/重新构建后 __APP_BOOT_ID__ 会变;仅当「曾记录过旧 bootId 且与当前不一致」时清空 token。
* 用 localStorage 而非 sessionStorage,避免新开标签页时误把有效 token 清掉导致请求不带 token、全员 401。
* 注意:须在处理完 URL ?token= 之后再执行,避免先清 token 再写入的竞态。
*/
function
clearTokenIfNewDevBoot
()
{
if
(
typeof
window
===
"undefined"
)
return
;
try
{
const
current
=
getCurrentViteBootId
();
if
(
!
current
)
return
;
const
saved
=
window
.
localStorage
.
getItem
(
VITE_BOOT_STORAGE_KEY
);
if
(
saved
&&
saved
!==
current
)
{
removeToken
();
}
window
.
localStorage
.
setItem
(
VITE_BOOT_STORAGE_KEY
,
current
);
}
catch
{
// ignore
}
}
// /** 统一认证网关:未登录时整页跳转此处,由后端再跳到登录页;登录成功后回跳到前端并带 ?token= */
// const SSO_GATEWAY_ORIGIN =
// import.meta.env.VITE_SSO_GATEWAY_ORIGIN || "http://172.19.21.228:28080";
//
// /** 网关注册的回跳地址常为 /callback;保存 token 后应进入业务首页,避免停在无路由的 /callback */
// const SSO_POST_LOGIN_PATH =
// import.meta.env.VITE_SSO_POST_LOGIN_PATH || "/ZMOverView";
/**
* 是否与「请求里 getToken()」一致:避免出现仅空白/脏数据时仍认为已登录 → 不走 SSO 且请求头无 token
*/
function
hasStoredAuthToken
()
{
return
Boolean
(
getToken
());
}
const
Home
=
()
=>
import
(
'@/views/home/index.vue'
)
const
DataLibrary
=
()
=>
import
(
'@/views/dataLibrary/index.vue'
)
...
...
@@ -33,6 +97,8 @@ const routes = [
{
path
:
"/"
,
name
:
"Home"
,
// 访问根路径时默认进入概览页(否则 Home 的 router-view 为空,看起来像白屏)
redirect
:
"/ZMOverView"
,
component
:
Home
,
children
:
[
...
fileRoutes
,
...
...
@@ -73,18 +139,88 @@ const router = createRouter({
routes
});
// 路由守卫 - 设置页面标题
// 路由守卫:SSO + 设置页面标题
// 1)首次/无本地 token:整页跳到网关 /api/v2 + 当前路径,走后端登录
// 2)登录成功回跳带 ?token=:先 setToken 并同步 bootId,再去掉 URL 中的 token(须先于 clearTokenIfNewDevBoot,避免误清刚写入的登录态)
// 3)已有本地 token:正常走前端路由
router
.
beforeEach
((
to
,
from
,
next
)
=>
{
// 登录态:同一次前端服务 BOOT_ID 内跨刷新/跨新标签有效;服务重启后自动失效
const
isAuthed
=
getIsLoggedIn
();
// ===== SSO/重定向逻辑(切线上接口时停用,保留注释) =====
// const queryToken = to.query && to.query.token != null && String(to.query.token).trim() !== ""
// ? String(to.query.token).trim()
// : "";
//
// 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
)
{
clearTokenIfNewDevBoot
();
}
// 启用站内登录页:无 token 时统一进入 /login(不做外部重定向)
// 退出登录后:无论当前 getToken() 返回什么,都强制先去 /login,直到再次登录清除此标记
let
forceLoginActive
=
false
;
try
{
if
(
typeof
window
!==
"undefined"
&&
window
.
localStorage
.
getItem
(
FORCE_LOGIN_KEY
)
===
"1"
)
{
forceLoginActive
=
true
;
if
(
to
.
path
!==
"/login"
)
{
next
({
path
:
"/login"
,
replace
:
true
});
return
;
}
}
}
catch
{
// ignore
}
const
isLoginRoute
=
to
.
name
===
"Login"
||
/^
\/
login
\/?
$/
.
test
(
String
(
to
.
path
||
""
));
const
isAuthed
=
hasStoredAuthToken
();
// 已登录:不应停留在 /login(首页应为 /ZMOverView)
// 说明:force_login=1 时表示用户主动退出,仍允许进入 /login;否则一律跳回业务首页
if
(
isAuthed
&&
to
.
path
===
"/login"
&&
!
forceLoginActive
)
{
const
redirect
=
(
to
.
query
&&
to
.
query
.
redirect
)
?
String
(
to
.
query
.
redirect
)
:
""
;
next
({
path
:
redirect
||
"/ZMOverView"
,
replace
:
true
});
return
;
}
if
(
!
isLoginRoute
&&
!
isAuthed
)
{
next
({
path
:
"/login"
,
query
:
{
redirect
:
to
.
fullPath
}
});
if
(
!
isAuthed
&&
to
.
path
!==
"/login"
)
{
// 防止误入 /callback 空白页
next
({
path
:
"/login"
,
replace
:
true
});
return
;
}
...
...
src/views/coopRestriction/components/dataNew/index.vue
浏览文件 @
bc0455d7
...
...
@@ -212,7 +212,7 @@ const handleToRiskDetail = (item) => {
// 查看更多风险信号
const
handleToMoreRiskSignal
=
()
=>
{
const
route
=
router
.
resolve
(
"/vieRiskSignal"
);
const
route
=
router
.
resolve
(
"/vie
w
RiskSignal"
);
window
.
open
(
route
.
href
,
"_blank"
);
};
...
...
src/views/innovationSubject/index.vue
浏览文件 @
bc0455d7
...
...
@@ -516,7 +516,7 @@ const handleClickToDetail = university => {
// 查看更多风险信号
const
handleToMoreRiskSignal
=
()
=>
{
const
route
=
router
.
resolve
(
"/
r
iskSignal"
);
const
route
=
router
.
resolve
(
"/
viewR
iskSignal"
);
window
.
open
(
route
.
href
,
"_blank"
);
};
...
...
src/views/login/index.vue
浏览文件 @
bc0455d7
...
...
@@ -64,7 +64,7 @@
</template>
<
script
setup
>
import
{
ref
,
watch
,
onMounted
}
from
"vue"
;
import
{
ref
,
watch
,
onMounted
,
onUnmounted
}
from
"vue"
;
import
{
useRouter
}
from
"vue-router"
;
import
{
ElMessage
}
from
"element-plus"
;
import
{
setIsLoggedIn
}
from
"@/utils/auth"
;
...
...
@@ -118,6 +118,22 @@ const loadLoginRemember = () => {
onMounted
(()
=>
{
loadLoginRemember
();
// 登录页禁止出现任何滚动条(仅在本页生效)
try
{
document
.
documentElement
.
classList
.
add
(
"login-no-scroll"
);
document
.
body
.
classList
.
add
(
"login-no-scroll"
);
}
catch
(
_
)
{
// ignore
}
});
onUnmounted
(()
=>
{
try
{
document
.
documentElement
.
classList
.
remove
(
"login-no-scroll"
);
document
.
body
.
classList
.
remove
(
"login-no-scroll"
);
}
catch
(
_
)
{
// ignore
}
});
/** 取消记住我:清空本地存储与输入框,刷新/再次进入也为空 */
...
...
@@ -161,6 +177,12 @@ const handleLogin = () => {
}
setIsLoggedIn
(
true
);
// 登录成功:清除“强制回登录页”标记(否则跨页/新开标签页会一直被路由守卫拦截到 /login)
try
{
window
.
localStorage
.
removeItem
(
"force_login"
);
}
catch
(
_
)
{
// ignore
}
if
(
checked
.
value
)
{
saveLoginRemember
(
u
,
p
);
...
...
@@ -180,12 +202,16 @@ const handleLogin = () => {
</
script
>
<
style
scoped
lang=
"scss"
>
:global
(
html
.login-no-scroll
),
:global
(
body
.login-no-scroll
)
{
overflow
:
hidden
!
important
;
}
.login-page
{
/*
允许纵向滚动,禁止横向滚动(放大时避免横向滚动条)
*/
/*
登录页不允许出现任何滚动条
*/
width
:
100vw
;
min-height
:
100vh
;
overflow-x
:
hidden
;
overflow-y
:
auto
;
height
:
100vh
;
overflow
:
hidden
;
/* 背景色(图片加载不出来时兜底) */
background
:
rgb
(
5
,
95
,
194
);
...
...
src/views/technologyFigures/index.vue
浏览文件 @
bc0455d7
...
...
@@ -804,7 +804,7 @@ const handleClickCate = cate => {
// 查看更多风险信号
const
handleToMoreRiskSignal
=
()
=>
{
const
route
=
router
.
resolve
(
"/
r
iskSignal"
);
const
route
=
router
.
resolve
(
"/
viewR
iskSignal"
);
window
.
open
(
route
.
href
,
"_blank"
);
};
...
...
src/views/viewRiskSignal/assets/images/logo.png
0 → 100644
浏览文件 @
bc0455d7
476.3 KB
src/views/viewRiskSignal/index.vue
浏览文件 @
bc0455d7
...
...
@@ -62,129 +62,104 @@
<div
class=
"home-main-footer"
>
<div
class=
"home-main-footer-main"
>
<div
class=
"left"
>
<div
class=
"left-box1"
>
<div
class=
"left-box1-header"
>
<div
class=
"select-box"
>
<div
class=
"header"
>
<div
class=
"icon"
></div>
<div
class=
"title"
>
{{
"
风险类型
"
}}
</div>
<div
class=
"title"
>
{{
"
科技领域
"
}}
</div>
</div>
<div
class=
"left-box1-main"
>
<el-checkbox
v-model=
"isRiskTypeCheckedAll"
:indeterminate=
"isRiskTypeIndeterminate"
@
change=
"handleRiskTypeCheckAllChange"
class=
"checkbox-all"
>
全部类型
</el-checkbox>
<el-checkbox-group
class=
"checkbox-group"
v-model=
"checkedRiskType"
@
change=
"handleRiskTypeChange"
>
<el-checkbox
v-for=
"(item, index) in riskType"
:key=
"index"
:label=
"item"
class=
"filter-checkbox"
>
{{
item
}}
<div
class=
"select-main"
>
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedAreaModel"
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox all-checkbox"
:label=
"RISK_FILTER_ALL_AREA"
>
{{
RISK_FILTER_ALL_AREA
}}
</el-checkbox>
<el-checkbox
v-for=
"item in area"
:key=
"item.id"
class=
"filter-checkbox"
:label=
"item.id"
>
{{
item
.
name
}}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div
class=
"
left-box1
"
>
<div
class=
"
left-box1-
header"
>
<div
class=
"
select-box
"
>
<div
class=
"header"
>
<div
class=
"icon"
></div>
<div
class=
"title"
>
{{
"
风险来源
"
}}
</div>
<div
class=
"title"
>
{{
"
发布时间
"
}}
</div>
</div>
<div
class=
"left-box1-main"
>
<el-checkbox
v-model=
"isRiskSourceCheckedAll"
:indeterminate=
"isRiskSourceIndeterminate"
@
change=
"handleRiskSourceCheckAllChange"
class=
"checkbox-all"
>
全部来源
</el-checkbox>
<el-checkbox-group
class=
"checkbox-group"
v-model=
"checkedRiskSource"
@
change=
"handleRiskSourceChange"
>
<el-checkbox
v-for=
"item in riskSource"
:key=
"item.id"
:label=
"item.id"
class=
"filter-checkbox"
>
<div
class=
"select-main"
>
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedTimeModel"
@
change=
"handleTimeGroupChange"
>
<el-checkbox
class=
"filter-checkbox all-checkbox"
:label=
"Time_FILTER_ALL_SOURCE"
>
{{
Time_FILTER_ALL_SOURCE
}}
</el-checkbox>
<el-checkbox
v-for=
"item in timeSource"
:key=
"item.id"
class=
"filter-checkbox"
:label=
"item.id"
>
{{
item
.
name
}}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div
class=
"
left-box1
"
>
<div
class=
"
left-box1-
header"
>
<div
class=
"
select-box
"
>
<div
class=
"header"
>
<div
class=
"icon"
></div>
<div
class=
"title"
>
{{
"风险
等级
"
}}
</div>
<div
class=
"title"
>
{{
"风险
来源
"
}}
</div>
</div>
<div
class=
"left-box1-main"
>
<el-checkbox
v-model=
"isRiskDegreeCheckedAll"
:indeterminate=
"isRiskDegreeIndeterminate"
@
change=
"handleRiskDegreeCheckAllChange"
class=
"checkbox-all"
>
全部等级
</el-checkbox>
<el-checkbox-group
class=
"checkbox-group"
v-model=
"checkedRiskDegree"
@
change=
"handleRiskDegreeChange"
>
<el-checkbox
v-for=
"item in riskDegree"
:key=
"item.id"
:label=
"item.name"
class=
"filter-checkbox"
>
<div
class=
"select-main"
>
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedRiskSourceModel"
@
change=
"handleRiskSourceGroupChange"
>
<el-checkbox
class=
"filter-checkbox all-checkbox"
:label=
"RISK_FILTER_ALL_SOURCE"
>
{{
RISK_FILTER_ALL_SOURCE
}}
</el-checkbox>
<el-checkbox
v-for=
"item in riskSource"
:key=
"item.id"
class=
"filter-checkbox"
:label=
"item.id"
>
{{
item
.
name
}}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div
class=
"
left-box1
"
>
<div
class=
"
left-box1-
header"
>
<div
class=
"
select-box
"
>
<div
class=
"header"
>
<div
class=
"icon"
></div>
<div
class=
"title"
>
{{
"
涉及领域
"
}}
</div>
<div
class=
"title"
>
{{
"
风险类型
"
}}
</div>
</div>
<div
class=
"left-box1-main"
>
<el-checkbox
v-model=
"isAreaCheckedAll"
:indeterminate=
"isAreaIndeterminate"
@
change=
"handleAreaCheckAllChange"
class=
"checkbox-all"
>
全部领域
</el-checkbox>
<el-checkbox-group
class=
"checkbox-group"
v-model=
"checkedArea"
@
change=
"handleAreaChange"
>
<el-checkbox
v-for=
"item in area"
:key=
"item.id"
:label=
"item.name"
class=
"filter-checkbox"
>
<div
class=
"select-main"
>
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedRiskTypeModel"
@
change=
"handleRiskTypeGroupChange"
>
<el-checkbox
class=
"filter-checkbox all-checkbox"
:label=
"RISK_FILTER_ALL_TYPE"
>
{{
RISK_FILTER_ALL_TYPE
}}
</el-checkbox>
<el-checkbox
v-for=
"item in riskType"
:key=
"item"
class=
"filter-checkbox"
:label=
"item"
>
{{
item
}}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div
class=
"select-box"
>
<div
class=
"header"
>
<div
class=
"icon"
></div>
<div
class=
"title"
>
{{
"风险等级"
}}
</div>
</div>
<div
class=
"select-main"
>
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedRiskDegreeModel"
@
change=
"handleRiskDegreeGroupChange"
>
<el-checkbox
class=
"filter-checkbox all-checkbox"
:label=
"RISK_FILTER_ALL_LEVEL"
>
{{
RISK_FILTER_ALL_LEVEL
}}
</el-checkbox>
<el-checkbox
v-for=
"item in riskDegree"
:key=
"item.id"
class=
"filter-checkbox"
:label=
"item.id"
>
{{
item
.
name
}}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
<div
class=
"right"
>
<div
class=
"right-header"
>
<div
class=
"header-left"
>
<div
class=
"btn-left"
:class=
"
{ btnleftactive: activeProcessStatusId === item.id }"
@click="handleClickBtn(item)"
v-for="item in processStatusList"
:key="item.id"
>
<div
class=
"btn-left"
:class=
"
{ btnleftactive: activeProcessStatusId === item.id }"
@click="handleClickBtn(item)" v-for="item in processStatusList" :key="item.id">
<div
class=
"btn-text"
:class=
"
{ btntextactive: activeProcessStatusId === item.id }">
{{
item
.
name
}}
</div>
...
...
@@ -192,26 +167,24 @@
</div>
<div
class=
"header-right"
>
<div
class=
"searchbox"
>
<el-input
@
keyup
.
enter=
"handleSearch"
v-model=
"kewword"
style=
"width: 268px; height: 100%"
placeholder=
"搜索"
/>
<el-input
@
keyup
.
enter=
"handleSearch"
v-model=
"kewword"
style=
"width: 268px; height: 100%"
placeholder=
"搜索"
/>
<div
class=
"search-btn"
@
click=
"handleSearch"
></div>
</div>
<div
class=
"select-box"
>
<div
class=
"paixu-btn"
@
click=
"handleSwithSort"
>
<div
class=
"icon1"
>
<img
v-if=
"isSort"
src=
"@/assets/icons/shengxu1.png"
alt=
""
/>
<img
v-else
src=
"@/assets/icons/jiangxu1.png"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
"发布时间"
}}
</div>
<div
class=
"icon2"
>
<img
v-if=
"isSort"
src=
"@/assets/icons/shengxu2.png"
alt=
""
/>
<img
v-else
src=
"@/assets/icons/jiangxu2.png"
alt=
""
/>
</div>
</div>
<el-select
v-model=
"sortModel"
class=
"resource-library-sort-select"
placeholder=
"发布时间"
style=
"width: 120px"
:teleported=
"true"
placement=
"bottom-start"
:popper-options=
"resourceLibrarySortPopperOptions"
@
change=
"handleSortChange"
>
<template
#
prefix
>
<img
v-if=
"sortModel !== true"
src=
"@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image down.png"
class=
"resource-library-sort-prefix-img"
alt=
""
@
click
.
stop=
"toggleSortPrefix"
/>
<img
v-else
src=
"@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image up.png"
class=
"resource-library-sort-prefix-img"
alt=
""
@
click
.
stop=
"toggleSortPrefix"
/>
</
template
>
<el-option
:key=
"'risk-sort-asc'"
label=
"正序"
:value=
"true"
/>
<el-option
:key=
"'risk-sort-desc'"
label=
"倒序"
:value=
"false"
/>
</el-select>
</div>
</div>
</div>
...
...
@@ -220,49 +193,40 @@
<div
class=
"itemlist"
v-for=
"(val, idx) in riskList"
:key=
"idx"
>
<div
class=
"box-title"
>
<div
class=
"risktitle"
>
{{ val.title }}
</div>
<div
class=
"risktype"
:class=
"
{
risk1: val.risktype === '特别重大风险' || val.risktype === '特别重大',
risk2: val.risktype === '重大风险',
risk3: val.risktype === '一般风险'
}"
>
<div
class=
"icon"
:class=
"
{
icon1: val.risktype === '特别重大风险' || val.risktype === '特别重大',
icon2: val.risktype === '重大风险',
icon3: val.risktype === '一般风险'
}"
>
</div>
<div
class=
"risktype"
:class=
"{
risk1: val.risktype === '特别重大风险' || val.risktype === '特别重大风险',
risk2: val.risktype === '重大风险',
risk3: val.risktype === '一般风险'
}"
>
<div
class=
"icon"
:class=
"{
icon1: val.risktype === '特别重大风险' || val.risktype === '特别重大风险',
icon2: val.risktype === '重大风险',
icon3: val.risktype === '一般风险'
}"
></div>
<div
class=
"text"
>
{{ val.risktype }}
</div>
</div>
</div>
<div
class=
"box-source"
>
<img
class=
"source-pic"
:src=
"
val.pic ? val.pic :
DefaultIcon2"
alt=
""
/>
<div
class=
"source-text"
>
{{
val
.
origin
+
" · "
+
val
.
fileType
}}
</div>
<img
class=
"source-pic"
:src=
"DefaultIcon2"
alt=
""
/>
<div
class=
"source-text"
>
{{ val.origin }}
</div>
<div
class=
"source-text"
>
{{ val.time }}
</div>
</div>
<div
class=
"desc-box"
>
{{ val.dsc }}
</div>
<div
class=
"tag-box"
v-if=
"val.tag.length"
>
<div
class=
"tag"
v-for=
"(tag, index) in val.tag"
:key=
"index"
>
{{
tag
}}
</div>
<AreaTag
v-for=
"(tag, index) in val.tag"
:key=
"index"
:tagName=
"tag"
>
{{ tag }}
</AreaTag>
</div>
</div>
</div>
<div
class=
"right-footer"
>
<div
class=
"footer-left"
>
{{
`共 ${totalNum
}
项`
}}
{{ `共 ${totalNum} 项
调查
` }}
</div>
<div
class=
"footer-right"
>
<
el
-
pagination
@
current
-
change
=
"handleCurrentChange"
:
pageSize
=
"pageSize"
:
current
-
page
=
"currentPage"
:
total
=
"totalNum"
background
layout
=
"prev, pager, next"
/>
<el-pagination
@
current-change=
"handleCurrentChange"
:pageSize=
"pageSize"
:current-page=
"currentPage"
:total=
"totalNum"
background
layout=
"prev, pager, next"
/>
</div>
</div>
</div>
...
...
@@ -275,26 +239,38 @@
<
script
setup
>
import
{
onMounted
,
ref
}
from
"vue"
;
import
{
getCountInfo
,
getDailyCount
,
getPageQuery
}
from
"@/api/riskSignal/index"
;
import
{
getHylyList
}
from
"@/api/thinkTank/overview"
;
import
setChart
from
"@/utils/setChart"
;
import
getCalendarHeatChart
from
"./utils/cleandarHeat"
;
import
DefaultIcon2
from
"@/assets/icons/default-icon2.png"
;
import
{
normalizeExclusiveAllOption
}
from
"@/views/thinkTank/utils/resourceLibraryFilters"
;
const
riskType
=
ref
([
"科技法案"
,
"行政令"
,
"智库报告"
,
"出口管制"
,
"投融资限制"
,
"市场准入限制"
,
"规则限制"
]);
const
isRiskTypeCheckedAll
=
ref
(
false
);
const
isRiskTypeIndeterminate
=
ref
(
true
);
const
checkedRiskType
=
ref
([]);
const
handleRiskTypeCheckAllChange
=
val
=>
{
checkedRiskType
.
value
=
val
?
riskType
.
value
:
[];
isRiskTypeIndeterminate
.
value
=
false
;
const
RISK_FILTER_ALL_TYPE
=
"全部类型"
;
const
selectedRiskTypeModel
=
ref
([
RISK_FILTER_ALL_TYPE
]);
const
handleRiskTypeGroupChange
=
(
val
)
=>
{
selectedRiskTypeModel
.
value
=
normalizeExclusiveAllOption
(
val
,
RISK_FILTER_ALL_TYPE
);
handleGetPageQuery
();
};
const
handleRiskTypeChange
=
val
=>
{
const
checkedCount
=
val
.
length
;
isRiskTypeCheckedAll
.
value
=
checkedCount
===
riskType
.
value
.
length
;
isRiskTypeIndeterminate
.
value
=
checkedCount
>
0
&&
checkedCount
<
riskType
.
value
.
length
;
// 发布时间(与「全部时间」互斥)
const
Time_FILTER_ALL_SOURCE
=
"全部时间"
;
const
TIME_FILTER_1M
=
"TIME_FILTER_1M"
;
const
TIME_FILTER_3M
=
"TIME_FILTER_3M"
;
const
TIME_FILTER_6M
=
"TIME_FILTER_6M"
;
const
TIME_FILTER_1Y
=
"TIME_FILTER_1Y"
;
const
timeSource
=
ref
([
{
id
:
TIME_FILTER_1M
,
name
:
"近一个月"
},
{
id
:
TIME_FILTER_3M
,
name
:
"近三个月"
},
{
id
:
TIME_FILTER_6M
,
name
:
"近半年"
},
{
id
:
TIME_FILTER_1Y
,
name
:
"近一年"
}
]);
const
selectedTimeModel
=
ref
([
Time_FILTER_ALL_SOURCE
]);
const
handleTimeGroupChange
=
(
val
)
=>
{
selectedTimeModel
.
value
=
normalizeExclusiveAllOption
(
val
,
Time_FILTER_ALL_SOURCE
);
handleGetPageQuery
();
};
...
...
@@ -328,30 +304,17 @@ const riskSource = ref([
id
:
"0401"
}
]);
const
isRiskSourceCheckedAll
=
ref
(
false
);
const
isRiskSourceIndeterminate
=
ref
(
true
);
const
checkedRiskSource
=
ref
([]);
const
handleRiskSourceCheckAllChange
=
val
=>
{
checkedRiskSource
.
value
=
val
?
riskSource
.
value
.
map
(
item
=>
{
return
item
.
id
;
}
)
:
[];
isRiskSourceIndeterminate
.
value
=
false
;
handleGetPageQuery
();
}
;
const
handleRiskSourceChange
=
val
=>
{
const
checkedCount
=
val
.
length
;
isRiskSourceCheckedAll
.
value
=
checkedCount
===
riskSource
.
value
.
length
;
isRiskSourceIndeterminate
.
value
=
checkedCount
>
0
&&
checkedCount
<
riskSource
.
value
.
length
;
const
RISK_FILTER_ALL_SOURCE
=
"全部国家"
;
const
selectedRiskSourceModel
=
ref
([
RISK_FILTER_ALL_SOURCE
]);
const
handleRiskSourceGroupChange
=
(
val
)
=>
{
selectedRiskSourceModel
.
value
=
normalizeExclusiveAllOption
(
val
,
RISK_FILTER_ALL_SOURCE
);
handleGetPageQuery
();
};
const
riskDegree
=
ref
([
{
name
:
"特别重大"
,
id
:
"特别重大"
name
:
"特别重大
风险
"
,
id
:
"特别重大
风险
"
},
{
name
:
"重大风险"
,
...
...
@@ -366,110 +329,40 @@ const riskDegree = ref([
id
:
"一般风险"
},
{
name
:
"
无
风险"
,
id
:
"
无
风险"
name
:
"
低
风险"
,
id
:
"
低
风险"
}
]);
const
isRiskDegreeCheckedAll
=
ref
(
false
);
const
isRiskDegreeIndeterminate
=
ref
(
true
);
const
checkedRiskDegree
=
ref
([]);
const
handleRiskDegreeCheckAllChange
=
val
=>
{
checkedRiskDegree
.
value
=
val
?
riskDegree
.
value
.
map
(
item
=>
{
return
item
.
id
;
}
)
:
[];
isRiskDegreeIndeterminate
.
value
=
false
;
handleGetPageQuery
();
}
;
const
handleRiskDegreeChange
=
val
=>
{
const
checkedCount
=
val
.
length
;
isRiskDegreeCheckedAll
.
value
=
checkedCount
===
riskDegree
.
value
.
length
;
isRiskDegreeIndeterminate
.
value
=
checkedCount
>
0
&&
checkedCount
<
riskDegree
.
value
.
length
;
const
RISK_FILTER_ALL_LEVEL
=
"全部等级"
;
const
selectedRiskDegreeModel
=
ref
([
RISK_FILTER_ALL_LEVEL
]);
const
handleRiskDegreeGroupChange
=
(
val
)
=>
{
selectedRiskDegreeModel
.
value
=
normalizeExclusiveAllOption
(
val
,
RISK_FILTER_ALL_LEVEL
);
handleGetPageQuery
();
};
const
area
=
ref
([
{
name
:
"人工智能"
,
id
:
1
}
,
{
name
:
"生物科技"
,
id
:
2
}
,
{
name
:
"新一代通信技术"
,
id
:
3
}
,
{
name
:
"量子科技"
,
id
:
4
}
,
{
name
:
"新能源"
,
id
:
5
}
,
{
name
:
"集成电路"
,
id
:
6
}
,
{
name
:
"海洋"
,
id
:
7
}
,
{
name
:
"先进制造"
,
id
:
8
}
,
{
name
:
"新材料"
,
id
:
9
}
,
{
name
:
"航空航天"
,
id
:
10
}
,
{
name
:
"深海"
,
id
:
11
}
,
{
name
:
"极地"
,
id
:
12
}
,
{
name
:
"太空"
,
id
:
13
}
,
{
name
:
"核"
,
id
:
14
}
,
{
name
:
"其他"
,
id
:
99
}
]);
const
isAreaCheckedAll
=
ref
(
false
);
const
isAreaIndeterminate
=
ref
(
true
);
const
checkedArea
=
ref
([]);
const
handleAreaCheckAllChange
=
val
=>
{
checkedArea
.
value
=
val
?
area
.
value
.
map
(
item
=>
{
return
item
.
id
;
}
)
:
[];
isAreaIndeterminate
.
value
=
false
;
handleGetPageQuery
();
// 科技领域字典:与智库资源库一致
const
handleGetHylyList
=
async
()
=>
{
try
{
const
res
=
await
getHylyList
();
if
(
res
.
code
===
200
&&
Array
.
isArray
(
res
.
data
))
{
area
.
value
=
res
.
data
;
}
else
{
area
.
value
=
[];
}
}
catch
(
e
)
{
console
.
error
(
"获取科技领域字典失败"
,
e
);
area
.
value
=
[];
}
};
const
handleAreaChange
=
val
=>
{
const
checkedCount
=
val
.
length
;
isAreaCheckedAll
.
value
=
checkedCount
===
area
.
value
.
length
;
isAreaIndeterminate
.
value
=
checkedCount
>
0
&&
checkedCount
<
area
.
value
.
length
;
const
RISK_FILTER_ALL_AREA
=
"全部领域"
;
const
selectedAreaModel
=
ref
([
RISK_FILTER_ALL_AREA
]);
const
handleAreaGroupChange
=
(
val
)
=>
{
selectedAreaModel
.
value
=
normalizeExclusiveAllOption
(
val
,
RISK_FILTER_ALL_AREA
);
handleGetPageQuery
();
};
...
...
@@ -479,16 +372,13 @@ const processStatusList = ref([
id
:
-
1
},
{
name
:
"未
处理
"
,
name
:
"未
读
"
,
id
:
0
},
{
name
:
"
处理中
"
,
name
:
"
已读
"
,
id
:
1
}
,
{
name
:
"已处理"
,
id
:
2
}
]);
const
activeProcessStatusId
=
ref
(
-
1
);
...
...
@@ -499,113 +389,75 @@ const handleClickBtn = item => {
};
const
riskList
=
ref
([
//
{
//
title: "扩大实体清单制裁范围,对中企子公司实施同等管制",
//
origin: "美国商务部",
//
fileType: "实体清单",
//
time: "2025年11月10日 16:14",
//
dsc: "任何被列入美国出口管制“实体清单”或“军事最终用户清单”的企业,如果其直接或间接持有另一家公司 50%或以上的股权,那么这家被控股的公司也将自动受到与清单上母公司同等的出口管制限制",
//
tag: ["生物科技", "人工智能"],
//
risktype: "特别重大风险",
//
pic: "src/views/riskSignal/assets/images/origin1.png"
//
}
//
{
//
title: "大而美法案通过国会众议院投票,将提交至总统签署",
//
origin: "美国国会 · 科技法案",
//
time: "2025年11月10日 16:14",
//
dsc: "",
//
tag: ["能源", "人工智能"],
//
risktype: "重大风险",
//
pic: "src/views/riskSignal/assets/images/origin2.png",
//
textcolor: "rgba(255, 149, 77, 1)",
//
bgcolor: "rgba(255, 149, 77, 0.1)"
//
}
,
//
{
//
title: "兰德公司发布智库报告《中美经济竞争:复杂经济和地缘政治关系中的收益和风险》",
//
origin: "兰德公司 · 科技智库",
//
time: "2025年11月10日 16:14",
//
dsc: "包括经济竞争在内的美中竞争自2017年以来一直在定义美国外交政策。这两个经济体是世界上第一和第二大国家经济体,并且深深交织在一起。改变关系,无论多么必要,可能是昂贵的。因此,美国面临着一项挑战,确保其经济在耦合的战略竞争条件下满足国家的需求。",
//
tag: ["生物科技", "人工智能", "集成电路"],
//
risktype: "一般风险",
//
pic: "src/views/riskSignal/assets/images/origin3.png",
//
textcolor: "rgba(5, 95, 194, 1)",
//
bgcolor: "rgba(5, 95, 194, 0.1)"
//
}
,
//
{
//
title: "美国白宫发布总统政令《关于进一步延长TikTok执法宽限期的行政令》",
//
origin: "美国白宫 · 总统政令",
//
time: "2025年11月10日 16:14",
//
dsc: "再次推迟(第四次)对TikTok禁令的执法,新的宽限期截止日为2025年12月16日。在宽限期内及对于宽限期前的行为,司法部不得强制执行《保护美国人免受外国对手控制应用程序法》或因此处罚相关实体(如TikTok及其分发平台)。",
//
tag: ["人工智能"],
//
risktype: "一般风险",
//
pic: "src/views/riskSignal/assets/images/origin2.png",
//
textcolor: "rgba(5, 95, 194, 1)",
//
bgcolor: "rgba(5, 95, 194, 0.1)"
//
}
,
//
{
//
title: "美国财政部更新《特别指定国民清单》",
//
origin: "美国财政部 · 特别指定国民清单",
//
time: "2025年11月10日 16:14",
//
dsc: "",
//
tag: ["生物科技"],
//
risktype: "特别重大风险",
//
pic: "src/views/riskSignal/assets/images/origin4.png",
//
textcolor: "rgba(206, 79, 81, 1)",
//
bgcolor: "rgba(206, 79, 81, 0.1)"
//
}
,
//
{
//
title: "美国FDA针对两家中国第三方检测机构的数据完整性问题采取行动",
//
origin: "美国食品药品监督管理局 · 规则限制",
//
time: "2025年11月10日 16:14",
//
dsc: "FDA因发现数据伪造或无效问题,向两家中国第三方检测公司(天津中联科技检测有限公司和苏州大学卫生与环境技术研究所)正式发出“一般信函”。",
//
tag: ["生物科技", "人工智能"],
//
risktype: "特别重大风险",
//
pic: "src/views/riskSignal/assets/images/origin5.png",
//
textcolor: "rgba(206, 79, 81, 1)",
//
bgcolor: "rgba(206, 79, 81, 0.1)"
//
}
{
title
:
"扩大实体清单制裁范围,对中企子公司实施同等管制"
,
origin
:
"美国商务部"
,
fileType
:
"实体清单"
,
time
:
"2025年11月10日 16:14"
,
dsc
:
"任何被列入美国出口管制“实体清单”或“军事最终用户清单”的企业,如果其直接或间接持有另一家公司 50%或以上的股权,那么这家被控股的公司也将自动受到与清单上母公司同等的出口管制限制"
,
tag
:
[
"生物科技"
,
"人工智能"
],
risktype
:
"特别重大风险"
,
pic
:
"src/views/riskSignal/assets/images/origin1.png"
},
{
title
:
"大而美法案通过国会众议院投票,将提交至总统签署"
,
origin
:
"美国国会 · 科技法案"
,
time
:
"2025年11月10日 16:14"
,
dsc
:
""
,
tag
:
[
"能源"
,
"人工智能"
],
risktype
:
"重大风险"
,
pic
:
"src/views/riskSignal/assets/images/origin2.png"
,
textcolor
:
"rgba(255, 149, 77, 1)"
,
bgcolor
:
"rgba(255, 149, 77, 0.1)"
},
{
title
:
"兰德公司发布智库报告《中美经济竞争:复杂经济和地缘政治关系中的收益和风险》"
,
origin
:
"兰德公司 · 科技智库"
,
time
:
"2025年11月10日 16:14"
,
dsc
:
"包括经济竞争在内的美中竞争自2017年以来一直在定义美国外交政策。这两个经济体是世界上第一和第二大国家经济体,并且深深交织在一起。改变关系,无论多么必要,可能是昂贵的。因此,美国面临着一项挑战,确保其经济在耦合的战略竞争条件下满足国家的需求。"
,
tag
:
[
"生物科技"
,
"人工智能"
,
"集成电路"
],
risktype
:
"一般风险"
,
pic
:
"src/views/riskSignal/assets/images/origin3.png"
,
textcolor
:
"rgba(5, 95, 194, 1)"
,
bgcolor
:
"rgba(5, 95, 194, 0.1)"
},
{
title
:
"美国白宫发布总统政令《关于进一步延长TikTok执法宽限期的行政令》"
,
origin
:
"美国白宫 · 总统政令"
,
time
:
"2025年11月10日 16:14"
,
dsc
:
"再次推迟(第四次)对TikTok禁令的执法,新的宽限期截止日为2025年12月16日。在宽限期内及对于宽限期前的行为,司法部不得强制执行《保护美国人免受外国对手控制应用程序法》或因此处罚相关实体(如TikTok及其分发平台)。"
,
tag
:
[
"人工智能"
],
risktype
:
"一般风险"
,
pic
:
"src/views/riskSignal/assets/images/origin2.png"
,
textcolor
:
"rgba(5, 95, 194, 1)"
,
bgcolor
:
"rgba(5, 95, 194, 0.1)"
},
{
title
:
"美国财政部更新《特别指定国民清单》"
,
origin
:
"美国财政部 · 特别指定国民清单"
,
time
:
"2025年11月10日 16:14"
,
dsc
:
""
,
tag
:
[
"生物科技"
],
risktype
:
"特别重大风险"
,
pic
:
"src/views/riskSignal/assets/images/origin4.png"
,
textcolor
:
"rgba(206, 79, 81, 1)"
,
bgcolor
:
"rgba(206, 79, 81, 0.1)"
},
{
title
:
"美国FDA针对两家中国第三方检测机构的数据完整性问题采取行动"
,
origin
:
"美国食品药品监督管理局 · 规则限制"
,
time
:
"2025年11月10日 16:14"
,
dsc
:
"FDA因发现数据伪造或无效问题,向两家中国第三方检测公司(天津中联科技检测有限公司和苏州大学卫生与环境技术研究所)正式发出“一般信函”。"
,
tag
:
[
"生物科技"
,
"人工智能"
],
risktype
:
"特别重大风险"
,
pic
:
"src/views/riskSignal/assets/images/origin5.png"
,
textcolor
:
"rgba(206, 79, 81, 1)"
,
bgcolor
:
"rgba(206, 79, 81, 0.1)"
}
]);
const
calendarData
=
ref
([
// ["2025-01-01", 20],
// ["2025-01-05", 120],
// ["2025-01-09", 220],
// ["2025-01-15", 320],
// ["2025-01-20", 120],
// ["2025-01-24", 420],
// ["2025-02-05", 80],
// ["2025-02-08", 280],
// ["2025-02-18", 480],
// ["2025-02-11", 420],
// ["2025-02-21", 320],
// ["2025-03-05", 160],
// ["2025-03-09", 260],
// ["2025-03-19", 460],
// ["2025-03-26", 430],
// ["2025-04-01", 70],
// ["2025-04-05", 170],
// ["2025-04-11", 270],
// ["2025-04-18", 370],
// ["2025-05-05", 210],
// ["2025-05-09", 210],
// ["2025-05-15", 410],
// ["2025-05-22", 480],
// ["2025-06-06", 45],
// ["2025-06-09", 415],
// ["2025-06-16", 245],
// ["2025-06-19", 332],
// ["2025-07-04", 127],
// ["2025-07-09", 327],
// ["2025-07-24", 427],
// ["2025-08-08", 150],
// ["2025-08-11", 250],
// ["2025-08-15", 350],
// ["2025-08-22", 460],
// ["2025-09-10", 480],
// ["2025-09-18", 312],
// ["2025-10-15", 60],
// ["2025-10-19", 80],
// ["2025-10-21", 190]
]);
// 基本统计信息
...
...
@@ -622,7 +474,7 @@ const handleGetCountInfo = async () => {
if
(
res
.
code
===
200
&&
res
.
data
)
{
basicInfo
.
value
=
res
.
data
;
}
}
catch
(
error
)
{
}
}
catch
(
error
)
{
}
};
// 每日统计信息
...
...
@@ -635,7 +487,7 @@ const handleGetDailyCount = async () => {
return
[
item
.
day
,
item
.
count
];
});
}
}
catch
(
error
)
{
}
}
catch
(
error
)
{
}
};
const
handleCleandarChart
=
async
()
=>
{
...
...
@@ -644,10 +496,22 @@ const handleCleandarChart = async () => {
setChart
(
chartCalendar
,
"chartCalendar"
);
};
//
const
isSort
=
ref
(
true
);
// true 升序 false 倒序
const
handleSwithSort
=
()
=>
{
isSort
.
value
=
!
isSort
.
value
;
/** null:占位「发布时间」且默认倒序;true 正序;false 倒序(显式选中),与智库概览资源库一致 */
const
sortModel
=
ref
(
null
);
const
resourceLibrarySortPopperOptions
=
{
modifiers
:
[
{
name
:
"preventOverflow"
,
options
:
{
mainAxis
:
false
,
altAxis
:
false
}
},
{
name
:
"flip"
,
enabled
:
false
}
]
};
const
toggleSortPrefix
=
()
=>
{
sortModel
.
value
=
sortModel
.
value
===
true
?
false
:
true
;
handleSortChange
();
};
const
handleSortChange
=
()
=>
{
handleGetPageQuery
();
};
...
...
@@ -658,7 +522,7 @@ const handleSearch = async () => {
};
// 风险信号总数
const
totalNum
=
ref
(
0
);
const
totalNum
=
ref
(
6
);
const
currentPage
=
ref
(
1
);
const
pageSize
=
ref
(
10
);
// 处理页码改变事件
...
...
@@ -669,31 +533,61 @@ const handleCurrentChange = page => {
// 按条件分页查询风险信号信息
const
handleGetPageQuery
=
async
()
=>
{
const
stripAll
=
(
list
,
allLabel
)
=>
(
Array
.
isArray
(
list
)
?
list
.
filter
((
x
)
=>
x
!==
allLabel
)
:
[]);
// 选中「全部xxx」时,传空数组表示不按该条件过滤(与之前未勾选时语义一致)
const
riskTypes
=
stripAll
(
selectedRiskTypeModel
.
value
,
RISK_FILTER_ALL_TYPE
);
const
srcCountryList
=
stripAll
(
selectedRiskSourceModel
.
value
,
RISK_FILTER_ALL_SOURCE
);
const
riskLevels
=
stripAll
(
selectedRiskDegreeModel
.
value
,
RISK_FILTER_ALL_LEVEL
);
const
techDomainIds
=
stripAll
(
selectedAreaModel
.
value
,
RISK_FILTER_ALL_AREA
);
const
timeFilters
=
stripAll
(
selectedTimeModel
.
value
,
Time_FILTER_ALL_SOURCE
);
const
pad2
=
(
n
)
=>
String
(
n
).
padStart
(
2
,
"0"
);
const
formatYmd
=
(
d
)
=>
`
${
d
.
getFullYear
()}
-
${
pad2
(
d
.
getMonth
()
+
1
)}
-
${
pad2
(
d
.
getDate
())}
`
;
const
buildStartDateFromTimeFilter
=
()
=>
{
if
(
!
timeFilters
.
length
)
return
""
;
// 单选时间窗:取第一个即可(normalizeExclusiveAllOption 已保证互斥)
const
id
=
timeFilters
[
0
];
const
now
=
new
Date
();
const
start
=
new
Date
(
now
);
if
(
id
===
TIME_FILTER_1M
)
start
.
setMonth
(
start
.
getMonth
()
-
1
);
else
if
(
id
===
TIME_FILTER_3M
)
start
.
setMonth
(
start
.
getMonth
()
-
3
);
else
if
(
id
===
TIME_FILTER_6M
)
start
.
setMonth
(
start
.
getMonth
()
-
6
);
else
if
(
id
===
TIME_FILTER_1Y
)
start
.
setFullYear
(
start
.
getFullYear
()
-
1
);
else
return
""
;
return
formatYmd
(
start
);
};
const
startDate
=
buildStartDateFromTimeFilter
();
const
endDate
=
timeFilters
.
length
?
formatYmd
(
new
Date
())
:
""
;
let
params
;
if
(
activeProcessStatusId
.
value
===
-
1
)
{
params
=
{
riskTypes
:
checkedRiskType
.
value
,
srcCountryList
:
checkedRiskSource
.
value
,
riskLevels
:
checkedRiskDegree
.
value
,
techDomainIds
:
checkedArea
.
value
,
riskTypes
,
srcCountryList
,
riskLevels
,
techDomainIds
,
startDate
,
endDate
,
keywords
:
kewword
.
value
,
pageNum
:
currentPage
.
value
,
pageSize
:
pageSize
.
value
,
sortField
:
"time"
,
sortOrder
:
isSort
?
"asc"
:
"desc"
sortOrder
:
sortModel
.
value
===
true
?
"asc"
:
"desc"
};
}
else
{
params
=
{
riskTypes
:
checkedRiskType
.
value
,
srcCountryList
:
checkedRiskSource
.
value
,
riskLevels
:
checkedRiskDegree
.
value
,
techDomainIds
:
checkedArea
.
value
,
riskTypes
,
srcCountryList
,
riskLevels
,
techDomainIds
,
dealStatus
:
activeProcessStatusId
.
value
,
startDate
,
endDate
,
keywords
:
kewword
.
value
,
pageNum
:
1
,
pageSize
:
10
,
sortField
:
"time"
,
sortOrder
:
isSort
?
"asc"
:
"desc"
sortOrder
:
sortModel
.
value
===
true
?
"asc"
:
"desc"
};
}
...
...
@@ -723,6 +617,7 @@ const handleGetPageQuery = async () => {
onMounted
(
async
()
=>
{
handleGetCountInfo
();
handleCleandarChart
();
await
handleGetHylyList
();
handleGetPageQuery
();
});
</
script
>
...
...
@@ -738,13 +633,16 @@ onMounted(async () => {
overflow
:
hidden
;
overflow-y
:
auto
;
background
:
rgba
(
248
,
249
,
250
,
1
);
.home-main
{
width
:
100%
;
height
:
100%
;
margin
:
0
auto
;
background-size
:
100%
100%
;
.home-main-center
{
margin-top
:
34px
;
.center-center
{
margin
:
0
auto
;
margin-top
:
24px
;
...
...
@@ -753,18 +651,22 @@ onMounted(async () => {
border-radius
:
10px
;
box-shadow
:
0px
0px
15px
0px
rgba
(
60
,
87
,
126
,
0
.2
);
background
:
rgba
(
255
,
255
,
255
,
1
);
.center-header
{
display
:
flex
;
justify-content
:
space-between
;
.center-header-left
{
margin-left
:
30px
;
display
:
flex
;
margin-top
:
15px
;
.iconstyle
{
width
:
27px
;
height
:
24px
;
margin-top
:
3px
;
}
.center-header-title
{
margin-left
:
10px
;
font-size
:
24px
;
...
...
@@ -772,9 +674,10 @@ onMounted(async () => {
line-height
:
32px
;
}
}
.center-header-right
{
margin
-
top
:
16
px
;
margin
-
right
:
59
px
;
margin-top
:
20
px
;
margin-right
:
36
px
;
width
:
118px
;
height
:
36px
;
border-radius
:
6px
;
...
...
@@ -784,10 +687,12 @@ onMounted(async () => {
justify-content
:
center
;
align-items
:
center
;
cursor
:
pointer
;
.img
{
width
:
16px
;
height
:
15px
;
}
.text
{
margin-left
:
8px
;
font-size
:
16px
;
...
...
@@ -797,25 +702,33 @@ onMounted(async () => {
}
}
}
.center-middle
{
display
:
flex
;
.center-middle-left
{
display
:
flex
;
flex-direction
:
column
;
.lineitem
{
display
:
flex
;
justify-content
:
space-between
;
.item
{
margin-left
:
61px
;
margin
-
top
:
33
px
;
margin-top
:
28px
;
margin-bottom
:
3px
;
width
:
111px
;
height
:
66px
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
flex-end
;
.top
{
display
:
flex
;
justify-content
:
flex-end
;
padding-right
:
2px
;
.dot
{
width
:
6px
;
height
:
6px
;
...
...
@@ -823,6 +736,7 @@ onMounted(async () => {
margin-right
:
7px
;
border-radius
:
3px
;
}
.text1
{
font-size
:
16px
;
font-weight
:
400
;
...
...
@@ -831,130 +745,145 @@ onMounted(async () => {
text-align
:
right
;
}
}
.text2
{
font-size
:
32px
;
font-weight
:
700
;
line-height
:
42px
;
text-align
:
right
;
/* 强制不换行 */
white-space
:
nowrap
;
}
}
}
}
.center-middle-right
{
width
:
1159px
;
height
:
189px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
border-radius
:
4px
;
margin
:
30
px
auto
0
;
margin-top
:
16px
;
margin-left
:
61px
;
}
}
}
}
.home-main-footer
{
// width: 100%;
// height: 1059px;
.
home
-
main
-
footer
-
header
{
width
:
1600
px
;
height
:
42
px
;
margin
:
36
px
auto
;
// background: orange;
display
:
flex
;
justify
-
content
:
space
-
between
;
.
btn
-
box
{
width
:
1000
px
;
display
:
flex
;
.
btn
{
color
:
rgba
(
95
,
101
,
108
,
1
);
font
-
family
:
Microsoft
YaHei
;
font
-
size
:
16
px
;
font
-
weight
:
400
;
line
-
height
:
42
px
;
padding
:
0
24
px
;
border
-
radius
:
21
px
;
background
:
rgba
(
20
,
89
,
187
,
0
);
margin
-
right
:
20
px
;
cursor
:
pointer
;
&
:
hover
{
background
:
rgba
(
20
,
89
,
187
,
0.1
);
}
}
.
btnActive
{
padding
:
0
24
px
;
border
-
radius
:
21
px
;
background
:
rgba
(
20
,
89
,
187
,
1
);
color
:
#
fff
;
&
:
hover
{
color
:
#
fff
;
background
:
rgba
(
20
,
89
,
187
,
1
);
}
}
}
}
.home-main-footer-main
{
width
:
1600px
;
margin
-
bottom
:
30
px
;
// box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
// background: rgba(255, 255, 255, 1);
margin
:
30px
auto
;
/* 意思:上30px / 左右auto / 下300px */
box-sizing
:
border-box
;
// padding: 20px;
display
:
flex
;
.left
{
width
:
38
9
px
;
height
:
950
px
;
width
:
38
8
px
;
border-radius
:
10px
;
box
-
shadow
:
0
px
0
px
15
px
0
px
rgba
(
60
,
87
,
126
,
0.2
);
padding-bottom
:
24px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
box-shadow
:
0px
0px
20px
0px
rgba
(
94
,
95
,
95
,
0
.1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
.
left
-
box1
{
margin
-
top
:
17
px
;
height
:
100%
;
.
left
-
box1
-
header
{
.select-box
{
margin-top
:
16px
;
.header
{
display
:
flex
;
gap
:
17px
;
.icon
{
margin-top
:
4px
;
width
:
8px
;
height
:
16px
;
margin
-
top
:
4
px
;
border
-
radius
:
0
px
4
px
4
px
0
;
background
:
var
(
--
color-main-active
);
border-radius
:
0
4px
4px
0
;
}
.title
{
height
:
2
px
;
margin
-
left
:
17
px
;
height
:
24px
;
color
:
var
(
--
color-main-active
);
font
-
family
:
Microsoft
YaHei
;
font-family
:
"Source Han Sans CN"
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
24px
;
letter-spacing
:
1px
;
text-align
:
left
;
}
}
.
left
-
box1
-
main
{
margin
-
top
:
10
px
;
.select-main
{
margin-left
:
25px
;
margin-top
:
12px
;
.checkbox-group
{
display
:
grid
;
grid-template-columns
:
repeat
(
2
,
160px
);
gap
:
8px
4px
;
:deep
(
.all-checkbox
)
{
width
:
160px
;
height
:
24px
;
margin
:
0
;
}
:deep
(
.filter-checkbox
)
{
width
:
160px
;
height
:
24px
;
margin-right
:
0
!
important
;
}
:deep
(
.el-checkbox__label
)
{
font-family
:
"Source Han Sans CN"
,
sans-serif
;
font-weight
:
400
;
font-size
:
16px
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
justify
;
color
:
rgb
(
95
,
101
,
108
);
}
}
}
}
}
.right
{
margin-left
:
16px
;
margin-bottom
:
24px
;
width
:
1196px
;
height
:
1821px
;
border-radius
:
10px
;
box-shadow
:
0px
0px
15px
0px
rgba
(
60
,
87
,
126
,
0
.2
);
background
:
rgba
(
255
,
255
,
255
,
1
);
.right-header
{
height
:
60px
;
display
:
flex
;
justify-content
:
space-between
;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
.header-left
{
display
:
flex
;
margin-left
:
44px
;
gap
:
36px
;
.btn-left
{
display
:
flex
;
height
:
60px
;
cursor
:
pointer
;
align-items
:
center
;
.btn-text
{
font-size
:
18px
;
font-weight
:
400
;
...
...
@@ -962,20 +891,24 @@ onMounted(async () => {
color
:
rgba
(
59
,
65
,
75
,
1
);
text-align
:
center
;
}
.btntextactive
{
font-weight
:
700
;
color
:
rgba
(
5
,
95
,
194
,
1
);
}
}
.btnleftactive
{
border-bottom
:
2px
solid
rgba
(
5
,
95
,
194
,
1
);
}
}
.header-right
{
display
:
flex
;
margin-right
:
42px
;
gap
:
12px
;
align-items
:
center
;
.searchbox
{
display
:
flex
;
...
...
@@ -985,6 +918,7 @@ onMounted(async () => {
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
box-sizing
:
border-box
;
.search-btn
{
width
:
16px
;
height
:
16px
;
...
...
@@ -993,68 +927,41 @@ onMounted(async () => {
background
:
url("./assets/images/search-icon.png")
;
}
}
.select-box
{
width
:
120px
;
height
:
32px
;
box-sizing
:
border-box
;
.
paixu
-
btn
{
display
:
flex
;
width
:
120
px
;
height
:
32
px
;
box
-
sizing
:
border
-
box
;
border
:
1
px
solid
rgba
(
230
,
231
,
232
,
1
);
border
-
radius
:
4
px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
&
:
hover
{
background
:
var
(
--
color
-
bg
-
hover
);
}
cursor
:
pointer
;
.
icon1
{
width
:
11
px
;
height
:
14
px
;
margin
-
top
:
10
px
;
margin
-
left
:
9
px
;
img
{
width
:
100
%
;
height
:
100
%
;
}
}
.
text
{
height
:
19
px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font
-
family
:
Microsoft
YaHei
;
font
-
size
:
14
px
;
font
-
weight
:
400
;
line
-
height
:
18
px
;
letter
-
spacing
:
0
px
;
text
-
align
:
left
;
margin
-
top
:
7
px
;
margin
-
left
:
9
px
;
}
.
icon2
{
width
:
10
px
;
height
:
5
px
;
margin
-
top
:
5
px
;
margin
-
left
:
13
px
;
img
{
width
:
100
%
;
height
:
100
%
;
}
.resource-library-sort-select
{
:deep
(
.el-select__wrapper
)
{
height
:
32px
;
min-height
:
32px
;
border-radius
:
4px
;
}
}
.resource-library-sort-prefix-img
{
width
:
6
.72px
;
height
:
14px
;
}
}
}
}
.right-main
{
width
:
1196px
;
min
-
height
:
790
px
;
min-height
:
1667px
;
padding-left
:
18px
;
padding-top
:
6px
;
.itemlist
{
padding-left
:
25px
;
padding
-
top
:
1
8
px
;
padding-top
:
1
6
px
;
padding-bottom
:
16px
;
width
:
1138px
;
display
:
flex
;
margin
-
left
:
18
px
;
flex-direction
:
column
;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
...
...
@@ -1062,12 +969,14 @@ onMounted(async () => {
height
:
28px
;
display
:
flex
;
justify-content
:
space-between
;
.risktitle
{
font-size
:
18px
;
font-weight
:
700
;
line-height
:
24px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
}
.risktype
{
height
:
28px
;
padding
:
0
8px
;
...
...
@@ -1079,34 +988,42 @@ onMounted(async () => {
justify-content
:
center
;
align-items
:
center
;
gap
:
6px
;
.icon
{
width
:
4px
;
height
:
4px
;
border-radius
:
2px
;
}
.icon1
{
background
:
rgba
(
206
,
79
,
81
,
1
);
}
.icon2
{
background
:
rgba
(
255
,
149
,
77
,
1
);
}
.icon3
{
background
:
var
(
--
color-main-active
);
}
}
.risk1
{
background
:
rgba
(
206
,
79
,
81
,
0
.1
);
color
:
rgba
(
206
,
79
,
81
,
1
);
}
.risk2
{
background
:
rgba
(
255
,
149
,
77
,
0
.1
);
color
:
rgba
(
255
,
149
,
77
,
1
);
}
.risk3
{
background
:
rgba
(
231
,
243
,
255
,
1
);
color
:
var
(
--
color-main-active
);
}
}
.box-source
{
margin-top
:
6px
;
display
:
flex
;
...
...
@@ -1114,10 +1031,12 @@ onMounted(async () => {
align-items
:
center
;
height
:
24px
;
gap
:
10px
;
.source-pic
{
width
:
16px
;
height
:
16px
;
}
.source-text
{
font-size
:
16px
;
font-weight
:
400
;
...
...
@@ -1125,6 +1044,7 @@ onMounted(async () => {
color
:
rgba
(
95
,
101
,
108
,
1
);
}
}
.desc-box
{
margin-top
:
9px
;
font-size
:
16px
;
...
...
@@ -1133,44 +1053,37 @@ onMounted(async () => {
color
:
rgba
(
59
,
65
,
75
,
1
);
text-align
:
justify
;
}
.tag-box
{
height
:
28px
;
margin-top
:
11px
;
display
:
flex
;
flex-direction
:
row
;
gap
:
8px
;
.
tag
{
height
:
28
px
;
padding
:
0
8
px
;
line
-
height
:
28
px
;
border
-
radius
:
4
px
;
background
:
rgba
(
231
,
243
,
255
,
1
);
color
:
var
(
--
color
-
main
-
active
);
font
-
family
:
Microsoft
YaHei
;
font
-
style
:
Regular
;
font
-
size
:
14
px
;
font
-
weight
:
400
;
letter
-
spacing
:
0
px
;
}
}
}
}
.right-footer
{
display
:
flex
;
height
:
90px
;
padding
-
top
:
30
px
;
padding-top
:
28
px
;
justify-content
:
space-between
;
.footer-left
{
color
:
rgba
(
59
,
65
,
75
,
1
);
font
-
family
:
Microsoft
YaHei
;
font-family
:
"Source Han Sans CN"
;
font-size
:
14px
;
font-weight
:
400
;
line-height
:
18px
;
margin
-
left
:
2
4
px
;
margin
-
top
:
6
px
;
margin-left
:
2
6
px
;
margin-top
:
4
px
;
}
.footer-right
{
margin
-
right
:
24
px
;
margin-right
:
58
px
;
}
}
}
...
...
@@ -1178,12 +1091,6 @@ onMounted(async () => {
}
}
}
.
checkbox
-
all
{
margin
-
left
:
20
px
;
width
:
260
px
;
}
.
filter
-
checkbox
{
width
:
130
px
;
margin
-
left
:
20
px
;
}
/* 复选框尺寸由 .checkbox-group 内统一控制,避免重复覆盖 */
</
style
>
\ No newline at end of file
src/views/viewRiskSignal/utils/cleandarHeat.js
浏览文件 @
bc0455d7
...
...
@@ -19,13 +19,13 @@ const getCalendarHeatChart = (data) => {
visualMap
:
{
show
:
false
,
min
:
0
,
max
:
50
0
,
max
:
2
0
,
calculable
:
true
,
orient
:
'horizontal'
,
left
:
'center'
,
top
:
65
,
inRange
:
{
color
:
[
'rgb
a(231, 243, 255, 1)'
,
'rgba(138, 196, 255, 1)'
,
'rgba(5, 95, 194, 1
)'
]
color
:
[
'rgb
(231, 243, 255)'
,
'rgb(137, 193, 255)'
,
'rgb(5, 95, 194
)'
]
},
textStyle
:
{
color
:
'rgba(95, 101, 108, 1)'
...
...
@@ -36,7 +36,7 @@ const getCalendarHeatChart = (data) => {
left
:
30
,
right
:
30
,
cellSize
:
[
'auto'
,
20
],
range
:
'202
5
'
,
range
:
'202
6
'
,
splitLine
:
{
show
:
false
},
...
...
vite.config.js
浏览文件 @
bc0455d7
...
...
@@ -58,9 +58,10 @@ export default defineConfig({
},
'/api'
:
{
target
:
'http://8.140.26.4:9085/'
,
target
:
'http://8.140.26.4:9085/'
,
// target: 'http://192.168.0.4:28080/',
changeOrigin
:
true
,
// 前端仍请求 /api/xxx,实际转发到后端 /api/v2/xxx
rewrite
:
(
path
)
=>
path
.
replace
(
/^
\/
api/
,
''
)
// '/api': {
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论