Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
1
合并请求
1
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
c675e799
提交
c675e799
authored
3月 23, 2026
作者:
朱政
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat:智库全选功能选择框逻辑统一,高度自适应,智库部分图表格式修改,概览页近期美国智库机构发布涉华报告数量汇总样式修改
上级
09b8b813
全部展开
隐藏空白字符变更
内嵌
并排
正在显示
24 个修改的文件
包含
849 行增加
和
374 行删除
+849
-374
index.js
src/api/aiAnalysis/index.js
+89
-3
api_doc.md
src/api/api_doc.md
+9
-0
overview.js
src/api/thinkTank/overview.js
+16
-10
index.vue
src/views/thinkTank/MultiThinkTankViewAnalysis/index.vue
+51
-21
index.vue
src/views/thinkTank/ReportDetail/policyTracking/index.vue
+3
-3
index.vue
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
+0
-0
index.vue
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
+0
-0
multiLineChart.js
...nk/ThinkTankDetail/PolicyTracking/utils/multiLineChart.js
+74
-33
index.vue
...k/ThinkTankDetail/thinkDynamics/CongressHearing/index.vue
+0
-0
index.vue
...nkTank/ThinkTankDetail/thinkDynamics/SurveyForm/index.vue
+72
-40
index.vue
...k/ThinkTankDetail/thinkDynamics/ThinkTankReport/index.vue
+73
-40
index.vue
src/views/thinkTank/ThinkTankDetail/thinkDynamics/index.vue
+0
-0
left-page-btn.png
...nkTank/ThinkTankDetail/thinkInfo/images/left-page-btn.png
+0
-0
right-page-btn.png
...kTank/ThinkTankDetail/thinkInfo/images/right-page-btn.png
+0
-0
index.vue
src/views/thinkTank/ThinkTankDetail/thinkInfo/index.vue
+0
-0
index.vue
src/views/thinkTank/allThinkTank/index.vue
+2
-2
HomeMainFooterMain.vue
src/views/thinkTank/components/HomeMainFooterMain.vue
+39
-46
HomeMainFooterSurvey.vue
src/views/thinkTank/components/HomeMainFooterSurvey.vue
+40
-46
ThinkTankCongressHearingOverview.vue
...thinkTank/components/ThinkTankCongressHearingOverview.vue
+78
-59
ThinkTankPolicyAdviceOverview.vue
...ws/thinkTank/components/ThinkTankPolicyAdviceOverview.vue
+51
-38
index.vue
src/views/thinkTank/index.vue
+0
-0
multiLineChart.js
src/views/thinkTank/utils/multiLineChart.js
+40
-32
resourceLibraryFilters.js
src/views/thinkTank/utils/resourceLibraryFilters.js
+211
-0
sankey.js
src/views/thinkTank/utils/sankey.js
+1
-1
没有找到文件。
src/api/aiAnalysis/index.js
浏览文件 @
c675e799
...
...
@@ -36,6 +36,47 @@ function parseChartInterpretationArray(buffer) {
throw
new
Error
(
"无法解析图表解读 JSON 数组"
);
}
/**
* 从数组结果中提取可展示的解读文本
* @param {unknown[]} arr
* @returns {string}
*/
function
pickInterpretationText
(
arr
)
{
if
(
!
Array
.
isArray
(
arr
)
||
arr
.
length
===
0
)
{
return
""
;
}
const
first
=
arr
[
0
]
||
{};
return
(
first
[
"解读"
]
||
first
[
"interpretation"
]
||
first
[
"analysis"
]
||
first
[
"content"
]
||
""
);
}
/**
* 从非标准 JSON 文本中兜底提取“解读”字段(兼容单引号/双引号)
* 示例:
* [{'图表标题': '数量变化趋势', '解读': 'xxx'}]
* [{"图表标题":"数量变化趋势","解读":"xxx"}]
* @param {string} text
* @returns {string}
*/
function
extractInterpretationFromLooseText
(
text
)
{
const
raw
=
String
(
text
||
""
);
if
(
!
raw
)
{
return
""
;
}
const
reg
=
/
[
"'
]
解读
[
"'
]\s
*:
\s
*
[
"'
]([\s\S]
*
?)[
"'
]\s
*
(?:[
,}
\]])
/
;
const
m
=
raw
.
match
(
reg
);
if
(
!
m
||
!
m
[
1
])
{
return
""
;
}
return
String
(
m
[
1
]).
replace
(
/
\\
n/g
,
"
\
n"
).
trim
();
}
/**
* 图表解读(SSE 流式)
* @param {object} data - 请求体
...
...
@@ -44,9 +85,15 @@ function parseChartInterpretationArray(buffer) {
* @returns {Promise<{data: unknown[]}>}
*/
export
function
getChartAnalysis
(
data
,
options
=
{})
{
const
{
onChunk
}
=
options
;
const
onDelta
=
typeof
options
?.
onChunk
===
"function"
?
options
.
onChunk
:
typeof
options
?.
onInterpretationDelta
===
"function"
?
options
.
onInterpretationDelta
:
null
;
return
new
Promise
((
resolve
,
reject
)
=>
{
let
buffer
=
""
;
let
latestInterpretation
=
""
;
let
settled
=
false
;
const
abortController
=
new
AbortController
();
...
...
@@ -99,6 +146,18 @@ export function getChartAnalysis(data, options = {}) {
if
(
msg
&&
typeof
msg
===
"object"
&&
"text"
in
msg
)
{
chunk
=
String
(
msg
.
text
??
""
);
buffer
+=
chunk
;
}
else
if
(
msg
&&
Array
.
isArray
(
msg
.
data
))
{
const
arr
=
msg
.
data
;
const
interpretation
=
pickInterpretationText
(
arr
);
if
(
interpretation
)
{
latestInterpretation
=
interpretation
;
if
(
onDelta
)
{
onDelta
(
interpretation
);
}
}
safeResolve
({
data
:
arr
});
abortController
.
abort
();
return
;
}
else
{
chunk
=
raw
;
buffer
+=
raw
;
...
...
@@ -108,9 +167,18 @@ export function getChartAnalysis(data, options = {}) {
buffer
+=
raw
;
}
// 兜底:非标准 JSON(如单引号 Python 风格)时,尝试直接从文本提取“解读”
const
looseInterpretation
=
extractInterpretationFromLooseText
(
raw
);
if
(
looseInterpretation
)
{
latestInterpretation
=
looseInterpretation
;
safeResolve
({
data
:
[{
解读
:
looseInterpretation
}]
});
abortController
.
abort
();
return
;
}
// 每收到一条消息即回调,用于流式渲染
if
(
chunk
&&
typeof
onChunk
===
"function"
)
{
on
Chunk
(
chunk
);
if
(
chunk
&&
onDelta
)
{
on
Delta
(
chunk
);
}
// 如果 buffer 已经拼完 markdown code fence,则提前解析并中断连接
...
...
@@ -118,6 +186,10 @@ export function getChartAnalysis(data, options = {}) {
if
(
trimmed
.
endsWith
(
"```"
))
{
try
{
const
arr
=
parseChartInterpretationArray
(
trimmed
);
const
interpretation
=
pickInterpretationText
(
arr
);
if
(
interpretation
)
{
latestInterpretation
=
interpretation
;
}
safeResolve
({
data
:
arr
});
abortController
.
abort
();
}
catch
(
_
)
{
}
...
...
@@ -126,8 +198,22 @@ export function getChartAnalysis(data, options = {}) {
onclose
:
()
=>
{
try
{
const
arr
=
parseChartInterpretationArray
(
buffer
);
const
interpretation
=
pickInterpretationText
(
arr
);
if
(
interpretation
)
{
latestInterpretation
=
interpretation
;
}
safeResolve
({
data
:
arr
});
}
catch
(
e
)
{
// 兜底:整体 buffer 不是标准 JSON(如单引号)时直接提取“解读”
const
looseInterpretation
=
extractInterpretationFromLooseText
(
buffer
);
if
(
looseInterpretation
)
{
safeResolve
({
data
:
[{
解读
:
looseInterpretation
}]
});
return
;
}
if
(
latestInterpretation
)
{
safeResolve
({
data
:
[{
解读
:
latestInterpretation
}]
});
return
;
}
safeReject
(
e
);
}
},
...
...
src/api/api_doc.md
浏览文件 @
c675e799
...
...
@@ -1156,6 +1156,15 @@
| -------- | -------- | ----- | -------- | -------- | ------ |
|areas|区域名称列表|query|false|array|string|
|researchTypeIds|研究类型ID列表|query|false|array|string|
|domainIds|科技领域 ID 列表(逗号分隔)|query|false|string||
|startDate|发布时间起 YYYY-MM-DD(与政策追踪发布时间逻辑一致)|query|false|string||
|endDate|发布时间止 YYYY-MM-DD|query|false|string||
|category|分类(如调查项目)|query|false|string||
|pageNum|页码|query|false|integer||
|pageSize|每页条数|query|false|integer||
|sortFun|排序|query|false|boolean||
|thinkTankId|智库 ID(详情页动态列表限定当前智库)|query|false|string||
|keyword|关键词搜索(智库动态)|query|false|string||
|token|Token Request Header|header|false|string||
...
...
src/api/thinkTank/overview.js
浏览文件 @
c675e799
...
...
@@ -87,7 +87,11 @@ export function getHylyList() {
}
//获取智库报告
/**
* 智库概览/智库动态-智库报告、调查项目
* GET /api/thinkTankOverview/report
* 常用 query:pageNum, pageSize, sortFun, domainIds, startDate, endDate, category(调查项目), thinkTankId(详情页), keyword(动态搜索)
*/
export
function
getThinkTankReport
(
params
)
{
return
request
({
method
:
'GET'
,
...
...
@@ -240,18 +244,12 @@ export function getThinkTankInfoBranch(params) {
})
}
//获取经费来源统计
export
function
getThinkTankFundsTotal
(
params
)
{
return
request
({
method
:
'GET'
,
url
:
`/api/thinkTankInfo/fundsTotal/
${
params
}
`
,
})
}
//获取经费来源
export
function
getThinkTankFundsSource
(
params
)
{
return
request
({
method
:
'GET'
,
url
:
`/api/thinkTankInfo/fundsS
ource
/
${
params
}
`
,
url
:
`/api/thinkTankInfo/fundsS
tatistics
/
${
params
}
`
,
})
}
...
...
@@ -265,9 +263,17 @@ export function getThinkTankResearchAreae(params) {
//获取核心研究人员
export
function
getThinkTankPerson
(
params
)
{
const
{
thinkTankId
,
currentPage
,
pageSize
}
=
params
return
request
({
method
:
'GET'
,
url
:
`/api/thinkTankInfo/person/
${
params
}
`
,
url
:
`/api/thinkTankInfo/person/page`
,
params
:
{
currentPage
,
pageNum
:
currentPage
,
page
:
currentPage
,
pageSize
,
thinkTankId
}
})
}
...
...
src/views/thinkTank/MultiThinkTankViewAnalysis/index.vue
浏览文件 @
c675e799
...
...
@@ -85,7 +85,7 @@
</div>
</div>
<div
class=
"box1-middle"
>
<div
class=
"box1-item"
v-for=
"(item, index) in curFooterList"
:key=
"item.id"
>
<div
class=
"box1-item"
v-for=
"(item, index) in curFooterList"
:key=
"item.id"
@
click=
"handleToReportDetail(item)"
>
<div
class=
"left"
>
<img
:src=
item.imageUrl
alt=
""
>
</div>
...
...
@@ -93,9 +93,9 @@
<div
class=
"right-header"
>
{{ item.name }}
</div>
<div
class=
"right-footer"
>
<div
class=
"time"
>
{{ item.times }}
</div>
<div
class=
"text-image"
>
<div
class=
"text-image
think-tank-link"
@
click
.
stop=
"handleToReportDetail(item)
"
>
<div
class=
"image"
>
<img
src=
"../assets/images/rand-image.png"
alt=
""
/>
<img
:src=
item.logoUrl
alt=
""
/>
</div>
<div
class=
"text"
>
{{ item.thinkTankName }}
...
...
@@ -105,8 +105,8 @@
</div>
<div
class=
"image-change"
>
<img
src=
"../assets/images/plus.png"
alt=
""
v-if=
"!selectedIds.has(item.id)"
@
click=
"toggleSelected(item.id, item)"
/>
<img
src=
"../assets/images/Minus.png"
alt=
""
v-else
@
click=
"toggleSelected(item.id, item)"
/>
@
click
.
stop
=
"toggleSelected(item.id, item)"
/>
<img
src=
"../assets/images/Minus.png"
alt=
""
v-else
@
click
.
stop
=
"toggleSelected(item.id, item)"
/>
</div>
</div>
</div>
...
...
@@ -125,7 +125,8 @@
<div
class=
"right-text-analysis"
>
{{ "共选择" }}{{ selectedReportList.length }}{{ "篇智库报告" }}
</div>
</div>
<div
class=
"box1-middle-analysis"
>
<div
class=
"box1-item"
v-for=
"(item, index) in selectedReportList"
:key=
"item.id || index"
>
<div
class=
"box1-item"
v-for=
"(item, index) in selectedReportList"
:key=
"item.id || index"
@
click=
"handleToReportDetail(item)"
>
<div
class=
"left"
>
<img
:src=
item.imageUrl
alt=
""
>
</div>
...
...
@@ -133,9 +134,9 @@
<div
class=
"right-header"
>
{{ item.name }}
</div>
<div
class=
"right-footer"
>
<div
class=
"time"
>
{{ item.times }}
</div>
<div
class=
"text-image"
>
<div
class=
"text-image
think-tank-link"
@
click
.
stop=
"handleToReportDetail(item)
"
>
<div
class=
"image"
>
<img
src=
"../assets/images/rand-image.png"
alt=
""
/>
<img
:src=
item.logoUrl
alt=
""
/>
</div>
<div
class=
"text"
>
{{ item.thinkTankName }}
...
...
@@ -145,8 +146,8 @@
</div>
<div
class=
"image-change"
>
<img
src=
"../assets/images/plus.png"
alt=
""
v-if=
"!selectedIds.has(item.id)"
@
click=
"toggleSelected(item.id, item)"
/>
<img
src=
"../assets/images/Minus.png"
alt=
""
v-else
@
click=
"toggleSelected(item.id, item)"
/>
@
click
.
stop
=
"toggleSelected(item.id, item)"
/>
<img
src=
"../assets/images/Minus.png"
alt=
""
v-else
@
click
.
stop
=
"toggleSelected(item.id, item)"
/>
</div>
</div>
</div>
...
...
@@ -285,7 +286,7 @@ import router from '@/router';
import
{
onMounted
,
ref
,
computed
,
reactive
,
nextTick
}
from
"vue"
;
import
AnalysisBox
from
"@/components/base/boxBackground/analysisBox.vue"
import
AnalysisResultBox
from
"./boxBackground/analysisBox.vue"
import
{
getThinkTankReport
,
getHylyList
,
getThinkDynamicsReport
,
postReportDomainViewAnalysis
}
from
"@/api/thinkTank/overview"
;
import
{
getThinkTankReport
,
getHylyList
,
postReportDomainViewAnalysis
}
from
"@/api/thinkTank/overview"
;
const
sort
=
ref
(
""
);
const
searchPolicy
=
ref
(
""
);
const
isBox2
=
ref
(
true
)
...
...
@@ -521,6 +522,18 @@ const handleCurrentChange = page => {
currentPage
.
value
=
page
;
handleGetetThinkTankReport
(
page
);
}
;
const
handleToReportDetail
=
(
item
)
=>
{
if
(
!
item
?.
id
)
return
;
window
.
sessionStorage
.
setItem
(
"curTabName"
,
item
.
name
);
const
route
=
router
.
resolve
({
name
:
"ReportDetail"
,
params
:
{
id
:
item
.
id
}
}
);
window
.
open
(
route
.
href
,
"_blank"
);
}
;
//获取行业领域字典
// getHylyList
const
handleGetHylyList
=
async
()
=>
{
...
...
@@ -536,8 +549,9 @@ const handleGetHylyList = async () => {
}
;
//获取智库报告
const
handleGetetThinkTankReport
=
async
(
page
=
currentPage
.
value
)
=>
{
const
id
=
router
.
currentRoute
?.
value
?.
params
?.
id
||
""
;
const
getDateYearsAgo
=
years
=>
{
const
thinkTankId
=
router
.
currentRoute
?.
value
?.
params
?.
id
||
""
;
const
getDateYearsAgo
=
(
years
)
=>
{
const
d
=
new
Date
();
d
.
setFullYear
(
d
.
getFullYear
()
-
Number
(
years
||
1
));
const
y
=
d
.
getFullYear
();
...
...
@@ -545,20 +559,32 @@ const handleGetetThinkTankReport = async (page = currentPage.value) => {
const
day
=
String
(
d
.
getDate
()).
padStart
(
2
,
"0"
);
return
`${y
}
-${m
}
-${day
}
`
;
}
;
const
getTodayYmd
=
()
=>
{
const
d
=
new
Date
();
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
}
`
;
}
;
const
domainIds
=
arrayToString
(
selectedAreaList
.
value
);
const
keyword
=
(
searchPolicy
.
value
||
""
).
trim
();
const
params
=
{
id
,
startDate
:
getDateYearsAgo
(
selectedYears
.
value
),
// 不传 authorName(避免后端按空字符串筛选)
currentPage
:
Number
(
page
)
-
1
,
// 1-based
thinkTankId
,
pageNum
:
Number
(
page
),
pageSize
:
pageSize
,
researchTypeIds
:
arrayToString
(
selectedAreaList
.
value
),
searchText
:
(
searchPolicy
.
value
||
""
).
trim
(),
sortFun
:
false
,
domainIds
,
startDate
:
getDateYearsAgo
(
selectedYears
.
value
),
endDate
:
getTodayYmd
(),
keyword
:
keyword
||
undefined
,
}
;
try
{
// 先清空,避免视觉上看起来没变化
curFooterList
.
value
=
[];
const
res
=
await
getThink
Dynamics
Report
(
params
);
const
res
=
await
getThink
Tank
Report
(
params
);
console
.
log
(
"智库报告"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
curFooterList
.
value
=
res
.
data
.
content
;
...
...
@@ -621,6 +647,10 @@ onMounted(async () => {
}
}
.
think
-
tank
-
link
{
cursor
:
pointer
;
}
.
box
{
display
:
flex
;
gap
:
16
px
;
...
...
src/views/thinkTank/ReportDetail/policyTracking/index.vue
浏览文件 @
c675e799
...
...
@@ -330,11 +330,11 @@ onMounted(async () => {
.box1
{
margin-top
:
19px
;
width
:
1600px
;
height
:
1173px
;
.box1-main
{
margin-top
:
8px
;
height
:
1097
px
;
min-height
:
778
px
;
padding-left
:
21px
;
padding-right
:
50px
;
padding-bottom
:
21px
;
...
...
@@ -421,7 +421,7 @@ onMounted(async () => {
.item-box
{
width
:
506px
;
height
:
100%
;
min-height
:
639
.2px
;
border-top
:
1px
solid
rgb
(
234
,
236
,
238
);
.item
{
...
...
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
浏览文件 @
c675e799
差异被折叠。
点击展开。
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
浏览文件 @
c675e799
差异被折叠。
点击展开。
src/views/thinkTank/ThinkTankDetail/PolicyTracking/utils/multiLineChart.js
浏览文件 @
c675e799
import
*
as
echarts
from
'echarts'
/** 政策追踪「研究领域变化趋势」图例分页:每页条数(与概览数量变化趋势逻辑一致,条数按产品要求为 4) */
export
const
POLICY_TRACKING_LEGEND_PAGE_SIZE
=
4
const
colorList
=
[
'rgba(5, 95, 194, 1)'
,
'rgba(19, 168, 168, 1)'
,
...
...
@@ -35,22 +32,16 @@ const parseRgba = (colorStr) => {
/**
* @param {{ title: unknown[], data: Array<{ name: string, value: unknown[], color?: string }> }} chartInput
* @param {{ legendShowCount?: number, legendPageIndex?: number }} [options]
*/
const
getMultiLineChart
=
(
chartInput
,
options
=
{}
)
=>
{
const
getMultiLineChart
=
(
chartInput
)
=>
{
const
title
=
chartInput
.
title
const
series
=
chartInput
.
data
||
[]
const
legendShowCount
=
typeof
options
.
legendShowCount
===
'number'
&&
options
.
legendShowCount
>
0
?
options
.
legendShowCount
:
POLICY_TRACKING_LEGEND_PAGE_SIZE
const
rawPageIndex
=
Number
(
options
.
legendPageIndex
)
||
0
const
allNames
=
series
.
map
((
item
)
=>
item
.
name
)
const
pageCount
=
Math
.
max
(
1
,
Math
.
ceil
(
allNames
.
length
/
legendShowCount
)
)
const
legend
PageIndex
=
Math
.
min
(
Math
.
max
(
0
,
rawPageIndex
),
pageCount
-
1
)
const
legend
Start
=
legendPageIndex
*
legendShowCount
const
legend
Data
=
allNames
.
slice
(
legendStart
,
legendStart
+
legendShowCount
)
const
lineSize
=
Math
.
ceil
(
allNames
.
length
/
3
)
const
legend
Line1
=
allNames
.
slice
(
0
,
lineSize
)
const
legend
Line2
=
allNames
.
slice
(
lineSize
,
lineSize
*
2
)
const
legend
Line3
=
allNames
.
slice
(
lineSize
*
2
)
const
xCount
=
Array
.
isArray
(
title
)
?
title
.
length
:
0
const
labelFontSize
=
xCount
>
8
?
10
:
xCount
>
5
?
11
:
12
...
...
@@ -91,31 +82,71 @@ const getMultiLineChart = (chartInput, options = {}) => {
},
/* 贴满 #box3Chart:四边 0,由 containLabel 在网格内为轴文字留位,避免左侧/底部大块留白 */
grid
:
{
top
:
50
,
top
:
92
,
right
:
10
,
bottom
:
0
,
left
:
20
,
containLabel
:
true
},
legend
:
{
show
:
true
,
type
:
'plain'
,
data
:
legendData
,
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)'
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
},
itemWidth
:
12
,
itemHeight
:
12
},
{
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
},
{
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
}
],
color
:
colorList
,
xAxis
:
[
{
...
...
@@ -142,6 +173,16 @@ const getMultiLineChart = (chartInput, options = {}) => {
yAxis
:
[
{
type
:
'value'
,
name
:
'数量'
,
nameLocation
:
'end'
,
nameGap
:
20
,
nameTextStyle
:
{
color
:
'rgb(132, 136, 142)'
,
fontFamily
:
'Source Han Sans CN'
,
fontWeight
:
400
,
fontSize
:
11
,
padding
:
[
0
,
0
,
0
,
-
20
]
// 👈 这个是左移 4px(上、右、下、左)
},
splitNumber
:
4
,
axisLabel
:
{
color
:
'rgb(132, 136, 142)'
,
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/CongressHearing/index.vue
浏览文件 @
c675e799
差异被折叠。
点击展开。
src/views/thinkTank/ThinkTankDetail/thinkDynamics/SurveyForm/index.vue
浏览文件 @
c675e799
...
...
@@ -21,16 +21,17 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTypeIds)"
@
change=
"val => handleToggleAll(val, researchTypeIds)"
>
全部领域
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedResearchIds"
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
v-model=
"selectedResearchIds"
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleGetThinkDynamicsReport()"
>
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
{{
type
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
<div
class=
"select-time-box"
>
...
...
@@ -39,16 +40,17 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTimeIds)"
@
change=
"val => handleToggleAll(val, researchTimeIds)"
>
全部时间
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedResearchTimeIds"
@
change=
"handleTimeGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
v-model=
"selectedResearchTimeIds"
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleGetThinkDynamicsReport()"
>
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
{{
type
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
<!--
<div
class=
"input-main"
>
<el-input
placeholder=
"输入作者名字"
v-model=
"author"
@
input=
"handleGetThinkDynamicsReport()"
/>
...
...
@@ -63,7 +65,7 @@
<img
:src=
"item.imageUrl"
alt=
""
/>
</div>
<div
class=
"footer-card-title"
>
{{
item
.
name
}}
<span
v-html=
"highlightText(item.name)"
></span>
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
item
.
times
}}
</div>
...
...
@@ -84,7 +86,14 @@
</div>
</
template
>
<
script
setup
>
import
{
computed
,
ref
,
toRefs
,
watch
}
from
"vue"
;
import
{
ref
,
toRefs
,
watch
}
from
"vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
normalizeExclusiveAllOption
,
stripAllAreaForRequest
,
stripAllTimeForRequest
}
from
"../../../utils/resourceLibraryFilters"
;
const
props
=
defineProps
({
researchTypeList
:
{
...
...
@@ -114,6 +123,10 @@ const props = defineProps({
currentPage
:
{
type
:
Number
,
default
:
1
},
searchKeyword
:
{
type
:
String
,
default
:
""
}
});
...
...
@@ -127,43 +140,58 @@ const emit = defineEmits([
// 解构 props,保持模板里变量名不变
const
{
researchTypeList
,
researchTimeList
,
curFooterList
,
total
,
currentPage
}
=
toRefs
(
props
);
const
selectedResearchIds
=
ref
([]);
const
selectedResearchTimeIds
=
ref
([]);
const
selectedResearchIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedResearchTimeIds
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
const
escapeHtml
=
(
text
)
=>
{
return
String
(
text
??
""
)
.
replace
(
/&/g
,
"&"
)
.
replace
(
/</g
,
"<"
)
.
replace
(
/>/g
,
">"
)
.
replace
(
/"/g
,
"""
)
.
replace
(
/'/g
,
"'"
);
};
const
escapeRegExp
=
(
text
)
=>
{
return
String
(
text
??
""
).
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
"
\\
$&"
);
};
const
highlightText
=
(
text
)
=>
{
const
safeText
=
escapeHtml
(
text
);
const
keyword
=
(
props
.
searchKeyword
||
""
).
trim
();
if
(
!
keyword
)
return
safeText
;
const
pattern
=
new
RegExp
(
`(
${
escapeRegExp
(
keyword
)}
)`
,
"gi"
);
return
safeText
.
replace
(
pattern
,
'<span class="keyword-highlight">$1</span>'
);
};
// 父组件更新时同步到子组件
watch
(
()
=>
props
.
selectedFilters
,
val
=>
{
selectedResearchIds
.
value
=
val
?.
researchTypeIds
?
[...
val
.
researchTypeIds
]
:
[];
selectedResearchTimeIds
.
value
=
val
?.
researchTimeIds
?
[...
val
.
researchTimeIds
]
:
[];
selectedResearchIds
.
value
=
val
?.
researchTypeIds
?.
length
>
0
?
[...
val
.
researchTypeIds
]
:
[
RESOURCE_FILTER_ALL_AREA
];
selectedResearchTimeIds
.
value
=
val
?.
researchTimeIds
?.
length
>
0
?
[...
val
.
researchTimeIds
]
:
[
RESOURCE_FILTER_ALL_TIME
];
},
{
immediate
:
true
,
deep
:
true
}
);
const
buildSelectedFiltersPayload
=
()
=>
({
researchTypeIds
:
[...
selectedResearchIds
.
value
]
,
researchTimeIds
:
[...
selectedResearchTimeIds
.
value
]
,
researchTypeIds
:
stripAllAreaForRequest
(
selectedResearchIds
.
value
)
,
researchTimeIds
:
stripAllTimeForRequest
(
selectedResearchTimeIds
.
value
)
,
researchHearingIds
:
[]
});
const
researchTypeIds
=
computed
(()
=>
(
researchTypeList
.
value
||
[]).
map
(
item
=>
item
.
id
));
const
researchTimeIds
=
computed
(()
=>
(
researchTimeList
.
value
||
[]).
map
(
item
=>
item
.
id
));
const
getTargetSelection
=
ids
=>
(
ids
===
researchTypeIds
.
value
?
selectedResearchIds
:
selectedResearchTimeIds
);
const
isGroupAllSelected
=
ids
=>
ids
.
length
>
0
&&
ids
.
every
(
id
=>
getTargetSelection
(
ids
).
value
.
includes
(
id
));
const
handleAreaGroupChange
=
(
val
)
=>
{
selectedResearchIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
);
handleGetThinkDynamicsReport
();
};
const
handleToggleAll
=
(
checked
,
ids
)
=>
{
if
(
!
ids
.
length
)
return
;
const
targetSelection
=
getTargetSelection
(
ids
);
const
nextSelected
=
new
Set
(
targetSelection
.
value
);
if
(
checked
)
{
ids
.
forEach
(
id
=>
nextSelected
.
add
(
id
));
}
else
{
ids
.
forEach
(
id
=>
nextSelected
.
delete
(
id
));
}
targetSelection
.
value
=
[...
nextSelected
];
const
handleTimeGroupChange
=
(
val
)
=>
{
selectedResearchTimeIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
);
handleGetThinkDynamicsReport
();
};
...
...
@@ -326,6 +354,10 @@ const handleToReportDetail = item => {
}
}
:deep
(
.keyword-highlight
)
{
background-color
:
#fff59d
;
}
.right
{
width
:
1284px
;
height
:
1377px
;
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/ThinkTankReport/index.vue
浏览文件 @
c675e799
...
...
@@ -21,16 +21,17 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTypeIds)"
@
change=
"val => handleToggleAll(val, researchTypeIds)"
>
全部领域
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedResearchIds"
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
v-model=
"selectedResearchIds"
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleGetThinkDynamicsReport()"
>
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
{{
type
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
<div
class=
"select-time-box"
>
...
...
@@ -39,16 +40,17 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTimeIds)"
@
change=
"val => handleToggleAll(val, researchTimeIds)"
>
全部时间
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedResearchTimeIds"
@
change=
"handleTimeGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
v-model=
"selectedResearchTimeIds"
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleGetThinkDynamicsReport()"
>
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
{{
type
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
<!--
<div
class=
"input-main"
>
<el-input
placeholder=
"输入作者名字"
v-model=
"author"
@
input=
"handleGetThinkDynamicsReport()"
/>
...
...
@@ -63,7 +65,7 @@
<img
:src=
"item.imageUrl"
alt=
""
/>
</div>
<div
class=
"footer-card-title"
>
{{
item
.
name
}}
<span
v-html=
"highlightText(item.name)"
></span>
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
item
.
times
}}
</div>
...
...
@@ -84,7 +86,14 @@
</div>
</
template
>
<
script
setup
>
import
{
computed
,
ref
,
toRefs
,
watch
}
from
"vue"
;
import
{
ref
,
toRefs
,
watch
}
from
"vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
normalizeExclusiveAllOption
,
stripAllAreaForRequest
,
stripAllTimeForRequest
}
from
"../../../utils/resourceLibraryFilters"
;
const
props
=
defineProps
({
researchTypeList
:
{
...
...
@@ -114,6 +123,10 @@ const props = defineProps({
currentPage
:
{
type
:
Number
,
default
:
1
},
searchKeyword
:
{
type
:
String
,
default
:
""
}
});
...
...
@@ -127,43 +140,59 @@ const emit = defineEmits([
// 解构 props,保持模板里变量名不变
const
{
researchTypeList
,
researchTimeList
,
curFooterList
,
total
,
currentPage
}
=
toRefs
(
props
);
const
selectedResearchIds
=
ref
([]);
const
selectedResearchTimeIds
=
ref
([]);
const
selectedResearchIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedResearchTimeIds
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
const
escapeHtml
=
(
text
)
=>
{
return
String
(
text
??
""
)
.
replace
(
/&/g
,
"&"
)
.
replace
(
/</g
,
"<"
)
.
replace
(
/>/g
,
">"
)
.
replace
(
/"/g
,
"""
)
.
replace
(
/'/g
,
"'"
);
};
const
escapeRegExp
=
(
text
)
=>
{
return
String
(
text
??
""
).
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
"
\\
$&"
);
};
const
highlightText
=
(
text
)
=>
{
const
safeText
=
escapeHtml
(
text
);
const
keyword
=
(
props
.
searchKeyword
||
""
).
trim
();
if
(
!
keyword
)
return
safeText
;
const
pattern
=
new
RegExp
(
`(
${
escapeRegExp
(
keyword
)}
)`
,
"gi"
);
return
safeText
.
replace
(
pattern
,
'<span class="keyword-highlight">$1</span>'
);
};
// 父组件更新时同步到子组件
// 父组件更新时同步到子组件
(接口层无选中时为「全部」)
watch
(
()
=>
props
.
selectedFilters
,
val
=>
{
selectedResearchIds
.
value
=
val
?.
researchTypeIds
?
[...
val
.
researchTypeIds
]
:
[];
selectedResearchTimeIds
.
value
=
val
?.
researchTimeIds
?
[...
val
.
researchTimeIds
]
:
[];
selectedResearchIds
.
value
=
val
?.
researchTypeIds
?.
length
>
0
?
[...
val
.
researchTypeIds
]
:
[
RESOURCE_FILTER_ALL_AREA
];
selectedResearchTimeIds
.
value
=
val
?.
researchTimeIds
?.
length
>
0
?
[...
val
.
researchTimeIds
]
:
[
RESOURCE_FILTER_ALL_TIME
];
},
{
immediate
:
true
,
deep
:
true
}
);
const
buildSelectedFiltersPayload
=
()
=>
({
researchTypeIds
:
[...
selectedResearchIds
.
value
]
,
researchTimeIds
:
[...
selectedResearchTimeIds
.
value
]
,
researchTypeIds
:
stripAllAreaForRequest
(
selectedResearchIds
.
value
)
,
researchTimeIds
:
stripAllTimeForRequest
(
selectedResearchTimeIds
.
value
)
,
researchHearingIds
:
[]
});
const
researchTypeIds
=
computed
(()
=>
(
researchTypeList
.
value
||
[]).
map
(
item
=>
item
.
id
));
const
researchTimeIds
=
computed
(()
=>
(
researchTimeList
.
value
||
[]).
map
(
item
=>
item
.
id
));
const
getTargetSelection
=
ids
=>
(
ids
===
researchTypeIds
.
value
?
selectedResearchIds
:
selectedResearchTimeIds
);
const
isGroupAllSelected
=
ids
=>
ids
.
length
>
0
&&
ids
.
every
(
id
=>
getTargetSelection
(
ids
).
value
.
includes
(
id
));
const
handleAreaGroupChange
=
(
val
)
=>
{
selectedResearchIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
);
handleGetThinkDynamicsReport
();
};
const
handleToggleAll
=
(
checked
,
ids
)
=>
{
if
(
!
ids
.
length
)
return
;
const
targetSelection
=
getTargetSelection
(
ids
);
const
nextSelected
=
new
Set
(
targetSelection
.
value
);
if
(
checked
)
{
ids
.
forEach
(
id
=>
nextSelected
.
add
(
id
));
}
else
{
ids
.
forEach
(
id
=>
nextSelected
.
delete
(
id
));
}
targetSelection
.
value
=
[...
nextSelected
];
const
handleTimeGroupChange
=
(
val
)
=>
{
selectedResearchTimeIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
);
handleGetThinkDynamicsReport
();
};
...
...
@@ -326,6 +355,10 @@ const handleToReportDetail = item => {
}
}
:deep
(
.keyword-highlight
)
{
background-color
:
#fff59d
;
}
.right
{
width
:
1284px
;
height
:
1377px
;
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/index.vue
浏览文件 @
c675e799
差异被折叠。
点击展开。
src/views/thinkTank/ThinkTankDetail/thinkInfo/images/left-page-btn.png
0 → 100644
浏览文件 @
c675e799
16.7 KB
src/views/thinkTank/ThinkTankDetail/thinkInfo/images/right-page-btn.png
0 → 100644
浏览文件 @
c675e799
16.7 KB
src/views/thinkTank/ThinkTankDetail/thinkInfo/index.vue
浏览文件 @
c675e799
差异被折叠。
点击展开。
src/views/thinkTank/allThinkTank/index.vue
浏览文件 @
c675e799
...
...
@@ -51,7 +51,7 @@
}"
>
<
template
#
prefix
>
<img
src=
"../assets/images/sort-asc.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSort()"
:key=
"true"
label=
"正序"
:value=
"true"
v-if=
"sort
=== tru
e"
/>
:key=
"true"
label=
"正序"
:value=
"true"
v-if=
"sort
!== fals
e"
/>
<img
src=
"../assets/images/sort-desc.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSort()"
:key=
"false"
label=
"倒序"
:value=
"false"
v-if=
"sort === false"
/>
</
template
>
...
...
@@ -191,7 +191,7 @@ const handleGetThinkTankList = async () => {
total
.
value
=
0
;
}
};
//
初始为 null:el-select 显示 placeholder;但排序仍按“正序”规则(见 sortedCardList)
//
默认显示 placeholder「报告数量」,但前缀图标显示正序
const
sort
=
ref
(
null
);
const
toggleSort
=
()
=>
{
sort
.
value
=
sort
.
value
===
false
?
true
:
false
...
...
src/views/thinkTank/components/HomeMainFooterMain.vue
浏览文件 @
c675e799
...
...
@@ -7,15 +7,21 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
v-model=
"checkAllModel"
class=
"all-checkbox"
@
change=
"emit('check-all-change', $event)"
>
全部领域
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedAreaList"
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox all-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
<el-checkbox
v-for=
"research in areaList"
:key=
"research.id"
v-model=
"selectedAreaListModel"
:label=
"research.id"
@
change=
"emit('checked-area-change')"
class=
"filter-checkbox"
>
<el-checkbox
v-for=
"research in areaList"
:key=
"research.id"
class=
"filter-checkbox"
:label=
"research.id"
>
{{
research
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
...
...
@@ -25,17 +31,21 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
v-model=
"checkAllTimeModel"
class=
"all-checkbox"
@
change=
"emit('check-all-time-change', $event)"
>
全部时间
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedPubTimeList"
@
change=
"handleTimeGroupChange"
>
<el-checkbox
class=
"filter-checkbox all-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
<el-checkbox
v-for=
"time in pubTimeList"
:key=
"time.id"
v-model=
"selectedPubTimeListModel"
:label=
"time.id"
class=
"filter-checkbox"
@
change=
"emit('checked-area-time-change')"
>
<el-checkbox
v-for=
"time in pubTimeList"
:key=
"time.id"
class=
"filter-checkbox"
:label=
"time.id"
>
{{
time
.
name
}}
</el-checkbox>
</div>
</el-checkbox-group>
</div>
</div>
</div>
...
...
@@ -68,56 +78,39 @@
</
template
>
<
script
setup
>
import
{
computed
}
from
"vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
normalizeExclusiveAllOption
}
from
"../utils/resourceLibraryFilters"
;
const
props
=
defineProps
({
checkAll
:
{
type
:
Boolean
,
default
:
false
},
isIndeterminate
:
{
type
:
Boolean
,
default
:
false
},
defineProps
({
areaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedAreaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
checkAllTime
:
{
type
:
Boolean
,
default
:
false
},
isIndeterminateTime
:
{
type
:
Boolean
,
default
:
false
},
pubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedPubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
curFooterList
:
{
type
:
Array
,
default
:
()
=>
[]
},
total
:
{
type
:
Number
,
default
:
0
},
currentPage
:
{
type
:
Number
,
default
:
1
}
});
const
emit
=
defineEmits
([
"update:checkAll"
,
"update:selectedAreaList"
,
"check-all-change"
,
"checked-area-change"
,
"update:checkAllTime"
,
"update:selectedPubTimeList"
,
"check-all-time-change"
,
"checked-area-time-change"
,
"filter-change"
,
"report-click"
,
"page-change"
]);
const
checkAllModel
=
computed
(
{
get
:
()
=>
props
.
checkAll
,
set
:
val
=>
emit
(
"update:checkAll"
,
val
)
}
)
;
const
handleAreaGroupChange
=
(
val
)
=>
{
emit
(
"update:selectedAreaList"
,
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
));
emit
(
"filter-change"
);
};
const
selectedAreaListModel
=
computed
({
get
:
()
=>
props
.
selectedAreaList
,
set
:
val
=>
emit
(
"update:selectedAreaList"
,
val
)
});
const
checkAllTimeModel
=
computed
({
get
:
()
=>
props
.
checkAllTime
,
set
:
val
=>
emit
(
"update:checkAllTime"
,
val
)
});
const
selectedPubTimeListModel
=
computed
({
get
:
()
=>
props
.
selectedPubTimeList
,
set
:
val
=>
emit
(
"update:selectedPubTimeList"
,
val
)
});
const
handleTimeGroupChange
=
(
val
)
=>
{
emit
(
"update:selectedPubTimeList"
,
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
));
emit
(
"filter-change"
);
};
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
src/views/thinkTank/components/HomeMainFooterSurvey.vue
浏览文件 @
c675e799
<
template
>
<!-- 调查项目:
结构/样式与智库报告一致,但组件独立,避免互相影响
-->
<!-- 调查项目:
与智库报告相同的「全部」互斥逻辑
-->
<div
class=
"home-main-footer-main"
>
<div
class=
"left"
>
<div
class=
"select-box"
>
...
...
@@ -8,15 +8,21 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
v-model=
"checkAllModel"
class=
"all-checkbox"
@
change=
"emit('check-all-change', $event)"
>
全部领域
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedAreaList"
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox all-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
<el-checkbox
v-for=
"research in areaList"
:key=
"research.id"
v-model=
"selectedAreaListModel"
:label=
"research.id"
@
change=
"emit('checked-area-change')"
class=
"filter-checkbox"
>
<el-checkbox
v-for=
"research in areaList"
:key=
"research.id"
class=
"filter-checkbox"
:label=
"research.id"
>
{{
research
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
...
...
@@ -26,16 +32,21 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
v-model=
"checkAllTimeModel"
class=
"all-checkbox"
@
change=
"emit('check-all-time-change', $event)"
>
全部时间
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedPubTimeList"
@
change=
"handleTimeGroupChange"
>
<el-checkbox
class=
"filter-checkbox all-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
<el-checkbox
v-model=
"selectedPubTimeListModel"
v-for=
"time in pubTimeList"
:key=
"time.id"
:label=
"time.id"
class=
"filter-checkbox"
@
change=
"emit('checked-area-time-change')"
>
<el-checkbox
v-for=
"time in pubTimeList"
:key=
"time.id"
class=
"filter-checkbox"
:label=
"time.id"
>
{{
time
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
...
...
@@ -68,56 +79,39 @@
</
template
>
<
script
setup
>
import
{
computed
}
from
"vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
normalizeExclusiveAllOption
}
from
"../utils/resourceLibraryFilters"
;
const
props
=
defineProps
({
checkAll
:
{
type
:
Boolean
,
default
:
false
},
isIndeterminate
:
{
type
:
Boolean
,
default
:
false
},
defineProps
({
areaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedAreaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
checkAllTime
:
{
type
:
Boolean
,
default
:
false
},
isIndeterminateTime
:
{
type
:
Boolean
,
default
:
false
},
pubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedPubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
curFooterList
:
{
type
:
Array
,
default
:
()
=>
[]
},
total
:
{
type
:
Number
,
default
:
0
},
currentPage
:
{
type
:
Number
,
default
:
1
}
});
const
emit
=
defineEmits
([
"update:checkAll"
,
"update:selectedAreaList"
,
"check-all-change"
,
"checked-area-change"
,
"update:checkAllTime"
,
"update:selectedPubTimeList"
,
"check-all-time-change"
,
"checked-area-time-change"
,
"filter-change"
,
"report-click"
,
"page-change"
]);
const
checkAllModel
=
computed
({
get
:
()
=>
props
.
checkAll
,
set
:
val
=>
emit
(
"update:checkAll"
,
val
)
});
const
selectedAreaListModel
=
computed
({
get
:
()
=>
props
.
selectedAreaList
,
set
:
val
=>
emit
(
"update:selectedAreaList"
,
val
)
});
const
handleAreaGroupChange
=
(
val
)
=>
{
emit
(
"update:selectedAreaList"
,
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
));
emit
(
"filter-change"
);
};
const
checkAllTimeModel
=
computed
({
get
:
()
=>
props
.
checkAllTime
,
set
:
val
=>
emit
(
"update:checkAllTime"
,
val
)
});
const
selectedPubTimeListModel
=
computed
({
get
:
()
=>
props
.
selectedPubTimeList
,
set
:
val
=>
emit
(
"update:selectedPubTimeList"
,
val
)
});
const
handleTimeGroupChange
=
(
val
)
=>
{
emit
(
"update:selectedPubTimeList"
,
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
));
emit
(
"filter-change"
);
};
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
src/views/thinkTank/components/ThinkTankCongressHearingOverview.vue
浏览文件 @
c675e799
...
...
@@ -7,16 +7,21 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTypeIds)"
@
change=
"val => handleToggleAll(val, researchTypeIds)"
>
全部领域
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedResearchIds"
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
v-model=
"selectedResearchIds"
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleFilterChange"
>
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
{{
type
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
...
...
@@ -26,16 +31,21 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTimeIds)"
@
change=
"val => handleToggleAll(val, researchTimeIds)"
>
全部时间
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedResearchTimeIds"
@
change=
"handleTimeGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
v-model=
"selectedResearchTimeIds"
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleFilterChange"
>
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
{{
type
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
...
...
@@ -45,16 +55,21 @@
<div
class=
"title"
>
{{
"听证会部门"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group hearing-grid"
>
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchHearingIds)"
@
change=
"val => handleToggleAll(val, researchHearingIds)"
>
全部部门
<el-checkbox-group
class=
"checkbox-group hearing-grid"
:model-value=
"selectedResearchHearingIds"
@
change=
"handleDeptGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_DEPT"
>
{{
RESOURCE_FILTER_ALL_DEPT
}}
</el-checkbox>
<el-checkbox
v-for=
"type in researchHearingList"
:key=
"type.id"
v-model=
"selectedResearchHearingIds"
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleFilterChange"
>
<el-checkbox
v-for=
"type in researchHearingList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
{{
type
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
...
...
@@ -76,8 +91,7 @@
class=
"card-open-image"
/>
</div>
<div
class=
"card-item-category"
>
<AreaTag
:key=
"index"
:tagName=
"item.category"
>
</AreaTag>
<AreaTag
:key=
"`cat-$
{item.id}`" :tagName="item.category" />
</div>
</div>
</div>
...
...
@@ -101,6 +115,18 @@
<
script
setup
>
import
{
computed
,
ref
}
from
"vue"
;
import
AreaTag
from
"@/components/base/AreaTag/index.vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
RESOURCE_FILTER_ALL_DEPT
,
RESOURCE_FILTER_EARLIER
,
normalizeExclusiveAllOption
,
stripAllAreaForRequest
,
stripAllTimeForRequest
,
stripAllDeptForRequest
,
matchesEarlierChineseDate
}
from
"../utils/resourceLibraryFilters"
;
const
props
=
defineProps
({
researchTypeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
...
...
@@ -112,11 +138,10 @@ const emit = defineEmits(["report-click"]);
const
pageSize
=
10
;
const
currentPage
=
ref
(
1
);
const
selectedResearchIds
=
ref
([]);
const
selectedResearchTimeIds
=
ref
([]);
const
selectedResearchHearingIds
=
ref
([]);
const
selectedResearchIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedResearchTimeIds
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
const
selectedResearchHearingIds
=
ref
([
RESOURCE_FILTER_ALL_DEPT
]);
// 概览页暂无独立接口时,先使用一份静态数据(结构与智库动态保持一致)
const
hearingData
=
ref
([
{
id
:
1
,
title
:
"美国国会听证会:人工智能与国家安全"
,
content
:
"美中经济与安全审查委员会"
,
category
:
"人工智能"
,
time
:
"2025年7月8日"
},
{
id
:
2
,
title
:
"美国国会听证会:先进制造供应链韧性"
,
content
:
"国会-行政部门中国委员会"
,
category
:
"先进制造"
,
time
:
"2025年6月15日"
},
...
...
@@ -138,46 +163,44 @@ const researchHearingList = ref([
{
id
:
"美国商务部"
,
name
:
"美国商务部"
},
]);
const
researchTypeIds
=
computed
(()
=>
(
props
.
researchTypeList
||
[]).
map
(
item
=>
item
.
id
));
const
researchTimeIds
=
computed
(()
=>
(
props
.
researchTimeList
||
[]).
map
(
item
=>
item
.
id
));
const
researchHearingIds
=
computed
(()
=>
(
researchHearingList
.
value
||
[]).
map
(
item
=>
item
.
id
));
const
handleAreaGroupChange
=
(
val
)
=>
{
selectedResearchIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
);
currentPage
.
value
=
1
;
};
const
getTargetSelection
=
ids
=>
{
if
(
ids
===
researchTypeIds
.
value
)
return
selectedResearchIds
;
if
(
ids
===
researchTimeIds
.
value
)
return
selectedResearchTimeIds
;
return
selectedResearchHearingIds
;
const
handleTimeGroupChange
=
(
val
)
=>
{
selectedResearchTimeIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
);
currentPage
.
value
=
1
;
};
const
isGroupAllSelected
=
ids
=>
ids
.
length
>
0
&&
ids
.
every
(
id
=>
getTargetSelection
(
ids
).
value
.
includes
(
id
));
const
handleToggleAll
=
(
checked
,
ids
)
=>
{
if
(
!
ids
.
length
)
return
;
const
targetSelection
=
getTargetSelection
(
ids
);
const
nextSelected
=
new
Set
(
targetSelection
.
value
);
if
(
checked
)
{
ids
.
forEach
(
id
=>
nextSelected
.
add
(
id
));
}
else
{
ids
.
forEach
(
id
=>
nextSelected
.
delete
(
id
));
}
targetSelection
.
value
=
[...
nextSelected
];
handleFilterChange
();
const
handleDeptGroupChange
=
(
val
)
=>
{
selectedResearchHearingIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_DEPT
);
currentPage
.
value
=
1
;
};
const
filteredHearingData
=
computed
(()
=>
{
const
areaSel
=
stripAllAreaForRequest
(
selectedResearchIds
.
value
);
const
timeSel
=
stripAllTimeForRequest
(
selectedResearchTimeIds
.
value
);
const
deptSel
=
stripAllDeptForRequest
(
selectedResearchHearingIds
.
value
);
return
(
hearingData
.
value
||
[]).
filter
(
item
=>
{
const
matchYear
=
selectedResearchTimeIds
.
value
.
length
===
0
||
selectedResearchTimeIds
.
value
.
some
(
year
=>
String
(
item
.
time
||
""
).
startsWith
(
year
));
timeSel
.
length
===
0
||
timeSel
.
some
(
sel
=>
{
if
(
sel
===
RESOURCE_FILTER_EARLIER
)
{
return
matchesEarlierChineseDate
(
item
.
time
);
}
return
String
(
item
.
time
||
""
).
startsWith
(
String
(
sel
));
});
const
matchDepartment
=
selectedResearchHearingIds
.
value
.
length
===
0
||
selectedResearchHearingIds
.
value
.
some
(
department
=>
deptSel
.
length
===
0
||
deptSel
.
some
(
department
=>
String
(
item
.
content
||
""
).
includes
(
department
)
||
String
(
item
.
title
||
""
).
includes
(
department
)
);
const
matchType
=
selectedResearchIds
.
value
.
length
===
0
||
selectedResearchIds
.
value
.
some
(
typeId
=>
String
(
item
.
category
||
""
).
includes
(
typeId
)
||
String
(
item
.
title
||
""
).
includes
(
typeId
)
areaSel
.
length
===
0
||
areaSel
.
some
(
typeId
=>
String
(
item
.
category
||
""
).
includes
(
String
(
typeId
))
||
String
(
item
.
title
||
""
).
includes
(
String
(
typeId
)
)
);
return
matchYear
&&
matchDepartment
&&
matchType
;
});
...
...
@@ -189,10 +212,6 @@ const displayList = computed(() => {
return
list
.
slice
(
start
,
start
+
pageSize
);
});
const
handleFilterChange
=
()
=>
{
currentPage
.
value
=
1
;
};
const
handlePageChange
=
page
=>
{
currentPage
.
value
=
page
;
};
...
...
src/views/thinkTank/components/ThinkTankPolicyAdviceOverview.vue
浏览文件 @
c675e799
...
...
@@ -7,16 +7,21 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isAllSelected(typeIds)"
@
change=
"val => toggleAll(val, typeIds)"
>
全部领域
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedTypeIds"
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
<el-checkbox
class=
"filter-checkbox"
v-for=
"t in (researchTypeList || [])"
:key=
"t.id"
v-model=
"selectedTypeIds"
:label=
"t.id"
@
change=
"emitChange"
>
<el-checkbox
class=
"filter-checkbox"
v-for=
"t in (researchTypeList || [])"
:key=
"t.id"
:label=
"t.id"
>
{{
t
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
...
...
@@ -26,16 +31,21 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isAllSelected(yearIds)"
@
change=
"val => toggleAll(val, yearIds)"
>
全部时间
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedYearIds"
@
change=
"handleYearGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
<el-checkbox
class=
"filter-checkbox"
v-for=
"y in (researchTimeList || [])"
:key=
"y.id"
v-model=
"selectedYearIds"
:label=
"y.id"
@
change=
"emitChange"
>
<el-checkbox
class=
"filter-checkbox"
v-for=
"y in (researchTimeList || [])"
:key=
"y.id"
:label=
"y.id"
>
{{
y
.
name
}}
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
...
...
@@ -95,10 +105,17 @@
</
template
>
<
script
setup
>
import
{
computed
,
ref
}
from
"vue"
;
import
{
ref
}
from
"vue"
;
import
AreaTag
from
"@/components/base/AreaTag/index.vue"
;
const
props
=
defineProps
({
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
normalizeExclusiveAllOption
,
stripAllAreaForRequest
,
stripAllTimeForRequest
}
from
"../utils/resourceLibraryFilters"
;
defineProps
({
researchTypeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
researchTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
list
:
{
type
:
Array
,
default
:
()
=>
[]
},
...
...
@@ -109,29 +126,25 @@ const props = defineProps({
const
emit
=
defineEmits
([
"filter-change"
,
"page-change"
,
"item-click"
]);
const
selectedTypeIds
=
ref
([]);
const
selectedYearIds
=
ref
([]);
const
selectedTypeIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedYearIds
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
const
typeIds
=
computed
(()
=>
(
props
.
researchTypeList
||
[]).
map
(
i
=>
i
.
id
));
const
yearIds
=
computed
(()
=>
(
props
.
researchTimeList
||
[]).
map
(
i
=>
i
.
id
));
const
isAllSelected
=
ids
=>
ids
.
length
>
0
&&
ids
.
every
(
id
=>
{
const
target
=
ids
===
typeIds
.
value
?
selectedTypeIds
.
value
:
selectedYearIds
.
value
return
target
.
includes
(
id
)
});
const
toggleAll
=
(
checked
,
ids
)
=>
{
const
target
=
ids
===
typeIds
.
value
?
selectedTypeIds
:
selectedYearIds
target
.
value
=
checked
?
[...
ids
]
:
[]
emitChange
()
}
const
emitChange
=
()
=>
{
const
emitFilterToParent
=
()
=>
{
emit
(
"filter-change"
,
{
researchTypeIds
:
[...
selectedTypeIds
.
value
],
researchTimeIds
:
[...
selectedYearIds
.
value
],
})
}
researchTypeIds
:
stripAllAreaForRequest
(
selectedTypeIds
.
value
),
researchTimeIds
:
stripAllTimeForRequest
(
selectedYearIds
.
value
),
});
};
const
handleAreaGroupChange
=
(
val
)
=>
{
selectedTypeIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
);
emitFilterToParent
();
};
const
handleYearGroupChange
=
(
val
)
=>
{
selectedYearIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
);
emitFilterToParent
();
};
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
src/views/thinkTank/index.vue
浏览文件 @
c675e799
差异被折叠。
点击展开。
src/views/thinkTank/utils/multiLineChart.js
浏览文件 @
c675e799
import
*
as
echarts
from
'echarts'
;
/** 图例分页:每页展示的图例项数量(box5 数量变化趋势) */
export
const
MULTILINE_LEGEND_SHOW_COUNT
=
11
;
/**
* @param {{ title: unknown[], data: Array<{ name: string, value: unknown[], color?: string }> }} data
* @param {{ legendShowCount?: number, legendPageIndex?: number }} [options]
*/
const
getMultiLineChart
=
(
data
,
options
=
{}
)
=>
{
const
getMultiLineChart
=
(
data
)
=>
{
// 提取标题和系列数据
const
title
=
data
.
title
const
series
=
data
.
data
const
legendShowCount
=
typeof
options
.
legendShowCount
===
'number'
&&
options
.
legendShowCount
>
0
?
options
.
legendShowCount
:
MULTILINE_LEGEND_SHOW_COUNT
const
rawPageIndex
=
Number
(
options
.
legendPageIndex
)
||
0
const
allNames
=
series
.
map
((
item
)
=>
item
.
name
)
const
pageCount
=
Math
.
max
(
1
,
Math
.
ceil
(
allNames
.
length
/
legendShowCount
))
const
legendPageIndex
=
Math
.
min
(
Math
.
max
(
0
,
rawPageIndex
),
pageCount
-
1
)
const
legendStart
=
legendPageIndex
*
legendShowCount
const
legendData
=
allNames
.
slice
(
legendStart
,
legendStart
+
legendShowCount
)
const
legendSplitAt
=
Math
.
ceil
(
allNames
.
length
/
2
)
const
legendFirstLine
=
allNames
.
slice
(
0
,
legendSplitAt
)
const
legendSecondLine
=
allNames
.
slice
(
legendSplitAt
)
// 定义配色数组
const
colorList
=
[
...
...
@@ -101,28 +91,46 @@ const getMultiLineChart = (data, options = {}) => {
},
/* 顶部预留足够空间:多行图例 + Y 轴标题「数量」在纵轴顶端,避免与图例重合 */
grid
:
{
top
:
'
28
%'
,
right
:
'
5
%'
,
top
:
'
34
%'
,
right
:
'
3
%'
,
bottom
:
'5%'
,
left
:
'
5
%'
,
left
:
'
2
%'
,
containLabel
:
true
},
legend
:
{
show
:
true
,
type
:
'plain'
,
data
:
legendData
,
top
:
8
,
left
:
'center'
,
icon
:
'circle'
,
textStyle
:
{
fontFamily
:
'Source Han Sans CN'
,
// 字体
fontWeight
:
400
,
// 字重值(Regular对应400)
fontSize
:
14
,
// 字号
lineHeight
:
24
,
// 行高
letterSpacing
:
0
,
// 字间距
align
:
'left'
// 文本左对齐
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'
}
}
}
,
]
,
color
:
colorList
,
// 使用预设的配色数组
xAxis
:
[
{
...
...
src/views/thinkTank/utils/resourceLibraryFilters.js
0 → 100644
浏览文件 @
c675e799
/** 与政策追踪侧筛选文案保持一致 */
export
const
RESOURCE_FILTER_ALL_AREA
=
"全部领域"
;
export
const
RESOURCE_FILTER_ALL_TIME
=
"全部时间"
;
export
const
RESOURCE_FILTER_ALL_DEPT
=
"全部部门"
;
/**
* 「全部」与具体项互斥;空选时回到「全部」
* (同市场准入/政策追踪 resourceLibrary normalizeWithAll)
*/
export
function
normalizeExclusiveAllOption
(
val
,
allLabel
)
{
if
(
!
Array
.
isArray
(
val
))
{
return
[
allLabel
];
}
if
(
val
.
includes
(
allLabel
)
&&
val
.
length
>
1
)
{
if
(
val
[
val
.
length
-
1
]
===
allLabel
)
{
return
[
allLabel
];
}
return
val
.
filter
((
item
)
=>
item
!==
allLabel
);
}
if
(
val
.
length
===
0
)
{
return
[
allLabel
];
}
return
val
;
}
/** 发请求前去掉「全部领域」占位 */
export
function
stripAllAreaForRequest
(
arr
)
{
return
(
Array
.
isArray
(
arr
)
?
arr
:
[]).
filter
((
id
)
=>
id
!==
RESOURCE_FILTER_ALL_AREA
);
}
/** 发请求前去掉「全部时间」占位 */
export
function
stripAllTimeForRequest
(
arr
)
{
return
(
Array
.
isArray
(
arr
)
?
arr
:
[]).
filter
(
(
id
)
=>
id
!==
RESOURCE_FILTER_ALL_TIME
&&
id
!==
""
&&
id
!=
null
);
}
/** 发请求前去掉「全部部门」占位 */
export
function
stripAllDeptForRequest
(
arr
)
{
return
(
Array
.
isArray
(
arr
)
?
arr
:
[]).
filter
((
id
)
=>
id
!==
RESOURCE_FILTER_ALL_DEPT
);
}
/**
* 已选中的具体项是否覆盖选项全集(与仅选「全部领域/全部时间」等价:请求层不按子项狭义过滤)
* @param {unknown[]} selectedStrippedIds 已去掉「全部」占位后的选中 id
* @param {unknown[]} allOptionIds 当前列表全部可选项 id(与勾选框 label 一致)
*/
export
function
isSelectionCoveringAllOptions
(
selectedStrippedIds
,
allOptionIds
)
{
if
(
!
Array
.
isArray
(
selectedStrippedIds
)
||
!
Array
.
isArray
(
allOptionIds
)
||
allOptionIds
.
length
===
0
)
{
return
false
;
}
if
(
selectedStrippedIds
.
length
!==
allOptionIds
.
length
)
{
return
false
;
}
const
setSel
=
new
Set
(
selectedStrippedIds
);
return
allOptionIds
.
every
((
id
)
=>
setSel
.
has
(
id
));
}
/** 与政策追踪一致:勾选「更早」表示 2000~2020 自然年 */
export
const
RESOURCE_FILTER_EARLIER
=
"更早"
;
export
const
RESOURCE_YEAR_EARLIER_START
=
2000
;
export
const
RESOURCE_YEAR_EARLIER_END
=
2020
;
/** 展开「更早」为逐年数字(概览 report 接口 years 多为数字年) */
export
function
expandEarlierNumericYears
()
{
const
out
=
[];
for
(
let
y
=
RESOURCE_YEAR_EARLIER_START
;
y
<=
RESOURCE_YEAR_EARLIER_END
;
y
+=
1
)
{
out
.
push
(
y
);
}
return
out
;
}
/**
* 概览资源库:去掉「全部时间」后的选中项 → years 查询串(含「更早」则展开为 2000…2020)
*/
export
function
buildNumericYearsQueryString
(
strippedTimeIds
)
{
const
list
=
Array
.
isArray
(
strippedTimeIds
)
?
strippedTimeIds
:
[];
const
yearsSet
=
new
Set
();
for
(
const
id
of
list
)
{
if
(
id
===
RESOURCE_FILTER_EARLIER
)
{
expandEarlierNumericYears
().
forEach
((
y
)
=>
yearsSet
.
add
(
y
));
}
else
if
(
id
!==
null
&&
id
!==
undefined
&&
id
!==
""
)
{
const
n
=
Number
(
id
);
if
(
!
Number
.
isNaN
(
n
))
{
yearsSet
.
add
(
n
);
}
}
}
return
[...
yearsSet
].
sort
((
a
,
b
)
=>
b
-
a
).
join
(
","
);
}
const
DYNAMICS_YEAR_LABEL_RE
=
/^
(\d{4})
年$/
;
/**
* 智库动态:时间为「2025年」等字符串 id → years 查询串(「更早」展开为 2000…2020)
*/
export
function
buildDynamicsYearsQueryString
(
strippedTimeIds
)
{
const
list
=
Array
.
isArray
(
strippedTimeIds
)
?
strippedTimeIds
:
[];
const
yearsSet
=
new
Set
();
for
(
const
id
of
list
)
{
if
(
id
===
RESOURCE_FILTER_EARLIER
)
{
expandEarlierNumericYears
().
forEach
((
y
)
=>
yearsSet
.
add
(
y
));
continue
;
}
const
m
=
String
(
id
).
match
(
DYNAMICS_YEAR_LABEL_RE
);
if
(
m
)
{
yearsSet
.
add
(
Number
(
m
[
1
]));
}
}
return
[...
yearsSet
].
sort
((
a
,
b
)
=>
b
-
a
).
join
(
","
);
}
/**
* 中文日期串如「2020年5月1日」是否落在「更早」区间(与政策追踪矩形树一致)
*/
export
function
matchesEarlierChineseDate
(
timeStr
)
{
const
m
=
String
(
timeStr
||
""
).
match
(
/^
(\d{4})
年/
);
if
(
!
m
)
{
return
false
;
}
const
y
=
Number
(
m
[
1
]);
return
y
>=
RESOURCE_YEAR_EARLIER_START
&&
y
<=
RESOURCE_YEAR_EARLIER_END
;
}
/** 与政策追踪「仅全部时间」一致:固定起止(结束日按产品要求) */
export
const
RESOURCE_REPORT_ALL_TIME_START
=
"2000-01-01"
;
export
const
RESOURCE_REPORT_ALL_TIME_END
=
"2025-12-31"
;
function
getResourceReportDateYearsAgo
(
years
)
{
const
currentDate
=
new
Date
();
const
pastDate
=
new
Date
(
currentDate
.
getFullYear
()
-
years
,
currentDate
.
getMonth
(),
currentDate
.
getDate
());
const
y
=
pastDate
.
getFullYear
();
const
month
=
String
(
pastDate
.
getMonth
()
+
1
).
padStart
(
2
,
"0"
);
const
day
=
String
(
pastDate
.
getDate
()).
padStart
(
2
,
"0"
);
return
`
${
y
}
-
${
month
}
-
${
day
}
`
;
}
function
getResourceReportTodayYmd
()
{
const
d
=
new
Date
();
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
}
`
;
}
/**
* 资源库 /thinkTankOverview/report:由发布时间多选(数字年 +「更早」)推导 startDate/endDate,语义对齐政策追踪 getPolicyListDateRangeFromYearList。
* - 仅「全部时间」或选满全部具体年份 → 2000-01-01 ~ 2025-12-31
* - 单选/多选自然年 +「更早」→ 取最小年 01-01 与最大年 12-31 包络(「更早」为 2000~2020)
* - 无法解析时的兜底:近 relativeYearsAgo 年至今天
*
* @param {unknown[]} strippedTimeIds stripAllTimeForRequest 后的选中项
* @param {unknown[]} allTimeOptionIds 当前 pubTimeList 全部 id(用于判断是否选满 = 全部时间)
* @param {number} [relativeYearsAgo=1] 兜底用「近 N 年」
* @returns {{ startDate: string, endDate: string }}
*/
export
function
getResourceLibraryReportDateRangeFromTimeSelection
(
strippedTimeIds
,
allTimeOptionIds
,
relativeYearsAgo
=
1
)
{
const
stripped
=
Array
.
isArray
(
strippedTimeIds
)
?
strippedTimeIds
:
[];
const
allIds
=
Array
.
isArray
(
allTimeOptionIds
)
?
allTimeOptionIds
:
[];
const
isAllTime
=
stripped
.
length
===
0
||
(
allIds
.
length
>
0
&&
isSelectionCoveringAllOptions
(
stripped
,
allIds
));
if
(
isAllTime
)
{
return
{
startDate
:
RESOURCE_REPORT_ALL_TIME_START
,
endDate
:
RESOURCE_REPORT_ALL_TIME_END
,
};
}
let
minY
=
null
;
let
maxY
=
null
;
const
set
=
new
Set
(
stripped
);
if
(
set
.
has
(
RESOURCE_FILTER_EARLIER
))
{
minY
=
RESOURCE_YEAR_EARLIER_START
;
maxY
=
RESOURCE_YEAR_EARLIER_END
;
}
const
yearLabelCnRe
=
/^
(\d{4})
年$/
;
for
(
const
id
of
set
)
{
if
(
id
===
RESOURCE_FILTER_EARLIER
)
{
continue
;
}
let
yNum
=
Number
(
id
);
if
(
Number
.
isNaN
(
yNum
))
{
const
m
=
String
(
id
).
match
(
yearLabelCnRe
);
if
(
m
)
{
yNum
=
Number
(
m
[
1
]);
}
}
if
(
!
Number
.
isNaN
(
yNum
))
{
minY
=
minY
==
null
?
yNum
:
Math
.
min
(
minY
,
yNum
);
maxY
=
maxY
==
null
?
yNum
:
Math
.
max
(
maxY
,
yNum
);
}
}
if
(
minY
==
null
||
maxY
==
null
)
{
const
y
=
Number
(
relativeYearsAgo
)
>
0
?
Number
(
relativeYearsAgo
)
:
1
;
return
{
startDate
:
getResourceReportDateYearsAgo
(
y
),
endDate
:
getResourceReportTodayYmd
(),
};
}
return
{
startDate
:
`
${
minY
}
-01-01`
,
endDate
:
`
${
maxY
}
-12-31`
,
};
}
src/views/thinkTank/utils/sankey.js
浏览文件 @
c675e799
...
...
@@ -6,7 +6,7 @@ const getSankeyChart = (nodes, links) => {
type
:
'sankey'
,
layout
:
'none'
,
left
:
'2%'
,
right
:
'1
8
%'
,
right
:
'1
5
%'
,
top
:
'5%'
,
bottom
:
'5%'
,
emphasis
:
{
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论