Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
a1036c92
提交
a1036c92
authored
4月 10, 2026
作者:
coderBryanFu
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'pre' of
http://8.140.26.4:10003/caijian/risk-monitor
into fk-dev
上级
1a2a37ac
42feb66d
流水线
#412
已通过 于阶段
in 1 分 30 秒
变更
46
流水线
1
显示空白字符变更
内嵌
并排
正在显示
46 个修改的文件
包含
2794 行增加
和
2045 行删除
+2794
-2045
config.js
public/js/config.js
+2
-2
index.js
src/api/aiAnalysis/index.js
+4
-3
billHome.js
src/api/bill/billHome.js
+1
-1
exportControl.js
src/api/exportControl.js
+3
-2
index.js
src/api/finance/index.js
+25
-0
service.js
src/api/finance/service.js
+40
-30
request.js
src/api/request.js
+43
-1
overview.js
src/api/thinkTank/overview.js
+3
-2
bootstrapAuth.js
src/bootstrapAuth.js
+20
-0
index.vue
src/components/base/SummaryCardsPanel/index.vue
+264
-0
back.png
src/components/base/moduleHeader/back.png
+0
-0
index.vue
src/components/base/moduleHeader/index.vue
+293
-4
IntelligentEntityText.vue
src/components/base/texts/IntelligentEntityText.vue
+56
-8
main.js
src/main.js
+1
-0
index.js
src/router/index.js
+146
-10
bill.js
src/router/modules/bill.js
+14
-0
writtingAsstaintStore.js
src/stores/writtingAsstaintStore.js
+1
-0
index.vue
src/views/bill/allCommittee/index.vue
+270
-0
ResourceLibrarySection.vue
src/views/bill/billHome/ResourceLibrarySection.vue
+1
-3
index.vue
src/views/bill/billHome/index.vue
+103
-73
index.vue
src/views/bill/billLayout/index.vue
+2
-1
index.vue
src/views/bill/deepDig/processAnalysis/index.vue
+12
-328
index.vue
src/views/bill/influence/industry/index.vue
+64
-28
index.vue
src/views/bill/template/index.vue
+25
-1
index.vue
src/views/coopRestriction/components/dataNew/index.vue
+1
-1
index.vue
src/views/decree/decreeHome/index.vue
+32
-32
index.vue
src/views/exportControl/index.vue
+4
-13
index.vue
...omponents/sanctionsOverview/components/listPage/index.vue
+13
-11
index.vue
...omponents/sanctionsOverview/components/listPage/index.vue
+3
-8
empty.png
...finance/entityList/components/deepMining/assets/empty.png
+0
-0
icon-mark.png
...nce/entityList/components/deepMining/assets/icon-mark.png
+0
-0
back.vue
...ance/entityList/components/deepMining/components/back.vue
+429
-0
constrainedAssociation.vue
...mponents/deepMining/components/constrainedAssociation.vue
+332
-902
mock.json
...nce/entityList/components/deepMining/components/mock.json
+74
-0
index.vue
src/views/finance/entityList/components/deepMining/index.vue
+8
-5
index.vue
...s/sanctionsOverview/components/introductionPage/index.vue
+0
-5
index.vue
...omponents/sanctionsOverview/components/listPage/index.vue
+6
-5
index.vue
src/views/finance/index.vue
+3
-3
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
+415
-507
cleandarHeat.js
src/views/viewRiskSignal/utils/cleandarHeat.js
+3
-3
vite.config.js
vite.config.js
+1
-0
出口管制接口-4.md
出口管制接口-4.md
+44
-46
没有找到文件。
public/js/config.js
浏览文件 @
a1036c92
const
baseUrl
=
`http://8.140.26.4:9085`
const
outImgbaseUrl
=
`http://172.19.21.9:8003/out/img`
// 写报图片URL
\ No newline at end of file
const
outImgbaseUrl
=
`http://8.140.26.4:10017/out/img`
// 写报图片URL
\ No newline at end of file
src/api/aiAnalysis/index.js
浏览文件 @
a1036c92
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/bill/billHome.js
浏览文件 @
a1036c92
...
...
@@ -16,7 +16,7 @@ export function getBillIndustry(params) {
return
request
({
method
:
'GET'
,
url
:
`/api/BillOverview/billIndustry/
${
params
.
year
}
`
,
params
:
{
sta
tus
:
params
.
status
}
params
:
{
sta
geName
:
params
.
stageName
}
})
}
...
...
src/api/exportControl.js
浏览文件 @
a1036c92
...
...
@@ -498,14 +498,15 @@ export function getEntitiesUpdateCount(sanTypeId = 1) {
* @param {string} rule - 规则
* @param {string} type - 类型
*/
export
function
getSanDomainCount
(
rule
,
type
)
{
export
function
getSanDomainCount
(
rule
,
sanTypeIds
,
type
)
{
return
request200
(
request
({
method
:
"GET"
,
url
:
"/api/entitiesDataCount/getSanDomainCount"
,
params
:
{
rule
,
type
sanTypeIds
// type
}
})
);
...
...
src/api/finance/index.js
浏览文件 @
a1036c92
...
...
@@ -136,3 +136,27 @@ export function getRelateNews(sanRecordId) {
export
function
getReasonAndSan
(
sanRecordId
)
{
return
http
.
get
(
`/api/sanctionList/invFin/getReasonAndSan?sanRecordId=
${
sanRecordId
}
`
);
}
/**
* 制裁历程
* url:/entitiesDataCount/getSanRecord
*/
export
function
getSanRecord
(
params
)
{
return
http
.
get
(
"/api/entitiesDataCount/getSanRecord"
,
params
);
}
/**
* 限制关系分析-限制举措关系图
* url:/sanctionList/invFin/recordRelation
*/
export
function
getRecordRelation
(
sanRecordIds
)
{
return
http
.
get
(
`/api/sanctionList/invFin/recordRelation?sanRecordIds=
${
sanRecordIds
}
`
);
}
/**
* 查询投融资限制关联-图谱-节点详情
* url:/sanctionList/invFin/getVertexInfo
*/
export
function
getVertexInfo
(
sanRecordId
)
{
return
http
.
get
(
`/api/sanctionList/invFin/getVertexInfo?sanRecordId=
${
sanRecordId
}
`
);
}
\ No newline at end of file
src/api/finance/service.js
浏览文件 @
a1036c92
...
...
@@ -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
浏览文件 @
a1036c92
...
...
@@ -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实例
...
...
@@ -90,6 +101,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
||
''
)
const
isAiAnalysisRequest
=
errUrl
.
includes
(
'aiAnalysis'
)
...
...
@@ -119,3 +152,11 @@ service.interceptors.response.use(
)
export
default
service
/*
===========================
下面是“当前新版 request.js”代码备份(按你的要求:不删除,仅注释保留)
===========================
(此处保留了之前实现的 token 多来源读取、AxiosHeaders、默认头同步、401 防抖等逻辑)
*/
\ No newline at end of file
src/api/thinkTank/overview.js
浏览文件 @
a1036c92
// 智库概览信息
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
浏览文件 @
a1036c92
/**
* 必须在 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/SummaryCardsPanel/index.vue
0 → 100644
浏览文件 @
a1036c92
<
template
>
<div
class=
"summary-cards-panel"
:style=
"
{ width: panelWidth }" v-loading="loading">
<div
class=
"date-box"
>
<div
class=
"date-icon"
v-if=
"tipIcon"
>
<img
:src=
"tipIcon"
alt=
""
/>
</div>
<div
class=
"date-text"
>
{{
descriptionText
}}
</div>
<TimeTabPane
@
time-click=
"onTimeClick"
:activeTime=
"activeTime"
/>
</div>
<div
class=
"cards-box"
v-if=
"cards.length"
>
<div
class=
"summary-item"
v-for=
"item in cards"
:key=
"item.id || item.name"
>
<div
class=
"item-left"
>
<img
:src=
"item.avatar || defaultAvatar"
alt=
""
/>
</div>
<div
class=
"item-center"
@
click=
"emit('name-click', item)"
>
<div
class=
"item-name one-line-ellipsis"
>
{{
item
.
name
}}
</div>
<div
v-if=
"item.subText"
class=
"item-sub-text one-line-ellipsis"
>
{{
item
.
subText
}}
</div>
</div>
<el-popover
content=
"跳转至数据资源库"
placement=
"top"
>
<template
#
reference
>
<div
class=
"item-total"
@
click=
"emit('count-click', item)"
>
{{
item
.
count
}}{{
countSuffix
}}
</div>
</
template
>
</el-popover>
<el-icon
color=
"var(--color-primary-100)"
>
<ArrowRightBold
/>
</el-icon>
<div
class=
"item-dot"
v-if=
"item.delta"
>
+{{ item.delta }}
</div>
</div>
<div
v-if=
"shouldShowMoreCard"
class=
"summary-item"
@
click=
"emit('more-click')"
>
<div
class=
"item-more"
>
{{ moreText }} ({{ totalCount }}家)
</div>
<el-icon
color=
"var(--color-primary-100)"
>
<ArrowRightBold
/>
</el-icon>
</div>
</div>
<div
v-else
class=
"cards-empty"
>
<el-empty
:description=
"emptyText"
:image-size=
"80"
/>
</div>
</div>
</template>
<
script
setup
>
import
TimeTabPane
from
"@/components/base/TimeTabPane/index.vue"
;
import
{
ArrowRightBold
}
from
"@element-plus/icons-vue"
;
import
{
computed
}
from
"vue"
;
const
props
=
defineProps
({
descriptionText
:
{
type
:
String
,
default
:
""
},
cards
:
{
type
:
Array
,
default
:
()
=>
[]
},
totalCount
:
{
type
:
Number
,
default
:
0
},
showMoreCard
:
{
type
:
Boolean
,
default
:
true
},
moreCardMinCount
:
{
type
:
Number
,
default
:
8
},
emptyText
:
{
type
:
String
,
default
:
"暂无数据"
},
activeTime
:
{
type
:
String
,
default
:
"近一年"
},
tipIcon
:
{
type
:
String
,
default
:
""
},
defaultAvatar
:
{
type
:
String
,
default
:
""
},
countSuffix
:
{
type
:
String
,
default
:
"项"
},
moreText
:
{
type
:
String
,
default
:
"查看全部机构"
},
panelWidth
:
{
type
:
String
,
default
:
"1600px"
},
loading
:
{
type
:
Boolean
,
default
:
false
}
});
const
emit
=
defineEmits
([
"time-click"
,
"name-click"
,
"count-click"
,
"more-click"
]);
const
shouldShowMoreCard
=
computed
(()
=>
{
if
(
!
props
.
showMoreCard
)
return
false
;
if
(
props
.
moreCardMinCount
<=
0
)
return
true
;
return
(
props
.
cards
?.
length
||
0
)
>=
props
.
moreCardMinCount
;
});
const
onTimeClick
=
event
=>
{
emit
(
"time-click"
,
event
);
};
</
script
>
<
style
scoped
lang=
"scss"
>
.summary-cards-panel
{
margin
:
48px
auto
0
;
.date-box
{
display
:
flex
;
align-items
:
center
;
.date-icon
{
width
:
16px
;
height
:
16px
;
font-size
:
0
;
margin-right
:
6px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.date-text
{
width
:
20px
;
flex
:
auto
;
font-size
:
18px
;
line-height
:
18px
;
font-family
:
Source
Han
Sans
CN
;
color
:
var
(
--
text-primary-80-color
);
}
}
.cards-box
{
margin
:
20px
0
64px
;
display
:
grid
;
grid-template-columns
:
repeat
(
4
,
1fr
);
grid-auto-rows
:
80px
;
gap
:
16px
;
font-family
:
Microsoft
YaHei
;
.summary-item
{
padding
:
0
16px
;
display
:
flex
;
box-sizing
:
border-box
;
background
:
rgba
(
255
,
255
,
255
,
0
.65
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
1
);
border-radius
:
10px
;
box-shadow
:
0
0
20px
0
rgba
(
25
,
69
,
130
,
0
.1
);
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
transition
:
transform
0
.3s
ease
,
box-shadow
0
.3s
ease
;
position
:
relative
;
&
:hover
{
transform
:
translateY
(
-3px
);
box-shadow
:
0
4px
16px
rgba
(
0
,
0
,
0
,
0
.15
);
}
.item-left
{
width
:
48px
;
height
:
48px
;
font-size
:
0
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.item-center
{
width
:
20px
;
flex
:
auto
;
margin
:
0
16px
;
cursor
:
pointer
;
}
.item-name
{
color
:
rgba
(
59
,
65
,
75
,
1
);
font-size
:
20px
;
font-weight
:
700
;
line-height
:
20px
;
&
:hover
{
color
:
var
(
--
color-primary-100
);
text-decoration
:
underline
;
}
}
.item-sub-text
{
margin-top
:
6px
;
color
:
var
(
--
text-primary-50-color
);
font-size
:
14px
;
line-height
:
18px
;
}
.item-total
{
font-size
:
20px
;
margin-right
:
2px
;
white-space
:
nowrap
;
font-weight
:
700
;
line-height
:
20px
;
color
:
var
(
--
color-primary-100
);
&
:hover
{
text-decoration
:
underline
;
}
}
.item-more
{
font-size
:
16px
;
margin-right
:
12px
;
white-space
:
nowrap
;
font-weight
:
700
;
line-height
:
16px
;
color
:
var
(
--
color-primary-100
);
}
.item-dot
{
position
:
absolute
;
right
:
-13px
;
top
:
-10px
;
padding
:
0
8px
;
height
:
26px
;
background-color
:
#ff4d4f
;
color
:
white
;
font-size
:
16px
;
line-height
:
26px
;
font-family
:
Source
Han
Sans
CN
;
border-radius
:
14px
;
letter-spacing
:
1px
;
}
}
}
.cards-empty
{
margin
:
20px
0
64px
;
height
:
120px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
background
:
rgba
(
255
,
255
,
255
,
0
.65
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
1
);
border-radius
:
10px
;
box-shadow
:
0
0
20px
0
rgba
(
25
,
69
,
130
,
0
.1
);
}
}
</
style
>
src/components/base/moduleHeader/back.png
0 → 100644
浏览文件 @
a1036c92
19.0 KB
src/components/base/moduleHeader/index.vue
浏览文件 @
a1036c92
...
...
@@ -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"
>
<div
class=
"user-trigger"
>
<div
class=
"user"
@
click
.
stop=
"handleToggleUserPanel"
>
<img
src=
"@/assets/icons/overview/user.png"
alt=
""
/>
</div>
<div
class=
"name text-regular"
>
{{
"管理员"
}}
</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>
</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/components/base/texts/IntelligentEntityText.vue
浏览文件 @
a1036c92
<
template
>
<p
class=
"p-regular-rereg"
>
<span
class=
"text-regular"
v-for=
"(segment, index) in processedText"
:key=
"index"
>
<span
v-if=
"segment.isEntity"
@
click=
"$emit('onEntityClick', segment.entity)"
class=
"entity-link"
>
<span
v-if=
"segment.isEntity"
@
click=
"$emit('onEntityClick', segment.entity)"
:class=
"['entity-link',
{ 'keyword-highlight': segment.isKeywordHit }]"
>
{{
segment
.
entity
?.
text_span
}}
<img
:src=
"SearchIcon"
:width=
"10"
:height=
"10"
alt=
"search"
/>
</span>
<span
v-else
>
{{
segment
.
text
}}
<span
:class=
"
{ 'keyword-highlight': segment.isKeywordHit }">
{{
segment
.
text
}}
</span>
</span>
</span>
</p>
...
...
@@ -20,6 +24,7 @@ export interface ProcessedTextSegment {
text
:
string
;
isEntity
:
boolean
;
entity
?:
TextEntity
;
isKeywordHit
?:
boolean
;
}
const
props
=
defineProps
({
text
:
{
...
...
@@ -29,15 +34,42 @@ const props = defineProps({
entities
:
{
type
:
Array
<
TextEntity
>
,
default
:
()
=>
[]
},
highlight
:
{
type
:
String
,
default
:
""
}
});
const
emit
=
defineEmits
([
"onEntityClick"
]);
// 处理后的文本段
const
processedText
=
ref
<
ProcessedTextSegment
[]
>
([]);
const
normalizeKeyword
=
(
value
:
unknown
)
=>
String
(
value
??
""
).
trim
();
const
getKeywordMatches
=
(
text
:
string
,
keyword
:
string
)
=>
{
if
(
!
keyword
)
return
[{
text
,
isKeywordHit
:
false
}];
const
lowerText
=
text
.
toLowerCase
();
const
lowerKeyword
=
keyword
.
toLowerCase
();
if
(
!
lowerKeyword
)
return
[{
text
,
isKeywordHit
:
false
}];
const
parts
:
Array
<
{
text
:
string
;
isKeywordHit
:
boolean
}
>
=
[];
let
start
=
0
;
while
(
start
<
text
.
length
)
{
const
index
=
lowerText
.
indexOf
(
lowerKeyword
,
start
);
if
(
index
===
-
1
)
{
parts
.
push
({
text
:
text
.
slice
(
start
),
isKeywordHit
:
false
});
break
;
}
if
(
index
>
start
)
{
parts
.
push
({
text
:
text
.
slice
(
start
,
index
),
isKeywordHit
:
false
});
}
parts
.
push
({
text
:
text
.
slice
(
index
,
index
+
keyword
.
length
),
isKeywordHit
:
true
});
start
=
index
+
keyword
.
length
;
}
return
parts
.
filter
(
part
=>
part
.
text
);
};
// 处理文本,识别并替换实体
const
processText
=
()
=>
{
console
.
log
(
"props.entities.length"
,
props
.
entities
.
length
);
if
(
!
props
.
text
||
!
props
.
entities
)
{
// console.log('props.text', props.entities.length)
processedText
.
value
=
[{
text
:
""
,
isEntity
:
false
}];
...
...
@@ -46,6 +78,7 @@ const processText = () => {
const
result
=
[];
let
currentPosition
=
0
;
const
keyword
=
normalizeKeyword
(
props
.
highlight
);
// 按实体文本长度排序,优先匹配长文本
const
sortedEntities
=
[...
props
.
entities
].
sort
((
a
,
b
)
=>
b
.
text_span
.
length
-
a
.
text_span
.
length
);
...
...
@@ -61,7 +94,8 @@ const processText = () => {
// 如果当前位置是实体,添加到结果
result
.
push
({
isEntity
:
true
,
entity
:
{
...
entity
}
entity
:
{
...
entity
},
isKeywordHit
:
keyword
?
entityText
.
toLowerCase
().
includes
(
keyword
.
toLowerCase
())
:
false
});
currentPosition
=
endPosition
;
matched
=
true
;
...
...
@@ -82,18 +116,26 @@ const processText = () => {
if
(
nextEntityStart
>
currentPosition
)
{
const
plainText
=
props
.
text
.
substring
(
currentPosition
,
nextEntityStart
);
const
parts
=
getKeywordMatches
(
plainText
,
keyword
);
parts
.
forEach
(
part
=>
{
result
.
push
({
text
:
plainText
,
isEntity
:
false
text
:
part
.
text
,
isEntity
:
false
,
isKeywordHit
:
part
.
isKeywordHit
});
});
currentPosition
=
nextEntityStart
;
}
else
{
// 没有更多实体,添加剩余文本
const
remainingText
=
props
.
text
.
substring
(
currentPosition
);
if
(
remainingText
)
{
const
parts
=
getKeywordMatches
(
remainingText
,
keyword
);
parts
.
forEach
(
part
=>
{
result
.
push
({
text
:
remainingText
,
isEntity
:
false
text
:
part
.
text
,
isEntity
:
false
,
isKeywordHit
:
part
.
isKeywordHit
});
});
}
currentPosition
=
props
.
text
.
length
;
...
...
@@ -106,6 +148,7 @@ const processText = () => {
// 监听文本和实体变化
watch
(()
=>
props
.
text
,
processText
);
watch
(()
=>
props
.
entities
,
processText
,
{
deep
:
true
});
watch
(()
=>
props
.
highlight
,
processText
);
// 初始化处理
onMounted
(
processText
);
...
...
@@ -113,6 +156,11 @@ onMounted(processText);
<
style
lang=
"scss"
scoped
>
@use
"@/styles/common.scss"
;
.keyword-highlight
{
background
:
rgba
(
255
,
199
,
0
,
0
.35
);
border-radius
:
2px
;
}
.entity-link
{
color
:
var
(
--
color-primary-100
);
&
:hover
{
...
...
src/main.js
浏览文件 @
a1036c92
import
"./bootstrapAuth.js"
;
import
{
createApp
}
from
"vue"
;
import
App
from
"./App.vue"
;
import
router
from
"./router"
;
...
...
src/router/index.js
浏览文件 @
a1036c92
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
is
LoginRoute
=
to
.
name
===
"Login"
||
/^
\/
login
\/?
$/
.
test
(
String
(
to
.
path
||
""
)
);
const
is
Authed
=
hasStoredAuthToken
(
);
if
(
!
isLoginRoute
&&
!
isAuthed
)
{
next
({
path
:
"/login"
,
query
:
{
redirect
:
to
.
fullPath
}
});
// 已登录:不应停留在 /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
(
!
isAuthed
&&
to
.
path
!==
"/login"
)
{
// 防止误入 /callback 空白页
next
({
path
:
"/login"
,
replace
:
true
});
return
;
}
...
...
src/router/modules/bill.js
浏览文件 @
a1036c92
...
...
@@ -15,6 +15,7 @@ const BillProgressForecast = () => import('@/views/bill/influence/ProgressForeca
const
BillInfluenceScientificResearch
=
()
=>
import
(
'@/views/bill/influence/scientificResearch/index.vue'
)
const
BillRelevantCircumstance
=
()
=>
import
(
'@/views/bill/relevantCircumstance/index.vue'
)
const
BillVersionCompare
=
()
=>
import
(
'@/views/bill/versionCompare/index.vue'
)
const
BillAllCommittee
=
()
=>
import
(
'@/views/bill/allCommittee/index.vue'
)
const
billRoutes
=
[
...
...
@@ -28,6 +29,19 @@ const billRoutes = [
isShowHeader
:
true
}
},
{
path
:
"/bill/allCommittee"
,
name
:
"BillAllCommittee"
,
component
:
BillAllCommittee
,
meta
:
{
title
:
"法案委员会列表"
,
isShowHeader
:
true
}
},
{
path
:
"/billAllCommittee"
,
redirect
:
"/bill/allCommittee"
},
{
path
:
"/billLayout"
,
name
:
"BillLayoutContainer"
,
...
...
src/stores/writtingAsstaintStore.js
浏览文件 @
a1036c92
...
...
@@ -511,6 +511,7 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
if
(
this
.
reportContent
.
includes
(
'./out/img'
))
{
this
.
reportContent
=
this
.
reportContent
.
replaceAll
(
'./out/img'
,
outImgbaseUrl
);
// console.log(reportContent)
}
};
...
...
src/views/bill/allCommittee/index.vue
0 → 100644
浏览文件 @
a1036c92
<
template
>
<div
class=
"view-box"
>
<div
class=
"container-box"
>
<div
class=
"hard-box"
>
<div
class=
"hard-name text-title-0-show"
>
美国国会委员会
</div>
<div
class=
"hard-num text-title-2-show"
>
{{
committeeInfo
.
total
}}
个
</div>
<div
style=
"width: 0; flex: auto"
></div>
<div
class=
"hard-input"
>
<el-input
v-model=
"committeeInfo.keyWord"
@
keyup
.
enter=
"onAllCommittee()"
style=
"width: 100%; height: 100%"
:suffix-icon=
"Search"
placeholder=
"搜索委员会"
/>
</div>
</div>
<div
class=
"date-box"
>
<div
class=
"date-text"
>
近期美国国会各委员会涉华提案数量汇总
</div>
<TimeTabPane
@
time-click=
"handleDateChange"
activeTime=
"近一年"
/>
</div>
<div
class=
"committee-list"
ref=
"refCommittee"
v-loading=
"committeeInfo.loading"
>
<div
class=
"committee-item"
v-for=
"item in committeeInfo.list"
:key=
"item.id"
@
click=
"handleToDataLibrary(item)"
>
<div
class=
"item-left"
>
<img
:src=
"iconCommit"
alt=
""
/>
</div>
<div
class=
"item-right"
>
<div
class=
"item-name one-line-ellipsis"
>
{{
item
.
name
}}
</div>
<div
class=
"item-chamber one-line-ellipsis"
>
{{
item
.
chamber
}}
</div>
</div>
<div
class=
"item-total"
>
{{
item
.
count
}}
项
</div>
<el-icon
color=
"var(--color-primary-100)"
>
<ArrowRightBold
/>
</el-icon>
</div>
</div>
<div
class=
"pagination-box"
>
<el-pagination
@
current-change=
"onAllCommittee"
:pageSize=
"committeeInfo.pageSize"
:current-page=
"committeeInfo.pageNum"
background
layout=
"prev, pager, next"
:total=
"committeeInfo.total"
/>
</div>
</div>
</div>
</
template
>
<
script
setup
name=
"BillAllCommittee"
>
import
{
onMounted
,
reactive
,
ref
}
from
"vue"
;
import
{
Search
}
from
"@element-plus/icons-vue"
;
import
{
ArrowRightBold
}
from
"@element-plus/icons-vue"
;
import
router
from
"@/router"
;
import
TimeTabPane
from
"@/components/base/TimeTabPane/index.vue"
;
import
{
getStatisticsBillCountByCommittee
}
from
"@/api/bill/billHome"
;
import
iconCommit
from
"../billHome/assets/icons/icon-commit.png"
;
const
committeeInfo
=
reactive
({
loading
:
false
,
pageNum
:
1
,
pageSize
:
8
,
total
:
0
,
keyWord
:
""
,
dateDesc
:
"近一年"
,
list
:
[]
});
const
getChamberLabel
=
orgType
=>
{
if
(
orgType
===
"Senate"
)
return
"参议院"
;
if
(
orgType
===
"House"
)
return
"众议院"
;
return
orgType
||
""
;
};
const
onAllCommittee
=
async
num
=>
{
committeeInfo
.
pageNum
=
num
||
1
;
committeeInfo
.
loading
=
true
;
try
{
const
res
=
await
getStatisticsBillCountByCommittee
({
dateDesc
:
committeeInfo
.
dateDesc
});
if
(
res
.
code
===
200
&&
Array
.
isArray
(
res
.
data
))
{
const
source
=
res
.
data
.
map
(
item
=>
({
id
:
`
${
item
.
orgType
||
""
}
-
${
item
.
orgName
||
""
}
`
,
name
:
item
.
orgName
,
chamber
:
getChamberLabel
(
item
.
orgType
),
count
:
Number
(
item
.
count
||
0
)
}))
.
filter
(
item
=>
!
committeeInfo
.
keyWord
||
item
.
name
?.
includes
(
committeeInfo
.
keyWord
))
.
sort
((
a
,
b
)
=>
(
b
.
count
||
0
)
-
(
a
.
count
||
0
));
committeeInfo
.
total
=
source
.
length
;
const
start
=
(
committeeInfo
.
pageNum
-
1
)
*
committeeInfo
.
pageSize
;
committeeInfo
.
list
=
source
.
slice
(
start
,
start
+
committeeInfo
.
pageSize
);
}
else
{
committeeInfo
.
total
=
0
;
committeeInfo
.
list
=
[];
}
}
catch
(
error
)
{
committeeInfo
.
total
=
0
;
committeeInfo
.
list
=
[];
}
committeeInfo
.
loading
=
false
;
};
const
handleDateChange
=
event
=>
{
committeeInfo
.
dateDesc
=
event
?.
time
||
"近一年"
;
onAllCommittee
();
};
const
handleToDataLibrary
=
item
=>
{
const
route
=
router
.
resolve
({
path
:
"/dataLibrary/countryBill"
,
query
:
{
selectedOrg
:
item
.
name
,
selectedCongress
:
item
.
chamber
}
});
window
.
open
(
route
.
href
,
"_blank"
);
};
const
refCommittee
=
ref
();
onMounted
(()
=>
{
let
height
=
2
;
if
(
refCommittee
.
value
)
{
height
=
Math
.
floor
(
refCommittee
.
value
?.
clientHeight
/
120
);
}
committeeInfo
.
pageSize
=
height
*
4
;
onAllCommittee
();
});
</
script
>
<
style
scoped
lang=
"scss"
>
.view-box
{
width
:
100%
;
height
:
100%
;
background
:
url("../billHome/assets/images/background.png")
,
linear-gradient
(
180deg
,
rgba
(
229
,
241
,
254
,
1
)
0%
,
rgba
(
246
,
251
,
255
,
0
)
30%
);
background-size
:
100%
100%
;
display
:
flex
;
justify-content
:
center
;
.container-box
{
width
:
1600px
;
padding
:
50px
0
20px
;
display
:
flex
;
flex-direction
:
column
;
.hard-box
{
display
:
flex
;
align-items
:
center
;
width
:
100%
;
.hard-name
{
color
:
var
(
--
text-primary-90-color
);
height
:
62px
;
line-height
:
62px
!
important
;
}
.hard-num
{
height
:
36px
;
background-color
:
var
(
--
color-primary-100
);
color
:
var
(
--
bg-white-100
);
border-radius
:
18px
;
line-height
:
36px
!
important
;
padding
:
0
16px
;
margin-left
:
16px
;
}
.hard-input
{
background-color
:
var
(
--
el-fill-color-blank
);
border-radius
:
var
(
--
el-border-radius-base
);
box-shadow
:
0
0
0
1px
var
(
--
el-border-color
)
inset
;
box-sizing
:
border-box
;
margin-left
:
20px
;
width
:
180px
;
height
:
32px
;
}
}
.date-box
{
margin-top
:
6px
;
display
:
flex
;
align-items
:
center
;
width
:
100%
;
.date-text
{
width
:
20px
;
flex
:
auto
;
font-size
:
18px
;
line-height
:
18px
;
font-family
:
Source
Han
Sans
CN
;
color
:
var
(
--
text-primary-80-color
);
}
}
.committee-list
{
width
:
100%
;
height
:
20px
;
padding
:
16px
0
;
margin-top
:
10px
;
flex
:
auto
;
display
:
grid
;
grid-template-columns
:
repeat
(
4
,
1fr
);
grid-auto-rows
:
104px
;
gap
:
16px
;
.committee-item
{
padding
:
0
16px
;
display
:
flex
;
background
:
rgba
(
255
,
255
,
255
,
0
.65
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
1
);
border-radius
:
10px
;
box-shadow
:
0
0
20px
0
rgba
(
25
,
69
,
130
,
0
.1
);
align-items
:
center
;
cursor
:
pointer
;
transition
:
transform
0
.3s
ease
,
box-shadow
0
.3s
ease
;
&
:hover
{
transform
:
translateY
(
-3px
);
box-shadow
:
0
4px
16px
rgba
(
0
,
0
,
0
,
0
.15
);
}
.item-left
{
width
:
48px
;
height
:
48px
;
margin-right
:
12px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.item-right
{
flex
:
auto
;
min-width
:
0
;
.item-name
{
font-size
:
20px
;
font-weight
:
700
;
line-height
:
22px
;
color
:
var
(
--
text-primary-80-color
);
}
.item-chamber
{
margin-top
:
6px
;
font-size
:
14px
;
color
:
var
(
--
text-primary-50-color
);
}
}
.item-total
{
font-size
:
20px
;
font-weight
:
700
;
line-height
:
20px
;
color
:
var
(
--
color-primary-100
);
margin-right
:
6px
;
}
}
}
.pagination-box
{
display
:
flex
;
justify-content
:
center
;
}
}
}
</
style
>
src/views/bill/billHome/ResourceLibrarySection.vue
浏览文件 @
a1036c92
...
...
@@ -93,7 +93,7 @@
<div
class=
"item"
>
<div
class=
"item-left"
>
法案进展:
</div>
<div
class=
"item-right2"
>
<div
class=
"tag"
v-for=
"(val, idx) in
getReversedProgress(item.progress)
"
:key=
"`$
{item.billId}-${val}-${idx}`" :style="{ zIndex: item.progress.length - idx }">
{{
val
}}
</div>
<div
class=
"tag"
v-for=
"(val, idx) in
item.progress
"
:key=
"`$
{item.billId}-${val}-${idx}`" :style="{ zIndex: item.progress.length - idx }">
{{
val
}}
</div>
</div>
</div>
</div>
...
...
@@ -458,8 +458,6 @@ const handleClickAvatar = async member => {
}
catch
(
error
)
{
}
}
;
const
getReversedProgress
=
progress
=>
(
Array
.
isArray
(
progress
)
?
[...
progress
].
reverse
()
:
[]);
const
handleClickCommitteeBill
=
bill
=>
{
if
(
!
bill
?.
billId
)
return
;
props
.
onClickToDetail
({
...
...
src/views/bill/billHome/index.vue
浏览文件 @
a1036c92
...
...
@@ -9,34 +9,22 @@
:containerRef=
"containerRef"
areaName=
"法案"
:enableBillTypeSwitch=
"true"
defaultBillSearchType=
"federal"
/>
</div>
<div
class=
"committee-cards-section"
>
<div
class=
"committee-cards-filter"
>
<span
class=
"committee-cards-desc"
>
近期美国国会各委员会涉华提案数量汇总
</span>
<el-radio-group
v-model=
"committeeTimeRange"
class=
"committee-time-switch"
size=
"default"
>
<el-radio-button
v-for=
"item in committeeTimeOptions"
:key=
"item.value"
:value=
"item.value"
>
<span
class=
"committee-time-switch-inner"
>
<el-icon
v-if=
"committeeTimeRange === item.value"
class=
"committee-time-switch-icon"
>
<Calendar
/>
</el-icon>
{{
item
.
label
}}
</span>
</el-radio-button>
</el-radio-group>
</div>
<div
class=
"committee-cards-row"
>
<div
v-for=
"item in committeeCardList"
:key=
"item.id"
class=
"committee-card"
@
click=
"handleToDataLibrary(item)"
>
<div
class=
"committee-card-icon"
>
<img
:src=
"iconCommit"
alt=
"委员会头像"
/>
</div>
<div
class=
"committee-card-content"
>
<div
class=
"committee-card-name"
>
{{
item
.
name
}}
</div>
<div
class=
"committee-card-chamber"
>
{{
item
.
chamber
}}
</div>
</div>
<div
class=
"committee-card-count"
>
{{
item
.
count
}}
项
>
</div>
</div>
</div>
</div>
<SummaryCardsPanel
descriptionText=
"近期美国国会各委员会涉华提案数量汇总"
:cards=
"committeeCards"
:totalCount=
"committeeTotalCount"
:tipIcon=
"box7HeaderIcon"
:defaultAvatar=
"iconCommit"
:loading=
"committeeLoading"
activeTime=
"近一年"
emptyText=
"暂无数据"
moreText=
"查看全部委员会"
:moreCardMinCount=
"7"
@
time-click=
"handleCommitteeTimeClick"
@
name-click=
"handleToDataLibrary"
@
count-click=
"handleToDataLibrary"
@
more-click=
"handleToCommitteeMore"
/>
<DivideHeader
id=
"position1"
class=
"divide1"
:titleText=
"'最新动态'"
></DivideHeader>
<div
class=
"home-content-center"
>
...
...
@@ -256,6 +244,7 @@
<
script
setup
>
import
RiskSignal
from
"@/components/base/riskSignal/index.vue"
;
import
SummaryCardsPanel
from
"@/components/base/SummaryCardsPanel/index.vue"
;
import
{
onMounted
,
ref
,
onUnmounted
,
nextTick
,
watch
,
computed
}
from
"vue"
;
import
router
from
"@/router/index"
;
import
setChart
from
"@/utils/setChart"
;
...
...
@@ -275,7 +264,7 @@ import {
import
{
getPersonSummaryInfo
}
from
"@/api/common/index"
;
import
{
getChartAnalysis
}
from
"@/api/aiAnalysis/index"
;
import
DivideHeader
from
"@/components/DivideHeader.vue"
;
import
o
verviewMainBox
from
"@/components/base/boxBackground/overviewMainBox.vue"
;
import
O
verviewMainBox
from
"@/components/base/boxBackground/overviewMainBox.vue"
;
import
OverviewCard
from
"./OverviewCard.vue"
;
import
ResourceLibrarySection
from
"./ResourceLibrarySection.vue"
;
import
{
useContainerScroll
}
from
"@/hooks/useScrollShow"
;
...
...
@@ -294,7 +283,6 @@ import box7HeaderIcon from "./assets/images/box7-header-icon.png";
import
iconCommit
from
"./assets/icons/icon-commit.png"
;
import
{
ElMessage
}
from
"element-plus"
;
import
{
Calendar
}
from
"@element-plus/icons-vue"
;
import
{
useGotoNewsDetail
}
from
"@/router/modules/news"
;
// 跳转人物主页(MessageBubble 的 person-click 传入整条列表项,需取 personId)
...
...
@@ -368,14 +356,11 @@ const handleClickToCharacter = async item => {
const
containerRef
=
ref
(
null
);
const
{
isShow
}
=
useContainerScroll
(
containerRef
);
// 委员会卡片占位数据
const
committeeTimeRange
=
ref
(
"近一月"
);
const
committeeTimeOptions
=
[
{
label
:
"近一周"
,
value
:
"近一周"
},
{
label
:
"近一月"
,
value
:
"近一月"
},
{
label
:
"近一年"
,
value
:
"近一年"
}
];
// 委员会卡片数据
const
committeeTimeRange
=
ref
(
"近一年"
);
const
committeeCardList
=
ref
([]);
const
committeeTotalCount
=
ref
(
0
);
const
committeeLoading
=
ref
(
false
);
const
getChamberLabel
=
orgType
=>
{
if
(
orgType
===
"Senate"
)
return
"参议院"
;
...
...
@@ -383,27 +368,51 @@ const getChamberLabel = orgType => {
return
orgType
||
""
;
};
const
committeeCards
=
computed
(()
=>
{
return
committeeCardList
.
value
.
map
(
item
=>
({
id
:
item
.
id
,
name
:
item
.
name
,
subText
:
item
.
chamber
,
count
:
item
.
count
}));
});
const
handleGetCommitteeBillCount
=
async
()
=>
{
committeeLoading
.
value
=
true
;
try
{
const
res
=
await
getStatisticsBillCountByCommittee
({
dateDesc
:
committeeTimeRange
.
value
});
if
(
res
.
code
===
200
&&
Array
.
isArray
(
res
.
data
))
{
co
mmitteeCardList
.
value
=
res
.
data
co
nst
mappedList
=
res
.
data
.
map
(
item
=>
({
id
:
`
${
item
.
orgType
||
""
}
-
${
item
.
orgName
||
""
}
`
,
name
:
item
.
orgName
,
chamber
:
getChamberLabel
(
item
.
orgType
),
count
:
Number
(
item
.
count
||
0
)
}))
.
sort
((
a
,
b
)
=>
(
b
.
count
||
0
)
-
(
a
.
count
||
0
))
.
slice
(
0
,
3
);
.
sort
((
a
,
b
)
=>
(
b
.
count
||
0
)
-
(
a
.
count
||
0
));
committeeTotalCount
.
value
=
mappedList
.
length
;
committeeCardList
.
value
=
mappedList
.
slice
(
0
,
3
);
}
else
{
committeeTotalCount
.
value
=
0
;
committeeCardList
.
value
=
[];
}
}
catch
(
error
)
{
committeeTotalCount
.
value
=
0
;
committeeCardList
.
value
=
[];
}
committeeLoading
.
value
=
false
;
};
const
handleCommitteeTimeClick
=
event
=>
{
committeeTimeRange
.
value
=
event
?.
time
||
"近一年"
;
handleGetCommitteeBillCount
();
};
const
handleToCommitteeMore
=
()
=>
{
const
route
=
router
.
resolve
({
path
:
"/bill/allCommittee"
});
window
.
open
(
route
.
href
,
"_blank"
);
};
const
hotBillList
=
ref
([]);
// 热门法案列表
...
...
@@ -1029,14 +1038,15 @@ const handleBox6 = async () => {
// 涉华领域分布
const
box9ChartData
=
ref
([]);
const
box9selectetedTime
=
ref
(
"2025"
);
// 立法状态下拉:提出法案、众议院通过、参议院通过、解决分歧、完成立法
// v-model 存储的是接口需要的 status 值
const
box9LegislativeStatus
=
ref
(
"提案"
);
// 立法状态下拉:提出法案、众议院通过、参议院通过、解决分歧、
呈交总统、
完成立法
// v-model 存储的是接口需要的 status 值
(直接作为接口参数)
const
box9LegislativeStatus
=
ref
(
"提
出法
案"
);
const
box9LegislativeStatusList
=
ref
([
{
label
:
"提出法案"
,
value
:
"提案"
},
{
label
:
"提出法案"
,
value
:
"提
出法
案"
},
{
label
:
"众议院通过"
,
value
:
"众议院通过"
},
{
label
:
"参议院通过"
,
value
:
"参议院通过"
},
{
label
:
"解决分歧"
,
value
:
"分歧已解决"
},
{
label
:
"解决分歧"
,
value
:
"解决分歧"
},
{
label
:
"呈交总统"
,
value
:
"呈交总统"
},
{
label
:
"完成立法"
,
value
:
"完成立法"
}
]);
const
box9YearList
=
ref
([
...
...
@@ -1063,18 +1073,39 @@ const box9YearList = ref([
]);
const
box9HasData
=
ref
(
true
);
let
box9ChartInstance
=
null
;
const
BOX9_MAX_DOMAIN_COUNT
=
7
;
const
BOX9_OTHER_DOMAIN_NAME
=
"其他"
;
const
formatBox9DomainData
=
(
list
=
[])
=>
{
if
(
!
Array
.
isArray
(
list
)
||
list
.
length
<=
BOX9_MAX_DOMAIN_COUNT
)
{
return
list
;
}
const
topDomainList
=
list
.
slice
(
0
,
BOX9_MAX_DOMAIN_COUNT
);
const
otherDomainCount
=
list
.
slice
(
BOX9_MAX_DOMAIN_COUNT
).
reduce
((
sum
,
item
)
=>
{
return
sum
+
Number
(
item
?.
countBill
||
0
);
},
0
);
if
(
!
otherDomainCount
)
{
return
topDomainList
;
}
return
[
...
topDomainList
,
{
industryName
:
BOX9_OTHER_DOMAIN_NAME
,
countBill
:
otherDomainCount
}
];
};
const
getBox9Data
=
async
()
=>
{
chartLoading
.
value
=
{
...
chartLoading
.
value
,
box6
:
true
};
const
params
=
{
year
:
box9selectetedTime
.
value
,
sta
tus
:
box9LegislativeStatus
.
value
sta
geName
:
box9LegislativeStatus
.
value
};
try
{
const
res
=
await
getBillIndustry
(
params
);
console
.
log
(
"box9-涉华法案领域分布"
,
res
.
data
);
if
(
res
.
code
===
200
&&
res
.
data
&&
res
.
data
.
length
>
0
)
{
box9HasData
.
value
=
true
;
box9ChartData
.
value
=
res
.
data
;
box9ChartData
.
value
=
formatBox9DomainData
(
res
.
data
)
;
}
else
{
box9HasData
.
value
=
false
;
box9ChartData
.
value
=
[];
...
...
@@ -1104,16 +1135,9 @@ const handleBox9Data = async () => {
const
selectedIndex
=
box9LegislativeStatusList
.
value
.
findIndex
(
item
=>
item
.
value
===
box9LegislativeStatus
.
value
);
const
arr
=
[
{
label
:
"提出法案"
,
value
:
"提案"
},
{
label
:
"众议院通过"
,
value
:
"众议院通过"
},
{
label
:
"参议院通过"
,
value
:
"参议院通过"
},
{
label
:
"解决分歧"
,
value
:
"分歧已解决"
},
{
label
:
"完成立法"
,
value
:
"完成立法"
}
]
const
status
=
arr
.
filter
(
item
=>
{
return
item
.
value
===
box9LegislativeStatus
.
value
})[
0
].
label
// 当前选中的立法状态中文名(直接等于接口传参值)
const
statusItem
=
box9LegislativeStatusList
.
value
[
selectedIndex
];
const
status
=
statusItem
?
statusItem
.
label
:
""
;
const
selectParam
=
{
moduleType
:
'国会法案'
,
key
:
2
,
...
...
@@ -1272,13 +1296,14 @@ const getBox8ChartOption = stageList => {
const
handleBox8Data
=
async
()
=>
{
chartLoading
.
value
=
{
...
chartLoading
.
value
,
box8
:
true
};
// 进展分布显示顺序:提出法案(对应进度“提案”)、众议院通过、参议院通过、
分歧已解决(解决分歧)
、完成立法
const
stageOrder
=
[
"提案"
,
"众议院通过"
,
"参议院通过"
,
"分歧已解决"
,
"完成立法"
];
// 进展分布显示顺序:提出法案(对应进度“提案”)、众议院通过、参议院通过、
解决分歧(对应进度“分歧已解决”)、呈交总统
、完成立法
const
stageOrder
=
[
"提案"
,
"众议院通过"
,
"参议院通过"
,
"分歧已解决"
,
"
呈交总统"
,
"
完成立法"
];
const
stageNameMap
=
{
提案
:
"提出法案"
,
众议院通过
:
"众议院通过"
,
参议院通过
:
"参议院通过"
,
分歧已解决
:
"解决分歧"
,
呈交总统
:
"呈交总统"
,
完成立法
:
"完成立法"
};
...
...
@@ -1347,13 +1372,9 @@ watch(box8selectetedTime, () => {
handleBox8Data
();
});
watch
(
committeeTimeRange
,
()
=>
{
onMounted
(()
=>
{
handleGetCommitteeBillCount
();
},
{
immediate
:
true
}
);
});
const
handleToPosi
=
id
=>
{
const
element
=
document
.
getElementById
(
id
);
...
...
@@ -1389,16 +1410,25 @@ const handleResize = () => {
// 下钻至资源库
const
handleToDataLibrary
=
(
item
)
=>
{
// console.log('item', item);
const
selectParam
=
{
selectedOrg
:
item
.
name
,
selectedCongress
:
item
.
chamber
window
.
sessionStorage
.
setItem
(
"curTabName"
,
item
.
id
);
const
curRoute
=
router
.
resolve
({
path
:
"/institution"
,
query
:
{
id
:
item
.
id
}
const
route
=
router
.
resolve
({
path
:
"/dataLibrary/countryBill"
,
query
:
selectParam
});
window
.
open
(
route
.
href
,
"_blank"
);
window
.
open
(
curRoute
.
href
,
"_blank"
);
// console.log('item', item);
// const selectParam = {
// selectedOrg: item.name,
// selectedCongress: item.chamber
// }
// const route = router.resolve({
// path: "/dataLibrary/countryBill",
// query: selectParam
// });
// window.open(route.href, "_blank");
}
...
...
src/views/bill/billLayout/index.vue
浏览文件 @
a1036c92
...
...
@@ -134,9 +134,10 @@ const handleAnalysisClick = analysisType => {
// 进展预测 -> 法案简介页(法案进展)
if
(
analysisType
===
"forsee"
)
{
router
.
push
({
const
target
=
router
.
resolve
({
path
:
`/billLayout/ProgressForecast/
${
billId
}
`
,
});
window
.
open
(
target
.
href
,
"_blank"
);
return
;
}
...
...
src/views/bill/deepDig/processAnalysis/index.vue
浏览文件 @
a1036c92
<
template
>
<BillPageShell
class=
"wrap"
>
<BillPageShell>
<div
class=
"wrap"
>
<div
class=
"left"
>
<div
class=
"box1"
>
<!--
<div
class=
"box-header"
>
<div
class=
"header-left"
></div>
<div
class=
"title"
>
典型阶段耗时
</div>
<div
class=
"header-right"
>
<div
class=
"icon"
>
<img
src=
"@/assets/icons/box-header-icon1.png"
alt=
""
/>
</div>
<div
class=
"icon"
>
<img
src=
"@/assets/icons/box-header-icon2.png"
alt=
""
/>
</div>
<div
class=
"icon"
>
<img
src=
"@/assets/icons/box-header-icon3.png"
alt=
""
/>
</div>
</div>
</div>
<div
class=
"box1-main"
>
<div
class=
"box1-main-center"
id=
"chart1"
></div>
<div
class=
"box1-main-footer"
>
<div
class=
"box-footer-left"
>
<img
src=
"@/assets/icons/box-footer-left-icon.png"
alt=
""
/>
</div>
<div
class=
"box-footer-center"
>
从立法耗时角度分析,大而美法案从提交到签署仅39天,远快于历史同类法案(通常需6个月以上),立法速度极快。
</div>
<div
class=
"box-footer-right"
>
<img
src=
"../assets/icons/arrow-right.png"
alt=
""
/>
</div>
</div>
</div>
-->
<AnalysisBox
title=
"典型阶段耗时分析"
>
<div
class=
"analysis-ai-wrapper analysis-ai-wrapper--box1"
>
<div
class=
"box1-main"
:class=
"
{ 'box1-main--full': !timeFooterText }">
...
...
@@ -48,7 +20,7 @@
</div>
</div>
<div
v-if=
"!aiPaneVisible.box1"
class=
"analysis-ai-tip-row"
>
<TipTab
class=
"analysis-ai-tip"
:text=
"'与历史同类法案的典型阶段耗时对比分析,数据来源:美国国会官网'"
/>
<TipTab
class=
"analysis-ai-tip"
:text=
"'与历史同类法案的典型阶段耗时对比分析,数据来源:美国国会官网'"
/>
<AiButton
class=
"analysis-ai-tip-action"
@
mouseenter=
"handleShowAiPane('box1')"
/>
</div>
<div
v-if=
"aiPaneVisible.box1"
class=
"analysis-ai-pane"
@
mouseleave=
"handleHideAiPane('box1')"
>
...
...
@@ -58,36 +30,6 @@
</AnalysisBox>
</div>
<div
class=
"box2"
>
<!--
<div
class=
"box-header"
>
<div
class=
"header-left"
></div>
<div
class=
"title"
>
修正案次数分析
</div>
<div
class=
"header-right"
>
<div
class=
"icon"
>
<img
src=
"@/assets/icons/box-header-icon1.png"
alt=
""
/>
</div>
<div
class=
"icon"
>
<img
src=
"@/assets/icons/box-header-icon2.png"
alt=
""
/>
</div>
<div
class=
"icon"
>
<img
src=
"@/assets/icons/box-header-icon3.png"
alt=
""
/>
</div>
</div>
</div>
<div
class=
"box2-main"
>
<div
class=
"box2-main-center"
id=
"chart2"
></div>
<div
class=
"box2-main-footer"
>
<div
class=
"box-footer-left"
>
<img
src=
"@/assets/icons/box-footer-left-icon.png"
alt=
""
/>
</div>
<div
class=
"box-footer-center"
>
法案本质是共和党与资本集团的深度联盟,共和党获超
80%利益集团献金,以减税、松监管、军工扩张为核心回报。
</div>
<div
class=
"box-footer-right"
>
<img
src=
"../assets/icons/arrow-right.png"
alt=
""
/>
</div>
</div>
</div>
-->
<AnalysisBox
title=
"修正案次数分析"
>
<div
class=
"analysis-ai-wrapper analysis-ai-wrapper--box2"
>
<div
class=
"box2-main"
:class=
"
{ 'box2-main--full': !amendFooterText }">
...
...
@@ -105,7 +47,7 @@
</div>
</div>
<div
v-if=
"!aiPaneVisible.box2"
class=
"analysis-ai-tip-row"
>
<TipTab
class=
"analysis-ai-tip"
:text=
"'与历史同类法案的修正案次数对比分析,数据来源:美国国会官网'"
/>
<TipTab
class=
"analysis-ai-tip"
:text=
"'与历史同类法案的修正案次数对比分析,数据来源:美国国会官网'"
/>
<AiButton
class=
"analysis-ai-tip-action"
@
mouseenter=
"handleShowAiPane('box2')"
/>
</div>
<div
v-if=
"aiPaneVisible.box2"
class=
"analysis-ai-pane"
@
mouseleave=
"handleHideAiPane('box2')"
>
...
...
@@ -117,272 +59,6 @@
</div>
<div
class=
"right"
>
<div
class=
"box3"
>
<!--
<div
class=
"box-header"
>
<div
class=
"header-left"
></div>
<div
class=
"title"
>
投票分析
</div>
<div
class=
"header-right"
>
<div
class=
"icon"
>
<img
src=
"@/assets/icons/box-header-icon1.png"
alt=
""
/>
</div>
<div
class=
"icon"
>
<img
src=
"@/assets/icons/box-header-icon2.png"
alt=
""
/>
</div>
<div
class=
"icon"
>
<img
src=
"@/assets/icons/box-header-icon3.png"
alt=
""
/>
</div>
</div>
</div>
<div
class=
"box3-main"
>
<div
class=
"box3-main-center"
>
<div
class=
"box3-main-center-header"
>
<div
class=
"box3-main-center-header-box1"
>
立法阶段
</div>
<div
class=
"box3-main-center-header-box2"
>
票数
</div>
<div
class=
"box3-main-center-header-box3"
>
平均区间
</div>
<div
class=
"box3-main-center-header-box4"
>
占比
</div>
<div
class=
"box3-main-center-header-box5"
>
倒戈人数
<span
style=
"font-weight: normal; display: inline-block"
>
(平均区间)
</span>
</div>
<div
class=
"box3-main-center-header-box6"
>
关键议员
</div>
</div>
<div
class=
"box3-main-center-content"
>
<div
class=
"box3-main-center-content-box"
v-for=
"item in voteAnalysisList"
:key=
"item.actionId"
>
<div
class=
"item"
>
<div
class=
"item-box1"
>
<div
class=
"box1-left"
>
<div
style=
"width: 100%; display: flex; flex-direction: column; align-items: flex-end"
>
<div
class=
"name nameBlod"
:title=
"item.actionTitle"
style=
"
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
"
>
{{
item
.
actionTitle
}}
</div>
<div
class=
"time"
>
{{
formatDate
(
item
.
actionDate
)
}}
</div>
</div>
</div>
<div
class=
"box1-right"
>
<div
class=
"box1-right-top"
>
<el-progress
:percentage=
"Number(item.agreePercent)"
:show-text=
"false"
color=
"rgb(33, 129, 57)"
>
</el-progress>
</div>
<div
class=
"box1-right-bottom"
>
<el-progress
:percentage=
"Number(item.againstPercent)"
:show-text=
"false"
color=
"rgb(206, 79, 81)"
>
</el-progress>
</div>
</div>
</div>
<div
class=
"item-box2"
>
<div
class=
"box2-1"
style=
"color: rgb(33, 129, 57)"
>
{{
item
.
agreeCount
+
"票"
}}
</div>
<div
class=
"box2-2"
style=
"color: rgb(206, 79, 81)"
>
{{
item
.
againstCount
+
"票"
}}
</div>
</div>
<div
class=
"item-box3"
>
<div
class=
"box3-1"
></div>
<div
class=
"box3-2"
></div>
</div>
<div
class=
"item-box4"
>
<div
class=
"box4-1"
style=
"color: rgb(33, 129, 57)"
>
{{
item
.
agreePercent
+
"%"
}}
</div>
<div
class=
"box4-2"
style=
"color: rgb(206, 79, 81)"
>
{{
item
.
againstPercent
+
"%"
}}
</div>
</div>
<div
class=
"item-box5"
></div>
<div
class=
"item-box6"
>
<el-icon
size=
"20"
color=
"#555"
>
<ArrowDownBold
/>
</el-icon>
</div>
</div>
<div
class=
"item"
>
<div
class=
"item-box1"
>
<div
class=
"box1-left"
>
<div
class=
"icon"
>
<img
:src=
"MZD"
alt=
""
/>
</div>
<div
class=
"name"
>
民主党
</div>
</div>
<div
class=
"box1-right"
>
<div
class=
"box1-right-top"
>
<el-progress
:percentage=
"Number(item.dagreePercent)"
:show-text=
"false"
color=
"rgb(33, 129, 57)"
>
</el-progress>
</div>
<div
class=
"box1-right-bottom"
>
<el-progress
:percentage=
"Number(item.dagainstPercent)"
:show-text=
"false"
color=
"rgb(206, 79, 81)"
>
</el-progress>
</div>
</div>
</div>
<div
class=
"item-box2"
>
<div
class=
"box2-1"
style=
"color: rgb(33, 129, 57)"
>
{{
item
.
dagreeCount
+
"票"
}}
</div>
<div
class=
"box2-2"
style=
"color: rgb(206, 79, 81)"
>
{{
item
.
dagainstCount
+
"票"
}}
</div>
</div>
<div
class=
"item-box3"
></div>
<div
class=
"item-box4"
>
<div
class=
"box4-1"
style=
"color: rgb(33, 129, 57)"
>
{{
item
.
dagreePercent
+
"%"
}}
</div>
<div
class=
"box4-2"
style=
"color: rgb(206, 79, 81)"
>
{{
item
.
dagainstPercent
+
"%"
}}
</div>
</div>
<div
class=
"item-box5"
>
<div
class=
"box5-1"
style=
"color: #ce4f51"
>
{{
item
.
dreverseCount
+
"人"
}}
</div>
</div>
<div
class=
"item-box6"
>
<div
class=
"img-box"
v-if=
"item.dpersonImageUrl"
>
<img
:src=
"item.dpersonImageUrl"
alt=
""
/>
</div>
</div>
</div>
<div
class=
"item"
>
<div
class=
"item-box1"
>
<div
class=
"box1-left"
>
<div
class=
"icon"
>
<img
:src=
"GHD"
alt=
""
/>
</div>
<div
class=
"name"
>
共和党
</div>
</div>
<div
class=
"box1-right"
>
<div
class=
"box1-right-top"
>
<el-progress
:percentage=
"Number(item.ragreePercent)"
:show-text=
"false"
color=
"rgb(33, 129, 57)"
>
</el-progress>
</div>
<div
class=
"box1-right-bottom"
>
<el-progress
:percentage=
"Number(item.ragainstPercent)"
:show-text=
"false"
color=
"rgb(206, 79, 81)"
>
</el-progress>
</div>
</div>
</div>
<div
class=
"item-box2"
>
<div
class=
"box2-1"
style=
"color: rgb(33, 129, 57)"
>
{{
item
.
ragreeCount
+
"票"
}}
</div>
<div
class=
"box2-2"
style=
"color: rgb(206, 79, 81)"
>
{{
item
.
ragainstCount
+
"票"
}}
</div>
</div>
<div
class=
"item-box3"
></div>
<div
class=
"item-box4"
>
<div
class=
"box4-1"
style=
"color: rgb(33, 129, 57)"
>
{{
item
.
ragreePercent
+
"%"
}}
</div>
<div
class=
"box4-2"
style=
"color: rgb(206, 79, 81)"
>
{{
item
.
ragainstPercent
+
"%"
}}
</div>
</div>
<div
class=
"item-box5"
>
<div
class=
"box5-1"
style=
"color: #ce4f51"
>
{{
item
.
rreverseCount
+
"人"
}}
</div>
</div>
<div
class=
"item-box6"
>
<div
class=
"img-box"
v-if=
"item.rpersonImageUrl"
>
<img
:src=
"item.rpersonImageUrl"
alt=
""
/>
</div>
</div>
</div>
</div>
<div
class=
"item"
v-for=
"(item, index) in voteAnalysisList3"
:key=
"index"
>
<div
class=
"item-box1"
>
<div
class=
"box1-left"
>
<div
class=
"icon"
v-if=
"item.nameIcon"
>
<img
:src=
"item.nameIcon"
alt=
""
/>
</div>
<div>
<div
class=
"name"
:class=
"
{ nameBlod: item.nameBold }">
{{
item
.
name
}}
</div>
<div
class=
"time"
>
{{
item
.
time
}}
</div>
</div>
</div>
<div
class=
"box1-right"
>
<div
class=
"box1-right-top"
>
<el-progress
:percentage=
"item.supportRate"
:show-text=
"false"
>
</el-progress>
</div>
<div
class=
"box1-right-bottom"
>
<el-progress
:percentage=
"item.againistRate"
:show-text=
"false"
>
</el-progress>
</div>
</div>
</div>
<div
class=
"item-box2"
>
<div
class=
"box2-1"
>
{{
item
.
support
+
"票"
}}
</div>
<div
class=
"box2-2"
>
{{
item
.
againist
+
"票"
}}
</div>
</div>
<div
class=
"item-box3"
>
<div
class=
"box3-1"
>
{{
item
.
supportRank
}}
</div>
<div
class=
"box3-2"
>
{{
item
.
againistRank
}}
</div>
</div>
<div
class=
"item-box4"
>
<div
class=
"box4-1"
>
{{
item
.
supportRate
+
"%"
}}
</div>
<div
class=
"box4-2"
>
{{
item
.
againistRate
+
"%"
}}
</div>
</div>
<div
class=
"item-box5"
>
<div
class=
"box5-1"
v-if=
"item.people"
>
{{
item
.
people
+
' 人'
}}
</div>
<div
class=
"box5-2"
v-if=
"item.peopleRank"
>
{{
`( ${item.peopleRank
}
)`
}}
<
/div
>
<
/div
>
<
div
class
=
"item-box6"
>
<
div
class
=
"img-box"
v
-
if
=
"item.keyUser"
>
<
img
:
src
=
"item.keyUser"
alt
=
""
/>
<
/div
>
<
div
v
-
else
>
<
el
-
icon
size
=
"20"
color
=
"#555"
><
ArrowDownBold
/><
/el-icon
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
div
class
=
"box3-main-center-content-box"
>
<
div
class
=
"item"
v
-
for
=
"(item, index) in voteAnalysisList4"
:
key
=
"index"
>
<
div
class
=
"item-box1"
>
<
div
class
=
"box1-left"
>
<
div
class
=
"icon"
v
-
if
=
"item.nameIcon"
>
<
img
:
src
=
"item.nameIcon"
alt
=
""
/>
<
/div
>
<
div
>
<
div
class
=
"name"
:
class
=
"{ nameBlod: item.nameBold
}
"
>
{{
item
.
name
}}
<
/div
>
<
div
class
=
"time"
>
{{
item
.
time
}}
<
/div
>
<
/div
>
<
/div
>
<
div
class
=
"box1-right"
>
<
div
class
=
"box1-right-top"
>
<
el
-
progress
:
percentage
=
"item.supportRate"
:
show
-
text
=
"false"
>
<
/el-progress
>
<
/div
>
<
div
class
=
"box1-right-bottom"
>
<
el
-
progress
:
percentage
=
"item.againistRate"
:
show
-
text
=
"false"
>
<
/el-progress
>
<
/div
>
<
/div
>
<
/div
>
<
div
class
=
"item-box2"
>
<
div
class
=
"box2-1"
>
{{
item
.
support
+
"票"
}}
<
/div
>
<
div
class
=
"box2-2"
>
{{
item
.
againist
+
"票"
}}
<
/div
>
<
/div
>
<
div
class
=
"item-box3"
>
<
div
class
=
"box3-1"
>
{{
item
.
supportRank
}}
<
/div
>
<
div
class
=
"box3-2"
>
{{
item
.
againistRank
}}
<
/div
>
<
/div
>
<
div
class
=
"item-box4"
>
<
div
class
=
"box4-1"
>
{{
item
.
supportRate
+
"%"
}}
<
/div
>
<
div
class
=
"box4-2"
>
{{
item
.
againistRate
+
"%"
}}
<
/div
>
<
/div
>
<
div
class
=
"item-box5"
>
<
div
class
=
"box5-1"
v
-
if
=
"item.people"
>
{{
item
.
people
+
' 人'
}}
<
/div
>
<
div
class
=
"box5-2"
v
-
if
=
"item.peopleRank"
>
{{
`( ${item.peopleRank
}
)`
}}
<
/div
>
<
/div
>
<
div
class
=
"item-box6"
>
<
div
class
=
"img-box"
v
-
if
=
"item.keyUser"
>
<
img
:
src
=
"item.keyUser"
alt
=
""
/>
<
/div
>
<
div
v
-
else
>
<
el
-
icon
size
=
"20"
color
=
"#555"
><
ArrowDownBold
/><
/el-icon
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
div
class
=
"box3-main-footer"
>
<
div
class
=
"box-footer-left"
>
<
img
src
=
"@/assets/icons/box-footer-left-icon.png"
alt
=
""
/>
<
/div
>
<
div
class
=
"box-footer-center"
>
法案以
218
:
214
(众议院)和
51
:
50
(副总统决胜票)微弱优势强行通过,暴露两党极端对立、党内倒戈频发的特点。
<
/div
>
<
div
class
=
"box-footer-right"
>
<
img
src
=
"../assets/icons/arrow-right.png"
alt
=
""
/>
<
/div
>
<
/div
>
<
/div> --
>
<AnalysisBox
title=
"投票分析"
>
<div
class=
"vote-legend"
>
<div
class=
"vote-legend-item"
>
...
...
@@ -716,6 +392,7 @@
<
/AnalysisBox
>
<
/div
>
<
/div
>
<
/div
>
<
/BillPageShell
>
<
/template
>
...
...
@@ -1169,6 +846,10 @@ onMounted(async () => {
<
style
lang
=
"scss"
scoped
>
.
wrap
{
display
:
flex
;
flex
-
direction
:
row
;
flex
-
wrap
:
nowrap
;
direction
:
ltr
;
justify
-
content
:
flex
-
start
;
margin
-
bottom
:
30
px
;
...
...
@@ -1254,6 +935,8 @@ onMounted(async () => {
.
left
{
margin
-
top
:
16
px
;
width
:
792
px
;
flex
:
0
0
792
px
;
.
box1
{
width
:
792
px
;
...
...
@@ -1651,6 +1334,7 @@ onMounted(async () => {
margin
-
left
:
16
px
;
margin
-
top
:
16
px
;
width
:
792
px
;
flex
:
0
0
792
px
;
height
:
847
px
;
.
box3
{
...
...
src/views/bill/influence/industry/index.vue
浏览文件 @
a1036c92
<
template
>
<div
class=
"industry-wrap"
>
<BillPageShell>
<div
class=
"wrap"
>
<div
class=
"left"
>
<AnalysisBox
title=
"涉及行业"
:showAllBtn=
"false"
width=
"100%"
height=
"100%"
>
<div
class=
"left-main"
>
<div
class=
"left-center"
>
<el-select
v-model=
"curHylyId"
placeholder=
"请选择领域"
class=
"left-center-select"
@
change=
"handleIndustryChange"
>
<el-option
v-for=
"item in industryList"
:key=
"item.id"
:label=
"item.name || item.hylymc"
:value=
"item.id"
/>
<el-select
v-model=
"curHylyId"
placeholder=
"请选择领域"
class=
"left-center-select"
@
change=
"handleIndustryChange"
>
<el-option
v-for=
"item in industryList"
:key=
"item.id"
:label=
"item.name || item.hylymc"
:value=
"item.id"
/>
</el-select>
<el-input
v-model=
"companySearchKeyword"
placeholder=
"搜索实体"
class=
"left-center-search"
:suffix-icon=
"Search"
clearable
/>
<el-input
v-model=
"companySearchKeyword"
placeholder=
"搜索实体"
class=
"left-center-search"
:suffix-icon=
"Search"
clearable
/>
</div>
<div
class=
"left-list"
>
<div
class=
"left-list-title"
>
实体名称
</div>
<div
class=
"left-list-content"
>
<el-empty
v-if=
"!curCompanyList?.length"
style=
"padding: 60px 0;"
description=
"暂无数据"
:image-size=
"100"
/>
<el-empty
v-if=
"!curCompanyList?.length"
style=
"padding: 60px 0;"
description=
"暂无数据"
:image-size=
"100"
/>
<el-scrollbar
v-else
height=
"100%"
always
>
<div
class=
"item-box"
>
<div
class=
"item"
<div
v-for=
"(val, idx) in curCompanyList"
:key=
"val.id"
class=
"item"
:class=
"
{ itemActive: companyActiveIndex === ((currentPage - 1) * pageSize + idx) }"
@click="handleClickCompany(val, idx)" v-for="(val, idx) in curCompanyList
"
:key="val.id"
>
@click="handleClickCompany(val, idx)
"
>
<div
class=
"item-icon"
>
<img
:src=
"defaultIcon2"
alt=
""
class=
"item-img"
/>
</div>
<div
class=
"title"
:class=
"
{ titleActive: companyActiveIndex === ((currentPage - 1) * pageSize + idx) }">
<div
class=
"title"
:class=
"
{ titleActive: companyActiveIndex === ((currentPage - 1) * pageSize + idx) }"
>
{{
val
.
name
}}
</div>
<div
class=
"icon"
>
...
...
@@ -42,28 +65,34 @@
<div
class=
"left-pagination"
>
<div
class=
"left-pagination-left"
>
{{
`共 ${filteredCompanyList.length
}
项`
}}
<
/div
>
<
div
class
=
"left-pagination-right"
>
<
el
-
pagination
@
current
-
change
=
"handleCurrentChange"
:
pageSize
=
"pageSize"
:
current
-
page
=
"currentPage"
size
=
"small"
background
layout
=
"prev, pager, next"
:
total
=
"filteredCompanyList.length"
/>
<
el
-
pagination
@
current
-
change
=
"handleCurrentChange"
:
pageSize
=
"pageSize"
:
current
-
page
=
"currentPage"
size
=
"small"
background
layout
=
"prev, pager, next"
:
total
=
"filteredCompanyList.length"
/>
<
/div
>
<
/div
>
<
/div
>
<
/AnalysisBox
>
<
/div
>
<
div
class
=
"box2
"
>
<
div
class
=
"right
"
>
<
AnalysisBox
:
showAllBtn
=
"false"
>
<
template
#
custom
-
title
>
<
div
class
=
"custom-title"
>
<
div
class
=
"title-left"
>
<
div
:
class
=
"['title-item', {'title-active': contentType==1
}
]"
@
click
=
"headerContentType(1)"
>
<
div
:
class
=
"['title-item', { 'title-active': contentType==1
}
]"
@
click
=
"headerContentType(1)"
>
<
div
class
=
"title-icon"
>
<
img
:
src
=
"contentType==1 ? icon1620 : icon1621"
alt
=
""
>
<
img
:
src
=
"contentType==1 ? icon1620 : icon1621"
alt
=
""
/
>
<
/div
>
<
div
>
产业链
<
/div
>
<
/div
>
<
div
:
class
=
"['title-item', {'title-active': contentType==2
}
]"
@
click
=
"headerContentType(2)"
>
<
div
:
class
=
"['title-item', { 'title-active': contentType==2
}
]"
@
click
=
"headerContentType(2)"
>
<
div
class
=
"title-icon"
>
<
img
:
src
=
"contentType==2 ? icon422 : icon423"
alt
=
""
>
<
img
:
src
=
"contentType==2 ? icon422 : icon423"
alt
=
""
/
>
<
/div
>
<
div
>
实体关系
<
/div
>
<
/div
>
...
...
@@ -87,6 +116,7 @@
<
/AnalysisBox
>
<
/div
>
<
/div
>
<
/BillPageShell
>
<
/template
>
<
script
setup
>
...
...
@@ -94,6 +124,8 @@ import { ref, onMounted, onBeforeUnmount, nextTick, computed, watch, reactive }
import
{
useRoute
}
from
"vue-router"
;
import
*
as
echarts
from
"echarts"
;
import
BillPageShell
from
"@/views/bill/components/layout/BillPageShell.vue"
;
const
route
=
useRoute
();
import
{
getCompanyList
,
getHylyList
,
getCompanyDetail
}
from
"@/api/influence"
;
import
getLineChart
from
"./utils/lineChart"
;
...
...
@@ -809,7 +841,7 @@ onMounted(async () => {
</script>
<style lang="scss" scoped>
.
industry-
wrap {
.wrap {
width: 100%;
height: 100%;
display: flex;
...
...
@@ -1012,7 +1044,7 @@ onMounted(async () => {
.right {
margin-top: 16px;
margin-left: 16px;
width: 1
247
px;
width: 1
104
px;
height: 847px;
position: relative;
...
...
@@ -1487,7 +1519,7 @@ onMounted(async () => {
}
.box-footer {
width: 1218px
;
width: 100%
;
height: 40px;
display: flex;
box-sizing: border-box;
...
...
@@ -1510,13 +1542,17 @@ onMounted(async () => {
.box-footer-center {
margin-left: 13px;
margin-top: 8px;
width: 1119px;
flex: 1;
min-width: 0;
height: 24px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.box-footer-right {
...
...
@@ -1539,10 +1575,10 @@ onMounted(async () => {
}
}
.
box2
{
.
right
{
margin-top: 16px;
margin-left: 16px;
width: 1
247
px;
width: 1
104
px;
height: 847px;
position: relative;
...
...
src/views/bill/template/index.vue
浏览文件 @
a1036c92
...
...
@@ -131,6 +131,7 @@
<IntelligentEntityText
:text=
"term?.fynr || ''"
:entities=
"termsHighlight ? getTermEntities(term, 'cn') : []"
:highlight=
"searchKeyword"
@
on-entity-click=
"e => gotoSearchResults(e.text_span, '')"
/>
</div>
...
...
@@ -141,6 +142,7 @@
<IntelligentEntityText
:text=
"term?.ywnr || ''"
:entities=
"termsHighlight ? getTermEntities(term, 'en') : []"
:highlight=
"searchKeyword"
@
on-entity-click=
"e => gotoSearchResults(e.text_span, '')"
/>
</div>
...
...
@@ -343,6 +345,27 @@ const chart1ColorList = ref([...MUTICHARTCOLORS]);
const
chart2ColorList
=
ref
([...
MUTICHARTCOLORS
]);
const
chart2Data
=
ref
([]);
const
DOMAIN_MAX_DISPLAY_COUNT
=
7
;
const
DOMAIN_OTHER_NAME
=
"其他"
;
const
formatDomainChartData
=
(
list
=
[])
=>
{
if
(
!
Array
.
isArray
(
list
)
||
list
.
length
<=
DOMAIN_MAX_DISPLAY_COUNT
)
{
return
list
;
}
const
topDomainList
=
list
.
slice
(
0
,
DOMAIN_MAX_DISPLAY_COUNT
);
const
otherCount
=
list
.
slice
(
DOMAIN_MAX_DISPLAY_COUNT
).
reduce
((
sum
,
item
)
=>
{
return
sum
+
Number
(
item
?.
value
||
0
);
}
,
0
);
if
(
!
otherCount
)
{
return
topDomainList
;
}
return
[
...
topDomainList
,
{
name
:
DOMAIN_OTHER_NAME
,
value
:
otherCount
}
];
}
;
const
aiPaneVisible
=
ref
({
domain
:
false
,
...
...
@@ -737,12 +760,13 @@ const handleGetBillHyly = async () => {
.
map
(
name
=>
{
return
{
label
:
name
,
value
:
name
}
;
}
);
c
hart2Data
.
value
=
res
.
data
.
map
(
item
=>
{
c
onst
domainChartData
=
res
.
data
.
map
(
item
=>
{
return
{
name
:
item
.
hylymc
,
value
:
item
.
countTk
}
;
}
);
chart2Data
.
value
=
formatDomainChartData
(
domainChartData
);
aiPaneFetched
.
value
=
{
...
aiPaneFetched
.
value
,
domain
:
false
}
;
let
chart2
=
getPieChart
(
chart2Data
.
value
,
chart2ColorList
.
value
);
...
...
src/views/coopRestriction/components/dataNew/index.vue
浏览文件 @
a1036c92
...
...
@@ -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/decree/decreeHome/index.vue
浏览文件 @
a1036c92
...
...
@@ -29,36 +29,22 @@
<div
class=
"item-footer"
>
分析报告
</div>
</div>
</div>
-->
<div
class=
"date-box"
v-if=
"keyOrganizationList.length"
>
<div
class=
"date-icon"
>
<img
:src=
"tipsTcon"
alt=
""
>
</div>
<div
class=
"date-text"
>
近期美国各联邦政府机构发布涉华政令数量汇总
</div>
<TimeTabPane
@
time-click=
"onKeyOrganization"
activeTime=
"近一年"
/>
</div>
<div
class=
"home-main-header-item-box"
v-if=
"keyOrganizationList.length"
>
<div
class=
"organization-item"
v-for=
"(item, index) in keyOrganizationList"
:key=
"index"
>
<div
class=
"item-left"
>
<img
:src=
"item.imgUrl || DefaultIcon2"
alt=
""
/>
</div>
<div
class=
"item-right one-line-ellipsis"
@
click=
"handleToInstitution(item)"
>
{{
item
.
orgName
}}
</div>
<el-popover
content=
"跳转至数据资源库"
placement=
"top"
>
<template
#
reference
>
<div
class=
"item-total"
@
click=
"handleToDataLibrary(item)"
>
{{
item
.
totalOrderNum
}}
项
</div>
</
template
>
</el-popover>
<el-icon
color=
"var(--color-primary-100)"
>
<ArrowRightBold
/>
</el-icon>
<div
class=
"item-dot"
v-if=
"item.recentOrderNum"
>
+{{ item.recentOrderNum }}
</div>
</div>
<div
class=
"organization-item"
@
click=
"onNavigateTo()"
>
<div
class=
"item-more"
>
查看全部机构 ({{ govInsList.length }}家)
</div>
<el-icon
color=
"var(--color-primary-100)"
>
<ArrowRightBold
/>
</el-icon>
</div>
</div>
<SummaryCardsPanel
descriptionText=
"近期美国各联邦政府机构发布涉华政令数量汇总"
:cards=
"keyOrganizationCards"
:totalCount=
"govInsList.length"
:tipIcon=
"tipsTcon"
:defaultAvatar=
"DefaultIcon2"
:loading=
"keyOrganizationLoading"
activeTime=
"近一年"
emptyText=
"暂无数据"
:moreCardMinCount=
"7"
moreText=
"查看全部机构"
@
time-click=
"onKeyOrganization"
@
name-click=
"handleToInstitution"
@
count-click=
"handleToDataLibrary"
@
more-click=
"onNavigateTo"
/>
</div>
<DivideHeader
id=
"position1"
class=
"divide"
:titleText=
"'最新动态'"
></DivideHeader>
<div
class=
"home-main-center"
>
...
...
@@ -421,11 +407,11 @@
</template>
<
script
setup
>
import
{
onMounted
,
ref
,
watch
,
nextTick
,
reactive
}
from
"vue"
;
import
{
onMounted
,
ref
,
watch
,
nextTick
,
reactive
,
computed
}
from
"vue"
;
import
router
from
"@/router"
;
import
WordCloudChart
from
"@/components/base/WordCloundChart/index.vue"
import
SimplePagination
from
"@/components/SimplePagination.vue"
;
import
TimeTabPane
from
'@/components/base/TimeTabPane/index.vue'
;
import
SummaryCardsPanel
from
"@/components/base/SummaryCardsPanel/index.vue"
;
import
AiButton
from
'@/components/base/Ai/AiButton/index.vue'
;
import
AiPane
from
'@/components/base/Ai/AiPane/index.vue'
;
import
{
...
...
@@ -1155,11 +1141,13 @@ const handleSwithCurDecree = name => {
// 关键机构
const
keyOrganizationList
=
ref
([]);
const
keyOrganizationLoading
=
ref
(
false
);
const
onKeyOrganization
=
async
(
event
)
=>
{
let
day
=
365
if
(
event
?.
time
===
'近一周'
)
day
=
7
if
(
event
?.
time
===
'近一月'
)
day
=
30
if
(
event
?.
time
===
'近一年'
)
day
=
365
keyOrganizationLoading
.
value
=
true
;
try
{
const
res
=
await
getKeyOrganization
({
day
});
console
.
log
(
"关键机构"
,
res
);
...
...
@@ -1167,8 +1155,20 @@ const onKeyOrganization = async (event) => {
keyOrganizationList
.
value
=
res
.
data
;
}
}
catch
(
error
)
{
}
keyOrganizationLoading
.
value
=
false
;
}
const
keyOrganizationCards
=
computed
(()
=>
{
return
keyOrganizationList
.
value
.
map
(
item
=>
({
...
item
,
id
:
item
.
orgId
,
name
:
item
.
orgName
,
avatar
:
item
.
imgUrl
,
count
:
item
.
totalOrderNum
,
delta
:
item
.
recentOrderNum
}));
});
// 下钻至数据资源库
const
handleToDataLibrary
=
(
item
)
=>
{
const
selectParam
=
{
...
...
src/views/exportControl/index.vue
浏览文件 @
a1036c92
...
...
@@ -1141,7 +1141,7 @@ const radarOption = ref({
// 获取雷达图数据
const
fetchRadarData
=
async
checked
=>
{
try
{
const
data
=
await
getSanDomainCount
(
checked
,
"export"
);
const
data
=
await
getSanDomainCount
(
checked
,
allSanTypeIds
.
value
.
join
(
","
)
);
if
(
data
&&
Array
.
isArray
(
data
)
&&
data
.
length
>
0
)
{
// 收集所有可能的领域名称
const
allDomains
=
new
Set
();
...
...
@@ -1785,15 +1785,6 @@ const handleMediaClick = item => {
</
script
>
<
style
lang=
"scss"
scoped
>
// * {
// margin: 0;
// padding: 0;
// }
:deep
(
.el-input__wrapper
)
{
// box-shadow: none;
}
.list-page
{
padding-top
:
0
;
}
...
...
@@ -3247,10 +3238,10 @@ const handleMediaClick = item => {
}
.text
{
font-size
:
20
px
;
font-size
:
16
px
;
font-weight
:
700
;
font-family
:
"
Microsoft YaHei
"
;
line-height
:
2
6
px
;
font-family
:
"
Source Han Sans CN
"
;
line-height
:
2
4
px
;
color
:
rgb
(
5
,
95
,
194
);
}
}
...
...
src/views/exportControl/v2.0CommercialControlList/components/sanctionsOverview/components/listPage/index.vue
浏览文件 @
a1036c92
...
...
@@ -2,14 +2,14 @@
<div
class=
"list-page"
>
<div
class=
"search-box"
>
<div
style=
"display: flex; justify-content: center"
>
<el-select
v-model=
"currentCCLVersion"
style=
"width: 3
88
px; height: 32px; margin-right: 14px"
>
<el-select
v-model=
"currentCCLVersion"
style=
"width: 3
60
px; height: 32px; margin-right: 14px"
>
<el-option
v-for=
"item in cclVersionList"
:key=
"item.key"
:label=
"item.value"
:value=
"item.key"
/>
</el-select>
<el-input
v-model=
"searchKeyword"
class=
"search-input"
placeholder=
"搜索物项或ECCN编码"
:suffix-icon=
"Search"
/>
</div>
<div
class=
"filters"
>
<el-checkbox
v-model=
"viewNew"
label=
"查看最近更新内容"
/>
<el-select
placeholder=
"全部类别"
v-model=
"currentCCLType"
style=
"width: 3
88
px; height: 32px; margin-right: 14px"
>
<el-select
placeholder=
"全部类别"
v-model=
"currentCCLType"
style=
"width: 3
60
px; height: 32px; margin-right: 14px"
>
<el-option
v-for=
"item in CCLTypeList"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
/>
</el-select>
</div>
...
...
@@ -381,14 +381,15 @@ onMounted(async () => {
align-items
:
center
;
.search-input
{
width
:
3
88
px
;
width
:
3
60
px
;
height
:
32px
;
:deep
(
.el-input__wrapper
)
{
padding
:
0
11px
;
border
:
1
.5px
solid
#dcdfe6
;
//
border: 1.5px solid #dcdfe6;
background-color
:
#fff
;
border-radius
:
4px
;
box-shadow
:
0
0
0
1px
var
(
--
el-input-border-color
,
var
(
--
el-border-color
))
inset
;
}
:deep
(
.el-input__inner
)
{
...
...
@@ -439,25 +440,26 @@ onMounted(async () => {
.left
{
padding-bottom
:
20px
;
width
:
3
88
px
;
width
:
3
60
px
;
height
:
auto
;
border-radius
:
10px
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
background-color
:
#fff
;
.checkbox-group
{
display
:
flex
;
flex-wrap
:
wrap
;
padding
:
0
0
0
2
4px
;
display
:
grid
;
grid-template-columns
:
repeat
(
2
,
160px
)
;
gap
:
8px
4px
;
padding-left
:
24px
;
.el-checkbox
{
width
:
50%
;
margin-right
:
0
;
margin-bottom
:
4px
;
//
margin-bottom: 4px;
font-size
:
16px
;
font-weight
:
400
;
font-family
:
"Microsoft YaHei"
;
line-height
:
24px
;
height
:
24px
;
color
:
rgb
(
95
,
101
,
108
);
}
...
...
@@ -504,7 +506,7 @@ onMounted(async () => {
}
.right
{
width
:
1
196
px
;
width
:
1
223
px
;
height
:
auto
;
.title
{
width
:
100%
;
...
...
src/views/exportControl/v2.0EntityList/components/sanctionsOverview/components/listPage/index.vue
浏览文件 @
a1036c92
...
...
@@ -514,11 +514,6 @@ watch(customDateRange, () => {
</
script
>
<
style
scoped
lang=
"scss"
>
*
{
margin
:
0
;
padding
:
0
;
}
.list-page
{
width
:
1601px
;
padding-bottom
:
50px
;
...
...
@@ -532,7 +527,7 @@ watch(customDateRange, () => {
align-items
:
center
;
.search-input
{
width
:
3
88
px
;
width
:
3
60
px
;
height
:
32px
;
:deep
(
.el-input__wrapper
)
{
...
...
@@ -583,7 +578,7 @@ watch(customDateRange, () => {
.left
{
padding-bottom
:
20px
;
width
:
3
88
px
;
width
:
3
60
px
;
height
:
auto
;
border-radius
:
10px
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
...
...
@@ -654,7 +649,7 @@ watch(customDateRange, () => {
}
.right
{
width
:
1
196
px
;
width
:
1
223
px
;
height
:
auto
;
border-radius
:
10px
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
...
...
src/views/finance/entityList/components/deepMining/assets/empty.png
0 → 100644
浏览文件 @
a1036c92
7.2 KB
src/views/finance/entityList/components/deepMining/assets/icon-mark.png
0 → 100644
浏览文件 @
a1036c92
2.8 KB
src/views/finance/entityList/components/deepMining/components/back.vue
0 → 100644
浏览文件 @
a1036c92
<
template
>
<div
class=
"main main-association"
>
<div
class=
"left"
>
<!-- ... 左侧代码保持不变 ... -->
<AnalysisBox
title=
"制裁历程"
>
<div
class=
"left-main"
>
<div
class=
"date-picker-box"
>
<el-date-picker
v-model=
"dateRange"
type=
"daterange"
range-separator=
"--"
start-placeholder=
"开始日期"
end-placeholder=
"结束日期"
value-format=
"YYYY-MM-DD"
style=
"width: 100%"
:clearable=
"false"
@
change=
"handleDateChange"
/>
</div>
<div
class=
"list-header"
>
<el-checkbox
v-model=
"isAllSelected"
:indeterminate=
"indeterminate"
@
change=
"handleCheckAllChange"
>
全选
</el-checkbox>
<div
class=
"count"
>
共
{{
sanctionList
.
length
}}
次制裁
</div>
</div>
<div
class=
"list-content"
v-loading=
"loading"
>
<div
class=
"list-item"
v-for=
"item in sanctionList"
:key=
"item.id"
@
click=
"handleSanctionSelect(item.id)"
>
<el-checkbox
v-model=
"item.checked"
@
change=
"val => handleCheckOneChange(val, item)"
@
click
.
stop
>
<div
class=
"item-label"
>
<div
class=
"item-left"
>
{{
item
.
date
}}
-
{{
"SDN清单更新"
}}
</div>
<div
class=
"item-right"
>
{{
item
.
count
}}{{
item
.
unit
}}
</div>
</div>
</el-checkbox>
</div>
</div>
<el-button
type=
"primary"
@
click=
"handleAssociationClick"
style=
"height: 36px"
>
多投融资限制举措关联分析
<el-icon><Right
/></el-icon>
</el-button>
</div>
</AnalysisBox>
</div>
<div
class=
"right"
>
<AnalysisBox
title=
"投融资限制举措关系图"
>
<div
class=
"right-main"
>
<div
class=
"relation-empty"
v-if=
"selectedSanctionIds.length == 0"
>
<el-empty
:image=
"emptyImg"
:image-size=
"200"
>
<template
#
description
>
<div
class=
"empty"
>
请在左侧勾选多次投融资限制制裁后点击“开始分析”查看结果
</div>
</
template
>
</el-empty>
</div>
<div
class=
"relation-content"
v-else
>
<!-- 修改点:绑定转换后的 graphNodes 和 graphLinks -->
<GraphChart
:nodes=
"graphNodes"
:links=
"graphLinks"
/>
</div>
</div>
</AnalysisBox>
</div>
</div>
</template>
<
script
setup
>
import
{
ref
,
onMounted
,
nextTick
,
onUnmounted
,
computed
}
from
"vue"
;
import
defaultTitle
from
"../../../assets/default-icon2.png"
;
import
GraphChart
from
"@/components/base/GraphChart/index.vue"
;
import
emptyImg
from
"../assets/empty.png"
;
import
markIcon
from
"../assets/icon-mark.png"
;
import
{
getSanRecord
,
getRecordRelation
}
from
"@/api/finance"
;
import
{
useRoute
}
from
"vue-router"
;
const
route
=
useRoute
();
const
colors
=
{
// 相似
similarity
:
{
fontColor
:
"rgba(5, 95, 194)"
,
color
:
"rgb(231, 243, 255)"
},
// 继承
inheritance
:
{
fontColor
:
"rgba(19, 168, 168)"
,
color
:
"rgba(230, 255, 251, 1)"
},
// 冲突
conflict
:
{
fontColor
:
"rgb(206, 79, 81)"
,
color
:
"rgba(5, 95, 194)"
}
};
// ... 其他原有变量保持不变 ...
const
selectedSanctionIds
=
ref
([]);
const
isAllSelected
=
computed
({
get
()
{
return
sanctionList
.
value
.
length
>
0
&&
sanctionList
.
value
.
every
(
item
=>
item
.
checked
);
},
set
(
val
)
{}
});
const
handleCheckAllChange
=
val
=>
{
sanctionList
.
value
.
forEach
(
item
=>
{
item
.
checked
=
val
;
});
updateSelectedIds
();
};
const
handleCheckOneChange
=
(
val
,
item
)
=>
{
item
.
checked
=
val
;
updateSelectedIds
();
};
const
updateSelectedIds
=
()
=>
{
selectedSanctionIds
.
value
=
sanctionList
.
value
.
filter
(
item
=>
item
.
checked
).
map
(
item
=>
item
.
id
);
if
(
selectedSanctionIds
.
value
.
length
===
1
)
{
currentSanctionId
.
value
=
selectedSanctionIds
.
value
[
0
];
}
};
const
indeterminate
=
computed
(()
=>
{
const
checkedCount
=
sanctionList
.
value
.
filter
(
item
=>
item
.
checked
).
length
;
return
checkedCount
>
0
&&
checkedCount
<
sanctionList
.
value
.
length
;
});
const
recordRelation
=
ref
({
noRelationVertices
:
[],
relationVoList
:
[]
});
// 【新增】计算属性:处理图表数据
const
graphNodes
=
computed
(()
=>
{
const
nodesMap
=
new
Map
();
// 1. 处理无关联节点 (noRelationVertices)
if
(
recordRelation
.
value
.
noRelationVertices
)
{
recordRelation
.
value
.
noRelationVertices
.
forEach
(
node
=>
{
if
(
!
nodesMap
.
has
(
node
.
id
))
{
nodesMap
.
set
(
node
.
id
,
{
id
:
node
.
id
,
name
:
node
.
name
,
// 可以根据需要添加 category 或其他样式属性
itemStyle
:
{
color
:
"#91cc75"
// 例如:无关联节点用绿色区分
}
});
}
});
}
// 2. 处理关联节点 (relationVoList 中的 fromVertex 和 toVertex)
if
(
recordRelation
.
value
.
relationVoList
)
{
recordRelation
.
value
.
relationVoList
.
forEach
(
rel
=>
{
const
from
=
rel
.
fromVertex
;
const
to
=
rel
.
toVertex
;
if
(
from
&&
!
nodesMap
.
has
(
from
.
id
))
{
nodesMap
.
set
(
from
.
id
,
{
id
:
from
.
id
,
name
:
from
.
name
,
itemStyle
:
{
color
:
"#5470c6"
// 例如:有关联节点用蓝色
}
});
}
if
(
to
&&
!
nodesMap
.
has
(
to
.
id
))
{
nodesMap
.
set
(
to
.
id
,
{
id
:
to
.
id
,
name
:
to
.
name
,
itemStyle
:
{
color
:
"#5470c6"
}
});
}
});
}
return
Array
.
from
(
nodesMap
.
values
());
});
const
graphLinks
=
computed
(()
=>
{
if
(
!
recordRelation
.
value
.
relationVoList
)
return
[];
return
recordRelation
.
value
.
relationVoList
.
map
(
rel
=>
{
return
{
source
:
rel
.
fromVertex
.
id
,
target
:
rel
.
toVertex
.
id
,
// 将 edgeInfo 挂载到 data 上,以便在 formatter 中访问
label
:
{
formatter
:
rel
.
edgeInfo
?
rel
.
edgeInfo
.
value
:
""
}
};
});
});
const
handleAssociationClick
=
()
=>
{
console
.
log
(
"handleAssociationClick"
,
selectedSanctionIds
.
value
);
fetchRecordRelation
();
};
const
fetchRecordRelation
=
async
()
=>
{
if
(
selectedSanctionIds
.
value
.
length
===
0
)
{
return
;
}
try
{
const
res
=
await
getRecordRelation
(
selectedSanctionIds
.
value
);
console
.
log
(
"getRecordRelation"
,
res
);
if
(
!!
res
)
{
recordRelation
.
value
=
res
;
}
else
{
recordRelation
.
value
=
{
noRelationVertices
:
[],
relationVoList
:
[]
};
}
}
catch
(
error
)
{
console
.
error
(
"获取制裁记录关联信息失败:"
,
error
);
recordRelation
.
value
=
{
noRelationVertices
:
[],
relationVoList
:
[]
};
}
};
const
loading
=
ref
(
false
);
const
currentPage
=
ref
(
1
);
const
fetchSanRecord
=
async
()
=>
{
loading
.
value
=
true
;
const
params
=
{
startDate
:
dateRange
.
value
&&
dateRange
.
value
[
0
]
?
dateRange
.
value
[
0
]
:
""
,
endDate
:
dateRange
.
value
&&
dateRange
.
value
[
1
]
?
dateRange
.
value
[
1
]
:
""
,
sanTypeId
:
sanTypeId
.
value
||
1
};
try
{
const
res
=
await
getSanRecord
(
params
);
if
(
res
&&
res
.
length
>
0
)
{
sanctionList
.
value
=
res
.
map
(
item
=>
({
id
:
item
.
sanRecordId
,
date
:
item
.
sanRecordDate
,
title
:
item
.
sanRecordName
,
count
:
item
.
cnEntitiesNum
,
unit
:
"家中国实体"
}))
.
reverse
();
if
(
sanctionList
.
value
.
length
>
0
)
{
currentSanctionId
.
value
=
sanctionList
.
value
[
0
].
id
;
}
}
else
{
sanctionList
.
value
=
[];
}
}
catch
(
error
)
{
console
.
error
(
"获取选择制裁数据失败:"
,
error
);
sanctionList
.
value
=
[];
}
finally
{
loading
.
value
=
false
;
}
};
const
handleDateChange
=
()
=>
{
fetchSanRecord
();
};
const
handleSanctionSelect
=
id
=>
{
currentSanctionId
.
value
=
id
;
};
const
dateRange
=
ref
([
"2025-01-01"
,
"2025-12-31"
]);
const
sanctionList
=
ref
([]);
const
currentSanctionId
=
ref
(
5
);
const
sanTypeId
=
ref
(
""
);
onMounted
(()
=>
{
sanTypeId
.
value
=
route
.
query
.
sanTypeId
||
""
;
fetchSanRecord
();
});
</
script
>
<
style
lang=
"scss"
scoped
>
/* 样式保持不变 */
.main
{
width
:
100%
;
padding-top
:
12px
;
padding-bottom
:
50px
;
display
:
flex
;
justify-content
:
space-between
;
.left
{
width
:
480px
;
height
:
828px
;
.left-main
{
margin-top
:
11px
;
padding
:
0
22px
0
23px
;
display
:
flex
;
flex-direction
:
column
;
height
:
calc
(
100%
-
25px
);
.date-picker-box
{
margin-bottom
:
16px
;
}
.list-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
16px
;
font-size
:
14px
;
color
:
#666
;
.count
{
font-size
:
16px
;
font-weight
:
400
;
font-family
:
"Microsoft YaHei"
;
color
:
rgb
(
95
,
101
,
108
);
}
}
.list-content
{
flex
:
1
;
overflow-y
:
auto
;
padding-bottom
:
20px
;
&
:
:-
webkit-scrollbar
{
width
:
6px
;
}
&
:
:-
webkit-scrollbar-thumb
{
background
:
#ccc
;
border-radius
:
3px
;
}
.list-item
{
border
:
1px
solid
rgb
(
234
,
236
,
238
);
border-radius
:
4px
;
margin-bottom
:
8px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
15px
16px
;
cursor
:
pointer
;
transition
:
all
0
.3s
;
position
:
relative
;
background
:
#fff
;
.item-label
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
width
:
100%
;
}
.item-left
{
width
:
260px
;
font-weight
:
700
;
color
:
rgb
(
59
,
65
,
75
);
font-size
:
16px
;
font-family
:
"Microsoft YaHei"
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
margin-right
:
10px
;
}
.item-right
{
color
:
rgb
(
132
,
136
,
142
);
font-size
:
16px
;
font-weight
:
400
;
font-family
:
"Microsoft YaHei"
;
flex-shrink
:
0
;
}
&
:hover
{
border-color
:
#055fc2
;
}
&
.active
{
border-color
:
rgb
(
5
,
95
,
194
);
background-color
:
rgba
(
246
,
250
,
255
,
1
);
.item-left
,
.item-right
{
color
:
rgb
(
5
,
95
,
194
);
}
&
:
:
after
{
content
:
""
;
position
:
absolute
;
right
:
0
;
top
:
10px
;
bottom
:
10px
;
width
:
4px
;
background-color
:
rgb
(
5
,
95
,
194
);
}
}
}
}
}
}
.right
{
width
:
1105px
;
height
:
828px
;
.right-main
{
margin-top
:
11px
;
height
:
calc
(
100%
-
10px
);
padding
:
0
16px
16px
16px
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
.relation-empty
{
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
height
:
300px
;
width
:
300px
;
.empty
{
font-size
:
16px
;
font-weight
:
400
;
font-family
:
Source
Han
Sans
CN
;
color
:
rgb
(
132
,
136
,
142
);
line-height
:
30px
;
}
}
.relation-content
{
width
:
100%
;
height
:
100%
;
}
}
}
}
.main-association
{
justify-content
:
flex-start
!
important
;
gap
:
16px
;
}
</
style
>
src/views/finance/entityList/components/deepMining/components/constrainedAssociation.vue
浏览文件 @
a1036c92
<
template
>
<div
class=
"main main-association"
>
<div
class=
"left"
>
<!-- ... 左侧代码保持不变 ... -->
<AnalysisBox
title=
"制裁历程"
>
<div
class=
"left-main"
>
<div
class=
"date-picker-box"
>
...
...
@@ -21,250 +22,154 @@
全选
</el-checkbox>
<div
class=
"count"
>
共
{{
sanctionList
.
length
}}
次制裁
</div>
<!-- 暂时隐藏,说这里可能是轮播图的效果 -->
<!--
<div
class=
"pagination"
>
<div
class=
"page-btn prev"
@
click=
"handlePrevClick"
>
<el-icon><ArrowLeft
/></el-icon>
</div>
<div
class=
"page-btn next"
@
click=
"handleNextClick"
>
<el-icon><ArrowRight
/></el-icon>
</div>
</div>
-->
</div>
<div
class=
"list-content"
v-loading=
"loading"
>
<div
class=
"list-item"
v-for=
"item in sanctionList"
:key=
"item.id"
:class=
"
{ active: currentSanctionId === item.id }"
@click="handleSanctionSelect(item.id)"
>
<div
class=
"list-item"
v-for=
"item in sanctionList"
:key=
"item.id"
@
click=
"handleSanctionSelect(item.id)"
>
<el-checkbox
v-model=
"item.checked"
@
change=
"val => handleCheckOneChange(val, item)"
@
click
.
stop
>
<span
class=
"item-label"
>
<span
class=
"item-left"
>
{{
item
.
date
}}
-
{{
item
.
title
}}
</span>
<span
class=
"item-right"
>
{{
item
.
count
}}{{
item
.
unit
}}
</span>
</span>
<div
class=
"item-label"
>
<div
class=
"item-left"
>
{{
dayjs
(
item
.
date
).
format
(
"YYYY年MM月DD日"
)
}}
-
{{
"SDN清单更新"
}}
</div>
<div
class=
"item-right"
>
{{
item
.
count
}}{{
item
.
unit
}}
</div>
</div>
</el-checkbox>
<!--
<div
class=
"item-left"
>
{{
item
.
date
}}
-
{{
item
.
title
}}
</div>
<div
class=
"item-right"
>
{{
item
.
count
}}{{
item
.
unit
}}
</div>
-->
</div>
</div>
<el-button
type=
"primary"
@
click=
"handleAssociationClick"
style=
"height: 36px"
>
多投融资限制举措关联分析
<el-icon><Right
/></el-icon>
</el-button>
</div>
</AnalysisBox>
</div>
<div
class=
"right"
>
<AnalysisBox
title=
"制裁产业链时序图"
>
<template
#
header-btn
>
<el-select
v-model=
"selectedIndustryId"
placeholder=
"请选择"
class=
"industry-select"
@
change=
"
() =>
{
getFishboneData();
getCnEntityOnChainData();
}
"
>
<el-option
v-for=
"item in industryList"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
/>
</el-select>
</
template
>
<AnalysisBox
title=
"投融资限制举措关系图"
>
<div
class=
"right-main"
>
<div
class=
"right-main-content"
>
<div
class=
"hintWrap"
>
<div
class=
"icon1"
></div>
<div
class=
"title"
>
2025年实体清单制裁范围扩大至芯片制造环节,为中国的芯片制造能力划定“技术天花板”,阻止其向更先进水平发展。制裁范围向上游设备和材料、下游先进封装以及关键工具(如EDA软件)延伸,意图瓦解中国构建自主可控产业链的努力。
</div>
<div
class=
"icon2Wrap"
>
<div
class=
"icon2"
></div>
</div>
</div>
<div
class=
"right-main-content-main"
>
<div
class=
"fishbone-wrapper"
>
<div
class=
"fishbone-scroll-container"
ref=
"scrollContainerRef"
>
<div
class=
"fishbone"
ref=
"fishboneRef"
v-if=
"fishboneDataList.length > 0"
>
<div
class=
"main-line"
:style=
"{ width: fishboneDataList.length * 200 + 300 + 'px' }"
>
<!-- 主轴上的标签 -->
<div
class=
"main-line-text"
v-for=
"(item, index) in mainLineLabels"
:key=
"'label-' + index"
:class=
"{
'blue-theme': index < 2,
'green-theme': index >= 2 && index < 4,
'purple-theme': index >= 4
}"
:style=
"{ left: index * 200 + 220 + 'px' }"
>
{{ item }}
</div>
</div>
<!-- 奇数索引的数据组放在上方 -->
<div
v-for=
"(causeGroup, groupIndex) in getOddGroups(fishboneDataList)"
:key=
"'top-' + groupIndex"
:class=
"getTopBoneClass(groupIndex)"
:style=
"{ left: groupIndex * 400 + 420 + 'px' }"
>
<div
class=
"left-bone"
>
<div
class=
"left-bone-item"
v-for=
"(item, index) in getLeftItems(causeGroup.causes)"
:key=
"'left-' + index"
>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{ item.name }}
</div>
<div
class=
"line"
></div>
</div>
</div>
<div
class=
"right-bone"
>
<div
class=
"right-bone-item"
v-for=
"(item, index) in getRightItems(causeGroup.causes)"
:key=
"'right-' + index"
>
<div
class=
"line"
></div>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{ item.name }}
</div>
</div>
</div>
</div>
<!-- 偶数索引的数据组放在下方 -->
<div
v-for=
"(causeGroup, groupIndex) in getEvenGroups(fishboneDataList)"
:key=
"'bottom-' + groupIndex"
:class=
"getBottomBoneClass(groupIndex)"
:style=
"{ left: groupIndex * 400 + 220 + 'px' }"
>
<div
class=
"left-bone"
>
<div
class=
"left-bone-item"
v-for=
"(item, index) in getLeftItems(causeGroup.causes)"
:key=
"'left-bottom-' + index"
>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{ item.name }}
</div>
<div
class=
"line"
></div>
</div>
</div>
<div
class=
"right-bone"
>
<div
class=
"right-bone-item"
v-for=
"(item, index) in getRightItems(causeGroup.causes)"
:key=
"'right-bottom-' + index"
>
<div
class=
"line"
></div>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{ item.name }}
</div>
</div>
</div>
</div>
</div>
<div
v-else
style=
"
display: flex;
justify-content: center;
align-items: center;
height: 200px;
width: 100%;
"
>
<el-empty
description=
"暂无相关数据"
/>
</div>
</div>
</div>
<div
class=
"relation-empty"
v-if=
"selectedSanctionIds.length == 0"
>
<el-empty
:image=
"emptyImg"
:image-size=
"200"
>
<template
#
description
>
<div
class=
"empty"
>
请在左侧勾选多次投融资限制制裁后点击“开始分析”查看结果
</div>
</
template
>
</el-empty>
</div>
<div
class=
"right-main-content-footer"
>
<div
class=
"footer-item1"
>
<div
class=
"footer-item1-bottom"
>
<div
class=
"icon"
>
<img
src=
"../../../../assets/images/warning.png"
alt=
""
/>
<div
class=
"relation-content"
v-else
>
<!-- 绑定转换后的 graphNodes 和 graphLinks -->
<GraphChart
:nodes=
"graphNodes"
:links=
"graphLinks"
@
handleClickNode=
"handleClickNode"
/>
</div>
<div
class=
"text"
>
{{
`中国企业${cnEntityOnChainData.upstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.upstreamInternalRate
)}%),受制裁${cnEntityOnChainData.upstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.upstreamEntityRate
)}%)`
}}
</div>
</AnalysisBox>
</div>
<div
class=
"footer-item1-top"
>
{{ "上游" }}
</div>
<el-dialog
v-model=
"visible"
:title=
"curNode.name"
width=
"960"
>
<div
class=
"dialog-content"
>
<div
class=
"content-item"
>
<div
class=
"item-label"
>
制裁标题:
</div>
<div
class=
"item-desc item-label"
>
{{ vertexInfo.name }}
</div>
<div
class=
"footer-item2"
>
<div
class=
"footer-item2-bottom"
>
<div
class=
"icon"
>
<img
src=
"../../../../assets/images/warning.png"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
`中国企业${cnEntityOnChainData.midstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.midstreamInternalRate
)}%),受制裁${cnEntityOnChainData.midstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.midstreamEntityRate
)}%)`
}}
<div
class=
"content-item"
>
<div
class=
"item-label"
>
制裁领域:
</div>
<div
class=
"item-desc"
>
<AreaTag
v-for=
"item in vertexInfo.domainList"
:key=
"item"
:tagName=
"item"
/>
</div>
</div>
<div
class=
"footer-item2-top"
>
{{ "中游" }}
</div>
<div
class=
"content-item"
>
<div
class=
"item-label"
>
依托文件:
</div>
<div
class=
"item-desc"
>
<div
class=
"item-file"
v-for=
"item in vertexInfo.relyFileList"
:key=
"item.id"
>
{{ item.name }}
</div>
</div>
<div
class=
"footer-item3"
>
<div
class=
"footer-item3-bottom"
>
<div
class=
"icon"
>
<img
src=
"../../../../assets/images/warning.png"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
`中国企业${cnEntityOnChainData.downstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.downstreamInternalRate
)}%),受制裁${cnEntityOnChainData.downstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.downstreamEntityRate
)}%)`
}}
<div
class=
"content-item"
>
<div
class=
"item-label"
>
依托制裁:
</div>
<div
class=
"item-desc"
>
<div
class=
"item-file"
v-for=
"item in vertexInfo.relySanList"
:key=
"item.id"
>
{{ item.title }}
</div>
</div>
</div>
<div
class=
"footer-item3-top"
>
{{ "下游" }}
</div>
<div
class=
"content-item"
>
<div
class=
"item-label"
>
制裁原因:
</div>
<div
class=
"item-desc"
>
<div
class=
"item-file"
v-for=
"item in vertexInfo.sanReasonList"
:key=
"item"
>
{{ item }}
</div>
</div>
</div>
<div
class=
"content-item"
>
<div
class=
"item-label"
>
制裁对象:
</div>
<div
class=
"item-desc"
>
<span
class=
"item-table-desc"
v-if=
"vertexInfo.addObjectList?.length || vertexInfo.delObjectList?.length"
>
{{ formatChangeSummary(vertexInfo.addObjectList, vertexInfo.delObjectList) }}
</span>
<el-table
:data=
"vertexInfo.sanList"
stripe
>
<el-table-column
property=
"entityNameZh"
label=
"制裁对象"
width=
"350"
/>
<el-table-column
property=
"domainNames"
label=
"所属领域"
width=
"400"
>
<
template
#
default=
"scope"
>
<AreaTag
v-for=
"item in scope.row.domainNames"
:key=
"item"
:tagName=
"item"
/>
</
template
>
</el-table-column>
</el-table>
</div>
</div>
</AnalysisBox>
</div>
</el-dialog>
</div>
</template>
<
script
setup
>
import
{
ref
,
onMounted
,
nextTick
,
onUnmounted
,
computed
}
from
"vue"
;
import
{
ArrowLeft
,
ArrowRight
}
from
"@element-plus/icons-vue"
;
import
defaultTitle
from
"../../../assets/default-icon2.png"
;
import
{
getDeepMiningSelect
,
getDeepMiningIndustry
,
getDeepMiningIndustryFishbone
,
getDeepMiningIndustryEntity
}
from
"@/api/exportControlV2.0"
;
import
{
ref
,
onMounted
,
computed
}
from
"vue"
;
import
GraphChart
from
"@/components/base/GraphChart/index.vue"
;
import
emptyImg
from
"../assets/empty.png"
;
import
markIcon
from
"../assets/icon-mark.png"
;
// 引入图标
import
{
getSanRecord
,
getRecordRelation
,
getVertexInfo
}
from
"@/api/finance"
;
import
{
useRoute
}
from
"vue-router"
;
import
dayjs
from
"dayjs"
;
const
route
=
useRoute
();
// 存储选中的ID列表 (如果需要获取选中项,可以使用这个,或者直接遍历 sanctionList 找 checked=true 的)
const
selectedSanctionIds
=
ref
([]);
// 定义颜色映射
const
colors
=
{
// 相似
similarity
:
{
fontColor
:
"rgba(5, 95, 194)"
,
color
:
"rgb(231, 243, 255)"
,
// 连线颜色 & 标签背景
lineColor
:
"rgba(5, 95, 194)"
// 专门用于连线的颜色,如果需要和背景色区分
},
// 继承
inheritance
:
{
fontColor
:
"rgba(19, 168, 168)"
,
color
:
"rgba(230, 255, 251, 1)"
,
lineColor
:
"rgba(19, 168, 168)"
},
// 冲突
conflict
:
{
fontColor
:
"rgb(206, 79, 81)"
,
color
:
"rgba(255, 241, 240, 1)"
,
// 修正:冲突背景通常偏红/浅红,这里沿用你提供的蓝色背景可能不太符合直觉,但我保留你的配置或微调
lineColor
:
"rgb(206, 79, 81)"
}
};
// 计算属性:是否全选
// 辅助函数:获取关系类型对应的颜色配置
const
getRelationStyle
=
relationType
=>
{
switch
(
relationType
)
{
case
"相似"
:
return
colors
.
similarity
;
case
"继承"
:
return
colors
.
inheritance
;
case
"冲突"
:
return
colors
.
conflict
;
default
:
return
{
fontColor
:
"#666"
,
color
:
"#f0f2f5"
,
lineColor
:
"#AED6FF"
};
}
};
const
selectedSanctionIds
=
ref
([]);
const
isAllSelected
=
computed
({
get
()
{
return
sanctionList
.
value
.
length
>
0
&&
sanctionList
.
value
.
every
(
item
=>
item
.
checked
);
},
set
(
val
)
{
// set 方法由 handleCheckAllChange 处理,这里主要是为了配合 v-model 的读取
}
});
// 全选/取消全选
const
handleCheckAllChange
=
val
=>
{
sanctionList
.
value
.
forEach
(
item
=>
{
item
.
checked
=
val
;
...
...
@@ -272,167 +177,235 @@ const handleCheckAllChange = val => {
updateSelectedIds
();
};
// 单个选中变化
const
handleCheckOneChange
=
(
val
,
item
)
=>
{
item
.
checked
=
val
;
updateSelectedIds
();
};
// 点击行触发复选框切换
const
handleItemClick
=
item
=>
{
item
.
checked
=
!
item
.
checked
;
updateSelectedIds
();
};
// 更新选中ID数组(供外部业务使用,如获取选中数据进行查询等)
const
updateSelectedIds
=
()
=>
{
selectedSanctionIds
.
value
=
sanctionList
.
value
.
filter
(
item
=>
item
.
checked
).
map
(
item
=>
item
.
id
);
// 如果业务需要:当选中项变化时,可能需要触发右侧图表更新?
// 原逻辑是点击选中一个就更新右侧。现在如果是多选,右侧图表如何展示?
// 假设:如果只选中了一个,更新右侧;如果选中多个或没选中,保持现状或显示提示?
// 这里暂时保留原逻辑的触发点,但建议根据实际需求调整。
// 例如:只有当选中项为1个时,才调用 getFishboneData
if
(
selectedSanctionIds
.
value
.
length
===
1
)
{
currentSanctionId
.
value
=
selectedSanctionIds
.
value
[
0
];
getFishboneData
();
getCnEntityOnChainData
();
}
else
if
(
selectedSanctionIds
.
value
.
length
===
0
)
{
// 可选:清空右侧或显示默认状态
}
};
// 计算属性:是否半选(不确定状态)
const
indeterminate
=
computed
(()
=>
{
const
checkedCount
=
sanctionList
.
value
.
filter
(
item
=>
item
.
checked
).
length
;
return
checkedCount
>
0
&&
checkedCount
<
sanctionList
.
value
.
length
;
});
// 实体清单-深度挖掘-产业链中国企业实体信息查询
const
getCnEntityOnChainData
=
async
()
=>
{
const
currentSanction
=
sanctionList
.
value
.
find
(
item
=>
item
.
id
===
currentSanctionId
.
value
);
const
date
=
currentSanction
?
currentSanction
.
date
:
""
;
// 确保 date 格式正确
const
formattedDate
=
date
&&
date
.
includes
(
"年"
)
?
date
.
replace
(
"年"
,
"-"
).
replace
(
"月"
,
"-"
).
replace
(
"日"
,
""
)
:
date
;
const
params
=
{
date
:
formattedDate
};
if
(
selectedIndustryId
.
value
)
{
params
.
chainId
=
selectedIndustryId
.
value
;
const
recordRelation
=
ref
({
noRelationVertices
:
[],
relationVoList
:
[]
});
// 【修改】处理图表节点数据
const
graphNodes
=
computed
(()
=>
{
const
nodesMap
=
new
Map
();
// 1. 处理无关联节点
if
(
recordRelation
.
value
.
noRelationVertices
)
{
recordRelation
.
value
.
noRelationVertices
.
forEach
(
node
=>
{
if
(
!
nodesMap
.
has
(
node
.
id
))
{
nodesMap
.
set
(
node
.
id
,
{
id
:
node
.
id
,
// name: node.name,
name
:
dayjs
(
node
.
date
).
format
(
"YYYY年MM月DD日"
)
+
" "
+
"SDN清单更新"
,
symbol
:
`image://
${
markIcon
}
`
,
// 设置自定义图标
symbolSize
:
[
50
,
50
],
// 根据图标实际大小调整
itemStyle
:
{
color
:
"#91cc75"
}
try
{
const
res
=
await
getDeepMiningIndustryEntity
(
params
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
cnEntityOnChainData
.
value
=
res
.
data
;
}
else
{
cnEntityOnChainData
.
value
=
{};
});
}
}
catch
(
error
)
{
console
.
error
(
"获取产业链中国企业实体信息失败:"
,
error
);
cnEntityOnChainData
.
value
=
{};
});
}
};
// 实体清单-深度挖掘-产业链鱼骨图信息
const
fishboneDataList
=
ref
([]);
const
getFishboneData
=
async
()
=>
{
const
currentSanction
=
sanctionList
.
value
.
find
(
item
=>
item
.
id
===
currentSanctionId
.
value
);
const
date
=
currentSanction
?
currentSanction
.
date
:
""
;
// 确保 date 格式正确
const
formattedDate
=
date
&&
date
.
includes
(
"年"
)
?
date
.
replace
(
"年"
,
"-"
).
replace
(
"月"
,
"-"
).
replace
(
"日"
,
""
)
:
date
;
// 2. 处理关联节点
if
(
recordRelation
.
value
.
relationVoList
)
{
recordRelation
.
value
.
relationVoList
.
forEach
(
rel
=>
{
const
from
=
rel
.
fromVertex
;
const
to
=
rel
.
toVertex
;
const
params
=
{
date
:
formattedDate
};
if
(
selectedIndustryId
.
value
)
{
params
.
chainId
=
selectedIndustryId
.
value
;
if
(
from
&&
!
nodesMap
.
has
(
from
.
id
))
{
nodesMap
.
set
(
from
.
id
,
{
id
:
from
.
id
,
// name: from.name,
name
:
dayjs
(
from
.
date
).
format
(
"YYYY年MM月DD日"
)
+
" "
+
"SDN清单更新"
,
symbol
:
`image://
${
markIcon
}
`
,
// 设置自定义图标
symbolSize
:
[
50
,
50
],
itemStyle
:
{
color
:
"#5470c6"
}
try
{
const
res
=
await
getDeepMiningIndustryFishbone
(
params
);
if
(
res
.
code
===
200
&&
res
.
data
&&
res
.
data
.
causes
&&
res
.
data
.
causes
.
length
>
0
)
{
const
rootCauses
=
res
.
data
.
causes
;
if
(
rootCauses
.
length
>
0
&&
rootCauses
[
0
].
causes
)
{
fishboneDataList
.
value
=
rootCauses
.
map
(
group
=>
{
});
}
if
(
to
&&
!
nodesMap
.
has
(
to
.
id
))
{
nodesMap
.
set
(
to
.
id
,
{
id
:
to
.
id
,
// name: to.name,
name
:
dayjs
(
to
.
date
).
format
(
"YYYY年MM月DD日"
)
+
" "
+
"SDN清单更新"
,
symbol
:
`image://
${
markIcon
}
`
,
// 设置自定义图标
symbolSize
:
[
50
,
50
],
itemStyle
:
{
color
:
"#5470c6"
}
});
}
});
}
return
Array
.
from
(
nodesMap
.
values
());
});
// 【修改】处理图表连线数据
const
graphLinks
=
computed
(()
=>
{
if
(
!
recordRelation
.
value
.
relationVoList
)
return
[];
return
recordRelation
.
value
.
relationVoList
.
map
(
rel
=>
{
const
relationType
=
rel
.
edgeInfo
?
rel
.
edgeInfo
.
value
:
""
;
const
style
=
getRelationStyle
(
relationType
);
return
{
causes
:
group
.
causes
||
[]
source
:
rel
.
fromVertex
.
id
,
target
:
rel
.
toVertex
.
id
,
// 将样式信息挂载到 label 或 data 上,供 formatter 和 lineStyle 使用
label
:
{
formatter
:
relationType
,
show
:
true
,
color
:
style
.
fontColor
,
// 字体颜色
backgroundColor
:
style
.
color
,
// 标签背景色
borderColor
:
style
.
color
,
// 标签边框色
padding
:
[
4
,
8
],
borderRadius
:
4
,
fontSize
:
12
},
lineStyle
:
{
color
:
style
.
lineColor
,
// 连线颜色
width
:
2
// curveness: 0.1 // 稍微有点弧度可能更好看
},
// 额外存储原始数据,以备后用
relationType
:
relationType
};
});
});
mainLineLabels
.
value
=
rootCauses
.
map
(
group
=>
group
.
text
||
""
);
}
else
{
fishboneDataList
.
value
=
[];
mainLineLabels
.
value
=
[];
const
handleAssociationClick
=
()
=>
{
console
.
log
(
"handleAssociationClick"
,
selectedSanctionIds
.
value
);
fetchRecordRelation
();
};
const
fetchRecordRelation
=
async
()
=>
{
if
(
selectedSanctionIds
.
value
.
length
===
0
)
{
return
;
}
try
{
const
res
=
await
getRecordRelation
(
selectedSanctionIds
.
value
);
console
.
log
(
"getRecordRelation"
,
res
);
if
(
!!
res
)
{
recordRelation
.
value
=
res
;
}
else
{
fishboneDataList
.
value
=
[];
mainLineLabels
.
value
=
[];
recordRelation
.
value
=
{
noRelationVertices
:
[],
relationVoList
:
[]
};
}
}
catch
(
error
)
{
console
.
error
(
"获取
产业链鱼骨图数据
失败:"
,
error
);
fishboneDataList
.
value
=
[]
;
console
.
error
(
"获取
制裁记录关联信息
失败:"
,
error
);
recordRelation
.
value
=
{
noRelationVertices
:
[],
relationVoList
:
[]
}
;
}
};
// 实体清单-深度挖掘-产业链列表信息
const
industryList
=
ref
([]);
const
selectedIndustryId
=
ref
(
null
);
const
getIndustryList
=
async
()
=>
{
try
{
const
res
=
await
getDeepMiningIndustry
();
if
(
res
.
code
===
200
&&
res
.
data
&&
res
.
data
.
length
>
0
)
{
industryList
.
value
=
res
.
data
;
selectedIndustryId
.
value
=
res
.
data
[
0
].
id
;
getFishboneData
();
getCnEntityOnChainData
();
const
vertexInfo
=
ref
({});
const
curNode
=
ref
({});
const
visible
=
ref
(
false
);
const
handleClickNode
=
node
=>
{
console
.
log
(
"节点点击"
,
node
);
curNode
.
value
=
node
.
data
;
getVertexInfo
(
node
.
data
.
id
).
then
(
res
=>
{
console
.
log
(
"getVertexInfo"
,
res
);
if
(
!!
res
)
{
vertexInfo
.
value
=
res
;
visible
.
value
=
true
;
}
else
{
industryList
.
value
=
[];
selectedIndustryId
.
value
=
null
;
vertexInfo
.
value
=
{};
}
}
catch
(
error
)
{
console
.
error
(
"获取产业链列表数据失败:"
,
error
);
industryList
.
value
=
[];
selectedIndustryId
.
value
=
null
;
});
};
// 【新增/修改】格式化变动 summary 的函数
const
formatChangeSummary
=
(
addList
,
delList
)
=>
{
const
parts
=
[];
// 处理新增列表
if
(
addList
&&
addList
.
length
>
0
)
{
// 将每个对象转换为 "value个实体" 或 "value名个人" 的形式
const
addItems
=
addList
.
map
(
item
=>
{
let
unit
=
"个"
;
let
noun
=
"实体"
;
if
(
item
.
key
===
"人物"
)
{
unit
=
"名"
;
noun
=
"个人"
;
}
else
if
(
item
.
key
===
"机构"
)
{
// 默认机构对应实体,也可以根据需求调整
unit
=
"个"
;
noun
=
"实体"
;
}
return
`
${
item
.
value
}${
unit
}${
noun
}
`
;
});
// 拼接:新增 + item1 + , + item2 ...
parts
.
push
(
`新增
${
addItems
.
join
(
","
)}
`
);
}
// 处理移除列表
if
(
delList
&&
delList
.
length
>
0
)
{
// 将每个对象转换为 "value个实体" 或 "value名个人" 的形式
const
delItems
=
delList
.
map
(
item
=>
{
let
unit
=
"个"
;
let
noun
=
"实体"
;
if
(
item
.
key
===
"人物"
)
{
unit
=
"名"
;
noun
=
"个人"
;
}
else
if
(
item
.
key
===
"机构"
)
{
unit
=
"个"
;
noun
=
"实体"
;
}
return
`
${
item
.
value
}${
unit
}${
noun
}
`
;
});
// 拼接:移除 + item1 + , + item2 ...
// 注意:题目要求“删除”,但之前代码用的是“移除”,这里统一使用“移除”或“删除”。
// 根据题目描述“展示样本为:新增12个实体,3名个人,移除1个实体”,这里使用“移除”更贴切上下文,
// 如果必须用“删除”,请将下面的 '移除' 改为 '删除'。
parts
.
push
(
`移除
${
delItems
.
join
(
","
)}
`
);
}
return
parts
.
length
>
0
?
parts
.
join
(
","
)
:
"无变动"
;
};
// 获取选择制裁
const
loading
=
ref
(
false
);
const
currentPage
=
ref
(
1
);
const
pageSize
=
ref
(
100
);
const
total
=
ref
(
0
);
const
totalPage
=
ref
(
0
);
const
sanctionList
=
ref
([]
);
const
currentSanctionId
=
ref
(
5
);
const
dateRange
=
ref
([
"2025-01-01"
,
"2025-12-31"
]
);
const
sanTypeId
=
ref
(
""
);
const
getDeepMiningSelectData
=
async
()
=>
{
const
fetchSanRecord
=
async
()
=>
{
loading
.
value
=
true
;
const
params
=
{
startDate
:
dateRange
.
value
&&
dateRange
.
value
[
0
]
?
dateRange
.
value
[
0
]
:
""
,
endDate
:
dateRange
.
value
&&
dateRange
.
value
[
1
]
?
dateRange
.
value
[
1
]
:
""
,
// typeName: "实体清单",
isCn
:
false
,
pageNum
:
currentPage
.
value
,
pageSize
:
pageSize
.
value
,
sanTypeIds
:
[
Number
(
sanTypeId
.
value
)]
||
1
// 实体清单固定1
sanTypeId
:
sanTypeId
.
value
||
1
};
try
{
const
res
=
await
get
DeepMiningSelect
(
params
);
if
(
res
.
code
===
200
&&
res
.
data
&&
res
.
data
.
content
)
{
sanctionList
.
value
=
res
.
data
.
content
const
res
=
await
get
SanRecord
(
params
);
if
(
res
&&
res
.
length
>
0
)
{
sanctionList
.
value
=
res
.
map
(
item
=>
({
id
:
item
.
id
,
date
:
item
.
postDate
,
title
:
item
.
name
,
count
:
item
.
cnEntityCount
,
unit
:
"家中国实体"
,
// 接口未返回单位,暂时固定
summary
:
item
.
summary
,
// 保留额外信息备用
techDomainList
:
item
.
techDomainList
// 保留额外信息备用
id
:
item
.
sanRecordId
,
date
:
item
.
sanRecordDate
,
title
:
item
.
sanRecordName
,
count
:
item
.
cnEntitiesNum
,
unit
:
"家中国实体"
}))
.
reverse
();
// 默认选中第一条
if
(
sanctionList
.
value
.
length
>
0
)
{
currentSanctionId
.
value
=
sanctionList
.
value
[
0
].
id
;
// getFishboneData(); // 这里不需要调用,因为getIndustryList会调用
}
}
else
{
sanctionList
.
value
=
[];
...
...
@@ -445,102 +418,25 @@ const getDeepMiningSelectData = async () => {
}
};
// 日期选择变化
const
handleDateChange
=
()
=>
{
currentPage
.
value
=
1
;
getDeepMiningSelectData
();
};
// ✅ 自动轮播定时器
const
autoPlayTimer
=
ref
(
null
);
// ✅ 自动下一个(支持循环)
const
handleNextClickAuto
=
()
=>
{
const
currentIndex
=
sanctionList
.
value
.
findIndex
(
item
=>
item
.
id
===
currentSanctionId
.
value
);
let
nextItem
;
if
(
currentIndex
<
sanctionList
.
value
.
length
-
1
)
{
nextItem
=
sanctionList
.
value
[
currentIndex
+
1
];
}
else
{
nextItem
=
sanctionList
.
value
[
0
];
// 循环到第一个
}
if
(
nextItem
)
{
handleSanctionSelect
(
nextItem
.
id
);
}
fetchSanRecord
();
};
const
handleSanctionSelect
=
id
=>
{
currentSanctionId
.
value
=
id
;
getFishboneData
();
getCnEntityOnChainData
();
};
const
activeTab
=
ref
([
"制裁时序分析"
,
"限制关联分析"
]);
const
activeIndex
=
ref
(
0
);
const
dateRange
=
ref
([
"2025-01-01"
,
"2025-12-31"
]);
const
sanctionList
=
ref
([]);
const
currentSanctionId
=
ref
(
5
);
const
cnEntityOnChainData
=
ref
({});
const
mainLineLabels
=
ref
([
"关键原材料"
,
"电池材料"
,
"电子元器件"
,
"动力电池"
,
"电子控制系统"
,
"动力电池"
]);
// 获取奇数索引的数据组(放在上方)
const
getOddGroups
=
data
=>
{
return
data
.
filter
((
_
,
index
)
=>
index
%
2
!==
0
);
};
// 获取偶数索引的数据组(放在下方)
const
getEvenGroups
=
data
=>
{
return
data
.
filter
((
_
,
index
)
=>
index
%
2
===
0
);
};
// 获取上方鱼骨图位置类名
const
getTopBoneClass
=
index
=>
{
const
positions
=
[
"top-bone"
,
"top-bone1"
,
"top-bone2"
];
return
positions
[
index
%
3
]
||
"top-bone"
;
};
// 获取下方鱼骨图位置类名
const
getBottomBoneClass
=
index
=>
{
const
positions
=
[
"bottom-bone"
,
"bottom-bone1"
,
"bottom-bone2"
];
return
positions
[
index
%
3
]
||
"bottom-bone"
;
};
// 获取左侧显示的项目(前半部分)
const
getLeftItems
=
items
=>
{
const
midpoint
=
Math
.
ceil
(
items
.
length
/
2
);
return
items
.
slice
(
0
,
midpoint
);
};
// 获取右侧显示的项目(后半部分)
const
getRightItems
=
items
=>
{
const
midpoint
=
Math
.
ceil
(
items
.
length
/
2
);
return
items
.
slice
(
midpoint
);
};
// 格式化比率
const
formatRate
=
rate
=>
{
if
(
rate
===
undefined
||
rate
===
null
)
return
"0.00"
;
return
(
rate
*
100
).
toFixed
(
2
);
};
const
sanTypeId
=
ref
(
""
);
onMounted
(()
=>
{
// 获取路由参数中的sanTypeId
sanTypeId
.
value
=
route
.
query
.
sanTypeId
||
""
;
// 获取选择制裁
getDeepMiningSelectData
();
// 获取产业链信息
getIndustryList
();
fetchSanRecord
();
});
</
script
>
<
style
lang=
"scss"
scoped
>
/* 样式保持不变 */
.main
{
width
:
100%
;
padding-top
:
1
6
px
;
padding-top
:
1
2
px
;
padding-bottom
:
50px
;
display
:
flex
;
justify-content
:
space-between
;
...
...
@@ -554,7 +450,7 @@ onMounted(() => {
padding
:
0
22px
0
23px
;
display
:
flex
;
flex-direction
:
column
;
height
:
calc
(
100%
-
56
px
);
height
:
calc
(
100%
-
25
px
);
.date-picker-box
{
margin-bottom
:
16px
;
...
...
@@ -574,34 +470,6 @@ onMounted(() => {
font-family
:
"Microsoft YaHei"
;
color
:
rgb
(
95
,
101
,
108
);
}
.pagination
{
display
:
flex
;
gap
:
12px
;
.page-btn
{
width
:
28px
;
height
:
28px
;
background
:
rgba
(
231
,
243
,
255
,
1
);
border-radius
:
4px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
color
:
rgb
(
5
,
95
,
194
);
font-size
:
16px
;
&
.disabled
{
cursor
:
not
-
allowed
;
background
:
#f5f7fa
;
color
:
#c0c4cc
;
}
&
:not
(
.disabled
)
:hover
{
background
:
#e1eeff
;
}
}
}
}
.list-content
{
...
...
@@ -619,7 +487,6 @@ onMounted(() => {
}
.list-item
{
// height: 60px;
border
:
1px
solid
rgb
(
234
,
236
,
238
);
border-radius
:
4px
;
margin-bottom
:
8px
;
...
...
@@ -631,6 +498,12 @@ onMounted(() => {
transition
:
all
0
.3s
;
position
:
relative
;
background
:
#fff
;
.item-label
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
width
:
100%
;
}
.item-left
{
width
:
260px
;
...
...
@@ -638,6 +511,10 @@ onMounted(() => {
color
:
rgb
(
59
,
65
,
75
);
font-size
:
16px
;
font-family
:
"Microsoft YaHei"
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
margin-right
:
10px
;
}
.item-right
{
...
...
@@ -645,6 +522,7 @@ onMounted(() => {
font-size
:
16px
;
font-weight
:
400
;
font-family
:
"Microsoft YaHei"
;
flex-shrink
:
0
;
}
&
:hover
{
...
...
@@ -668,7 +546,6 @@ onMounted(() => {
bottom
:
10px
;
width
:
4px
;
background-color
:
rgb
(
5
,
95
,
194
);
// border-radius: 4px 0 0 4px;
}
}
}
...
...
@@ -682,508 +559,61 @@ onMounted(() => {
.right-main
{
margin-top
:
11px
;
height
:
calc
(
100%
-
56
px
);
height
:
calc
(
100%
-
10
px
);
padding
:
0
16px
16px
16px
;
.right-main-content
{
height
:
100%
;
display
:
flex
;
flex-direction
:
column
;
.hintWrap
{
display
:
flex
;
align-items
:
center
;
padding
:
7px
12px
;
border
:
1px
solid
rgba
(
231
,
243
,
255
,
1
);
border-radius
:
4px
;
background
:
rgba
(
246
,
250
,
255
,
1
);
margin-bottom
:
9px
;
.icon1
{
width
:
19px
;
height
:
20px
;
background-image
:
url("../assets/ai.png")
;
background-size
:
100%
100%
;
flex-shrink
:
0
;
}
.title
{
color
:
rgb
(
5
,
95
,
194
);
font-size
:
16px
;
font-weight
:
400
;
line-height
:
24px
;
margin-left
:
13px
;
flex
:
1
;
}
.icon2Wrap
{
width
:
24px
;
height
:
24px
;
background-color
:
rgba
(
231
,
243
,
255
,
1
);
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
border-radius
:
12px
;
margin-left
:
20px
;
flex-shrink
:
0
;
.icon2
{
width
:
24px
;
height
:
24px
;
background-image
:
url("../assets/right.png")
;
background-size
:
100%
100%
;
}
}
}
.right-main-content-main
{
flex
:
1
;
// border: 1px solid #eaecee;
// border-radius: 4px;
// background: #f7f8f9;
position
:
relative
;
overflow
:
hidden
;
.fishbone-wrapper
{
position
:
relative
;
width
:
100%
;
height
:
100%
;
}
.fishbone-scroll-container
{
.relation-empty
{
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
width
:
100%
;
height
:
100%
;
overflow-x
:
auto
;
overflow-y
:
hidden
;
scrollbar-width
:
thin
;
scrollbar-color
:
rgba
(
144
,
202
,
249
,
0
.5
)
transparent
;
&
:
:-
webkit-scrollbar
{
height
:
6px
;
}
&
:
:-
webkit-scrollbar-track
{
background
:
transparent
;
}
&
:
:-
webkit-scrollbar-thumb
{
background-color
:
rgba
(
144
,
202
,
249
,
0
.5
);
border-radius
:
3px
;
height
:
300px
;
width
:
300px
;
.empty
{
font-size
:
16px
;
font-weight
:
400
;
font-family
:
Source
Han
Sans
CN
;
color
:
rgb
(
132
,
136
,
142
);
line-height
:
30px
;
}
}
.fishbone
{
position
:
relative
;
width
:
fit-content
;
height
:
100%
;
margin-top
:
40px
;
min-width
:
100%
;
padding-left
:
275px
;
margin-left
:
40px
;
.main-line
{
margin-top
:
280px
;
width
:
1888px
;
height
:
3px
;
background
:
rgb
(
230
,
231
,
232
);
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
padding
:
0
100px
;
// 虚线
&
:
:
after
{
content
:
""
;
position
:
absolute
;
top
:
0
;
left
:
0
;
.relation-content
{
width
:
100%
;
height
:
100%
;
// background: repeating-linear-gradient(to right, rgba(174, 208, 255, 1) 0, rgba(174, 208, 255, 1) 10px, transparent 10px, transparent 20px);
}
// 添加中间的文字块
.main-line-text
{
position
:
absolute
;
// top: -14px;
font-size
:
16px
;
color
:
#055fc2
;
font-weight
:
bold
;
background-color
:
#f7f8f9
;
padding
:
0
10px
;
z-index
:
2
;
// 箭头背景
height
:
32px
;
line-height
:
32px
;
width
:
160px
;
text-align
:
center
;
background
:
rgba
(
231
,
243
,
255
,
1
);
clip-path
:
polygon
(
0%
0%
,
90%
0%
,
100%
50%
,
90%
100%
,
0%
100%
,
10%
50%
);
&
.blue-theme
{
background
:
rgba
(
231
,
243
,
255
,
1
);
color
:
rgba
(
22
,
119
,
255
,
1
);
}
&
.green-theme
{
background
:
rgba
(
225
,
255
,
251
,
1
);
color
:
rgba
(
19
,
168
,
168
,
1
);
}
&
.purple-theme
{
background
:
rgba
(
246
,
235
,
255
,
1
);
color
:
rgba
(
146
,
84
,
222
,
1
);
}
}
}
}
.company-icon
{
width
:
16px
;
height
:
16px
;
margin
:
0
4px
;
object-fit
:
contain
;
}
.top-bone
{
position
:
absolute
;
top
:
20px
;
right
:
200px
;
width
:
3px
;
height
:
260px
;
background
:
rgb
(
230
,
231
,
232
);
transform
:
skew
(
30deg
);
z-index
:
1
;
.left-bone
{
color
:
#777
;
position
:
absolute
;
top
:
0
;
left
:
-150px
;
width
:
150px
;
height
:
50px
;
// overflow: hidden;
.left-bone-item
{
transform
:
skew
(
-30deg
);
height
:
45px
;
margin-bottom
:
2px
;
margin-top
:
2px
;
display
:
flex
;
justify-content
:
flex-end
;
align-items
:
center
;
.text
{
margin-left
:
4px
;
height
:
25px
;
line-height
:
25px
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.line
{
margin-left
:
7px
;
width
:
40px
;
height
:
2px
;
background
:
rgb
(
230
,
231
,
232
);
}
}
}
.right-bone
{
color
:
#777
;
position
:
absolute
;
top
:
0
;
right
:
-150px
;
width
:
150px
;
height
:
210px
;
overflow
:
hidden
;
.right-bone-item
{
transform
:
skew
(
-30deg
);
height
:
39px
;
margin-bottom
:
2px
;
margin-top
:
2px
;
display
:
flex
;
justify-content
:
flex-start
;
align-items
:
center
;
.line
{
margin-right
:
7px
;
width
:
30px
;
height
:
2px
;
background
:
rgb
(
230
,
231
,
232
);
}
.text
{
max-width
:
100px
;
margin-right
:
4px
;
height
:
25px
;
line-height
:
25px
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
}
}
}
.top-bone1
{
@extend
.top-bone
;
right
:
500px
;
}
.top-bone2
{
@extend
.top-bone
;
right
:
800px
;
}
.bottom-bone
{
position
:
absolute
;
top
:
280px
;
right
:
360px
;
width
:
3px
;
height
:
260px
;
background
:
rgb
(
230
,
231
,
232
);
transform
:
skew
(
-30deg
);
z-index
:
1
;
.left-bone
{
color
:
#777
;
position
:
absolute
;
top
:
50px
;
left
:
-150px
;
width
:
150px
;
height
:
260px
;
.left-bone-item
{
transform
:
skew
(
30deg
);
height
:
39px
;
margin-bottom
:
2px
;
margin-top
:
2px
;
}
.main-association
{
padding-top
:
12px
!
important
;
justify-content
:
flex-start
!
important
;
gap
:
16px
;
}
.dialog-content
{
padding
:
20px
;
display
:
flex
;
justify-content
:
flex-end
;
align-items
:
center
;
.text
{
margin-left
:
4px
;
height
:
25px
;
max-width
:
130px
;
line-height
:
25px
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.line
{
margin-left
:
7px
;
width
:
40px
;
height
:
2px
;
background
:
rgb
(
230
,
231
,
232
);
}
}
}
.right-bone
{
color
:
#777
;
position
:
absolute
;
top
:
50px
;
right
:
-150px
;
width
:
150px
;
height
:
260px
;
.right-bone-item
{
transform
:
skew
(
30deg
);
height
:
35px
;
margin-bottom
:
2px
;
margin-top
:
2px
;
flex-direction
:
column
;
gap
:
8px
;
border-top
:
1px
solid
rgb
(
238
,
238
,
238
);
.content-item
{
display
:
flex
;
justify-content
:
flex-start
;
align-items
:
center
;
.line
{
margin-right
:
7px
;
width
:
30px
;
height
:
2px
;
background
:
rgb
(
230
,
231
,
232
);
}
.text
{
max-width
:
100px
;
margin-right
:
4px
;
height
:
25px
;
line-height
:
25px
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
}
}
}
.bottom-bone1
{
@extend
.bottom-bone
;
right
:
660px
;
}
.bottom-bone2
{
@extend
.bottom-bone
;
right
:
960px
;
}
}
.right-main-content-footer
{
height
:
84px
;
margin-top
:
16px
;
display
:
flex
;
justify-content
:
space-between
;
.footer-item1
,
.footer-item2
,
.footer-item3
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
flex-end
;
}
.footer-item1
{
.footer-item1-top
{
height
:
28px
;
text-align
:
center
;
line-height
:
28px
;
background
:
url("../../../../assets/images/bg3.png")
;
background-size
:
100%
100%
;
color
:
rgba
(
22
,
119
,
255
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
margin-top
:
15px
;
margin-right
:
-10px
;
// Negative margin to overlap/connect
position
:
relative
;
// Ensure z-index works if needed
z-index
:
1
;
}
.footer-item1-bottom
{
display
:
flex
;
justify-content
:
center
;
.icon
{
margin-top
:
9px
;
width
:
16px
;
height
:
16px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.text
{
margin-top
:
7px
;
margin-left
:
8px
;
height
:
22px
;
color
:
rgba
(
206
,
79
,
81
,
1
);
font-size
:
14px
;
}
}
}
.footer-item2
{
.footer-item2-top
{
height
:
28px
;
text-align
:
center
;
line-height
:
28px
;
background
:
url("../../../../assets/images/bg2.png")
;
background-size
:
100%
100%
;
color
:
rgba
(
19
,
168
,
168
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
margin-top
:
15px
;
margin-right
:
-10px
;
// Negative margin to connect with next item
margin-left
:
-10px
;
// Negative margin to connect with prev item
position
:
relative
;
z-index
:
1
;
}
.footer-item2-bottom
{
display
:
flex
;
justify-content
:
center
;
.icon
{
margin-top
:
9px
;
width
:
16px
;
height
:
16px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.text
{
margin-top
:
7px
;
margin-left
:
8px
;
height
:
22px
;
color
:
rgba
(
206
,
79
,
81
,
1
);
font-size
:
14px
;
}
}
}
.footer-item3
{
.footer-item3-top
{
height
:
28px
;
text-align
:
center
;
line-height
:
28px
;
background
:
url("../../../../assets/images/bg1.png")
;
background-size
:
100%
100%
;
color
:
rgba
(
146
,
84
,
222
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
gap
:
8px
;
.item-label
{
font-size
:
146x
;
font-weight
:
700
;
margin-top
:
15px
;
margin-left
:
-10px
;
// Negative margin to connect
position
:
relative
;
z-index
:
1
;
}
.footer-item3-bottom
{
display
:
flex
;
justify-content
:
center
;
.icon
{
margin-top
:
9px
;
width
:
16px
;
height
:
16px
;
img
{
width
:
100%
;
height
:
100%
;
}
font-family
:
Source
Han
Sans
CN
;
color
:
rgba
(
59
,
65
,
75
,
1
);
line-height
:
24px
;
}
.text
{
margin-top
:
7px
;
margin-left
:
8px
;
height
:
22px
;
color
:
rgba
(
206
,
79
,
81
,
1
);
.item-desc
{
font-size
:
14px
;
font-family
:
Source
Han
Sans
CN
;
margin-top
:
3px
;
}
}
}
}
}
}
}
}
.main-association
{
justify-content
:
flex-start
!
important
;
gap
:
16px
;
}
</
style
>
src/views/finance/entityList/components/deepMining/components/mock.json
0 → 100644
浏览文件 @
a1036c92
{
"id"
:
2140
,
"name"
:
"美国以伊朗石油非法贸易为由实施制裁,多家中国企业被列入制裁名单"
,
"domainList"
:
[
"海洋"
],
"relyFileList"
:
[
{
"id"
:
null
,
"name"
:
"第13382号行政命令"
},
{
"id"
:
null
,
"name"
:
"第14530号行政命令"
}
],
"relySanList"
:
[
{
"sanTypeId"
:
2
,
"id"
:
2145
,
"title"
:
"OFAC将38个实体及4名个人列入SDN清单,涉及中国关联主体"
,
"postDate"
:
"2025-10-14"
}
],
"sanReasonList"
:
[
"参与了从伊朗购买、收购、销售、运输或营销石油化工产品"
,
"参与了与采购、获取、销售、运输或销售伊朗石油及石油制品相关的重大交易"
],
"addObjectList"
:
[
{
"key"
:
"机构"
,
"value"
:
3
}
],
"delObjectList"
:
[
{
"key"
:
"人物"
,
"value"
:
1
}
],
"sanList"
:
[
{
"entityId"
:
"91310115MA1HBB8PXH"
,
"entityName"
:
"SHANGHAI QIZHANG SHIP MANAGEMENT CO., LTD."
,
"entityNameZh"
:
"上海启章船舶管理有限公司"
,
"entityTypeId"
:
2
,
"entityTypeName"
:
"机构"
,
"domainNames"
:
[
"海洋"
]
},
{
"entityId"
:
"71180883"
,
"entityName"
:
"ALL WIN SHIPPING MANAGEMENT LIMITED"
,
"entityNameZh"
:
"誠安船舶管理有限公司"
,
"entityTypeId"
:
2
,
"entityTypeName"
:
"机构"
,
"domainNames"
:
[
"海洋"
]
},
{
"entityId"
:
"91370211MAEBUA7E2Q"
,
"entityName"
:
"QINGDAO OCEAN KIMO SHIP MANAGEMENT CO LTD"
,
"entityNameZh"
:
"青岛明洋凯茂船舶管理有限公司"
,
"entityTypeId"
:
2
,
"entityTypeName"
:
"机构"
,
"domainNames"
:
[
"海洋"
]
}
]
}
\ No newline at end of file
src/views/finance/entityList/components/deepMining/index.vue
浏览文件 @
a1036c92
...
...
@@ -13,7 +13,7 @@
</div>
</div>
<div
class=
"main"
>
<div
v-if=
"activeIndex == 0"
>
<div
class=
"sanctionTime"
v-if=
"activeIndex == 0"
>
<div
class=
"left"
>
<AnalysisBox
title=
"选择制裁"
>
<div
class=
"left-main"
>
...
...
@@ -76,7 +76,7 @@
</
template
>
<div
class=
"right-main"
>
<div
class=
"right-main-content"
>
<div
class=
"hintWrap"
>
<
!-- <
div class="hintWrap">
<div class="icon1"></div>
<div class="title">
2025年实体清单制裁范围扩大至芯片制造环节,为中国的芯片制造能力划定“技术天花板”,阻止其向更先进水平发展。制裁范围向上游设备和材料、下游先进封装以及关键工具(如EDA软件)延伸,意图瓦解中国构建自主可控产业链的努力。
...
...
@@ -84,7 +84,7 @@
<div class="icon2Wrap">
<div class="icon2"></div>
</div>
</div>
</div>
-->
<div
class=
"right-main-content-main"
>
<div
class=
"fishbone-wrapper"
>
<div
class=
"fishbone-scroll-container"
ref=
"scrollContainerRef"
>
...
...
@@ -557,8 +557,6 @@ onUnmounted(() => {
</
script
>
<
style
scoped
lang=
"scss"
>
.deep-mining
{
width
:
1601px
;
margin
:
0
auto
;
...
...
@@ -610,6 +608,11 @@ onUnmounted(() => {
padding-bottom
:
50px
;
display
:
flex
;
justify-content
:
space-between
;
.sanctionTime
{
display
:
flex
;
padding-top
:
12px
;
gap
:
10px
;
}
.left
{
width
:
480px
;
...
...
src/views/finance/entityList/components/sanctionsOverview/components/introductionPage/index.vue
浏览文件 @
a1036c92
...
...
@@ -433,11 +433,6 @@ onMounted(() => {
<
/script
>
<
style
scoped
lang
=
"scss"
>
*
{
margin
:
0
;
padding
:
0
;
}
.
introduction
-
page
{
width
:
1601
px
;
margin
:
0
auto
;
...
...
src/views/finance/entityList/components/sanctionsOverview/components/listPage/index.vue
浏览文件 @
a1036c92
...
...
@@ -532,12 +532,12 @@ watch(customDateRange, () => {
align-items
:
center
;
.search-input
{
width
:
3
88
px
;
width
:
3
60
px
;
height
:
32px
;
:deep
(
.el-input__wrapper
)
{
padding
:
0
11px
;
border
:
1px
solid
rgba
(
170
,
173
,
177
,
1
);
border
:
1px
solid
rgba
(
170
,
173
,
177
,
0
.5
);
background-color
:
#fff
;
border-radius
:
3px
;
}
...
...
@@ -579,7 +579,7 @@ watch(customDateRange, () => {
.left
{
padding-bottom
:
20px
;
width
:
3
88
px
;
width
:
3
60
px
;
height
:
auto
;
border-radius
:
10px
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
...
...
@@ -598,11 +598,12 @@ watch(customDateRange, () => {
.el-checkbox
{
width
:
50%
;
margin-right
:
0
;
margin-bottom
:
4px
;
//
margin-bottom: 4px;
font-size
:
16px
;
font-weight
:
400
;
font-family
:
"Microsoft YaHei"
;
line-height
:
24px
;
height
:
24px
;
color
:
rgb
(
95
,
101
,
108
);
}
...
...
@@ -649,7 +650,7 @@ watch(customDateRange, () => {
}
.right
{
width
:
1
196
px
;
width
:
1
223
px
;
height
:
auto
;
border-radius
:
10px
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
...
...
src/views/finance/index.vue
浏览文件 @
a1036c92
...
...
@@ -724,7 +724,7 @@ import {
getCountDomainByYear
,
getEntitiesList
,
getSanctionProcess
,
//
getSanDomainCount,
getSanDomainCount
,
// getRiskSignal,
// getSocialMediaInfo,
// getNewsInfo,
...
...
@@ -738,7 +738,7 @@ import {
getNewsInfo
,
getSocialMediaInfo
,
getReleaseCount
,
getSanDomainCount
,
//
getSanDomainCount,
getAnnualSanDomain
// getSanctionProcess
}
from
"@/api/finance"
;
...
...
@@ -1143,7 +1143,7 @@ const radarOption = ref({
// 获取雷达图数据
const
fetchRadarData
=
async
checked
=>
{
try
{
const
data
=
await
getSanDomainCount
(
checked
,
"export"
);
const
data
=
await
getSanDomainCount
(
checked
,
allSanTypeIds
.
value
.
join
(
","
)
);
if
(
data
&&
Array
.
isArray
(
data
)
&&
data
.
length
>
0
)
{
// 收集所有可能的领域名称
const
allDomains
=
new
Set
();
...
...
src/views/innovationSubject/index.vue
浏览文件 @
a1036c92
...
...
@@ -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
浏览文件 @
a1036c92
...
...
@@ -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
浏览文件 @
a1036c92
...
...
@@ -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
浏览文件 @
a1036c92
476.3 KB
src/views/viewRiskSignal/index.vue
浏览文件 @
a1036c92
...
...
@@ -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"
>
全部类型
<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-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
}}
<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"
>
全部来源
<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-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"
>
<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"
>
全部等级
<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-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"
>
<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>
<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
class=
"title"
>
{{
"
风险等级
"
}}
</div>
</div>
<div
class=
"left-box1-main"
>
<el-checkbox
v-model=
"isAreaCheckedAll"
:indeterminate=
"isAreaIndeterminate"
@
change=
"handleAreaCheckAllChange"
class=
"checkbox-all"
>
全部领域
<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-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"
>
<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 === '特别重大',
<div
class=
"risktype"
:class=
"{
risk1: val.risktype === '特别重大风险' || val.risktype === '特别重大风险',
risk2: val.risktype === '重大风险',
risk3: val.risktype === '一般风险'
}"
>
<div
class=
"icon"
:class=
"
{
icon1: val.risktype === '特别重大风险' || val.risktype === '特别重大',
}"
>
<div
class=
"icon"
:class=
"{
icon1: val.risktype === '特别重大风险' || val.risktype === '特别重大风险',
icon2: val.risktype === '重大风险',
icon3: val.risktype === '一般风险'
}"
>
</div>
}"
></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%
;
.select-box
{
margin-top
:
16px
;
.
left
-
box1
-
header
{
.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
;
}
}
.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
;
}
.
left
-
box1
-
main
{
margin
-
top
:
10
px
;
: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
;
.resource-library-sort-select
{
:deep
(
.el-select__wrapper
)
{
height
:
32px
;
box
-
sizing
:
border
-
box
;
border
:
1
px
solid
rgba
(
230
,
231
,
232
,
1
);
min-height
:
32px
;
border-radius
:
4px
;
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-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
浏览文件 @
a1036c92
...
...
@@ -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
浏览文件 @
a1036c92
...
...
@@ -61,6 +61,7 @@ export default defineConfig({
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': {
...
...
出口管制接口-4.md
浏览文件 @
a1036c92
...
...
@@ -730,8 +730,6 @@ public class RemarksVO {
}
```
# 字典
## 领域类别(id:name)
...
...
@@ -764,7 +762,7 @@ public class RemarksVO {
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult
<List
<
SanctionTypeBean
>
>
输出结果:ApiResult
## 最新出口管制政策(4条)
...
...
@@ -782,7 +780,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult
<LatestExportControlInfo>
输出结果:ApiResult
## 发布(更新)频度
...
...
@@ -802,7 +800,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult
<List
<
AnnualCount
>
>
输出结果:ApiResult
## **制裁领域分析**(20251215)
...
...
@@ -820,7 +818,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult
<List
<
DomainCount
>
>
输出结果:ApiResult
## **历次制裁过程**
...
...
@@ -838,7 +836,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult
<Page
<
SanctionProcess
>
>
输出结果:ApiResult
## **制裁实体清单**列表(20251215)
...
...
@@ -860,7 +858,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult
<Page
<
SanctionListBean
>
>
输出结果:ApiResult
## **发布机构与重点人物**
...
...
@@ -876,7 +874,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<OrgInfo>
输出结果:ApiResult
## **领域分布查询**
...
...
@@ -890,7 +888,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
BaseCount
>
>
输出结果:ApiResult
## **类型分布查询**
...
...
@@ -904,7 +902,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
BaseCount
>
>
输出结果:ApiResult
## **区域分布查询**
...
...
@@ -918,7 +916,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
RegionCount
>
>
输出结果:ApiResult
## **制裁理由查询**
...
...
@@ -932,7 +930,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
String
>
>
输出结果:ApiResult
## **深度挖掘-制裁信息变化统计**
...
...
@@ -946,7 +944,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<SanCountInfo>
输出结果:ApiResult
## **年度实体数统计**
...
...
@@ -962,7 +960,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<SanCountInfo>
输出结果:ApiResult
## **重点实体列表查询**
...
...
@@ -976,7 +974,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
OrgInfo
>
>
输出结果:ApiResult
## **上市企业制裁强度**
...
...
@@ -990,7 +988,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
BaseCount
>
>
输出结果:ApiResult
## **上市企业融资变化情况**
...
...
@@ -1004,7 +1002,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
BaseCount
>
>
输出结果:ApiResult
## **上市企业市值变化情况**
...
...
@@ -1018,7 +1016,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
BaseCount
>
>
输出结果:ApiResult
## **重点上市企业列表**
...
...
@@ -1032,7 +1030,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
OrgInfo
>
>
输出结果:ApiResult
## **历次制裁涉及领域数查询**
...
...
@@ -1046,7 +1044,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
BaseCount
>
>
输出结果:ApiResult
## **具体领域的制裁实体数统计**
...
...
@@ -1060,7 +1058,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
AnnualCount
>
>
输出结果:ApiResult
## **具体实体类型的制裁实体数统计**
...
...
@@ -1074,7 +1072,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
AnnualCount
>
>
输出结果:ApiResult
## **产业链结构查询**
...
...
@@ -1088,7 +1086,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
Chain
>
>
输出结果:ApiResult
## **根据领域获取产业链信息**
...
...
@@ -1102,7 +1100,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
Chain
>
>
输出结果:ApiResult
## **产业链鱼骨图信息查询**
...
...
@@ -1116,7 +1114,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<FishboneResp>
输出结果:ApiResult
## **产业链中国企业实体信息查询**
...
...
@@ -1130,7 +1128,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<AreasStreamResp>
输出结果:ApiResult
## **实体列表查询**
...
...
@@ -1144,7 +1142,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
OrgInfo
>
>
输出结果:ApiResult
## **历年制裁领域统计**
...
...
@@ -1158,7 +1156,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
AnnualDomainCount
>
>
输出结果:ApiResult
## **新增实体数量增长趋势**
...
...
@@ -1172,7 +1170,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
AnnualCount
>
>
输出结果:ApiResult
## **获取机构的详情信息**
...
...
@@ -1186,7 +1184,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<Organization>
输出结果:ApiResult
## **获取美国前序事件**
...
...
@@ -1200,7 +1198,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<Page
<
EventInfo
>
>
输出结果:ApiResult
## **新增科研机构列表**
...
...
@@ -1214,7 +1212,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
BaseCount
>
>
输出结果:ApiResult
## **各类别仪器对美依赖情况**
...
...
@@ -1224,11 +1222,11 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
输入参数:
参数:List
<String>
orgIds 机构公司ID列表
参数:List orgIds 机构公司ID列表
请求头:携带token
输出结果:ApiResult
<List
<
BaseCount
>
>
输出结果:ApiResult
## **仪器对美依赖度升高风险分析**
...
...
@@ -1242,7 +1240,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
BaseCount
>
>
输出结果:ApiResult
## **仪器进口国可替代性分析**
...
...
@@ -1252,11 +1250,11 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
输入参数:
参数:List
<String>
orgIds 机构公司ID列表
参数:List orgIds 机构公司ID列表
请求头:携带token
输出结果:ApiResult
<List
<
BaseCount
>
>
输出结果:ApiResult
## **仪器国产化降低风险分析**
...
...
@@ -1270,7 +1268,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
BaseCount
>
>
输出结果:ApiResult
## **制裁实体清单50%规则实体数**
...
...
@@ -1284,7 +1282,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<RuleEntityCount>
输出结果:ApiResult
## **科研院所类实体历史制裁情况**
...
...
@@ -1298,7 +1296,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
AnnualCount
>
>
输出结果:ApiResult
## **企业类实体历史制裁情况**
...
...
@@ -1312,7 +1310,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
AnnualCount
>
>
输出结果:ApiResult
## **风险信号**
...
...
@@ -1326,7 +1324,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
RiskSignalVO
>
>
输出结果:ApiResult
## **新闻资讯**
...
...
@@ -1340,7 +1338,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
NewsVO
>
>
输出结果:ApiResult
## **社交媒体**
...
...
@@ -1354,4 +1352,4 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiI
请求头:携带token
输出结果:ApiResult
<List
<
RemarksVO
>
>
\ No newline at end of file
输出结果:ApiResult
\ No newline at end of file
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论