Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
4d67858d
提交
4d67858d
authored
4月 22, 2026
作者:
张烨
浏览文件
操作
浏览文件
下载
差异文件
Merge remote-tracking branch 'origin/pre' into zy-dev
上级
c2f806a2
7cd28bf2
全部展开
隐藏空白字符变更
内嵌
并排
正在显示
44 个修改的文件
包含
1069 行增加
和
513 行删除
+1069
-513
overview.js
src/api/thinkTank/overview.js
+3
-2
down.svg
src/components/base/GradeSortSelectBox/down.svg
+6
-0
index.vue
src/components/base/GradeSortSelectBox/index.vue
+40
-0
up.svg
src/components/base/GradeSortSelectBox/up.svg
+6
-0
down.svg
src/components/base/HeatSortSelectBox/down.svg
+9
-0
index.vue
src/components/base/HeatSortSelectBox/index.vue
+40
-0
up.svg
src/components/base/HeatSortSelectBox/up.svg
+9
-0
down1.svg
src/components/base/TimeSortSelectBox/down1.svg
+9
-0
down2.svg
src/components/base/TimeSortSelectBox/down2.svg
+6
-0
index.vue
src/components/base/TimeSortSelectBox/index.vue
+37
-11
thinktank.js
src/router/modules/thinktank.js
+1
-1
index.vue
src/styles/components/TimeSortSelectBox/index.vue
+25
-6
index.vue
src/styles/components/index.vue
+3
-4
setChart.js
src/utils/setChart.js
+95
-0
index.vue
src/views/coopRestriction/components/dataSub/index.vue
+36
-6
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
+74
-27
index.vue
src/views/scientificFunding/components/resLib/index.vue
+7
-3
index.vue
src/views/thinkTank/CongressHearingView/index.vue
+21
-9
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
+22
-10
index.vue
src/views/thinkTank/SurveyProjectView/index.vue
+21
-9
index.vue
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
+0
-0
multiLineChart.js
...nk/ThinkTankDetail/PolicyTracking/utils/multiLineChart.js
+61
-109
piechart.js
...hinkTank/ThinkTankDetail/PolicyTracking/utils/piechart.js
+14
-9
index.vue
...k/ThinkTankDetail/thinkDynamics/CongressHearing/index.vue
+51
-21
index.vue
...nkTank/ThinkTankDetail/thinkDynamics/SurveyForm/index.vue
+43
-14
index.vue
...k/ThinkTankDetail/thinkDynamics/ThinkTankReport/index.vue
+44
-14
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
+43
-16
HomeMainFooterSurvey.vue
src/views/thinkTank/components/HomeMainFooterSurvey.vue
+43
-16
ThinkTankCongressHearingOverview.vue
...thinkTank/components/ThinkTankCongressHearingOverview.vue
+49
-26
ThinkTankPolicyAdviceOverview.vue
...ws/thinkTank/components/ThinkTankPolicyAdviceOverview.vue
+63
-37
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
+34
-95
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
浏览文件 @
4d67858d
...
...
@@ -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/components/base/GradeSortSelectBox/down.svg
0 → 100644
浏览文件 @
4d67858d
<svg
viewBox=
"0 0 16 16"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"16.000000"
height=
"16.000000"
fill=
"none"
>
<rect
id=
"降序 12"
width=
"16.000000"
height=
"16.000000"
x=
"0.000000"
y=
"0.000000"
/>
<path
id=
"路径"
d=
"M12.6473 7.70235L14.0825 6.30313C14.1606 6.22657 14.2121 6.12657 14.2278 6.01719C14.27 5.74376 14.0793 5.49063 13.8059 5.45001L9.83871 4.87344L8.06527 1.27813C8.01683 1.17969 7.93715 1.10001 7.83871 1.05157C7.59183 0.929693 7.29183 1.03126 7.1684 1.27813L5.39496 4.87344L1.42777 5.45001C1.3184 5.46563 1.2184 5.51719 1.14183 5.59532C0.949647 5.79376 0.952772 6.10938 1.15121 6.30313L4.02152 9.10157L3.3434 13.0531C3.32465 13.1609 3.34183 13.2734 3.3934 13.3703C3.52152 13.6141 3.82465 13.7094 4.0684 13.5797L7.61684 11.7141L8.50394 12.1805"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
/>
<path
id=
"矢量 1983"
d=
"M0 0L4 0"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
transform=
"matrix(0,1,-1,0,12,9)"
/>
<path
id=
"矢量 1984"
d=
"M10 12L12 14L14 12"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
/>
</svg>
src/components/base/GradeSortSelectBox/index.vue
0 → 100644
浏览文件 @
4d67858d
<
template
>
<div
class=
"grade-sort-select-box"
>
<el-select
v-model=
"isSort"
placeholder=
"评分排序"
style=
"width: 130px"
@
change=
"handlePxChange"
>
<template
#
prefix
>
<div
style=
"display: flex; align-items: center; height: 100%"
>
<img
v-if=
"isSort"
src=
"./down.svg"
style=
"width: 16px; height: 16px"
/>
<img
v-else
src=
"./up.svg"
style=
"width: 16px; height: 16px"
/>
</div>
</
template
>
<el-option
v-for=
"item in gradeList"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
</div>
</template>
<
script
setup
>
import
{
ref
}
from
'vue'
const
isSort
=
ref
(
true
)
const
gradeList
=
ref
([
{
label
:
"评分倒序"
,
value
:
true
},
{
label
:
"评分正序"
,
value
:
false
}
])
const
emits
=
defineEmits
([
'handlePxChange'
])
const
handlePxChange
=
()
=>
{
emits
(
'handlePxChange'
,
isSort
.
value
)
}
</
script
>
<
style
scoped
>
.grade-sort-select-box
{
height
:
42px
;
box-sizing
:
border-box
;
padding
:
5px
0
;
}
</
style
>
\ No newline at end of file
src/components/base/GradeSortSelectBox/up.svg
0 → 100644
浏览文件 @
4d67858d
<svg
viewBox=
"0 0 16 16"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"16.000000"
height=
"16.000000"
fill=
"none"
>
<rect
id=
"降序 11"
width=
"16.000000"
height=
"16.000000"
x=
"0.000000"
y=
"0.000000"
/>
<path
id=
"矢量 1983"
d=
"M0 0L4 0"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
transform=
"matrix(0,1,-1,0,12,10)"
/>
<path
id=
"矢量 1984"
d=
"M10 10.9969L12.0003 9L14 10.9969"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
/>
<path
id=
"路径"
d=
"M12.6473 7.70235L14.0825 6.30313C14.1606 6.22657 14.2121 6.12657 14.2278 6.01719C14.27 5.74376 14.0793 5.49063 13.8059 5.45001L9.83871 4.87344L8.06527 1.27813C8.01683 1.17969 7.93715 1.10001 7.83871 1.05157C7.59183 0.929693 7.29183 1.03126 7.1684 1.27813L5.39496 4.87344L1.42777 5.45001C1.3184 5.46563 1.2184 5.51719 1.14183 5.59532C0.949647 5.79376 0.952772 6.10938 1.15121 6.30313L4.02152 9.10157L3.3434 13.0531C3.32465 13.1609 3.34183 13.2734 3.3934 13.3703C3.52152 13.6141 3.82465 13.7094 4.0684 13.5797L7.61684 11.7141L9.39105 12.6469"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
/>
</svg>
src/components/base/HeatSortSelectBox/down.svg
0 → 100644
浏览文件 @
4d67858d
<svg
viewBox=
"0 0 16 16"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"16.000000"
height=
"16.000000"
fill=
"none"
>
<rect
id=
"降序 8"
width=
"16.000000"
height=
"16.000000"
x=
"0.000000"
y=
"0.000000"
/>
<rect
id=
"矩形 6349"
width=
"12.000000"
height=
"1.000000"
x=
"2.000000"
y=
"3.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
<rect
id=
"矩形 6350"
width=
"12.000000"
height=
"1.000000"
x=
"2.000000"
y=
"6.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
<rect
id=
"矩形 6351"
width=
"6.000000"
height=
"1.000000"
x=
"2.000000"
y=
"9.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
<rect
id=
"矩形 6352"
width=
"6.000000"
height=
"1.000000"
x=
"2.000000"
y=
"12.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
<path
id=
"矢量 1983"
d=
"M0 0L4 0"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
transform=
"matrix(0,1,-1,0,12,9)"
/>
<path
id=
"矢量 1984"
d=
"M10 12L12 14L14 12"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
/>
</svg>
src/components/base/HeatSortSelectBox/index.vue
0 → 100644
浏览文件 @
4d67858d
<
template
>
<div
class=
"heat-sort-select-box"
>
<el-select
v-model=
"isSort"
placeholder=
"热度排序"
style=
"width: 130px"
@
change=
"handlePxChange"
>
<template
#
prefix
>
<div
style=
"display: flex; align-items: center; height: 100%"
>
<img
v-if=
"isSort"
src=
"./down.svg"
style=
"width: 16px; height: 16px"
/>
<img
v-else
src=
"./up.svg"
style=
"width: 16px; height: 16px"
/>
</div>
</
template
>
<el-option
v-for=
"item in heatList"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
</div>
</template>
<
script
setup
>
import
{
ref
}
from
'vue'
const
isSort
=
ref
(
true
)
const
heatList
=
ref
([
{
label
:
"热度倒序"
,
value
:
true
},
{
label
:
"热度正序"
,
value
:
false
}
])
const
emits
=
defineEmits
([
'handlePxChange'
])
const
handlePxChange
=
()
=>
{
emits
(
'handlePxChange'
,
isSort
.
value
)
}
</
script
>
<
style
scoped
>
.heat-sort-select-box
{
height
:
42px
;
box-sizing
:
border-box
;
padding
:
5px
0
;
}
</
style
>
\ No newline at end of file
src/components/base/HeatSortSelectBox/up.svg
0 → 100644
浏览文件 @
4d67858d
<svg
viewBox=
"0 0 16 16"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"16.000000"
height=
"16.000000"
fill=
"none"
>
<rect
id=
"降序 10"
width=
"16.000000"
height=
"16.000000"
x=
"0.000000"
y=
"0.000000"
/>
<path
id=
"矢量 1983"
d=
"M0 0L4 0"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
transform=
"matrix(0,1,-1,0,12,10)"
/>
<path
id=
"矢量 1984"
d=
"M10 10.9969L12.0003 9L14 10.9969"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
/>
<rect
id=
"矩形 6349"
width=
"12.000000"
height=
"1.000000"
x=
"2.000000"
y=
"3.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
<rect
id=
"矩形 6350"
width=
"12.000000"
height=
"1.000000"
x=
"2.000000"
y=
"6.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
<rect
id=
"矩形 6351"
width=
"6.000000"
height=
"1.000000"
x=
"2.000000"
y=
"9.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
<rect
id=
"矩形 6352"
width=
"6.000000"
height=
"1.000000"
x=
"2.000000"
y=
"12.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
</svg>
src/components/base/TimeSortSelectBox/down1.svg
0 → 100644
浏览文件 @
4d67858d
<svg
viewBox=
"0 0 16 16"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"16.000000"
height=
"16.000000"
fill=
"none"
>
<rect
id=
"降序 8"
width=
"16.000000"
height=
"16.000000"
x=
"0.000000"
y=
"0.000000"
/>
<rect
id=
"矩形 6349"
width=
"12.000000"
height=
"1.000000"
x=
"2.000000"
y=
"3.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
<rect
id=
"矩形 6350"
width=
"12.000000"
height=
"1.000000"
x=
"2.000000"
y=
"6.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
<rect
id=
"矩形 6351"
width=
"6.000000"
height=
"1.000000"
x=
"2.000000"
y=
"9.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
<rect
id=
"矩形 6352"
width=
"6.000000"
height=
"1.000000"
x=
"2.000000"
y=
"12.000000"
rx=
"0.500000"
fill=
"rgb(95, 101, 108)"
/>
<path
id=
"矢量 1983"
d=
"M0 0L4 0"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
transform=
"matrix(0,1,-1,0,12,9)"
/>
<path
id=
"矢量 1984"
d=
"M10 12L12 14L14 12"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
/>
</svg>
src/components/base/TimeSortSelectBox/down2.svg
0 → 100644
浏览文件 @
4d67858d
<svg
viewBox=
"0 0 16 16"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"16.000000"
height=
"16.000000"
fill=
"none"
>
<rect
id=
"降序 12"
width=
"16.000000"
height=
"16.000000"
x=
"0.000000"
y=
"0.000000"
/>
<path
id=
"路径"
d=
"M12.6473 7.70235L14.0825 6.30313C14.1606 6.22657 14.2121 6.12657 14.2278 6.01719C14.27 5.74376 14.0793 5.49063 13.8059 5.45001L9.83871 4.87344L8.06527 1.27813C8.01683 1.17969 7.93715 1.10001 7.83871 1.05157C7.59183 0.929693 7.29183 1.03126 7.1684 1.27813L5.39496 4.87344L1.42777 5.45001C1.3184 5.46563 1.2184 5.51719 1.14183 5.59532C0.949647 5.79376 0.952772 6.10938 1.15121 6.30313L4.02152 9.10157L3.3434 13.0531C3.32465 13.1609 3.34183 13.2734 3.3934 13.3703C3.52152 13.6141 3.82465 13.7094 4.0684 13.5797L7.61684 11.7141L8.50394 12.1805"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
/>
<path
id=
"矢量 1983"
d=
"M0 0L4 0"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
transform=
"matrix(0,1,-1,0,12,9)"
/>
<path
id=
"矢量 1984"
d=
"M10 12L12 14L14 12"
stroke=
"rgb(95, 101, 108)"
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"1.000000"
/>
</svg>
src/components/base/TimeSortSelectBox/index.vue
浏览文件 @
4d67858d
<
template
>
<div
class=
"time-sort-select-box"
>
<el-select
v-model=
"
isSort"
placeholder=
"发布时间
"
style=
"width: 130px"
@
change=
"handlePxChange"
>
<el-select
v-model=
"
sortValue"
placeholder=
"排序方式
"
style=
"width: 130px"
@
change=
"handlePxChange"
>
<template
#
prefix
>
<div
style=
"display: flex; align-items: center; height: 100%"
>
<img
v-if=
"isSort"
src=
"./down.svg"
style=
"width: 16px; height: 16px"
/>
<img
v-else
src=
"./up.svg"
style=
"width: 16px; height: 16px"
/>
<img
v-if=
"sortValue === 1"
src=
"./down.svg"
style=
"width: 16px; height: 16px"
/>
<img
v-else-if=
"sortValue === 2"
src=
"./up.svg"
style=
"width: 16px; height: 16px"
/>
<img
v-else-if=
"sortValue === 3"
src=
"./down1.svg"
style=
"width: 16px; height: 16px"
/>
<img
v-else-if=
"sortValue === 4"
src=
"./down2.svg"
style=
"width: 16px; height: 16px"
/>
</div>
</
template
>
<el-option
v-for=
"item in
time
List"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
<el-option
v-for=
"item in
sort
List"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
</div>
</template>
<
script
setup
>
import
{
ref
}
from
'vue'
import
{
computed
,
ref
}
from
'vue'
const
isSort
=
ref
(
true
)
const
props
=
defineProps
({
sortDemension
:
{
type
:
Number
,
default
:
1
}
})
const
timeList
=
ref
([
{
label
:
"时间倒序"
,
value
:
true
},
{
label
:
"时间正序"
,
value
:
false
}
])
const
sortValue
=
ref
(
1
)
const
sortList
=
computed
(()
=>
{
if
(
props
.
sortDemension
===
1
)
{
return
[
{
label
:
"时间倒序"
,
value
:
1
},
{
label
:
"时间正序"
,
value
:
2
},
]
}
else
if
(
props
.
sortDemension
===
2
)
{
return
[
{
label
:
"时间倒序"
,
value
:
1
},
{
label
:
"时间正序"
,
value
:
2
},
{
label
:
"评分倒序"
,
value
:
3
},
]
}
else
{
return
[
{
label
:
"时间倒序"
,
value
:
1
},
{
label
:
"时间正序"
,
value
:
2
},
{
label
:
"评分倒序"
,
value
:
3
},
{
label
:
"热度倒序"
,
value
:
4
},
]
}
})
const
emits
=
defineEmits
([
'handlePxChange'
])
const
handlePxChange
=
()
=>
{
emits
(
'handlePxChange'
,
isSort
.
value
)
emits
(
'handlePxChange'
,
sortValue
.
value
)
}
</
script
>
...
...
src/router/modules/thinktank.js
浏览文件 @
4d67858d
...
...
@@ -15,7 +15,7 @@ const thinktankRoutes = [
name
:
"thinkTank"
,
component
:
thinkTank
,
meta
:
{
title
:
"科技智库概览"
,
title
:
"
美国
科技智库概览"
,
isShowHeader
:
true
}
},
...
...
src/styles/components/TimeSortSelectBox/index.vue
浏览文件 @
4d67858d
...
...
@@ -4,15 +4,19 @@
<pre>
{{
`
import TimeSortSelectBox from '@/components/base/TimeSortSelectBox/index.vue'
<div class="time-box">
<TimeSortSelectBox @handle-px-change="handlePx" />
</div>
import TimeSortSelectBox from '@/components/base/TimeSortSelectBox/index.vue'
<div class="time-box">
<TimeSortSelectBox @handle-px-change="handleTimePx" />
<TimeSortSelectBox :sort-demension="2" @handle-px-change="handleTimePx" />
<TimeSortSelectBox :sort-demension="3" @handle-px-change="handleTimePx" />
</div>
`
}}
</pre>
<div
class=
"time-box"
>
<TimeSortSelectBox
@
handle-px-change=
"handlePx"
/>
<TimeSortSelectBox
@
handle-px-change=
"handleTimePx"
/>
<TimeSortSelectBox
:sort-demension=
"2"
@
handle-px-change=
"handleTimePx"
/>
<TimeSortSelectBox
:sort-demension=
"3"
@
handle-px-change=
"handleTimePx"
/>
</div>
</el-col>
...
...
@@ -24,12 +28,24 @@
import
{
ref
}
from
'vue'
import
'@/styles/common.scss'
import
TimeSortSelectBox
from
'@/components/base/TimeSortSelectBox/index.vue'
import
HeatSortSelectBox
from
'@/components/base/HeatSortSelectBox/index.vue'
import
GradeSortSelectBox
from
'@/components/base/GradeSortSelectBox/index.vue'
const
span
=
12
const
handlePx
=
(
val
)
=>
{
const
handleTimePx
=
(
val
)
=>
{
alert
(
val
)
}
const
handleHeatPx
=
(
val
)
=>
{
alert
(
val
)
}
const
handleGradePx
=
(
val
)
=>
{
alert
(
val
)
}
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
@@ -39,5 +55,7 @@ const handlePx = (val) => {
padding
:
100px
;
background
:
#F2F8FF
;
border
:
1px
solid
var
(
--
bg-black-5
);
display
:
flex
;
gap
:
8px
;
}
</
style
>
\ No newline at end of file
src/styles/components/index.vue
浏览文件 @
4d67858d
...
...
@@ -8,8 +8,8 @@
<div
class=
"text-title-1-show"
>
文字样式
</div>
<TextStyle
/>
<div
class=
"text-title-1-show"
>
通用样式/组件
</div>
<div
style=
"position: relative;
min-height: 7
00px;"
>
<el-tabs
tabPosition=
"left"
class=
"tabs-nav-no-wrap left-float-nav-tabs dev-style-tabs"
>
<div
style=
"position: relative;
height: 8
00px;"
>
<el-tabs
tabPosition=
"left"
style=
"position: relative; height: 700px;"
class=
"tabs-nav-no-wrap left-float-nav-tabs dev-style-tabs"
>
<el-tab-pane
label=
"通用"
lazy
>
<common-page
/>
</el-tab-pane>
...
...
@@ -73,10 +73,9 @@
<el-tab-pane
label=
"激活工作框"
lazy
>
<WorkingBox
/>
</el-tab-pane>
<el-tab-pane
label=
"
时间
排序"
lazy
>
<el-tab-pane
label=
"
自定义
排序"
lazy
>
<TimeSortSelectBox
/>
</el-tab-pane>
</el-tabs>
</div>
</el-space>
...
...
src/utils/setChart.js
浏览文件 @
4d67858d
...
...
@@ -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
浏览文件 @
4d67858d
...
...
@@ -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
)
{
console
.
error
(
"合作限制政策对比图表解读请求失败"
,
error
);
aiContentLeft
.
value
=
"解读加载失败"
;
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
)
{
console
.
error
(
"合作限制领域分布图表解读请求失败"
,
error
);
aiContentRight
.
value
=
"解读加载失败"
;
if
(
error
?.
name
!==
"AbortError"
)
{
console
.
error
(
"合作限制领域分布图表解读请求失败"
,
error
);
aiContentRight
.
value
=
"解读加载失败"
;
}
}
finally
{
isRightInterpretLoading
.
value
=
false
;
rightAiAbortController
.
value
=
null
;
}
};
...
...
src/views/coopRestriction/components/resLib/index.vue
浏览文件 @
4d67858d
...
...
@@ -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
浏览文件 @
4d67858d
...
...
@@ -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
浏览文件 @
4d67858d
...
...
@@ -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
;
console
.
error
(
"图表解读请求失败"
,
e
);
targetRef
.
value
=
"解读加载失败"
;
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
浏览文件 @
4d67858d
...
...
@@ -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
浏览文件 @
4d67858d
...
...
@@ -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
)
{
console
.
error
(
"报告关键词云图表解读请求失败"
,
error
);
aiContentBox5
.
value
=
"解读加载失败"
;
if
(
error
?.
name
!==
"AbortError"
)
{
console
.
error
(
"报告关键词云图表解读请求失败"
,
error
);
aiContentBox5
.
value
=
"解读加载失败"
;
}
}
finally
{
isBox5InterpretLoading
.
value
=
false
;
box5AiAbortController
.
value
=
null
;
}
};
...
...
src/views/thinkTank/ReportDetail/index.vue
浏览文件 @
4d67858d
...
...
@@ -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
浏览文件 @
4d67858d
...
...
@@ -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
浏览文件 @
4d67858d
...
...
@@ -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
)
{
console
.
error
(
"报告关键词云图表解读请求失败"
,
error
);
aiContentBox5
.
value
=
"解读加载失败"
;
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
浏览文件 @
4d67858d
...
...
@@ -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
)
{
console
.
error
(
"报告关键词云图表解读请求失败"
,
error
);
aiContentBox5
.
value
=
"解读加载失败"
;
if
(
error
?.
name
!==
"AbortError"
)
{
console
.
error
(
"报告关键词云图表解读请求失败"
,
error
);
aiContentBox5
.
value
=
"解读加载失败"
;
}
}
finally
{
isBox5InterpretLoading
.
value
=
false
;
box5AiAbortController
.
value
=
null
;
}
};
...
...
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
浏览文件 @
4d67858d
差异被折叠。
点击展开。
src/views/thinkTank/ThinkTankDetail/PolicyTracking/utils/multiLineChart.js
浏览文件 @
4d67858d
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
)
{
return
{
r
:
parseInt
(
match
[
1
],
10
),
g
:
parseInt
(
match
[
2
],
10
),
b
:
parseInt
(
match
[
3
],
10
),
a
:
parseFloat
(
match
[
4
])
}
const
parseHexToRgb
=
(
hex
)
=>
{
const
h
=
String
(
hex
||
''
).
replace
(
'#'
,
''
).
trim
()
if
(
h
.
length
!==
6
)
return
{
r
:
0
,
g
:
0
,
b
:
0
}
return
{
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,69 +58,65 @@ const getMultiLineChart = (chartInput) => {
},
/* 贴满 #box3Chart:四边 0,由 containLabel 在网格内为轴文字留位,避免左侧/底部大块留白 */
grid
:
{
top
:
92
,
top
:
60
,
right
:
10
,
bottom
:
0
,
left
:
20
,
containLabel
:
true
},
legend
:
[
{
show
:
true
,
type
:
'plain'
,
data
:
legendLine1
,
top
:
4
,
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)'
},
itemWidth
:
12
,
itemHeight
:
12
},
legend
:
{
show
:
true
,
type
:
'scroll'
,
orient
:
'horizontal'
,
left
:
8
,
top
:
6
,
width
:
'95%'
,
height
:
24
,
padding
:
[
0
,
24
,
0
,
24
],
icon
:
'circle'
,
itemWidth
:
12
,
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
,
fontSize
:
14
,
lineHeight
:
24
,
letterSpacing
:
0
,
align
:
'left'
,
color
:
'rgb(95, 101, 108)'
}
},
graphic
:
[
{
show
:
legendLine2
.
length
>
0
,
type
:
'plain'
,
data
:
legendLine2
,
top
:
30
,
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)'
},
itemWidth
:
12
,
itemHeight
:
12
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
},
{
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)'
},
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
浏览文件 @
4d67858d
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
浏览文件 @
4d67858d
...
...
@@ -72,38 +72,46 @@
</div>
-->
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<div
class=
"card-content"
>
<div
v-for=
"(item, index) in hearingData"
:key=
"item.id"
>
<div
class=
"card-item"
>
<img
class=
"card-item-img"
:src=
"item.coverImgUrl"
alt=
"report image"
/>
<div
class=
"card-item-text"
>
<div
class=
"card-item-title"
@
click=
"emit('report-click', item)"
>
<span
v-html=
"highlightText(item.title)"
></span>
<template
v-if=
"hasData"
>
<div
class=
"card-content"
>
<div
v-for=
"(item, index) in hearingData"
:key=
"item.id"
>
<div
class=
"card-item"
>
<img
class=
"card-item-img"
:src=
"item.coverImgUrl"
alt=
"report image"
/>
<div
class=
"card-item-text"
>
<div
class=
"card-item-title"
@
click=
"emit('report-click', item)"
>
<span
v-html=
"highlightText(item.title)"
></span>
</div>
<div
class=
"card-item-time"
>
<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"
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>
<div
class=
"card-item-time"
>
<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"
>
<AreaTag
v-for=
"(val, idx) in item.category"
:key=
"idx"
:tagName=
"val"
/>
</div>
</div>
</div>
<div
class=
"divider"
v-if=
"index !== hearingData.length - 1"
></div>
</div>
<div
class=
"divider"
v-if=
"index !== hearingData.length - 1"
></div>
</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=
"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
浏览文件 @
4d67858d
...
...
@@ -52,28 +52,35 @@
</div>
-->
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<div
class=
"footer-card"
v-for=
"(item, index) in curFooterList"
:key=
"index"
@
click=
"handleToReportDetail(item)"
>
<div
class=
"footer-card-top"
>
<img
:src=
item.projectCoverImgUrl
alt=
""
/>
<template
v-if=
"hasData"
>
<div
class=
"footer-card"
v-for=
"(item, index) in curFooterList"
:key=
"index"
@
click=
"handleToReportDetail(item)"
>
<div
class=
"footer-card-top"
>
<img
:src=
item.projectCoverImgUrl
alt=
""
/>
</div>
<div
class=
"footer-card-title"
>
<span
v-html=
"highlightText(item.projectNameZh)"
></span>
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
formatDate
(
item
.
startDate
)
}}
</div>
<div
class=
"from"
>
{{
item
.
thinktankName
}}
</div>
</div>
</div>
<div
class=
"footer-card-title"
>
<span
v-html=
"highlightText(item.projectNameZh)"
></span>
</
template
>
<
template
v-else-if=
"!loading"
>
<div
class=
"right-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
formatDate
(
item
.
startDate
)
}}
</div>
<div
class=
"from"
>
{{
item
.
thinktankName
}}
</div>
</div>
</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
浏览文件 @
4d67858d
...
...
@@ -52,28 +52,35 @@
</div>
-->
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<div
class=
"footer-card"
v-for=
"(item, index) in curFooterList"
:key=
"index"
@
click=
"handleToReportDetail(item)"
>
<div
class=
"footer-card-top"
>
<img
:src=
"item.imageUrl"
alt=
""
/>
<template
v-if=
"hasData"
>
<div
class=
"footer-card"
v-for=
"(item, index) in curFooterList"
:key=
"index"
@
click=
"handleToReportDetail(item)"
>
<div
class=
"footer-card-top"
>
<img
:src=
"item.imageUrl"
alt=
""
/>
</div>
<div
class=
"footer-card-title"
>
<span
v-html=
"highlightText(item.name)"
></span>
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
formatDate
(
item
.
times
)
}}
</div>
<div
class=
"from"
>
{{
item
.
thinkTankName
}}
</div>
</div>
</div>
<div
class=
"footer-card-title"
>
<span
v-html=
"highlightText(item.name)"
></span>
</
template
>
<
template
v-else-if=
"!loading"
>
<div
class=
"right-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
formatDate
(
item
.
times
)
}}
</div>
<div
class=
"from"
>
{{
item
.
thinkTankName
}}
</div>
</div>
</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
浏览文件 @
4d67858d
...
...
@@ -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
浏览文件 @
4d67858d
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
浏览文件 @
4d67858d
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
浏览文件 @
4d67858d
...
...
@@ -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
浏览文件 @
4d67858d
...
...
@@ -36,26 +36,33 @@
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<div
class=
"footer-card"
v-for=
"(item, index) in curFooterList"
:key=
"index"
@
click=
"emit('report-click', item)"
>
<div
class=
"footer-card-top"
>
<img
:src=
"item.imageUrl"
alt=
""
/>
<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"
>
<img
:src=
"item.imageUrl"
alt=
""
/>
</div>
<div
class=
"footer-card-title"
>
{{
item
.
name
}}
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
formatDate
(
item
.
times
)
}}
</div>
<div
class=
"from"
>
{{
item
.
thinkTankName
}}
</div>
</div>
</div>
<div
class=
"footer-card-title"
>
{{
item
.
name
}}
</
template
>
<
template
v-else-if=
"!loading"
>
<div
class=
"right-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
formatDate
(
item
.
times
)
}}
</div>
<div
class=
"from"
>
{{
item
.
thinkTankName
}}
</div>
</div>
</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
浏览文件 @
4d67858d
...
...
@@ -36,26 +36,33 @@
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<div
class=
"footer-card"
v-for=
"(item, index) in curFooterList"
:key=
"index"
@
click=
"emit('report-click', item)"
>
<div
class=
"footer-card-top"
>
<img
:src=
"item.projectCoverImgUrl"
alt=
""
/>
<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"
>
<img
:src=
"item.projectCoverImgUrl"
alt=
""
/>
</div>
<div
class=
"footer-card-title"
>
{{
item
.
projectNameZh
}}
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
formatDate
(
item
.
startDate
)
}}
</div>
<div
class=
"from"
>
{{
item
.
thinktankName
}}
</div>
</div>
</div>
<div
class=
"footer-card-title"
>
{{
item
.
projectNameZh
}}
</
template
>
<
template
v-else-if=
"!loading"
>
<div
class=
"right-empty"
>
<el-empty
class=
"right-el-empty"
description=
"暂无数据"
:image-size=
"100"
/>
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
formatDate
(
item
.
startDate
)
}}
</div>
<div
class=
"from"
>
{{
item
.
thinktankName
}}
</div>
</div>
</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
浏览文件 @
4d67858d
...
...
@@ -55,42 +55,47 @@
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<
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"
/>
<div
class=
"card-item-text"
>
<div
class=
"card-item-title"
@
click=
"emit('report-click', item)"
>
{{
item
.
titleZh
}}
</div>
<div
class=
"card-item-time"
>
{{
item
.
testimonyDate
+
' · '
+
item
.
thinkTankName
+
' · '
+
item
.
committeeZh
}}
<img
src=
"../ThinkTankDetail/thinkDynamics/images/image open.png"
alt=
"open icon"
class=
"card-open-image"
/>
</div>
<
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"
/>
<div
class=
"card-item-text"
>
<div
class=
"card-item-title"
@
click=
"emit('report-click', item)"
>
{{
item
.
titleZh
}}
</div>
<div
class=
"card-item-time"
>
{{
item
.
testimonyDate
+
' · '
+
item
.
thinkTankName
+
' · '
+
item
.
committeeZh
}}
<img
src=
"../ThinkTankDetail/thinkDynamics/images/image open.png"
alt=
"open icon"
class=
"card-open-image"
/>
</div>
<div
class=
"card-item-category"
v-if=
"item.domains"
>
<div
v-for=
"(value, index) in item.domains"
:key=
"`domain-$
{index}`">
<AreaTag
:key=
"`cat-$
{item.id}`" :tagName="value" />
<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>
</div>
</div>
</div>
<div
class=
"divider"
v-if=
"index !== hearingData.length - 1"
></div>
</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
浏览文件 @
4d67858d
...
...
@@ -36,54 +36,61 @@
</div>
</div>
<div
class=
"right"
>
<div
class=
"right"
v-loading=
"loading"
>
<div
class=
"card-box"
>
<div
class=
"card-content"
>
<div
v-for=
"(item, index) in list"
:key=
"item.id ?? index"
>
<div
class=
"card-item"
@
click=
"emit('item-click', item)"
>
<div
class=
"card-item-img"
>
<img
:src=
"item.imageUrl"
alt=
""
/>
</div>
<div
class=
"card-item-text"
>
<div
class=
"card-item-title"
>
{{
item
.
name
}}
</div>
<div
class=
"card-item-time"
>
<span
class=
"info-text"
>
{{
formatDate
(
item
.
times
)
}}
·
{{
item
.
thinkTankName
}}
·
{{
item
.
reportName
}}
</span>
<div
class=
"card-open-image"
@
click
.
stop=
"handleOpenReportOriginal(item)"
>
<img
src=
"@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image open.png"
alt=
""
/>
</div>
<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)"
>
<div
class=
"card-item-img"
>
<img
:src=
"item.imageUrl"
alt=
""
/>
</div>
<div
class=
"card-item-category"
v-if=
"item.tagList && item.tagList.length"
>
<AreaTag
v-for=
"d in item.tagList"
:key=
"d"
:tagName=
"d"
/>
</div>
<div
class=
"file-box"
>
<div
class=
"file"
v-for=
"sv in item.billInfoList"
:key=
"sv.id || sv.name"
>
<div
class=
"type"
>
法案
</div>
<div
class=
"title"
>
{{
sv
.
name
}}
</div>
<div
class=
"more"
@
click
.
stop=
"handleBillMoreClick(sv)"
>
<img
src=
"../assets/images/image-right.png"
alt=
""
/>
<div
class=
"card-item-text"
>
<div
class=
"card-item-title"
>
{{
item
.
name
}}
</div>
<div
class=
"card-item-time"
>
<span
class=
"info-text"
>
{{
formatDate
(
item
.
times
)
}}
·
{{
item
.
thinkTankName
}}
·
{{
item
.
reportName
}}
</span>
<div
class=
"card-open-image"
@
click
.
stop=
"handleOpenReportOriginal(item)"
>
<img
src=
"@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image open.png"
alt=
""
/>
</div>
</div>
<div
class=
"file"
v-for=
"(sv, index) in item.administrativeOrderInfoVOList"
:key=
"index"
>
<div
class=
"type"
>
政令
</div>
<div
class=
"title"
>
{{
sv
.
content
}}
</div>
<div
class=
"more"
@
click
.
stop=
"handleAdministrativeMoreClick(sv)"
>
<img
src=
"../assets/images/image-right.png"
alt=
""
/>
</div>
<div
class=
"card-item-category"
v-if=
"item.tagList && item.tagList.length"
>
<AreaTag
v-for=
"d in item.tagList"
:key=
"d"
:tagName=
"d"
/>
</div>
<div
class=
"file-box"
>
<div
class=
"file"
v-for=
"sv in item.billInfoList"
:key=
"sv.id || sv.name"
>
<div
class=
"type"
>
法案
</div>
<div
class=
"title"
>
{{
sv
.
name
}}
</div>
<div
class=
"more"
@
click
.
stop=
"handleBillMoreClick(sv)"
>
<img
src=
"../assets/images/image-right.png"
alt=
""
/>
</div>
</div>
<div
class=
"file"
v-for=
"(sv, index) in item.administrativeOrderInfoVOList"
:key=
"index"
>
<div
class=
"type"
>
政令
</div>
<div
class=
"title"
>
{{
sv
.
content
}}
</div>
<div
class=
"more"
@
click
.
stop=
"handleAdministrativeMoreClick(sv)"
>
<img
src=
"../assets/images/image-right.png"
alt=
""
/>
</div>
</div>
</div>
</div>
</div>
<div
class=
"divider"
v-if=
"index !== list.length - 1"
></div>
</div>
<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
浏览文件 @
4d67858d
差异被折叠。
点击展开。
src/views/thinkTank/reportOriginal/index.vue
浏览文件 @
4d67858d
...
...
@@ -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
浏览文件 @
4d67858d
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
)
{
return
{
r
:
parseInt
(
match
[
1
]),
g
:
parseInt
(
match
[
2
]),
b
:
parseInt
(
match
[
3
]),
a
:
parseFloat
(
match
[
4
])
};
const
parseHexToRgb
=
(
hex
)
=>
{
const
h
=
String
(
hex
||
''
).
replace
(
'#'
,
''
).
trim
()
if
(
h
.
length
!==
6
)
return
{
r
:
0
,
g
:
0
,
b
:
0
}
return
{
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,46 +70,33 @@ const getMultiLineChart = (data) => {
},
/* 顶部预留足够空间:多行图例 + Y 轴标题「数量」在纵轴顶端,避免与图例重合 */
grid
:
{
top
:
'34%'
,
top
:
68
,
right
:
'3%'
,
bottom
:
'5%'
,
left
:
'2%'
,
containLabel
:
true
},
legend
:
[
{
show
:
true
,
type
:
'plain'
,
data
:
legendFirstLine
,
top
:
8
,
left
:
'center'
,
icon
:
'circle'
,
textStyle
:
{
fontFamily
:
'Source Han Sans CN'
,
fontWeight
:
400
,
fontSize
:
14
,
lineHeight
:
24
,
letterSpacing
:
0
,
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'
}
legend
:
{
show
:
true
,
type
:
'plain'
,
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
,
fontSize
:
14
,
lineHeight
:
24
,
letterSpacing
:
0
,
align
:
'left'
}
]
,
}
,
// 不使用全局 color,改为每条 series 自己定色(与 AreaTag 一致)
xAxis
:
[
{
...
...
src/views/thinkTank/utils/piechart.js
浏览文件 @
4d67858d
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
浏览文件 @
4d67858d
...
...
@@ -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
浏览文件 @
4d67858d
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
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论