Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
bd14b59f
提交
bd14b59f
authored
4月 09, 2026
作者:
朱政
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat:登录功能和用户信息功能开发,以及风险信号管理列表页样式开发
上级
82883bfb
流水线
#398
已通过 于阶段
in 1 分 39 秒
变更
14
流水线
1
全部展开
隐藏空白字符变更
内嵌
并排
正在显示
14 个修改的文件
包含
577 行增加
和
62 行删除
+577
-62
index.js
src/api/aiAnalysis/index.js
+4
-3
service.js
src/api/finance/service.js
+40
-30
request.js
src/api/request.js
+33
-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/login/index.vue
+31
-5
logo.png
src/views/viewRiskSignal/assets/images/logo.png
+0
-0
index.vue
src/views/viewRiskSignal/index.vue
+0
-0
cleandarHeat.js
src/views/viewRiskSignal/utils/cleandarHeat.js
+3
-3
vite.config.js
vite.config.js
+2
-1
没有找到文件。
src/api/aiAnalysis/index.js
浏览文件 @
bd14b59f
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
浏览文件 @
bd14b59f
...
...
@@ -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
浏览文件 @
bd14b59f
...
...
@@ -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,17 @@ 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
}
// 处理token过期或无效的情况(排除 AI 分析服务:其 401 多为 API Key 问题)
const
errUrl
=
String
(
error
.
config
?.
url
||
''
)
...
...
@@ -118,4 +140,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
浏览文件 @
bd14b59f
// 智库概览信息
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
浏览文件 @
bd14b59f
/**
* 必须在 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
浏览文件 @
bd14b59f
19.0 KB
src/components/base/moduleHeader/index.vue
浏览文件 @
bd14b59f
...
...
@@ -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
浏览文件 @
bd14b59f
import
"./bootstrapAuth.js"
;
import
{
createApp
}
from
"vue"
;
import
App
from
"./App.vue"
;
import
router
from
"./router"
;
...
...
src/router/index.js
浏览文件 @
bd14b59f
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/login/index.vue
浏览文件 @
bd14b59f
...
...
@@ -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/viewRiskSignal/assets/images/logo.png
0 → 100644
浏览文件 @
bd14b59f
476.3 KB
src/views/viewRiskSignal/index.vue
浏览文件 @
bd14b59f
差异被折叠。
点击展开。
src/views/viewRiskSignal/utils/cleandarHeat.js
浏览文件 @
bd14b59f
...
...
@@ -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
浏览文件 @
bd14b59f
...
...
@@ -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
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论