Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
7cd28bf2
提交
7cd28bf2
authored
4月 22, 2026
作者:
张伊明
浏览文件
操作
浏览文件
下载
差异文件
合并分支 'zz-dev' 到 'pre'
feat:智库概览页风险信号的风险等级显示功能修改;智库bug修复 查看合并请求
!379
上级
db5f7d4e
f58ff710
流水线
#583
已通过 于阶段
in 4 分 1 秒
变更
33
流水线
1
全部展开
显示空白字符变更
内嵌
并排
正在显示
33 个修改的文件
包含
731 行增加
和
344 行删除
+731
-344
overview.js
src/api/thinkTank/overview.js
+3
-2
thinktank.js
src/router/modules/thinktank.js
+1
-1
setChart.js
src/utils/setChart.js
+95
-0
index.vue
src/views/coopRestriction/components/dataSub/index.vue
+32
-2
index.vue
src/views/coopRestriction/components/resLib/index.vue
+14
-6
index.vue
...ews/exportControl/v2.0SingleSanction/originPage/index.vue
+13
-33
index.vue
src/views/scientificFunding/components/dataSub/index.vue
+72
-25
index.vue
src/views/scientificFunding/components/resLib/index.vue
+7
-3
index.vue
src/views/thinkTank/CongressHearingView/index.vue
+19
-7
index.vue
src/views/thinkTank/ReportDetail/index.vue
+1
-1
index.vue
src/views/thinkTank/ReportDetail/policyTracking/index.vue
+10
-4
index.vue
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
+20
-8
index.vue
src/views/thinkTank/SurveyProjectView/index.vue
+19
-7
index.vue
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
+0
-0
multiLineChart.js
...nk/ThinkTankDetail/PolicyTracking/utils/multiLineChart.js
+49
-97
piechart.js
...hinkTank/ThinkTankDetail/PolicyTracking/utils/piechart.js
+14
-9
index.vue
...k/ThinkTankDetail/thinkDynamics/CongressHearing/index.vue
+33
-3
index.vue
...nkTank/ThinkTankDetail/thinkDynamics/SurveyForm/index.vue
+32
-3
index.vue
...k/ThinkTankDetail/thinkDynamics/ThinkTankReport/index.vue
+33
-3
index.vue
src/views/thinkTank/ThinkTankDetail/thinkDynamics/index.vue
+24
-2
piechart.js
...ews/thinkTank/ThinkTankDetail/thinkInfo/utils/piechart.js
+6
-1
treeMapChart.js
...thinkTank/ThinkTankDetail/thinkInfo/utils/treeMapChart.js
+12
-1
index.vue
src/views/thinkTank/allThinkTank/index.vue
+1
-1
HomeMainFooterMain.vue
src/views/thinkTank/components/HomeMainFooterMain.vue
+32
-5
HomeMainFooterSurvey.vue
src/views/thinkTank/components/HomeMainFooterSurvey.vue
+32
-5
ThinkTankCongressHearingOverview.vue
...thinkTank/components/ThinkTankCongressHearingOverview.vue
+32
-9
ThinkTankPolicyAdviceOverview.vue
...ws/thinkTank/components/ThinkTankPolicyAdviceOverview.vue
+31
-5
index.vue
src/views/thinkTank/index.vue
+0
-0
index.vue
src/views/thinkTank/reportOriginal/index.vue
+8
-6
multiLineChart.js
src/views/thinkTank/utils/multiLineChart.js
+21
-82
piechart.js
src/views/thinkTank/utils/piechart.js
+15
-9
resourceLibraryFilters.js
src/views/thinkTank/utils/resourceLibraryFilters.js
+17
-4
sankey.js
src/views/thinkTank/utils/sankey.js
+33
-0
没有找到文件。
src/api/thinkTank/overview.js
浏览文件 @
7cd28bf2
...
...
@@ -75,7 +75,7 @@ export function getThinkTankReportDomainStats(params) {
export
function
getThinkTankPolicyIndustry
(
params
)
{
return
request
({
method
:
'GET'
,
url
:
`/api/thinkTankOverview/policyIndustry
/
${
params
.
year
}
`
,
url
:
`/api/thinkTankOverview/policyIndustry`
,
params
})
}
...
...
@@ -180,7 +180,8 @@ export function getThinkTankTestimoniesByThinkTankId(params) {
params
:
{
pageNum
:
params
.
pageNum
,
pageSize
:
params
.
pageSize
,
sortField
:
params
.
sortField
,
sortOrder
:
params
.
sortOrder
,
domainIds
:
params
.
domainIds
,
startDate
:
params
.
startDate
,
endDate
:
params
.
endDate
,
...
...
src/router/modules/thinktank.js
浏览文件 @
7cd28bf2
...
...
@@ -15,7 +15,7 @@ const thinktankRoutes = [
name
:
"thinkTank"
,
component
:
thinkTank
,
meta
:
{
title
:
"科技智库概览"
,
title
:
"
美国
科技智库概览"
,
isShowHeader
:
true
}
},
...
...
src/utils/setChart.js
浏览文件 @
7cd28bf2
...
...
@@ -4,6 +4,69 @@ import getQuarterRange from './getQuarterRange';
import
*
as
echarts
from
'echarts'
import
'echarts-wordcloud'
;
import
router
from
'@/router/index'
const
LEGEND_ARROW_NAMES
=
new
Set
([
'__legend_prev__'
,
'__legend_next__'
])
const
shouldShowLegendPagingArrows
=
(
chart
,
option
)
=>
{
const
w
=
typeof
chart
?.
getWidth
===
'function'
?
chart
.
getWidth
()
:
0
const
legendOpt
=
option
?.
legend
const
legend
=
Array
.
isArray
(
legendOpt
)
?
legendOpt
[
0
]
:
legendOpt
if
(
!
legend
||
legend
.
type
!==
'scroll'
)
return
false
const
names
=
Array
.
isArray
(
legend
.
data
)
?
legend
.
data
:
[]
if
(
!
names
.
length
)
return
false
const
resolveSize
=
(
val
,
base
)
=>
{
if
(
val
==
null
)
return
0
if
(
typeof
val
===
'number'
&&
Number
.
isFinite
(
val
))
return
val
const
s
=
String
(
val
).
trim
()
if
(
!
s
)
return
0
if
(
s
.
endsWith
(
'%'
))
{
const
n
=
parseFloat
(
s
.
slice
(
0
,
-
1
))
return
Number
.
isFinite
(
n
)
?
(
base
*
n
)
/
100
:
0
}
const
n
=
Number
(
s
)
return
Number
.
isFinite
(
n
)
?
n
:
0
}
const
available
=
resolveSize
(
legend
.
width
,
w
)
||
Math
.
floor
(
w
*
0.82
)
if
(
!
available
||
available
<=
0
)
return
true
const
fontSize
=
Number
(
legend
?.
textStyle
?.
fontSize
||
14
)
const
fontFamily
=
legend
?.
textStyle
?.
fontFamily
||
'Source Han Sans CN'
const
fontWeight
=
legend
?.
textStyle
?.
fontWeight
||
400
const
iconW
=
Number
(
legend
?.
itemWidth
||
12
)
const
gap
=
10
const
paddingPerItem
=
iconW
+
gap
+
10
const
canvas
=
document
.
createElement
(
'canvas'
)
const
ctx
=
canvas
.
getContext
(
'2d'
)
if
(
!
ctx
)
return
true
ctx
.
font
=
`
${
fontWeight
}
${
fontSize
}
px
${
fontFamily
}
`
const
total
=
names
.
reduce
((
sum
,
n
)
=>
{
const
text
=
String
(
n
??
''
)
const
textW
=
ctx
.
measureText
(
text
).
width
||
0
return
sum
+
textW
+
paddingPerItem
},
0
)
return
total
>
available
}
const
applyLegendPagingArrowVisibility
=
(
chart
,
option
)
=>
{
const
shouldShow
=
shouldShowLegendPagingArrows
(
chart
,
option
)
const
graphic
=
option
?.
graphic
if
(
!
Array
.
isArray
(
graphic
)
||
graphic
.
length
===
0
)
return
const
nextGraphic
=
graphic
.
map
((
g
)
=>
{
if
(
!
g
||
!
LEGEND_ARROW_NAMES
.
has
(
g
.
name
))
return
g
return
{
...
g
,
invisible
:
!
shouldShow
,
silent
:
!
shouldShow
}
})
chart
.
setOption
({
graphic
:
nextGraphic
},
false
,
true
)
}
const
setChart
=
(
option
,
chartId
,
allowClick
,
selectParam
)
=>
{
let
chartDom
=
document
.
getElementById
(
chartId
);
if
(
!
chartDom
)
{
...
...
@@ -12,8 +75,39 @@ const setChart = (option, chartId, allowClick, selectParam) => {
chartDom
.
removeAttribute
(
"_echarts_instance_"
);
let
chart
=
echarts
.
init
(
chartDom
);
chart
.
setOption
(
option
);
// 处理自定义图例分页箭头(左右分布,隐藏页码)
// 约定:graphic 元素 name 为 __legend_prev__ / __legend_next__
chart
.
on
(
'click'
,
function
(
params
)
{
if
(
params
?.
componentType
!==
'graphic'
)
return
;
if
(
!
LEGEND_ARROW_NAMES
.
has
(
params
?.
name
))
return
;
const
opt
=
chart
.
getOption
?.()
||
{};
const
legend
=
Array
.
isArray
(
opt
.
legend
)
?
opt
.
legend
[
0
]
:
null
;
if
(
!
legend
||
legend
.
type
!==
'scroll'
)
return
;
const
dataLen
=
Array
.
isArray
(
legend
.
data
)
?
legend
.
data
.
length
:
0
;
if
(
dataLen
<=
0
)
return
;
const
cur
=
Number
(
legend
.
scrollDataIndex
||
0
);
const
nextIndex
=
params
.
name
===
'__legend_prev__'
?
Math
.
max
(
0
,
cur
-
1
)
:
Math
.
min
(
dataLen
-
1
,
cur
+
1
);
if
(
nextIndex
===
cur
)
return
;
chart
.
dispatchAction
({
type
:
'legendScroll'
,
scrollDataIndex
:
nextIndex
});
});
// 初次渲染后判断是否需要显示左右箭头(可一行展示则隐藏)
applyLegendPagingArrowVisibility
(
chart
,
option
)
if
(
allowClick
)
{
chart
.
on
(
'click'
,
function
(
params
)
{
// 图例分页箭头只负责翻页,不走任何跳转
if
(
params
?.
componentType
===
'graphic'
&&
LEGEND_ARROW_NAMES
.
has
(
params
?.
name
))
{
return
}
switch
(
selectParam
.
moduleType
)
{
case
'国会法案'
:
if
(
selectParam
.
key
===
1
)
{
...
...
@@ -112,6 +206,7 @@ const setChart = (option, chartId, allowClick, selectParam) => {
// 容器可能受布局/异步渲染影响,强制一次 resize 保证 canvas 与容器一致
setTimeout
(()
=>
{
chart
.
resize
();
applyLegendPagingArrowVisibility
(
chart
,
option
)
},
0
);
return
chart
;
};
...
...
src/views/coopRestriction/components/dataSub/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -186,18 +186,26 @@ let leftChart;
const
rightChartRef
=
ref
(
null
);
let
rightChart
;
const
isShowAiLeft
=
ref
(
tru
e
);
const
isShowAiLeft
=
ref
(
fals
e
);
const
aiContentLeft
=
ref
(
""
);
const
isLeftInterpretLoading
=
ref
(
false
);
const
leftAiAbortController
=
ref
(
null
);
const
isShowAiRight
=
ref
(
tru
e
);
const
isShowAiRight
=
ref
(
fals
e
);
const
aiContentRight
=
ref
(
""
);
const
isRightInterpretLoading
=
ref
(
false
);
const
rightAiAbortController
=
ref
(
null
);
const
handleSwitchAiLeft
=
(
val
)
=>
{
isShowAiLeft
.
value
=
val
;
if
(
val
)
{
fetchLeftInterpretation
();
}
else
{
if
(
leftAiAbortController
.
value
)
{
leftAiAbortController
.
value
.
abort
();
leftAiAbortController
.
value
=
null
;
}
isLeftInterpretLoading
.
value
=
false
;
}
};
...
...
@@ -205,6 +213,12 @@ const handleSwitchAiRight = (val) => {
isShowAiRight
.
value
=
val
;
if
(
val
)
{
fetchRightInterpretation
();
}
else
{
if
(
rightAiAbortController
.
value
)
{
rightAiAbortController
.
value
.
abort
();
rightAiAbortController
.
value
=
null
;
}
isRightInterpretLoading
.
value
=
false
;
}
};
...
...
@@ -276,12 +290,17 @@ const fetchLeftInterpretation = async () => {
if
(
hasValidContent
||
isLeftInterpretLoading
.
value
)
{
return
;
}
if
(
leftAiAbortController
.
value
)
{
leftAiAbortController
.
value
.
abort
();
}
leftAiAbortController
.
value
=
new
AbortController
();
isLeftInterpretLoading
.
value
=
true
;
aiContentLeft
.
value
=
"解读生成中…"
;
try
{
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
payload
)
},
{
signal
:
leftAiAbortController
.
value
.
signal
,
onChunk
:
(
chunk
)
=>
{
// 与智库概览「数量变化趋势」一致:按 chunk 增量拼接展示
appendAiInterpretationChunk
(
aiContentLeft
,
chunk
);
...
...
@@ -292,10 +311,13 @@ const fetchLeftInterpretation = async () => {
// 与智库概览一致:优先用最终「解读」收口;否则保留已拼接内容
aiContentLeft
.
value
=
text
||
aiContentLeft
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
if
(
error
?.
name
!==
"AbortError"
)
{
console
.
error
(
"合作限制政策对比图表解读请求失败"
,
error
);
aiContentLeft
.
value
=
"解读加载失败"
;
}
}
finally
{
isLeftInterpretLoading
.
value
=
false
;
leftAiAbortController
.
value
=
null
;
}
};
...
...
@@ -348,12 +370,17 @@ const fetchRightInterpretation = async () => {
if
(
hasValidContent
||
isRightInterpretLoading
.
value
)
{
return
;
}
if
(
rightAiAbortController
.
value
)
{
rightAiAbortController
.
value
.
abort
();
}
rightAiAbortController
.
value
=
new
AbortController
();
isRightInterpretLoading
.
value
=
true
;
aiContentRight
.
value
=
"解读生成中…"
;
try
{
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
payload
)
},
{
signal
:
rightAiAbortController
.
value
.
signal
,
onChunk
:
(
chunk
)
=>
{
appendAiInterpretationChunk
(
aiContentRight
,
chunk
);
}
...
...
@@ -362,10 +389,13 @@ const fetchRightInterpretation = async () => {
const
text
=
getInterpretationTextFromChartResponse
(
res
);
aiContentRight
.
value
=
text
||
aiContentRight
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
if
(
error
?.
name
!==
"AbortError"
)
{
console
.
error
(
"合作限制领域分布图表解读请求失败"
,
error
);
aiContentRight
.
value
=
"解读加载失败"
;
}
}
finally
{
isRightInterpretLoading
.
value
=
false
;
rightAiAbortController
.
value
=
null
;
}
};
...
...
src/views/coopRestriction/components/resLib/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -61,17 +61,17 @@
</div>
</div>
</div>
<div
class=
"page"
>
<div
class=
"count"
>
共
{{
total
}}
项调查
</div>
<el-pagination
v-model:current-page=
"currentPage"
:page-size=
"pageSize"
:total=
"total"
layout=
"prev, pager, next"
background
@
current-change=
"handlePageChange"
/>
</div>
</
template
>
<
template
v-else
>
<div
class=
"right-main-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
</
template
>
<div
class=
"page"
>
<div
class=
"count"
>
共 {{ total }} 项调查
</div>
<el-pagination
v-model:current-page=
"currentPage"
:page-size=
"pageSize"
:page-count=
"pageCount"
layout=
"prev, pager, next"
background
@
current-change=
"handlePageChange"
/>
</div>
</div>
</div>
</div>
...
...
@@ -293,6 +293,11 @@ const total = ref(0);
const
pageSize
=
ref
(
10
);
const
currentPage
=
ref
(
1
);
const
reslibContainer
=
ref
(
null
);
const
pageCount
=
computed
(()
=>
{
const
size
=
Number
(
pageSize
.
value
||
10
)
||
10
;
const
t
=
Number
(
total
.
value
||
0
)
||
0
;
return
Math
.
max
(
1
,
Math
.
ceil
(
t
/
size
));
});
const
getTypeClass
=
(
type
)
=>
{
const
map
=
{
...
...
@@ -658,13 +663,16 @@ watch(currentPage, () => {
.title
{
font-size
:
20px
;
width
:
950px
;
max-
width
:
950px
;
font-weight
:
700
;
font-family
:
"Microsoft YaHei"
;
line-height
:
26px
;
color
:
rgb
(
59
,
65
,
75
);
margin-bottom
:
9px
;
cursor
:
pointer
;
white-space
:
normal
;
overflow-wrap
:
anywhere
;
word-break
:
break-word
;
}
.content
{
...
...
src/views/exportControl/v2.0SingleSanction/originPage/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -26,27 +26,15 @@
<div
class=
"btn-box"
>
<div
class=
"translate"
>
<div
class=
"search-input-wrap"
v-if=
"showSearchInput"
>
<input
v-model=
"searchKeywordText"
class=
"search-input"
placeholder=
"回车查询"
@
keyup
.
enter=
"handleSearchInPdf"
/>
<input
v-model=
"searchKeywordText"
class=
"search-input"
placeholder=
"回车查询"
@
keyup
.
enter=
"handleSearchInPdf"
/>
<div
class=
"search-match-count"
>
{{
matchInfo
.
current
}}
/
{{
matchInfo
.
total
}}
</div>
<button
class=
"search-nav-btn"
type=
"button"
@
click=
"handlePrevMatch"
:disabled=
"matchInfo.total === 0 || matchInfo.current
<
=
1
"
>
<button
class=
"search-nav-btn"
type=
"button"
@
click=
"handlePrevMatch"
:disabled=
"matchInfo.total === 0 || matchInfo.current
<
=
1
"
>
上一个
</button>
<button
class=
"search-nav-btn"
type=
"button"
@
click=
"handleNextMatch"
:disabled=
"matchInfo.total === 0 || matchInfo.current >= matchInfo.total"
>
<button
class=
"search-nav-btn"
type=
"button"
@
click=
"handleNextMatch"
:disabled=
"matchInfo.total === 0 || matchInfo.current >= matchInfo.total"
>
下一个
</button>
</div>
...
...
@@ -54,19 +42,14 @@
<el-switch
v-model=
"valueSwitch"
/>
</div>
<div
class=
"translate-image"
>
<img
class=
"translate-icon"
src=
"../assets/icon-translation.png"
alt=
""
style=
"
<img
class=
"translate-icon"
src=
"../assets/icon-translation.png"
alt=
""
style=
"
width: 16px;
height: 16px;
max-width: 16px;
max-height: 16px;
display: block;
object-fit: contain;
"
/>
"
/>
</div>
<div
class=
"translate-text"
>
{{
"显示原文"
}}
</div>
</div>
...
...
@@ -83,12 +66,8 @@
<pdf
ref=
"leftPdfRef"
:pdfUrl=
"reportUrlEnWithPage"
class=
"pdf-pane-inner"
/>
</div>
<div
class=
"pdf-pane-wrap"
:class=
"
{ 'is-full': !valueSwitch }" v-if="reportUrlWithPage">
<pdf
:key=
"`right-pdf-$
{valueSwitch ? 'split' : 'full'}`"
ref="rightPdfRef"
:pdfUrl="reportUrlWithPage"
class="pdf-pane-inner"
/>
<pdf
:key=
"`right-pdf-$
{valueSwitch ? 'split' : 'full'}`" ref="rightPdfRef" :pdfUrl="reportUrlWithPage"
class="pdf-pane-inner" />
</div>
</div>
</div>
...
...
@@ -203,7 +182,7 @@ const handleSearchInPdf = async () => {
try
{
const
{
ElMessage
}
=
await
import
(
"element-plus"
);
ElMessage
.
warning
(
"未找到包含该关键词的页面"
);
}
catch
(
_
)
{}
}
catch
(
_
)
{
}
}
};
...
...
@@ -246,7 +225,7 @@ const handleDownload = async () => {
try
{
const
{
ElMessage
}
=
await
import
(
"element-plus"
);
ElMessage
.
warning
(
"暂无下载链接"
);
}
catch
(
_
)
{}
}
catch
(
_
)
{
}
return
;
}
const
baseName
=
(
thinkInfo
.
value
?.
name
||
"报告原文"
).
replace
(
/
[/\\
?%*:|"<>
]
/g
,
"-"
);
...
...
@@ -659,6 +638,7 @@ onMounted(async () => {
display
:
flex
;
gap
:
8px
;
cursor
:
pointer
;
.icon
{
width
:
16px
;
height
:
16px
;
...
...
src/views/scientificFunding/components/dataSub/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -171,6 +171,10 @@ const options = [
];
/** value 须与 v-model 类型一致(数字),否则 el-select 无法匹配 label,会显示成「2025」而非「2025年」 */
const
options1
=
[
{
value
:
2026
,
label
:
"2026年"
},
{
value
:
2025
,
label
:
"2025年"
...
...
@@ -719,11 +723,11 @@ let rightChart1;
let
leftSankey
;
let
boxplotChart
;
// ------- AI 解读(
刷新后默认展开,行为对齐智库概览
) -------
const
isShowAiContentLeft1
=
ref
(
tru
e
);
const
isShowAiContentLeft2
=
ref
(
tru
e
);
const
isShowAiContentRight1
=
ref
(
tru
e
);
const
isShowAiContentRight2
=
ref
(
tru
e
);
// ------- AI 解读(
默认仅展示 AiButton,悬停后再请求 AI
) -------
const
isShowAiContentLeft1
=
ref
(
fals
e
);
const
isShowAiContentLeft2
=
ref
(
fals
e
);
const
isShowAiContentRight1
=
ref
(
fals
e
);
const
isShowAiContentRight2
=
ref
(
fals
e
);
const
aiContentLeft1
=
ref
(
""
);
const
aiContentLeft2
=
ref
(
""
);
...
...
@@ -735,6 +739,11 @@ const isAiLoadingLeft2 = ref(false);
const
isAiLoadingRight1
=
ref
(
false
);
const
isAiLoadingRight2
=
ref
(
false
);
const
left1AiAbortController
=
ref
(
null
);
const
left2AiAbortController
=
ref
(
null
);
const
right1AiAbortController
=
ref
(
null
);
const
right2AiAbortController
=
ref
(
null
);
const
AI_LOADING_TEXT
=
"解读生成中…"
;
// 用于保证“切换筛选后只写入最新一次解读结果”
const
left1AiSeq
=
ref
(
0
);
...
...
@@ -767,7 +776,13 @@ const getInterpretationTextFromChartResponse = (res) => {
);
};
const
fetchChartInterpretationOnce
=
async
(
payload
,
targetRef
,
loadingRef
,
aiSeqRef
)
=>
{
const
fetchChartInterpretationOnce
=
async
(
payload
,
targetRef
,
loadingRef
,
aiSeqRef
,
abortControllerRef
)
=>
{
if
(
loadingRef
.
value
)
return
;
const
hasValidContent
=
...
...
@@ -778,12 +793,19 @@ const fetchChartInterpretationOnce = async (payload, targetRef, loadingRef, aiSe
if
(
hasValidContent
)
return
;
const
localSeq
=
aiSeqRef
.
value
;
if
(
abortControllerRef
?.
value
)
{
abortControllerRef
.
value
.
abort
();
}
if
(
abortControllerRef
)
{
abortControllerRef
.
value
=
new
AbortController
();
}
loadingRef
.
value
=
true
;
targetRef
.
value
=
AI_LOADING_TEXT
;
try
{
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
payload
)
},
{
...(
abortControllerRef
?.
value
?.
signal
?
{
signal
:
abortControllerRef
.
value
.
signal
}
:
{}),
onChunk
:
(
chunk
)
=>
{
if
(
aiSeqRef
.
value
!==
localSeq
)
return
;
appendAiInterpretationChunk
(
targetRef
,
chunk
,
AI_LOADING_TEXT
);
...
...
@@ -796,10 +818,13 @@ const fetchChartInterpretationOnce = async (payload, targetRef, loadingRef, aiSe
targetRef
.
value
=
text
||
targetRef
.
value
||
"未返回有效解读内容"
;
}
catch
(
e
)
{
if
(
aiSeqRef
.
value
!==
localSeq
)
return
;
if
(
e
?.
name
!==
"AbortError"
)
{
console
.
error
(
"图表解读请求失败"
,
e
);
targetRef
.
value
=
"解读加载失败"
;
}
}
finally
{
if
(
aiSeqRef
.
value
===
localSeq
)
loadingRef
.
value
=
false
;
if
(
abortControllerRef
)
abortControllerRef
.
value
=
null
;
}
};
...
...
@@ -842,43 +867,71 @@ const buildPayloadRight2 = () => {
const
handleSwitchAiLeft1
=
async
(
val
)
=>
{
isShowAiContentLeft1
.
value
=
val
;
if
(
!
val
)
return
;
if
(
!
val
)
{
if
(
left1AiAbortController
.
value
)
{
left1AiAbortController
.
value
.
abort
();
left1AiAbortController
.
value
=
null
;
}
isAiLoadingLeft1
.
value
=
false
;
return
;
}
const
payload
=
buildPayloadLeft1
();
if
(
!
payload
)
{
aiContentLeft1
.
value
=
"暂无图表数据"
;
return
;
}
await
fetchChartInterpretationOnce
(
payload
,
aiContentLeft1
,
isAiLoadingLeft1
,
left1AiSeq
);
await
fetchChartInterpretationOnce
(
payload
,
aiContentLeft1
,
isAiLoadingLeft1
,
left1AiSeq
,
left1AiAbortController
);
};
const
handleSwitchAiLeft2
=
async
(
val
)
=>
{
isShowAiContentLeft2
.
value
=
val
;
if
(
!
val
)
return
;
if
(
!
val
)
{
if
(
left2AiAbortController
.
value
)
{
left2AiAbortController
.
value
.
abort
();
left2AiAbortController
.
value
=
null
;
}
isAiLoadingLeft2
.
value
=
false
;
return
;
}
const
payload
=
buildPayloadLeft2
();
if
(
!
payload
)
{
aiContentLeft2
.
value
=
"暂无图表数据"
;
return
;
}
await
fetchChartInterpretationOnce
(
payload
,
aiContentLeft2
,
isAiLoadingLeft2
,
left2AiSeq
);
await
fetchChartInterpretationOnce
(
payload
,
aiContentLeft2
,
isAiLoadingLeft2
,
left2AiSeq
,
left2AiAbortController
);
};
const
handleSwitchAiRight1
=
async
(
val
)
=>
{
isShowAiContentRight1
.
value
=
val
;
if
(
!
val
)
return
;
if
(
!
val
)
{
if
(
right1AiAbortController
.
value
)
{
right1AiAbortController
.
value
.
abort
();
right1AiAbortController
.
value
=
null
;
}
isAiLoadingRight1
.
value
=
false
;
return
;
}
const
payload
=
buildPayloadRight1
();
if
(
!
payload
)
{
aiContentRight1
.
value
=
"暂无图表数据"
;
return
;
}
await
fetchChartInterpretationOnce
(
payload
,
aiContentRight1
,
isAiLoadingRight1
,
right1AiSeq
);
await
fetchChartInterpretationOnce
(
payload
,
aiContentRight1
,
isAiLoadingRight1
,
right1AiSeq
,
right1AiAbortController
);
};
const
handleSwitchAiRight2
=
async
(
val
)
=>
{
isShowAiContentRight2
.
value
=
val
;
if
(
!
val
)
return
;
if
(
!
val
)
{
if
(
right2AiAbortController
.
value
)
{
right2AiAbortController
.
value
.
abort
();
right2AiAbortController
.
value
=
null
;
}
isAiLoadingRight2
.
value
=
false
;
return
;
}
const
payload
=
buildPayloadRight2
();
if
(
!
payload
)
{
aiContentRight2
.
value
=
"暂无图表数据"
;
return
;
}
await
fetchChartInterpretationOnce
(
payload
,
aiContentRight2
,
isAiLoadingRight2
,
right2AiSeq
);
await
fetchChartInterpretationOnce
(
payload
,
aiContentRight2
,
isAiLoadingRight2
,
right2AiSeq
,
right2AiAbortController
);
};
...
...
@@ -1136,21 +1189,15 @@ const initLeftSankey = (data) => {
// };
onMounted
(()
=>
{
// 刷新后 AiPane 默认展开:先给出“解读生成中…”占位,再在数据到位后触发解读请求
aiContentLeft1
.
value
=
"解读生成中…"
;
aiContentLeft2
.
value
=
"解读生成中…"
;
aiContentRight1
.
value
=
"解读生成中…"
;
aiContentRight2
.
value
=
"解读生成中…"
;
// 先拉数据;每块数据到位后立即触发一次 AI 解读(不必等其它块完成)
void
handleGetFundField
().
then
(()
=>
handleSwitchAiLeft1
(
true
));
// 仅拉取图表数据;AI 解读仅在用户悬停打开面板时触发
void
handleGetFundField
();
void
handleFindCountryProjectAreaList
();
void
handlegetCountryFundingChange
()
.
then
(()
=>
handleSwitchAiRight1
(
true
))
;
void
handlegetCountryFundingChange
();
void
handlegetCountryFundProjectChange
();
void
handleGetOrgFundsArea
()
.
then
(()
=>
handleSwitchAiLeft2
(
true
))
;
void
handlegetOrgFundStrength
()
.
then
(()
=>
handleSwitchAiRight2
(
true
))
;
void
handleGetOrgFundsArea
();
void
handlegetOrgFundStrength
();
});
// onBeforeUnmount(() => {
// window.removeEventListener("resize", handleResize);
...
...
src/views/scientificFunding/components/resLib/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -166,6 +166,10 @@ const handleTimeGroupChange = (val) => {
};
const
pubTimeList
=
ref
([
{
id
:
2026
,
name
:
"2026年"
},
{
id
:
2025
,
name
:
"2025年"
...
...
@@ -192,13 +196,13 @@ const pubTimeList = ref([
}
]);
/** 选择「全部时间」时,yearlist 传 2000~202
5
逐年 */
/** 选择「全部时间」时,yearlist 传 2000~202
6
逐年 */
const
YEAR_ALL_RANGE_START
=
2000
;
const
YEAR_ALL_RANGE_END
=
202
5
;
const
YEAR_ALL_RANGE_END
=
202
6
;
const
buildYearlistForRequest
=
(
selectedTimeModel
)
=>
{
const
strippedTime
=
stripAllTimeForRequest
(
selectedTimeModel
);
// 仅勾选「全部时间」、未选具体年份时,传 2000~202
5
逐年
// 仅勾选「全部时间」、未选具体年份时,传 2000~202
6
逐年
if
(
strippedTime
.
length
===
0
)
{
const
out
=
[];
for
(
let
y
=
YEAR_ALL_RANGE_START
;
y
<=
YEAR_ALL_RANGE_END
;
y
+=
1
)
{
...
...
src/views/thinkTank/CongressHearingView/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -136,7 +136,7 @@
<div
class=
"box5-footer"
>
<TipTab
:text=
"REPORT_ANALYSIS_TIP_BOX5"
/>
</div>
<div
class=
"ai-wrap"
@
mouseenter=
"handleSwitchAiContentShowBox5(true)"
>
<div
class=
"ai-wrap"
v-if=
"!isShowAiContentBox5"
@
mouseenter=
"handleSwitchAiContentShowBox5(true)"
>
<AiButton
/>
</div>
...
...
@@ -305,14 +305,21 @@ const handleGetThinkTankHearingInfo = async () => {
};
const
REPORT_ANALYSIS_TIP_BOX5
=
"国会听证会关键词云,数据来源:美国兰德公司官网"
;
//
刷新后默认展示「报告关键词云」AI 总结
const
isShowAiContentBox5
=
ref
(
tru
e
);
//
默认仅展示 AiButton,悬停后再请求 AI
const
isShowAiContentBox5
=
ref
(
fals
e
);
const
aiContentBox5
=
ref
(
""
);
const
isBox5InterpretLoading
=
ref
(
false
);
const
box5AiAbortController
=
ref
(
null
);
const
handleSwitchAiContentShowBox5
=
(
val
)
=>
{
isShowAiContentBox5
.
value
=
val
;
if
(
val
)
{
fetchBox5ChartInterpretation
();
}
else
{
if
(
box5AiAbortController
.
value
)
{
box5AiAbortController
.
value
.
abort
();
box5AiAbortController
.
value
=
null
;
}
isBox5InterpretLoading
.
value
=
false
;
}
};
...
...
@@ -510,10 +517,7 @@ const handleGetThinkTankReportIndustryCloud = async () => {
if
(
data
.
length
)
{
box5WordCloudKey
.
value
+=
1
;
}
// 刷新后默认展开 AI:数据就绪即触发解读
if
(
isShowAiContentBox5
.
value
)
{
fetchBox5ChartInterpretation
();
}
// 仅在用户打开 AI 面板时才请求解读
}
else
{
box5Data
.
value
=
[];
}
...
...
@@ -663,6 +667,10 @@ const fetchBox5ChartInterpretation = async () => {
if
(
hasValidContent
||
isBox5InterpretLoading
.
value
)
{
return
;
}
if
(
box5AiAbortController
.
value
)
{
box5AiAbortController
.
value
.
abort
();
}
box5AiAbortController
.
value
=
new
AbortController
();
isBox5InterpretLoading
.
value
=
true
;
aiContentBox5
.
value
=
"解读生成中…"
;
const
chartPayload
=
{
...
...
@@ -677,6 +685,7 @@ const fetchBox5ChartInterpretation = async () => {
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
chartPayload
)
},
{
signal
:
box5AiAbortController
.
value
.
signal
,
onChunk
:
chunk
=>
{
appendAiInterpretationChunk
(
aiContentBox5
,
chunk
);
}
...
...
@@ -685,10 +694,13 @@ const fetchBox5ChartInterpretation = async () => {
const
text
=
getInterpretationTextFromChartResponse
(
res
);
aiContentBox5
.
value
=
text
||
aiContentBox5
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
if
(
error
?.
name
!==
"AbortError"
)
{
console
.
error
(
"报告关键词云图表解读请求失败"
,
error
);
aiContentBox5
.
value
=
"解读加载失败"
;
}
}
finally
{
isBox5InterpretLoading
.
value
=
false
;
box5AiAbortController
.
value
=
null
;
}
};
...
...
src/views/thinkTank/ReportDetail/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -248,7 +248,7 @@ const handleDownloadDocument = async () => {
display
:
flex
;
img
{
width
:
72
px
;
width
:
178
px
;
height
:
88px
;
}
...
...
src/views/thinkTank/ReportDetail/policyTracking/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -68,11 +68,11 @@
<div
class=
"info-content"
>
<div
class=
"info-item"
>
<div
class=
"info-text"
>
{{
"相关领域:"
}}
</div>
<div
class=
"info-right"
v-if=
"
box1DataItem && box1DataItem.domain
s"
>
<div
class=
"info-right"
v-if=
"
hasDomainTag
s"
>
<div
class=
"tag-box"
>
<div
class=
"tag"
v-for=
"(
item, index) in box1DataItem.domains"
:key=
"index"
v-show=
"item"
>
{{
item
.
industryName
}}
</div>
<div
class=
"tag"
v-for=
"(
name, index) in domainTags"
:key=
"name + '-' + index"
>
{{
name
}}
</div>
</div>
</div>
</div>
...
...
@@ -155,6 +155,12 @@ const activeItemIndex = ref(0);
const
pageSize
=
ref
(
10
);
// 当前选中 item 的数据
const
box1DataItem
=
ref
(
null
);
const
domainTags
=
computed
(()
=>
{
const
domains
=
box1DataItem
.
value
?.
domains
;
const
list
=
Array
.
isArray
(
domains
)
?
domains
:
[];
return
list
.
map
((
d
)
=>
d
?.
industryName
).
filter
(
Boolean
);
});
const
hasDomainTags
=
computed
(()
=>
domainTags
.
value
.
length
>
0
);
// 整个页面容器,用于分页后滚回 wrap 顶部
const
wrapRef
=
ref
(
null
);
const
reportUrl
=
ref
(
""
);
...
...
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -79,7 +79,7 @@
<div
class=
"box5-footer"
>
<TipTab
:text=
"REPORT_ANALYSIS_TIP_BOX5"
/>
</div>
<div
class=
"ai-wrap"
@
mouseenter=
"handleSwitchAiContentShowBox5(true)"
>
<div
class=
"ai-wrap"
v-if=
"!isShowAiContentBox5"
@
mouseenter=
"handleSwitchAiContentShowBox5(true)"
>
<AiButton
/>
</div>
...
...
@@ -259,14 +259,21 @@ const props = defineProps({
});
const
REPORT_ANALYSIS_TIP_BOX5
=
"智库报告关键词云,数据来源:美国兰德公司官网"
;
//
刷新后默认展示「报告关键词云」AI 总结
const
isShowAiContentBox5
=
ref
(
tru
e
);
//
默认仅展示 AiButton,悬停后再请求 AI
const
isShowAiContentBox5
=
ref
(
fals
e
);
const
aiContentBox5
=
ref
(
""
);
const
isBox5InterpretLoading
=
ref
(
false
);
const
box5AiAbortController
=
ref
(
null
);
const
handleSwitchAiContentShowBox5
=
(
val
)
=>
{
isShowAiContentBox5
.
value
=
val
;
if
(
val
)
{
fetchBox5ChartInterpretation
();
}
else
{
if
(
box5AiAbortController
.
value
)
{
box5AiAbortController
.
value
.
abort
();
box5AiAbortController
.
value
=
null
;
}
isBox5InterpretLoading
.
value
=
false
;
}
};
...
...
@@ -463,10 +470,7 @@ const handleGetThinkTankReportIndustryCloud = async () => {
if
(
data
.
length
)
{
box5WordCloudKey
.
value
+=
1
;
}
// 刷新后默认展开 AI:数据就绪即触发解读
if
(
isShowAiContentBox5
.
value
)
{
fetchBox5ChartInterpretation
();
}
// 仅在用户打开 AI 面板时才请求解读
}
else
{
box5Data
.
value
=
[];
}
...
...
@@ -617,6 +621,10 @@ const fetchBox5ChartInterpretation = async () => {
if
(
hasValidContent
||
isBox5InterpretLoading
.
value
)
{
return
;
}
if
(
box5AiAbortController
.
value
)
{
box5AiAbortController
.
value
.
abort
();
}
box5AiAbortController
.
value
=
new
AbortController
();
isBox5InterpretLoading
.
value
=
true
;
aiContentBox5
.
value
=
"解读生成中…"
;
const
chartPayload
=
{
...
...
@@ -631,6 +639,7 @@ const fetchBox5ChartInterpretation = async () => {
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
chartPayload
)
},
{
signal
:
box5AiAbortController
.
value
.
signal
,
onChunk
:
chunk
=>
{
appendAiInterpretationChunk
(
aiContentBox5
,
chunk
);
}
...
...
@@ -639,10 +648,13 @@ const fetchBox5ChartInterpretation = async () => {
const
text
=
getInterpretationTextFromChartResponse
(
res
);
aiContentBox5
.
value
=
text
||
aiContentBox5
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
if
(
error
?.
name
!==
"AbortError"
)
{
console
.
error
(
"报告关键词云图表解读请求失败"
,
error
);
aiContentBox5
.
value
=
"解读加载失败"
;
}
}
finally
{
isBox5InterpretLoading
.
value
=
false
;
box5AiAbortController
.
value
=
null
;
}
};
...
...
@@ -963,7 +975,7 @@ onMounted(() => {
display
:
flex
;
.left
{
width
:
56
px
;
width
:
150
px
;
height
:
74px
;
margin-top
:
8px
;
...
...
src/views/thinkTank/SurveyProjectView/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -43,7 +43,7 @@
<div
class=
"box1-footer"
>
<TipTab
:text=
"REPORT_ANALYSIS_TIP_BOX5"
/>
</div>
<div
class=
"ai-wrap"
@
mouseenter=
"handleSwitchAiContentShowBox5(true)"
>
<div
class=
"ai-wrap"
v-if=
"!isShowAiContentBox5"
@
mouseenter=
"handleSwitchAiContentShowBox5(true)"
>
<AiButton
/>
</div>
...
...
@@ -185,14 +185,21 @@ const applySurveyProjectDocumentTitle = (title) => {
};
const
REPORT_ANALYSIS_TIP_BOX5
=
"调查项目关键词云,数据来源:美国兰德公司官网"
;
//
刷新后默认展示「报告关键词云」AI 总结
const
isShowAiContentBox5
=
ref
(
tru
e
);
//
默认仅展示 AiButton,悬停后再请求 AI
const
isShowAiContentBox5
=
ref
(
fals
e
);
const
aiContentBox5
=
ref
(
""
);
const
isBox5InterpretLoading
=
ref
(
false
);
const
box5AiAbortController
=
ref
(
null
);
const
handleSwitchAiContentShowBox5
=
(
val
)
=>
{
isShowAiContentBox5
.
value
=
val
;
if
(
val
)
{
fetchBox5ChartInterpretation
();
}
else
{
if
(
box5AiAbortController
.
value
)
{
box5AiAbortController
.
value
.
abort
();
box5AiAbortController
.
value
=
null
;
}
isBox5InterpretLoading
.
value
=
false
;
}
};
const
searchOpinions
=
ref
(
''
);
...
...
@@ -403,10 +410,7 @@ const handleGetThinkTankReportIndustryCloud = async () => {
if
(
data
.
length
)
{
box5WordCloudKey
.
value
+=
1
;
}
// 刷新后默认展开 AI:数据就绪即触发解读
if
(
isShowAiContentBox5
.
value
)
{
fetchBox5ChartInterpretation
();
}
// 仅在用户打开 AI 面板时才请求解读
}
else
{
box5Data
.
value
=
[];
}
...
...
@@ -548,6 +552,10 @@ const fetchBox5ChartInterpretation = async () => {
if
(
hasValidContent
||
isBox5InterpretLoading
.
value
)
{
return
;
}
if
(
box5AiAbortController
.
value
)
{
box5AiAbortController
.
value
.
abort
();
}
box5AiAbortController
.
value
=
new
AbortController
();
isBox5InterpretLoading
.
value
=
true
;
aiContentBox5
.
value
=
"解读生成中…"
;
const
chartPayload
=
{
...
...
@@ -562,6 +570,7 @@ const fetchBox5ChartInterpretation = async () => {
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
chartPayload
)
},
{
signal
:
box5AiAbortController
.
value
.
signal
,
onChunk
:
chunk
=>
{
appendAiInterpretationChunk
(
aiContentBox5
,
chunk
);
}
...
...
@@ -570,10 +579,13 @@ const fetchBox5ChartInterpretation = async () => {
const
text
=
getInterpretationTextFromChartResponse
(
res
);
aiContentBox5
.
value
=
text
||
aiContentBox5
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
if
(
error
?.
name
!==
"AbortError"
)
{
console
.
error
(
"报告关键词云图表解读请求失败"
,
error
);
aiContentBox5
.
value
=
"解读加载失败"
;
}
}
finally
{
isBox5InterpretLoading
.
value
=
false
;
box5AiAbortController
.
value
=
null
;
}
};
...
...
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
浏览文件 @
7cd28bf2
差异被折叠。
点击展开。
src/views/thinkTank/ThinkTankDetail/PolicyTracking/utils/multiLineChart.js
浏览文件 @
7cd28bf2
import
*
as
echarts
from
'echarts'
import
{
MUTICHARTCOLORS
}
from
"@/common/constant.js"
;
// 按 AreaTag 的颜色规则映射到折线图配色(取 tag 的文字色)
const
AREA_TAG_COLOR_BY_NAME
=
{
'人工智能'
:
'rgba(245, 34, 45, 1)'
,
// tag1
'生物科技'
:
'rgba(19, 168, 168, 1)'
,
// tag2
'新一代通信网络'
:
'rgba(5, 95, 194, 1)'
,
// tag3
// 兼容常见写法
'通信网络'
:
'rgba(5, 95, 194, 1)'
,
'量子科技'
:
'rgba(114, 46, 209, 1)'
,
// tag4
'新能源'
:
'rgba(82, 196, 26, 1)'
,
// tag5
'集成电路'
:
'rgba(22, 119, 255, 1)'
,
// tag6
'海洋'
:
'rgba(15, 120, 199, 1)'
,
// tag7
'先进制造'
:
'rgba(250, 173, 20, 1)'
,
// tag8
'新材料'
:
'rgba(250, 140, 22, 1)'
,
// tag9
'航空航天'
:
'rgba(47, 84, 235, 1)'
,
// tag10
'太空'
:
'rgba(47, 84, 235, 1)'
,
// tag11
'深海'
:
'rgba(73, 104, 161, 1)'
,
// tag12
'极地'
:
'rgba(133, 165, 255, 1)'
,
// tag13
'核'
:
'rgba(250, 84, 28, 1)'
,
// tag14
'其他'
:
'rgba(82, 196, 26, 1)'
// tag15
}
const
fallbackColorList
=
[
'rgba(5, 95, 194, 1)'
,
'rgba(245, 34, 45, 1)'
,
'rgba(19, 168, 168, 1)'
,
'rgba(250, 140, 22, 1)'
,
'rgba(114, 46, 209, 1)'
,
'rgba(82, 196, 26, 1)'
,
'rgba(22, 119, 255, 1)'
,
'rgba(250, 84, 28, 1)'
,
'rgba(47, 84, 235, 1)'
,
'rgba(133, 165, 255, 1)'
]
const
parseRgba
=
(
colorStr
)
=>
{
const
match
=
colorStr
.
match
(
/rgba
\((\d
+
)
,
\s
*
(\d
+
)
,
\s
*
(\d
+
)
,
\s
*
(\d
+
(\.\d
+
)?)\)
/
)
if
(
match
)
{
const
parseHexToRgb
=
(
hex
)
=>
{
const
h
=
String
(
hex
||
''
).
replace
(
'#'
,
''
).
trim
()
if
(
h
.
length
!==
6
)
return
{
r
:
0
,
g
:
0
,
b
:
0
}
return
{
r
:
parseInt
(
match
[
1
],
10
),
g
:
parseInt
(
match
[
2
],
10
),
b
:
parseInt
(
match
[
3
],
10
),
a
:
parseFloat
(
match
[
4
])
}
r
:
parseInt
(
h
.
slice
(
0
,
2
),
16
),
g
:
parseInt
(
h
.
slice
(
2
,
4
),
16
),
b
:
parseInt
(
h
.
slice
(
4
,
6
),
16
),
}
return
{
r
:
0
,
g
:
0
,
b
:
0
,
a
:
1
}
}
/**
...
...
@@ -55,22 +19,14 @@ const getMultiLineChart = (chartInput) => {
const
series
=
chartInput
.
data
||
[]
const
allNames
=
series
.
map
((
item
)
=>
item
.
name
)
const
lineSize
=
Math
.
ceil
(
allNames
.
length
/
3
)
const
legendLine1
=
allNames
.
slice
(
0
,
lineSize
)
const
legendLine2
=
allNames
.
slice
(
lineSize
,
lineSize
*
2
)
const
legendLine3
=
allNames
.
slice
(
lineSize
*
2
)
const
xCount
=
Array
.
isArray
(
title
)
?
title
.
length
:
0
const
labelFontSize
=
xCount
>
8
?
10
:
xCount
>
5
?
11
:
12
const
labelRotate
=
xCount
>
6
?
28
:
0
const
echartsSeries
=
series
.
map
((
item
,
index
)
=>
{
const
baseColor
=
item
.
color
||
AREA_TAG_COLOR_BY_NAME
[
item
.
name
]
||
fallbackColorList
[
index
%
fallbackColorList
.
length
]
||
`rgba(
${
Math
.
floor
(
Math
.
random
()
*
256
)}
,
${
Math
.
floor
(
Math
.
random
()
*
256
)}
,
${
Math
.
floor
(
Math
.
random
()
*
256
)}
, 1)`
const
{
r
,
g
,
b
}
=
parseRgba
(
baseColor
)
const
baseColor
=
MUTICHARTCOLORS
[
index
%
MUTICHARTCOLORS
.
length
]
||
'#055FC2'
const
{
r
,
g
,
b
}
=
parseHexToRgb
(
baseColor
)
return
{
name
:
item
.
name
,
...
...
@@ -102,39 +58,31 @@ const getMultiLineChart = (chartInput) => {
},
/* 贴满 #box3Chart:四边 0,由 containLabel 在网格内为轴文字留位,避免左侧/底部大块留白 */
grid
:
{
top
:
92
,
top
:
60
,
right
:
10
,
bottom
:
0
,
left
:
20
,
containLabel
:
true
},
legend
:
[
{
legend
:
{
show
:
true
,
type
:
'plain'
,
data
:
legendLine1
,
top
:
4
,
left
:
'center'
,
type
:
'scroll'
,
orient
:
'horizontal'
,
left
:
8
,
top
:
6
,
width
:
'95%'
,
height
:
24
,
padding
:
[
0
,
24
,
0
,
24
],
icon
:
'circle'
,
textStyle
:
{
fontFamily
:
'Source Han Sans CN'
,
fontWeight
:
400
,
fontSize
:
14
,
lineHeight
:
24
,
letterSpacing
:
0
,
align
:
'left'
,
color
:
'rgb(95, 101, 108)'
},
itemWidth
:
12
,
itemHeight
:
12
},
{
show
:
legendLine2
.
length
>
0
,
type
:
'plain'
,
data
:
legendLine2
,
top
:
30
,
left
:
'center'
,
icon
:
'circle'
,
itemHeight
:
12
,
data
:
allNames
,
// 隐藏内置分页按钮与页码(使用 graphic 自定义左右箭头)
pageButtonPosition
:
'end'
,
pageIconSize
:
0
,
pageButtonGap
:
0
,
pageFormatter
:
()
=>
''
,
pageTextStyle
:
{
fontSize
:
0
,
color
:
'transparent'
},
textStyle
:
{
fontFamily
:
'Source Han Sans CN'
,
fontWeight
:
400
,
...
...
@@ -143,28 +91,32 @@ const getMultiLineChart = (chartInput) => {
letterSpacing
:
0
,
align
:
'left'
,
color
:
'rgb(95, 101, 108)'
}
},
itemWidth
:
12
,
itemHeight
:
12
},
graphic
:
[
{
show
:
legendLine3
.
length
>
0
,
type
:
'plain'
,
data
:
legendLine3
,
top
:
56
,
left
:
'center'
,
icon
:
'circle'
,
textStyle
:
{
fontFamily
:
'Source Han Sans CN'
,
fontWeight
:
400
,
fontSize
:
14
,
lineHeight
:
24
,
letterSpacing
:
0
,
align
:
'left'
,
color
:
'rgb(95, 101, 108)'
type
:
'polygon'
,
name
:
'__legend_prev__'
,
left
:
10
,
top
:
14
,
shape
:
{
points
:
[[
8
,
0
],
[
0
,
6
],
[
8
,
12
]]
},
style
:
{
fill
:
'rgb(95, 101, 108)'
},
cursor
:
'pointer'
,
tooltip
:
{
show
:
false
},
silent
:
false
,
z
:
100
},
itemWidth
:
12
,
itemHeight
:
12
{
type
:
'polygon'
,
name
:
'__legend_next__'
,
right
:
10
,
top
:
14
,
shape
:
{
points
:
[[
0
,
0
],
[
8
,
6
],
[
0
,
12
]]
},
style
:
{
fill
:
'rgb(95, 101, 108)'
},
cursor
:
'pointer'
,
tooltip
:
{
show
:
false
},
silent
:
false
,
z
:
100
}
],
// 不使用全局 color,改为每条 series 自己定色(与 AreaTag 一致)
...
...
src/views/thinkTank/ThinkTankDetail/PolicyTracking/utils/piechart.js
浏览文件 @
7cd28bf2
import
{
MUTICHARTCOLORS
}
from
"@/common/constant.js"
;
const
getPieChart
=
(
data
)
=>
{
const
seriesData
=
(
Array
.
isArray
(
data
)
?
data
:
[]).
map
((
d
)
=>
{
const
color
=
d
?.
color
if
(
!
color
)
return
d
const
seriesData
=
(
Array
.
isArray
(
data
)
?
data
:
[]).
map
((
d
,
index
)
=>
{
const
color
=
MUTICHARTCOLORS
[
index
%
MUTICHARTCOLORS
.
length
]
return
{
...
d
,
itemStyle
:
{
...(
d
.
itemStyle
||
{}),
color
},
itemStyle
:
{
...(
d
?
.
itemStyle
||
{}),
color
},
// “飞线”(labelLine)跟随领域色
labelLine
:
{
...(
d
.
labelLine
||
{}),
lineStyle
:
{
...(
d
.
labelLine
?.
lineStyle
||
{}),
color
}
...(
d
?
.
labelLine
||
{}),
lineStyle
:
{
...(
d
?
.
labelLine
?.
lineStyle
||
{}),
color
}
}
}
})
...
...
@@ -29,10 +30,14 @@ const getPieChart = (data) => {
alignTo
:
'edge'
,
formatter
:
params
=>
{
const
name
=
params
.
name
||
""
;
const
value
=
params
.
value
??
""
;
const
percent
=
params
.
percent
!=
null
?
Math
.
round
(
params
.
percent
)
:
0
;
const
value
=
Number
(
params
.
value
??
0
)
||
0
;
const
rawPercent
=
params
?.
data
?.
percent
!=
null
?
params
.
data
.
percent
:
params
.
percent
;
const
percent
=
rawPercent
!=
null
?
Number
(
rawPercent
).
toFixed
(
2
)
:
"0.00"
;
return
`{name|
${
name
}
}\n{time|
${
percent
}
%}`
;
return
`{name|
${
name
}
}\n{time|
${
value
}
项
${
percent
}
%}`
;
},
minMargin
:
10
,
edgeDistance
:
20
,
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/CongressHearing/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -72,8 +72,9 @@
</div>
-->
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<template
v-if=
"hasData"
>
<div
class=
"card-content"
>
<div
v-for=
"(item, index) in hearingData"
:key=
"item.id"
>
<div
class=
"card-item"
>
...
...
@@ -87,7 +88,8 @@
<span
v-html=
"highlightText(item.time + ' · ' + item.content)"
></span>
<img
src=
"../images/image open.png"
alt=
"open icon"
class=
"card-open-image"
/>
</div>
<div
class=
"card-item-category"
>
<div
class=
"card-item-category"
v-if=
"Array.isArray(item.category) && item.category.some(v => String(v || '').trim())"
>
<AreaTag
v-for=
"(val, idx) in item.category"
:key=
"idx"
:tagName=
"val"
/>
</div>
</div>
...
...
@@ -97,13 +99,19 @@
</div>
</div>
</
template
>
<
template
v-else-if=
"!loading"
>
<div
class=
"right-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
</
template
>
</div>
<div
class=
"right-footer"
>
<div
class=
"info"
>
共 {{ total }} 篇国会听证会
</div>
<div
class=
"page-box"
>
<el-pagination
:page-size=
"10"
background
layout=
"prev, pager, next"
:total=
"total
"
<el-pagination
:page-size=
"10"
:page-count=
"pageCount"
background
layout=
"prev, pager, next
"
@
current-change=
"handleCurrentChange"
:current-page=
"currentPage"
/>
</div>
</div>
...
...
@@ -169,6 +177,10 @@ const props = defineProps({
searchKeyword
:
{
type
:
String
,
default
:
""
},
loading
:
{
type
:
Boolean
,
default
:
false
}
});
...
...
@@ -181,6 +193,12 @@ const emit = defineEmits([
// 解构 props,保持模板里变量名不变
const
{
researchTypeList
,
researchTimeList
,
curFooterList
,
total
,
currentPage
,
researchHearingList
,
hearingData
,
selectedYear
}
=
toRefs
(
props
);
const
hasData
=
computed
(()
=>
Array
.
isArray
(
hearingData
.
value
)
&&
hearingData
.
value
.
length
>
0
);
const
pageCount
=
computed
(()
=>
{
const
size
=
10
;
const
t
=
Number
(
total
.
value
||
0
)
||
0
;
return
Math
.
max
(
1
,
Math
.
ceil
(
t
/
size
));
});
const
pageSize
=
10
;
function
getDateYearsAgo
(
years
)
{
...
...
@@ -488,8 +506,11 @@ const handleToReportDetail = item => {
.right
{
width
:
1224px
;
display
:
flex
;
flex-direction
:
column
;
.card-box
{
flex
:
1
;
...
...
@@ -498,6 +519,15 @@ const handleToReportDetail = item => {
background
:
rgba
(
255
,
255
,
255
,
1
);
.right-empty
{
width
:
100%
;
flex
:
1
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
padding
:
24px
;
}
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
border-radius
:
10px
;
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/SurveyForm/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -52,8 +52,9 @@
</div>
-->
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<template
v-if=
"hasData"
>
<div
class=
"footer-card"
v-for=
"(item, index) in curFooterList"
:key=
"index"
@
click=
"handleToReportDetail(item)"
>
<div
class=
"footer-card-top"
>
...
...
@@ -67,13 +68,19 @@
<div
class=
"from"
>
{{
item
.
thinktankName
}}
</div>
</div>
</div>
</
template
>
<
template
v-else-if=
"!loading"
>
<div
class=
"right-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
</
template
>
</div>
<div
class=
"right-footer"
>
<div
class=
"info"
>
共 {{ total }} 篇调查项目
</div>
<div
class=
"page-box"
>
<el-pagination
:page-size=
"12"
background
layout=
"prev, pager, next"
:total=
"total
"
<el-pagination
:page-size=
"12"
:page-count=
"pageCount"
background
layout=
"prev, pager, next
"
@
current-change=
"handleCurrentChange"
:current-page=
"currentPage"
/>
</div>
</div>
...
...
@@ -81,7 +88,7 @@
</div>
</template>
<
script
setup
>
import
{
ref
,
toRefs
,
watch
}
from
"vue"
;
import
{
ref
,
toRefs
,
watch
,
computed
}
from
"vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
...
...
@@ -126,6 +133,10 @@ const props = defineProps({
searchKeyword
:
{
type
:
String
,
default
:
""
},
loading
:
{
type
:
Boolean
,
default
:
false
}
});
...
...
@@ -138,6 +149,12 @@ const emit = defineEmits([
// 解构 props,保持模板里变量名不变
const
{
researchTypeList
,
researchTimeList
,
curFooterList
,
total
,
currentPage
}
=
toRefs
(
props
);
const
hasData
=
computed
(()
=>
Array
.
isArray
(
curFooterList
.
value
)
&&
curFooterList
.
value
.
length
>
0
);
const
pageCount
=
computed
(()
=>
{
const
size
=
12
;
const
t
=
Number
(
total
.
value
||
0
)
||
0
;
return
Math
.
max
(
1
,
Math
.
ceil
(
t
/
size
));
});
const
selectedResearchIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedResearchTimeIds
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
...
...
@@ -360,15 +377,27 @@ const handleToReportDetail = item => {
.right
{
width
:
1284px
;
display
:
flex
;
flex-direction
:
column
;
.card-box
{
flex
:
1
;
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
13px
;
.right-empty
{
width
:
100%
;
flex
:
1
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
padding
:
24px
;
}
.footer-card
{
width
:
398px
;
height
:
300px
;
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/ThinkTankReport/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -52,8 +52,9 @@
</div>
-->
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<template
v-if=
"hasData"
>
<div
class=
"footer-card"
v-for=
"(item, index) in curFooterList"
:key=
"index"
@
click=
"handleToReportDetail(item)"
>
<div
class=
"footer-card-top"
>
...
...
@@ -67,13 +68,19 @@
<div
class=
"from"
>
{{
item
.
thinkTankName
}}
</div>
</div>
</div>
</
template
>
<
template
v-else-if=
"!loading"
>
<div
class=
"right-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
</
template
>
</div>
<div
class=
"right-footer"
>
<div
class=
"info"
>
共 {{ total }} 篇智库报告
</div>
<div
class=
"page-box"
>
<el-pagination
:page-size=
"12"
background
layout=
"prev, pager, next"
:total=
"total
"
<el-pagination
:page-size=
"12"
:page-count=
"pageCount"
background
layout=
"prev, pager, next
"
@
current-change=
"handleCurrentChange"
:current-page=
"currentPage"
/>
</div>
</div>
...
...
@@ -81,7 +88,7 @@
</div>
</template>
<
script
setup
>
import
{
ref
,
toRefs
,
watch
}
from
"vue"
;
import
{
ref
,
toRefs
,
watch
,
computed
}
from
"vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
...
...
@@ -126,6 +133,10 @@ const props = defineProps({
searchKeyword
:
{
type
:
String
,
default
:
""
},
loading
:
{
type
:
Boolean
,
default
:
false
}
});
...
...
@@ -138,6 +149,12 @@ const emit = defineEmits([
// 解构 props,保持模板里变量名不变
const
{
researchTypeList
,
researchTimeList
,
curFooterList
,
total
,
currentPage
}
=
toRefs
(
props
);
const
hasData
=
computed
(()
=>
Array
.
isArray
(
curFooterList
.
value
)
&&
curFooterList
.
value
.
length
>
0
);
const
pageCount
=
computed
(()
=>
{
const
size
=
12
;
const
t
=
Number
(
total
.
value
||
0
)
||
0
;
return
Math
.
max
(
1
,
Math
.
ceil
(
t
/
size
));
});
const
selectedResearchIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedResearchTimeIds
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
...
...
@@ -360,15 +377,28 @@ const handleToReportDetail = item => {
}
.right
{
width
:
1284px
;
display
:
flex
;
flex-direction
:
column
;
.card-box
{
flex
:
1
;
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
13px
;
.right-empty
{
width
:
100%
;
flex
:
1
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
padding
:
24px
;
}
.footer-card
{
width
:
398px
;
height
:
300px
;
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -53,18 +53,20 @@
<ThinkTankReport
v-if=
"isThinkTankReport"
:research-type-list=
"researchTypeList"
:research-time-list=
"researchTimeList"
:key=
"`智库报告-${tabResetKey}`"
:selected-filters=
"selectedFilters"
:cur-footer-list=
"curFooterList"
:total=
"total"
:current-page=
"currentPage"
:search-keyword=
"searchReport"
:loading=
"isThinkTankReportLoading"
@
update:selected-filters=
"handleSelectedFiltersUpdate"
@
filter-change=
"(payload) => handleGetThinkDynamicsReport(payload)"
@
page-change=
"handleCurrentChange"
@
report-click=
"handleToReportDetail"
/>
<CongressHearing
v-else-if=
"isCongressHearing"
:research-type-list=
"researchTypeList"
:research-time-list=
"researchTimeList"
:key=
"`国会听证会-${tabResetKey}`"
:research-hearing-list=
"researchHearingList"
:selected-filters=
"selectedFilters"
:selected-year=
"selectedYear"
:total=
"total"
:current-page=
"currentPage"
:search-keyword=
"searchReport"
:hearing-data=
"hearingData"
@
update:selected-filters=
"handleSelectedFiltersUpdate"
:search-keyword=
"searchReport"
:hearing-data=
"hearingData"
:loading=
"isCongressHearingLoading"
@
update:selected-filters=
"handleSelectedFiltersUpdate"
@
filter-change=
"(payload) => handleGetThinkDynamicsReport(payload)"
@
page-change=
"handleCurrentChange"
@
report-click=
"handleToHearingDetail"
/>
<SurveyForm
v-else-if=
"isSurveyForm"
:research-type-list=
"researchTypeList"
:research-time-list=
"researchTimeList"
:key=
"`调查项目-${tabResetKey}`"
:selected-filters=
"selectedFilters"
:cur-footer-list=
"curFooterProjectList"
:total=
"total"
:current-page=
"currentPage"
:search-keyword=
"searchReport"
:total=
"total"
:current-page=
"currentPage"
:search-keyword=
"searchReport"
:loading=
"isSurveyFormLoading"
@
update:selected-filters=
"handleSelectedFiltersUpdate"
@
filter-change=
"(payload) => handleGetThinkDynamicsReport(payload)"
@
page-change=
"handleCurrentChange"
@
report-click=
"handleToProjectDetail"
/>
...
...
@@ -108,6 +110,10 @@ const isThinkTankReport = ref(true);
const
isSurveyForm
=
ref
(
false
);
const
isCongressHearing
=
ref
(
false
);
const
searchReport
=
ref
(
''
)
// 智库详情-智库动态:右侧列表 loading(居中显示)
const
isThinkTankReportLoading
=
ref
(
false
);
const
isSurveyFormLoading
=
ref
(
false
);
const
isCongressHearingLoading
=
ref
(
false
);
const
handleToReportDetail
=
(
item
)
=>
{
window
.
sessionStorage
.
setItem
(
'curTabName'
,
item
.
name
)
...
...
@@ -248,6 +254,10 @@ const handleChooseType = async (type) => {
await
handleGetThinkDynamicsReport
()
}
const
researchTimeList
=
ref
([
{
id
:
'2026年'
,
name
:
'2026年'
,
},
{
id
:
'2025年'
,
name
:
'2025年'
,
...
...
@@ -395,6 +405,11 @@ const handleGetThinkDynamicsReport = async (payload) => {
return
;
}
try
{
// 仅当前 tab 显示 loading,其它置 false,避免切换后残留
isThinkTankReportLoading
.
value
=
isThinkTankReport
.
value
;
isSurveyFormLoading
.
value
=
isSurveyForm
.
value
;
isCongressHearingLoading
.
value
=
isCongressHearing
.
value
;
const
strippedTime
=
stripAllTimeForRequest
(
nextFilters
.
researchTimeIds
||
[]);
const
allTimeIds
=
(
researchTimeList
.
value
||
[]).
map
((
x
)
=>
x
.
id
);
const
{
startDate
,
endDate
}
=
getResourceLibraryReportDateRangeFromTimeSelection
(
...
...
@@ -415,6 +430,9 @@ const handleGetThinkDynamicsReport = async (payload) => {
thinkTankId
,
pageNum
:
Math
.
max
(
0
,
(
currentPage
.
value
||
1
)),
pageSize
:
10
,
// 国会听证会:排序语义与其它资源库相反(正序→desc,倒序→asc)
sortField
:
"createTime"
,
sortOrder
:
sort
.
value
===
true
?
"desc"
:
"asc"
,
domainIds
,
startDate
,
...
...
@@ -481,6 +499,10 @@ const handleGetThinkDynamicsReport = async (payload) => {
}
}
catch
(
error
)
{
console
.
error
(
"获取智库动态报告error"
,
error
);
}
finally
{
isThinkTankReportLoading
.
value
=
false
;
isSurveyFormLoading
.
value
=
false
;
isCongressHearingLoading
.
value
=
false
;
}
};
...
...
src/views/thinkTank/ThinkTankDetail/thinkInfo/utils/piechart.js
浏览文件 @
7cd28bf2
import
{
MUTICHARTCOLORS
}
from
"@/common/constant.js"
;
const
getPieChart
=
(
data
)
=>
{
let
option
=
{
series
:
[
...
...
@@ -53,7 +55,10 @@ const getPieChart = (data) => {
labelLinePoints
:
points
};
},
data
:
data
data
:
(
Array
.
isArray
(
data
)
?
data
:
[]).
map
((
d
,
index
)
=>
({
...
d
,
itemStyle
:
{
...(
d
?.
itemStyle
||
{}),
color
:
d
?.
color
||
MUTICHARTCOLORS
[
index
%
MUTICHARTCOLORS
.
length
]
}
}))
}]
}
return
option
...
...
src/views/thinkTank/ThinkTankDetail/thinkInfo/utils/treeMapChart.js
浏览文件 @
7cd28bf2
import
{
MUTICHARTCOLORS
}
from
"@/common/constant.js"
;
const
getTreeMapChart
=
(
treemapData
)
=>
{
const
list
=
Array
.
isArray
(
treemapData
)
?
treemapData
:
[]
const
dataWithColors
=
list
.
map
((
node
,
idx
)
=>
({
...
node
,
itemStyle
:
{
...(
node
?.
itemStyle
||
{}),
color
:
node
?.
itemStyle
?.
color
||
MUTICHARTCOLORS
[
idx
%
MUTICHARTCOLORS
.
length
]
}
}))
const
option
=
{
tooltip
:
{
trigger
:
'item'
,
...
...
@@ -14,7 +25,7 @@ const getTreeMapChart = (treemapData) => {
series
:
[
{
type
:
'treemap'
,
data
:
treemapData
,
data
:
dataWithColors
,
roam
:
false
,
nodeClick
:
false
,
breadcrumb
:
{
...
...
src/views/thinkTank/allThinkTank/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -474,7 +474,7 @@ onMounted(async () => {
display
:
inline-flex
;
position
:
absolute
;
left
:
277
px
;
right
:
-8
px
;
bottom
:
208px
;
background-color
:
rgba
(
255
,
77
,
79
,
1
);
align-items
:
center
;
...
...
src/views/thinkTank/components/HomeMainFooterMain.vue
浏览文件 @
7cd28bf2
...
...
@@ -36,8 +36,9 @@
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<template
v-if=
"hasData"
>
<div
class=
"footer-card"
v-for=
"(item, index) in curFooterList"
:key=
"index"
@
click=
"emit('report-click', item)"
>
<div
class=
"footer-card-top"
>
...
...
@@ -51,11 +52,17 @@
<div
class=
"from"
>
{{
item
.
thinkTankName
}}
</div>
</div>
</div>
</
template
>
<
template
v-else-if=
"!loading"
>
<div
class=
"right-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
</
template
>
</div>
<div
class=
"right-footer"
>
<div
class=
"info"
>
共 {{ total }} 篇智库报告
</div>
<div
class=
"page-box"
>
<el-pagination
:page-size=
"12"
background
layout=
"prev, pager, next"
:total=
"total
"
<el-pagination
:page-size=
"12"
:page-count=
"pageCount"
background
layout=
"prev, pager, next
"
@
current-change=
"emit('page-change', $event)"
:current-page=
"currentPage"
/>
</div>
</div>
...
...
@@ -64,20 +71,22 @@
</template>
<
script
setup
>
import
{
computed
}
from
"vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
normalizeExclusiveAllOption
}
from
"../utils/resourceLibraryFilters"
;
defineProps
({
const
props
=
defineProps
({
areaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedAreaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
pubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedPubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
curFooterList
:
{
type
:
Array
,
default
:
()
=>
[]
},
total
:
{
type
:
Number
,
default
:
0
},
currentPage
:
{
type
:
Number
,
default
:
1
}
currentPage
:
{
type
:
Number
,
default
:
1
},
loading
:
{
type
:
Boolean
,
default
:
false
}
});
const
emit
=
defineEmits
([
...
...
@@ -102,6 +111,13 @@ const formatDate = (str) => {
const
[
y
,
m
,
d
]
=
str
.
split
(
'T'
)[
0
].
split
(
'-'
)
return
`
${
y
}
年
${
+
m
}
月
${
+
d
}
日`
};
const
hasData
=
computed
(()
=>
Array
.
isArray
(
props
.
curFooterList
)
&&
props
.
curFooterList
.
length
>
0
);
const
pageCount
=
computed
(()
=>
{
const
size
=
12
;
const
total
=
Number
(
props
.
total
||
0
)
||
0
;
return
Math
.
max
(
1
,
Math
.
ceil
(
total
/
size
));
});
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
@@ -195,14 +211,25 @@ const formatDate = (str) => {
.right
{
width
:
1284px
;
max-height
:
1377px
;
display
:
flex
;
flex-direction
:
column
;
.card-box
{
width
:
1226px
;
flex
:
1
;
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
16px
16px
;
.right-empty
{
width
:
100%
;
flex
:
1
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
padding
:
24px
;
}
.footer-card
{
width
:
398px
;
height
:
300px
;
...
...
src/views/thinkTank/components/HomeMainFooterSurvey.vue
浏览文件 @
7cd28bf2
...
...
@@ -36,8 +36,9 @@
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<template
v-if=
"hasData"
>
<div
class=
"footer-card"
v-for=
"(item, index) in curFooterList"
:key=
"index"
@
click=
"emit('report-click', item)"
>
<div
class=
"footer-card-top"
>
...
...
@@ -51,11 +52,17 @@
<div
class=
"from"
>
{{
item
.
thinktankName
}}
</div>
</div>
</div>
</
template
>
<
template
v-else-if=
"!loading"
>
<div
class=
"right-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
</
template
>
</div>
<div
class=
"right-footer"
>
<div
class=
"info"
>
共 {{ total }} 篇调查项目
</div>
<div
class=
"page-box"
>
<el-pagination
:page-size=
"12"
background
layout=
"prev, pager, next"
:total=
"total
"
<el-pagination
:page-size=
"12"
:page-count=
"pageCount"
background
layout=
"prev, pager, next
"
@
current-change=
"emit('page-change', $event)"
:current-page=
"currentPage"
/>
</div>
</div>
...
...
@@ -64,20 +71,22 @@
</template>
<
script
setup
>
import
{
computed
}
from
"vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
normalizeExclusiveAllOption
}
from
"../utils/resourceLibraryFilters"
;
defineProps
({
const
props
=
defineProps
({
areaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedAreaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
pubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedPubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
curFooterList
:
{
type
:
Array
,
default
:
()
=>
[]
},
total
:
{
type
:
Number
,
default
:
0
},
currentPage
:
{
type
:
Number
,
default
:
1
}
currentPage
:
{
type
:
Number
,
default
:
1
},
loading
:
{
type
:
Boolean
,
default
:
false
}
});
const
formatDate
=
(
str
)
=>
{
if
(
!
str
)
return
''
...
...
@@ -101,6 +110,13 @@ const handleTimeGroupChange = (val) => {
emit
(
"update:selectedPubTimeList"
,
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
));
emit
(
"filter-change"
);
};
const
hasData
=
computed
(()
=>
Array
.
isArray
(
props
.
curFooterList
)
&&
props
.
curFooterList
.
length
>
0
);
const
pageCount
=
computed
(()
=>
{
const
size
=
12
;
const
total
=
Number
(
props
.
total
||
0
)
||
0
;
return
Math
.
max
(
1
,
Math
.
ceil
(
total
/
size
));
});
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
@@ -170,14 +186,25 @@ const handleTimeGroupChange = (val) => {
.right
{
width
:
1284px
;
max-height
:
1377px
;
display
:
flex
;
flex-direction
:
column
;
.card-box
{
width
:
1226px
;
flex
:
1
;
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
16px
16px
;
.right-empty
{
width
:
100%
;
flex
:
1
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
padding
:
24px
;
}
.footer-card
{
width
:
398px
;
...
...
src/views/thinkTank/components/ThinkTankCongressHearingOverview.vue
浏览文件 @
7cd28bf2
...
...
@@ -55,10 +55,10 @@
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<template
v-if=
"hasData"
>
<div
class=
"card-content"
>
<div
v-for=
"(item, index) in hearingData"
:key=
"item.id ?? index"
>
<div
class=
"card-item"
>
<img
class=
"card-item-img"
:src=
"item.coverImgUrl"
alt=
"report image"
/>
...
...
@@ -72,7 +72,8 @@
class=
"card-open-image"
/>
</div>
<div
class=
"card-item-category"
v-if=
"item.domains"
>
<div
class=
"card-item-category"
v-if=
"Array.isArray(item.domains) && item.domains.some(v => String(v || '').trim())"
>
<div
v-for=
"(value, index) in item.domains"
:key=
"`domain-$
{index}`">
<AreaTag
:key=
"`cat-$
{item.id}`" :tagName="value" />
</div>
...
...
@@ -81,16 +82,20 @@
</div>
<div
class=
"divider"
v-if=
"index !== hearingData.length - 1"
></div>
</div>
</div>
</
template
>
<
template
v-else-if=
"!loading"
>
<div
class=
"right-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
</
template
>
</div>
<div
class=
"right-footer"
>
<div
class=
"info"
>
共
{{
hearingData
.
length
}}
篇国会听证会
共 {{
total
}} 篇国会听证会
</div>
<div
class=
"page-box"
>
<el-pagination
:page-size=
"pageSize"
background
layout=
"prev, pager, next"
:total=
"total
"
<el-pagination
:page-size=
"pageSize"
:page-count=
"pageCount"
background
layout=
"prev, pager, next
"
@
current-change=
"handlePageChange"
:current-page=
"currentPage"
/>
</div>
</div>
...
...
@@ -121,7 +126,8 @@ const props = defineProps({
selectedPubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
hearingData
:
{
type
:
Array
,
default
:
()
=>
[]
},
total
:
{
type
:
Number
,
default
:
0
},
currentPage
:
{
type
:
Number
,
default
:
1
}
currentPage
:
{
type
:
Number
,
default
:
1
},
loading
:
{
type
:
Boolean
,
default
:
false
}
});
const
emit
=
defineEmits
([
...
...
@@ -133,6 +139,12 @@ const emit = defineEmits([
]);
const
pageSize
=
10
;
const
hasData
=
computed
(()
=>
Array
.
isArray
(
props
.
hearingData
)
&&
props
.
hearingData
.
length
>
0
);
const
pageCount
=
computed
(()
=>
{
const
size
=
Number
(
pageSize
||
10
)
||
10
;
const
total
=
Number
(
props
.
total
||
0
)
||
0
;
return
Math
.
max
(
1
,
Math
.
ceil
(
total
/
size
));
});
const
selectedResearchIds
=
computed
(()
=>
(
Array
.
isArray
(
props
.
selectedAreaList
)
&&
props
.
selectedAreaList
.
length
...
...
@@ -274,11 +286,13 @@ const handlePageChange = page => {
.right
{
width
:
1224px
;
display
:
flex
;
flex-direction
:
column
;
.card-box
{
width
:
100%
;
flex
:
1
;
display
:
flex
;
background
:
rgba
(
255
,
255
,
255
,
1
);
box-sizing
:
border-box
;
...
...
@@ -287,6 +301,15 @@ const handlePageChange = page => {
box-shadow
:
0px
0px
20px
0px
rgba
(
94
,
95
,
95
,
0
.1
);
padding-right
:
36px
;
.right-empty
{
width
:
100%
;
flex
:
1
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
padding
:
24px
;
}
.card-content
{
width
:
1211px
;
...
...
src/views/thinkTank/components/ThinkTankPolicyAdviceOverview.vue
浏览文件 @
7cd28bf2
...
...
@@ -36,8 +36,9 @@
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<template
v-if=
"hasData"
>
<div
class=
"card-content"
>
<div
v-for=
"(item, index) in list"
:key=
"item.id ?? index"
>
<div
class=
"card-item"
@
click=
"emit('item-click', item)"
>
...
...
@@ -79,11 +80,17 @@
<div
class=
"divider"
v-if=
"index !== list.length - 1"
></div>
</div>
</div>
</
template
>
<
template
v-else-if=
"!loading"
>
<div
class=
"right-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
</
template
>
</div>
<div
class=
"right-footer"
>
<div
class=
"info"
>
共{{ total }}篇政策建议
</div>
<div
class=
"page-box"
>
<el-pagination
:page-size=
"pageSize"
background
layout=
"prev, pager, next"
:total=
"total
"
<el-pagination
:page-size=
"pageSize"
:page-count=
"pageCount"
background
layout=
"prev, pager, next
"
@
current-change=
"p => emit('page-change', p)"
:current-page=
"currentPage"
/>
</div>
</div>
...
...
@@ -92,7 +99,7 @@
</template>
<
script
setup
>
import
{
ref
}
from
"vue"
;
import
{
ref
,
computed
}
from
"vue"
;
import
{
useRouter
}
from
"vue-router"
;
import
AreaTag
from
"@/components/base/AreaTag/index.vue"
;
import
{
...
...
@@ -103,13 +110,14 @@ import {
stripAllTimeForRequest
}
from
"../utils/resourceLibraryFilters"
;
defineProps
({
const
props
=
defineProps
({
researchTypeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
researchTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
list
:
{
type
:
Array
,
default
:
()
=>
[]
},
total
:
{
type
:
Number
,
default
:
0
},
currentPage
:
{
type
:
Number
,
default
:
1
},
pageSize
:
{
type
:
Number
,
default
:
7
},
loading
:
{
type
:
Boolean
,
default
:
false
},
});
const
emit
=
defineEmits
([
"filter-change"
,
"page-change"
,
"item-click"
]);
...
...
@@ -158,6 +166,13 @@ const handleAdministrativeMoreClick = (ad) => {
const
selectedTypeIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedYearIds
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
const
hasData
=
computed
(()
=>
Array
.
isArray
(
props
.
list
)
&&
props
.
list
.
length
>
0
);
const
pageCount
=
computed
(()
=>
{
const
size
=
Number
(
props
.
pageSize
||
7
)
||
7
;
const
total
=
Number
(
props
.
total
||
0
)
||
0
;
return
Math
.
max
(
1
,
Math
.
ceil
(
total
/
size
));
});
const
emitFilterToParent
=
()
=>
{
emit
(
"filter-change"
,
{
researchTypeIds
:
stripAllAreaForRequest
(
selectedTypeIds
.
value
),
...
...
@@ -298,6 +313,8 @@ const handleYearGroupChange = (val) => {
.right
{
width
:
1224px
;
display
:
flex
;
flex-direction
:
column
;
.card-box
{
...
...
@@ -311,6 +328,15 @@ const handleYearGroupChange = (val) => {
box-shadow
:
0px
0px
20px
0px
rgba
(
94
,
95
,
95
,
0
.1
);
padding-right
:
36px
;
.right-empty
{
width
:
100%
;
flex
:
1
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
padding
:
24px
;
}
.card-content
{
width
:
1211px
;
height
:
1067px
;
...
...
@@ -332,7 +358,7 @@ const handleYearGroupChange = (val) => {
.card-item-img
{
width
:
56px
;
width
:
1
56px
;
height
:
77px
;
margin-right
:
22px
;
flex-shrink
:
0
;
...
...
src/views/thinkTank/index.vue
浏览文件 @
7cd28bf2
差异被折叠。
点击展开。
src/views/thinkTank/reportOriginal/index.vue
浏览文件 @
7cd28bf2
...
...
@@ -49,7 +49,7 @@
<img
class=
"translate-icon"
src=
"../ReportDetail/images/image-translate.png"
alt=
""
style=
"width: 16px; height: 16px; max-width: 16px; max-height: 16px; display: block; object-fit: contain;"
/>
</div>
<div
class=
"translate-text"
>
{{
"显示
原
文"
}}
</div>
<div
class=
"translate-text"
>
{{
"显示
译
文"
}}
</div>
</div>
<div
class=
"btn"
@
click=
"handleDownload"
>
<div
class=
"icon"
>
...
...
@@ -61,13 +61,15 @@
</div>
</div>
<div
class=
"report-box"
>
<div
class=
"pdf-pane-wrap"
v-if=
"valueSwitch && reportUrlEnWithPage"
>
<pdf
ref=
"leftPdfRef"
:pdfUrl=
"reportUrlEnWithPage"
class=
"pdf-pane-inner"
/>
</div>
<div
class=
"pdf-pane-wrap"
:class=
"
{ 'is-full': !valueSwitch }" v-if="reportUrlWithPage">
<pdf
:key=
"`right-pdf-$
{valueSwitch ? 'split' : 'full'}`" ref="rightPdfRef" :pdfUrl="reportUrlWithPage"
<!-- 英文原文:始终展示;关闭「显示译文」时占满宽度 -->
<div
class=
"pdf-pane-wrap"
:class=
"
{ 'is-full': !valueSwitch }" v-if="reportUrlEnWithPage">
<pdf
:key=
"`left-pdf-$
{valueSwitch ? 'split' : 'full'}`" ref="leftPdfRef" :pdfUrl="reportUrlEnWithPage"
class="pdf-pane-inner" />
</div>
<!-- 中文译文:仅在开关打开时展示 -->
<div
class=
"pdf-pane-wrap"
v-if=
"valueSwitch && reportUrlWithPage"
>
<pdf
ref=
"rightPdfRef"
:pdfUrl=
"reportUrlWithPage"
class=
"pdf-pane-inner"
/>
</div>
</div>
</div>
</div>
...
...
src/views/thinkTank/utils/multiLineChart.js
浏览文件 @
7cd28bf2
import
*
as
echarts
from
'echarts'
;
import
{
MUTICHARTCOLORS
}
from
"@/common/constant.js"
;
/**
* @param {{ title: unknown[], data: Array<{ name: string, value: unknown[], color?: string }> }} data
...
...
@@ -9,70 +10,21 @@ const getMultiLineChart = (data) => {
const
series
=
data
.
data
const
allNames
=
series
.
map
((
item
)
=>
item
.
name
)
const
legendSplitAt
=
Math
.
ceil
(
allNames
.
length
/
2
)
const
legendFirstLine
=
allNames
.
slice
(
0
,
legendSplitAt
)
const
legendSecondLine
=
allNames
.
slice
(
legendSplitAt
)
// 按 AreaTag 的颜色规则映射到折线图配色(取 tag 的文字色)
const
AREA_TAG_COLOR_BY_NAME
=
{
'人工智能'
:
'rgba(245, 34, 45, 1)'
,
// tag1
'生物科技'
:
'rgba(19, 168, 168, 1)'
,
// tag2
'新一代通信网络'
:
'rgba(5, 95, 194, 1)'
,
// tag3
// 兼容后端/页面常见写法
'通信网络'
:
'rgba(5, 95, 194, 1)'
,
'量子科技'
:
'rgba(114, 46, 209, 1)'
,
// tag4
'新能源'
:
'rgba(82, 196, 26, 1)'
,
// tag5
'集成电路'
:
'rgba(22, 119, 255, 1)'
,
// tag6
'海洋'
:
'rgba(15, 120, 199, 1)'
,
// tag7
'先进制造'
:
'rgba(250, 173, 20, 1)'
,
// tag8
'新材料'
:
'rgba(250, 140, 22, 1)'
,
// tag9
'航空航天'
:
'rgba(47, 84, 235, 1)'
,
// tag10
'太空'
:
'rgba(47, 84, 235, 1)'
,
// tag11
'深海'
:
'rgba(73, 104, 161, 1)'
,
// tag12
'极地'
:
'rgba(133, 165, 255, 1)'
,
// tag13
'核'
:
'rgba(250, 84, 28, 1)'
,
// tag14
'其他'
:
'rgba(82, 196, 26, 1)'
// tag15
}
// 兜底颜色池(未命中 AreaTag 映射时使用)
const
fallbackColorList
=
[
'rgba(5, 95, 194, 1)'
,
'rgba(19, 168, 168, 1)'
,
'rgba(250, 140, 22, 1)'
,
'rgba(114, 46, 209, 1)'
,
'rgba(82, 196, 26, 1)'
,
'rgba(250, 84, 28, 1)'
,
'rgba(22, 119, 255, 1)'
,
'rgba(95, 101, 108, 1)'
,
'rgba(47, 84, 235, 1)'
,
'rgba(133, 165, 255, 1)'
,
]
// 解析 RGBA 颜色的辅助函数
const
parseRgba
=
(
colorStr
)
=>
{
// 匹配 rgba(r, g, b, a) 格式
const
match
=
colorStr
.
match
(
/rgba
\((\d
+
)
,
\s
*
(\d
+
)
,
\s
*
(\d
+
)
,
\s
*
(\d
+
(\.\d
+
)?)\)
/
);
if
(
match
)
{
const
parseHexToRgb
=
(
hex
)
=>
{
const
h
=
String
(
hex
||
''
).
replace
(
'#'
,
''
).
trim
()
if
(
h
.
length
!==
6
)
return
{
r
:
0
,
g
:
0
,
b
:
0
}
return
{
r
:
parseInt
(
match
[
1
]),
g
:
parseInt
(
match
[
2
]),
b
:
parseInt
(
match
[
3
]),
a
:
parseFloat
(
match
[
4
])
};
r
:
parseInt
(
h
.
slice
(
0
,
2
),
16
),
g
:
parseInt
(
h
.
slice
(
2
,
4
),
16
),
b
:
parseInt
(
h
.
slice
(
4
,
6
),
16
),
}
}
// 默认返回黑色
return
{
r
:
0
,
g
:
0
,
b
:
0
,
a
:
1
};
};
// 动态生成 series 配置
const
echartsSeries
=
series
.
map
((
item
,
index
)
=>
{
// 获取当前系列的颜色(优先使用item.color,否则用预设颜色,再否则随机)
const
baseColor
=
item
.
color
||
AREA_TAG_COLOR_BY_NAME
[
item
.
name
]
||
fallbackColorList
[
index
%
fallbackColorList
.
length
]
||
`rgba(
${
Math
.
floor
(
Math
.
random
()
*
256
)}
,
${
Math
.
floor
(
Math
.
random
()
*
256
)}
,
${
Math
.
floor
(
Math
.
random
()
*
256
)}
, 1)`
;
const
{
r
,
g
,
b
}
=
parseRgba
(
baseColor
);
const
baseColor
=
item
.
color
||
MUTICHARTCOLORS
[
index
%
MUTICHARTCOLORS
.
length
]
||
'#055FC2'
const
{
r
,
g
,
b
}
=
parseHexToRgb
(
baseColor
)
return
({
name
:
item
.
name
,
...
...
@@ -89,11 +41,11 @@ const getMultiLineChart = (data) => {
color
:
new
echarts
.
graphic
.
LinearGradient
(
0
,
0
,
0
,
1
,
[
{
offset
:
0
,
// 顶部
color
:
`rgba(
${
r
}
,
${
g
}
,
${
b
}
, 0.1)`
// 按需求:0.1 -> 0
color
:
`rgba(
${
r
}
,
${
g
}
,
${
b
}
, 0.1)`
},
{
offset
:
1
,
// 底部
color
:
`rgba(
${
r
}
,
${
g
}
,
${
b
}
, 0)`
// 0 透明度
color
:
`rgba(
${
r
}
,
${
g
}
,
${
b
}
, 0)`
}
])
},
...
...
@@ -118,20 +70,24 @@ const getMultiLineChart = (data) => {
},
/* 顶部预留足够空间:多行图例 + Y 轴标题「数量」在纵轴顶端,避免与图例重合 */
grid
:
{
top
:
'34%'
,
top
:
68
,
right
:
'3%'
,
bottom
:
'5%'
,
left
:
'2%'
,
containLabel
:
true
},
legend
:
[
{
legend
:
{
show
:
true
,
type
:
'plain'
,
data
:
legendFirstLine
,
top
:
8
,
orient
:
'horizontal'
,
left
:
'center'
,
top
:
8
,
width
:
'90%'
,
height
:
24
,
icon
:
'circle'
,
itemWidth
:
12
,
itemHeight
:
12
,
data
:
allNames
,
textStyle
:
{
fontFamily
:
'Source Han Sans CN'
,
fontWeight
:
400
,
...
...
@@ -141,23 +97,6 @@ const getMultiLineChart = (data) => {
align
:
'left'
}
},
{
show
:
legendSecondLine
.
length
>
0
,
type
:
'plain'
,
data
:
legendSecondLine
,
top
:
32
,
left
:
'center'
,
icon
:
'circle'
,
textStyle
:
{
fontFamily
:
'Source Han Sans CN'
,
fontWeight
:
400
,
fontSize
:
14
,
lineHeight
:
24
,
letterSpacing
:
0
,
align
:
'left'
}
}
],
// 不使用全局 color,改为每条 series 自己定色(与 AreaTag 一致)
xAxis
:
[
{
...
...
src/views/thinkTank/utils/piechart.js
浏览文件 @
7cd28bf2
import
{
MUTICHARTCOLORS
}
from
"@/common/constant.js"
;
const
getPieChart
=
(
data
)
=>
{
const
seriesData
=
(
Array
.
isArray
(
data
)
?
data
:
[]).
map
((
d
)
=>
{
const
color
=
d
?.
color
if
(
!
color
)
return
d
const
seriesData
=
(
Array
.
isArray
(
data
)
?
data
:
[]).
map
((
d
,
index
)
=>
{
const
color
=
d
?.
color
||
MUTICHARTCOLORS
[
index
%
MUTICHARTCOLORS
.
length
]
return
{
...
d
,
itemStyle
:
{
...(
d
.
itemStyle
||
{}),
color
},
itemStyle
:
{
...(
d
?
.
itemStyle
||
{}),
color
},
// “飞线”(labelLine)跟随领域色
labelLine
:
{
...(
d
.
labelLine
||
{}),
lineStyle
:
{
...(
d
.
labelLine
?.
lineStyle
||
{}),
color
}
...(
d
?
.
labelLine
||
{}),
lineStyle
:
{
...(
d
?
.
labelLine
?.
lineStyle
||
{}),
color
}
}
}
})
...
...
@@ -37,10 +38,15 @@ const getPieChart = (data) => {
alignTo
:
'edge'
,
formatter
:
params
=>
{
const
name
=
params
.
name
||
""
;
const
value
=
params
.
value
??
""
;
const
percent
=
params
.
percent
!=
null
?
Math
.
round
(
params
.
percent
)
:
0
;
const
value
=
Number
(
params
.
value
??
0
)
||
0
;
const
rawPercent
=
params
?.
data
?.
percent
!=
null
?
params
.
data
.
percent
:
params
.
percent
;
const
percent
=
rawPercent
!=
null
?
Number
(
rawPercent
).
toFixed
(
2
)
:
"0.00"
;
return
`{name|
${
name
}
}\n{time|
${
percent
}
%}`
;
// 第二行:数值 + 百分比(同一行、同一文字样式),保持与旧版一致的两行结构
return
`{name|
${
name
}
}\n{time|
${
value
}
项
${
percent
}
%}`
;
},
minMargin
:
5
,
edgeDistance
:
10
,
...
...
src/views/thinkTank/utils/resourceLibraryFilters.js
浏览文件 @
7cd28bf2
...
...
@@ -124,7 +124,20 @@ export function matchesEarlierChineseDate(timeStr) {
/** 与政策追踪「仅全部时间」一致:固定起止(结束日按产品要求) */
export
const
RESOURCE_REPORT_ALL_TIME_START
=
"2000-01-01"
;
export
const
RESOURCE_REPORT_ALL_TIME_END
=
"2025-12-31"
;
function
getYesterdayYmd
()
{
const
d
=
new
Date
();
// JS Date 自动处理跨月/跨年
d
.
setDate
(
d
.
getDate
()
-
1
);
const
y
=
d
.
getFullYear
();
const
m
=
String
(
d
.
getMonth
()
+
1
).
padStart
(
2
,
"0"
);
const
day
=
String
(
d
.
getDate
()).
padStart
(
2
,
"0"
);
return
`
${
y
}
-
${
m
}
-
${
day
}
`
;
}
export
function
getResourceReportAllTimeEndYmd
()
{
return
getYesterdayYmd
();
}
function
getResourceReportDateYearsAgo
(
years
)
{
const
currentDate
=
new
Date
();
...
...
@@ -145,7 +158,7 @@ function getResourceReportTodayYmd() {
/**
* 资源库 /thinkTankOverview/report:由发布时间多选(数字年 +「更早」)推导 startDate/endDate,语义对齐政策追踪 getPolicyListDateRangeFromYearList。
* - 仅「全部时间」或选满全部具体年份 → 2000-01-01 ~
2025-12-31
* - 仅「全部时间」或选满全部具体年份 → 2000-01-01 ~
昨天
* - 单选/多选自然年 +「更早」→ 取最小年 01-01 与最大年 12-31 包络(「更早」为 2000~2020)
* - 无法解析时的兜底:近 relativeYearsAgo 年至今天
*
...
...
@@ -169,7 +182,7 @@ export function getResourceLibraryReportDateRangeFromTimeSelection(
if
(
isAllTime
)
{
return
{
startDate
:
RESOURCE_REPORT_ALL_TIME_START
,
endDate
:
RESOURCE_REPORT_ALL_TIME_END
,
endDate
:
getResourceReportAllTimeEndYmd
()
,
};
}
...
...
@@ -206,6 +219,6 @@ export function getResourceLibraryReportDateRangeFromTimeSelection(
}
return
{
startDate
:
`
${
minY
}
-01-01`
,
endDate
:
`
${
maxY
}
-12-31`
,
endDate
:
maxY
===
new
Date
().
getFullYear
()
?
getYesterdayYmd
()
:
`
${
maxY
}
-12-31`
,
};
}
src/views/thinkTank/utils/sankey.js
浏览文件 @
7cd28bf2
import
{
MUTICHARTCOLORS
}
from
"@/common/constant.js"
;
const
getSankeyChart
=
(
nodes
,
links
)
=>
{
const
formatAmountWan
=
(
v
)
=>
{
const
n
=
Number
(
v
)
if
(
!
Number
.
isFinite
(
n
))
return
'0.00万'
return
`
${
n
.
toFixed
(
2
)}
万`
}
const
option
=
{
tooltip
:
{
trigger
:
'item'
,
backgroundColor
:
'rgba(255, 255, 255, 0.95)'
,
borderColor
:
'rgba(234, 236, 238, 1)'
,
borderWidth
:
1
,
textStyle
:
{
color
:
'rgb(59, 65, 75)'
,
fontFamily
:
'Microsoft YaHei'
,
fontSize
:
14
,
lineHeight
:
22
},
formatter
:
function
(
params
)
{
// 仅对连线展示资金金额;节点保持默认名称提示
if
(
params
?.
dataType
===
'edge'
)
{
const
amount
=
formatAmountWan
(
params
?.
data
?.
value
)
return
`资金金额:
${
amount
}
`
}
return
params
?.
name
??
''
}
},
series
:
{
type
:
'sankey'
,
layout
:
'none'
,
...
...
@@ -9,6 +37,11 @@ const getSankeyChart = (nodes, links) => {
right
:
'15%'
,
top
:
'5%'
,
bottom
:
'5%'
,
color
:
MUTICHARTCOLORS
,
lineStyle
:
{
color
:
'rgb(230, 231, 232)'
,
opacity
:
1
},
emphasis
:
{
focus
:
'adjacency'
},
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论