Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
c675e799
提交
c675e799
authored
3月 23, 2026
作者:
朱政
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat:智库全选功能选择框逻辑统一,高度自适应,智库部分图表格式修改,概览页近期美国智库机构发布涉华报告数量汇总样式修改
上级
09b8b813
显示空白字符变更
内嵌
并排
正在显示
24 个修改的文件
包含
1606 行增加
和
1110 行删除
+1606
-1110
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
+178
-55
index.vue
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
+62
-149
multiLineChart.js
...nk/ThinkTankDetail/PolicyTracking/utils/multiLineChart.js
+58
-17
index.vue
...k/ThinkTankDetail/thinkDynamics/CongressHearing/index.vue
+108
-60
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
+64
-91
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
+156
-64
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
+212
-340
multiLineChart.js
src/views/thinkTank/utils/multiLineChart.js
+33
-25
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) {
...
@@ -36,6 +36,47 @@ function parseChartInterpretationArray(buffer) {
throw
new
Error
(
"无法解析图表解读 JSON 数组"
);
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 流式)
* 图表解读(SSE 流式)
* @param {object} data - 请求体
* @param {object} data - 请求体
...
@@ -44,9 +85,15 @@ function parseChartInterpretationArray(buffer) {
...
@@ -44,9 +85,15 @@ function parseChartInterpretationArray(buffer) {
* @returns {Promise<{data: unknown[]}>}
* @returns {Promise<{data: unknown[]}>}
*/
*/
export
function
getChartAnalysis
(
data
,
options
=
{})
{
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
)
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
let
buffer
=
""
;
let
buffer
=
""
;
let
latestInterpretation
=
""
;
let
settled
=
false
;
let
settled
=
false
;
const
abortController
=
new
AbortController
();
const
abortController
=
new
AbortController
();
...
@@ -99,6 +146,18 @@ export function getChartAnalysis(data, options = {}) {
...
@@ -99,6 +146,18 @@ export function getChartAnalysis(data, options = {}) {
if
(
msg
&&
typeof
msg
===
"object"
&&
"text"
in
msg
)
{
if
(
msg
&&
typeof
msg
===
"object"
&&
"text"
in
msg
)
{
chunk
=
String
(
msg
.
text
??
""
);
chunk
=
String
(
msg
.
text
??
""
);
buffer
+=
chunk
;
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
{
}
else
{
chunk
=
raw
;
chunk
=
raw
;
buffer
+=
raw
;
buffer
+=
raw
;
...
@@ -108,9 +167,18 @@ export function getChartAnalysis(data, options = {}) {
...
@@ -108,9 +167,18 @@ export function getChartAnalysis(data, options = {}) {
buffer
+=
raw
;
buffer
+=
raw
;
}
}
// 兜底:非标准 JSON(如单引号 Python 风格)时,尝试直接从文本提取“解读”
const
looseInterpretation
=
extractInterpretationFromLooseText
(
raw
);
if
(
looseInterpretation
)
{
latestInterpretation
=
looseInterpretation
;
safeResolve
({
data
:
[{
解读
:
looseInterpretation
}]
});
abortController
.
abort
();
return
;
}
// 每收到一条消息即回调,用于流式渲染
// 每收到一条消息即回调,用于流式渲染
if
(
chunk
&&
typeof
onChunk
===
"function"
)
{
if
(
chunk
&&
onDelta
)
{
on
Chunk
(
chunk
);
on
Delta
(
chunk
);
}
}
// 如果 buffer 已经拼完 markdown code fence,则提前解析并中断连接
// 如果 buffer 已经拼完 markdown code fence,则提前解析并中断连接
...
@@ -118,6 +186,10 @@ export function getChartAnalysis(data, options = {}) {
...
@@ -118,6 +186,10 @@ export function getChartAnalysis(data, options = {}) {
if
(
trimmed
.
endsWith
(
"```"
))
{
if
(
trimmed
.
endsWith
(
"```"
))
{
try
{
try
{
const
arr
=
parseChartInterpretationArray
(
trimmed
);
const
arr
=
parseChartInterpretationArray
(
trimmed
);
const
interpretation
=
pickInterpretationText
(
arr
);
if
(
interpretation
)
{
latestInterpretation
=
interpretation
;
}
safeResolve
({
data
:
arr
});
safeResolve
({
data
:
arr
});
abortController
.
abort
();
abortController
.
abort
();
}
catch
(
_
)
{
}
}
catch
(
_
)
{
}
...
@@ -126,8 +198,22 @@ export function getChartAnalysis(data, options = {}) {
...
@@ -126,8 +198,22 @@ export function getChartAnalysis(data, options = {}) {
onclose
:
()
=>
{
onclose
:
()
=>
{
try
{
try
{
const
arr
=
parseChartInterpretationArray
(
buffer
);
const
arr
=
parseChartInterpretationArray
(
buffer
);
const
interpretation
=
pickInterpretationText
(
arr
);
if
(
interpretation
)
{
latestInterpretation
=
interpretation
;
}
safeResolve
({
data
:
arr
});
safeResolve
({
data
:
arr
});
}
catch
(
e
)
{
}
catch
(
e
)
{
// 兜底:整体 buffer 不是标准 JSON(如单引号)时直接提取“解读”
const
looseInterpretation
=
extractInterpretationFromLooseText
(
buffer
);
if
(
looseInterpretation
)
{
safeResolve
({
data
:
[{
解读
:
looseInterpretation
}]
});
return
;
}
if
(
latestInterpretation
)
{
safeResolve
({
data
:
[{
解读
:
latestInterpretation
}]
});
return
;
}
safeReject
(
e
);
safeReject
(
e
);
}
}
},
},
...
...
src/api/api_doc.md
浏览文件 @
c675e799
...
@@ -1156,6 +1156,15 @@
...
@@ -1156,6 +1156,15 @@
| -------- | -------- | ----- | -------- | -------- | ------ |
| -------- | -------- | ----- | -------- | -------- | ------ |
|areas|区域名称列表|query|false|array|string|
|areas|区域名称列表|query|false|array|string|
|researchTypeIds|研究类型ID列表|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||
|token|Token Request Header|header|false|string||
...
...
src/api/thinkTank/overview.js
浏览文件 @
c675e799
...
@@ -87,7 +87,11 @@ export function getHylyList() {
...
@@ -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
)
{
export
function
getThinkTankReport
(
params
)
{
return
request
({
return
request
({
method
:
'GET'
,
method
:
'GET'
,
...
@@ -240,18 +244,12 @@ export function getThinkTankInfoBranch(params) {
...
@@ -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
)
{
export
function
getThinkTankFundsSource
(
params
)
{
return
request
({
return
request
({
method
:
'GET'
,
method
:
'GET'
,
url
:
`/api/thinkTankInfo/fundsS
ource
/
${
params
}
`
,
url
:
`/api/thinkTankInfo/fundsS
tatistics
/
${
params
}
`
,
})
})
}
}
...
@@ -265,9 +263,17 @@ export function getThinkTankResearchAreae(params) {
...
@@ -265,9 +263,17 @@ export function getThinkTankResearchAreae(params) {
//获取核心研究人员
//获取核心研究人员
export
function
getThinkTankPerson
(
params
)
{
export
function
getThinkTankPerson
(
params
)
{
const
{
thinkTankId
,
currentPage
,
pageSize
}
=
params
return
request
({
return
request
({
method
:
'GET'
,
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 @@
...
@@ -85,7 +85,7 @@
</div>
</div>
</div>
</div>
<div
class=
"box1-middle"
>
<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"
>
<div
class=
"left"
>
<img
:src=
item.imageUrl
alt=
""
>
<img
:src=
item.imageUrl
alt=
""
>
</div>
</div>
...
@@ -93,9 +93,9 @@
...
@@ -93,9 +93,9 @@
<div
class=
"right-header"
>
{{ item.name }}
</div>
<div
class=
"right-header"
>
{{ item.name }}
</div>
<div
class=
"right-footer"
>
<div
class=
"right-footer"
>
<div
class=
"time"
>
{{ item.times }}
</div>
<div
class=
"time"
>
{{ item.times }}
</div>
<div
class=
"text-image"
>
<div
class=
"text-image
think-tank-link"
@
click
.
stop=
"handleToReportDetail(item)
"
>
<div
class=
"image"
>
<div
class=
"image"
>
<img
src=
"../assets/images/rand-image.png"
alt=
""
/>
<img
:src=
item.logoUrl
alt=
""
/>
</div>
</div>
<div
class=
"text"
>
<div
class=
"text"
>
{{ item.thinkTankName }}
{{ item.thinkTankName }}
...
@@ -105,8 +105,8 @@
...
@@ -105,8 +105,8 @@
</div>
</div>
<div
class=
"image-change"
>
<div
class=
"image-change"
>
<img
src=
"../assets/images/plus.png"
alt=
""
v-if=
"!selectedIds.has(item.id)"
<img
src=
"../assets/images/plus.png"
alt=
""
v-if=
"!selectedIds.has(item.id)"
@
click=
"toggleSelected(item.id, item)"
/>
@
click
.
stop
=
"toggleSelected(item.id, item)"
/>
<img
src=
"../assets/images/Minus.png"
alt=
""
v-else
@
click=
"toggleSelected(item.id, item)"
/>
<img
src=
"../assets/images/Minus.png"
alt=
""
v-else
@
click
.
stop
=
"toggleSelected(item.id, item)"
/>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -125,7 +125,8 @@
...
@@ -125,7 +125,8 @@
<div
class=
"right-text-analysis"
>
{{ "共选择" }}{{ selectedReportList.length }}{{ "篇智库报告" }}
</div>
<div
class=
"right-text-analysis"
>
{{ "共选择" }}{{ selectedReportList.length }}{{ "篇智库报告" }}
</div>
</div>
</div>
<div
class=
"box1-middle-analysis"
>
<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"
>
<div
class=
"left"
>
<img
:src=
item.imageUrl
alt=
""
>
<img
:src=
item.imageUrl
alt=
""
>
</div>
</div>
...
@@ -133,9 +134,9 @@
...
@@ -133,9 +134,9 @@
<div
class=
"right-header"
>
{{ item.name }}
</div>
<div
class=
"right-header"
>
{{ item.name }}
</div>
<div
class=
"right-footer"
>
<div
class=
"right-footer"
>
<div
class=
"time"
>
{{ item.times }}
</div>
<div
class=
"time"
>
{{ item.times }}
</div>
<div
class=
"text-image"
>
<div
class=
"text-image
think-tank-link"
@
click
.
stop=
"handleToReportDetail(item)
"
>
<div
class=
"image"
>
<div
class=
"image"
>
<img
src=
"../assets/images/rand-image.png"
alt=
""
/>
<img
:src=
item.logoUrl
alt=
""
/>
</div>
</div>
<div
class=
"text"
>
<div
class=
"text"
>
{{ item.thinkTankName }}
{{ item.thinkTankName }}
...
@@ -145,8 +146,8 @@
...
@@ -145,8 +146,8 @@
</div>
</div>
<div
class=
"image-change"
>
<div
class=
"image-change"
>
<img
src=
"../assets/images/plus.png"
alt=
""
v-if=
"!selectedIds.has(item.id)"
<img
src=
"../assets/images/plus.png"
alt=
""
v-if=
"!selectedIds.has(item.id)"
@
click=
"toggleSelected(item.id, item)"
/>
@
click
.
stop
=
"toggleSelected(item.id, item)"
/>
<img
src=
"../assets/images/Minus.png"
alt=
""
v-else
@
click=
"toggleSelected(item.id, item)"
/>
<img
src=
"../assets/images/Minus.png"
alt=
""
v-else
@
click
.
stop
=
"toggleSelected(item.id, item)"
/>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -285,7 +286,7 @@ import router from '@/router';
...
@@ -285,7 +286,7 @@ import router from '@/router';
import
{
onMounted
,
ref
,
computed
,
reactive
,
nextTick
}
from
"vue"
;
import
{
onMounted
,
ref
,
computed
,
reactive
,
nextTick
}
from
"vue"
;
import
AnalysisBox
from
"@/components/base/boxBackground/analysisBox.vue"
import
AnalysisBox
from
"@/components/base/boxBackground/analysisBox.vue"
import
AnalysisResultBox
from
"./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
sort
=
ref
(
""
);
const
searchPolicy
=
ref
(
""
);
const
searchPolicy
=
ref
(
""
);
const
isBox2
=
ref
(
true
)
const
isBox2
=
ref
(
true
)
...
@@ -521,6 +522,18 @@ const handleCurrentChange = page => {
...
@@ -521,6 +522,18 @@ const handleCurrentChange = page => {
currentPage
.
value
=
page
;
currentPage
.
value
=
page
;
handleGetetThinkTankReport
(
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
// getHylyList
const
handleGetHylyList
=
async
()
=>
{
const
handleGetHylyList
=
async
()
=>
{
...
@@ -536,8 +549,9 @@ const handleGetHylyList = async () => {
...
@@ -536,8 +549,9 @@ const handleGetHylyList = async () => {
}
;
}
;
//获取智库报告
//获取智库报告
const
handleGetetThinkTankReport
=
async
(
page
=
currentPage
.
value
)
=>
{
const
handleGetetThinkTankReport
=
async
(
page
=
currentPage
.
value
)
=>
{
const
id
=
router
.
currentRoute
?.
value
?.
params
?.
id
||
""
;
const
thinkTankId
=
router
.
currentRoute
?.
value
?.
params
?.
id
||
""
;
const
getDateYearsAgo
=
years
=>
{
const
getDateYearsAgo
=
(
years
)
=>
{
const
d
=
new
Date
();
const
d
=
new
Date
();
d
.
setFullYear
(
d
.
getFullYear
()
-
Number
(
years
||
1
));
d
.
setFullYear
(
d
.
getFullYear
()
-
Number
(
years
||
1
));
const
y
=
d
.
getFullYear
();
const
y
=
d
.
getFullYear
();
...
@@ -545,20 +559,32 @@ const handleGetetThinkTankReport = async (page = currentPage.value) => {
...
@@ -545,20 +559,32 @@ const handleGetetThinkTankReport = async (page = currentPage.value) => {
const
day
=
String
(
d
.
getDate
()).
padStart
(
2
,
"0"
);
const
day
=
String
(
d
.
getDate
()).
padStart
(
2
,
"0"
);
return
`${y
}
-${m
}
-${day
}
`
;
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
=
{
const
params
=
{
id
,
thinkTankId
,
startDate
:
getDateYearsAgo
(
selectedYears
.
value
),
pageNum
:
Number
(
page
),
// 不传 authorName(避免后端按空字符串筛选)
currentPage
:
Number
(
page
)
-
1
,
// 1-based
pageSize
:
pageSize
,
pageSize
:
pageSize
,
researchTypeIds
:
arrayToString
(
selectedAreaList
.
value
),
sortFun
:
false
,
searchText
:
(
searchPolicy
.
value
||
""
).
trim
(),
domainIds
,
startDate
:
getDateYearsAgo
(
selectedYears
.
value
),
endDate
:
getTodayYmd
(),
keyword
:
keyword
||
undefined
,
}
;
}
;
try
{
try
{
// 先清空,避免视觉上看起来没变化
// 先清空,避免视觉上看起来没变化
curFooterList
.
value
=
[];
curFooterList
.
value
=
[];
const
res
=
await
getThink
Dynamics
Report
(
params
);
const
res
=
await
getThink
Tank
Report
(
params
);
console
.
log
(
"智库报告"
,
res
);
console
.
log
(
"智库报告"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
if
(
res
.
code
===
200
&&
res
.
data
)
{
curFooterList
.
value
=
res
.
data
.
content
;
curFooterList
.
value
=
res
.
data
.
content
;
...
@@ -621,6 +647,10 @@ onMounted(async () => {
...
@@ -621,6 +647,10 @@ onMounted(async () => {
}
}
}
}
.
think
-
tank
-
link
{
cursor
:
pointer
;
}
.
box
{
.
box
{
display
:
flex
;
display
:
flex
;
gap
:
16
px
;
gap
:
16
px
;
...
...
src/views/thinkTank/ReportDetail/policyTracking/index.vue
浏览文件 @
c675e799
...
@@ -330,11 +330,11 @@ onMounted(async () => {
...
@@ -330,11 +330,11 @@ onMounted(async () => {
.box1
{
.box1
{
margin-top
:
19px
;
margin-top
:
19px
;
width
:
1600px
;
width
:
1600px
;
height
:
1173px
;
.box1-main
{
.box1-main
{
margin-top
:
8px
;
margin-top
:
8px
;
height
:
1097
px
;
min-height
:
778
px
;
padding-left
:
21px
;
padding-left
:
21px
;
padding-right
:
50px
;
padding-right
:
50px
;
padding-bottom
:
21px
;
padding-bottom
:
21px
;
...
@@ -421,7 +421,7 @@ onMounted(async () => {
...
@@ -421,7 +421,7 @@ onMounted(async () => {
.item-box
{
.item-box
{
width
:
506px
;
width
:
506px
;
height
:
100%
;
min-height
:
639
.2px
;
border-top
:
1px
solid
rgb
(
234
,
236
,
238
);
border-top
:
1px
solid
rgb
(
234
,
236
,
238
);
.item
{
.item
{
...
...
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
浏览文件 @
c675e799
<
template
>
<
template
>
<div
class=
"wrap"
>
<div
class=
"wrap"
>
<div
class=
"top"
>
<div
class=
"top"
>
<WarningPane
:warnningLevel=
"riskSignal?.level"
:warnningContent=
"riskSignal?.content"
>
<WarningPane
:warnningLevel=
"riskSignal?.level"
:warnningContent=
"riskSignal?.content"
v-if=
"riskSignal?.level"
>
</WarningPane>
</WarningPane>
</div>
</div>
<div
class=
"bottom-row"
>
<div
class=
"bottom-row"
>
...
@@ -52,8 +52,9 @@
...
@@ -52,8 +52,9 @@
</div>
</div>
<div
class=
"author-box"
>
<div
class=
"author-box"
>
<div
class=
"author-item"
v-for=
"(author, idx) in reportAuthors"
:key=
"idx"
<div
class=
"author-item"
v-for=
"(author, idx) in reportAuthors"
:key=
"idx"
v-if=
"Array.isArray(reportAuthors) && reportAuthors.length"
>
v-if=
"Array.isArray(reportAuthors) && reportAuthors.length"
@
click=
"handleClickReportAuthor(author)"
>
<div
class=
"image"
><img
:src=
"author.avatar"
alt=
""
/></div>
<div
class=
"image"
><img
:src=
"author.avatar ? author.avatar : DefaultIcon1"
alt=
""
@
error=
"() => { if (author.avatar) author.avatar = null; }"
/></div>
<div
class=
"author-text"
>
<div
class=
"author-text"
>
<div
class=
"author-name"
>
{{ author.name }}
</div>
<div
class=
"author-name"
>
{{ author.name }}
</div>
<div
class=
"author-position"
>
{{ author.job }}
</div>
<div
class=
"author-position"
>
{{ author.job }}
</div>
...
@@ -66,7 +67,12 @@
...
@@ -66,7 +67,12 @@
<div
class=
"box5"
>
<div
class=
"box5"
>
<AnalysisBox
title=
"报告关键词云"
:showAllBtn=
"true"
>
<AnalysisBox
title=
"报告关键词云"
:showAllBtn=
"true"
>
<div
class=
"box5-main"
>
<div
class=
"box5-main"
>
<div
id=
"box5Chart"
></div>
<div
class=
"box5Chart"
>
<!-- 有数据后再挂载子组件:子组件仅在 onMounted 初始化,异步数据到达后需 v-if + key 强制重新挂载 -->
<WordCloudChart
v-if=
"box5Data.length"
:key=
"box5WordCloudKey"
:data=
"box5Data"
width=
"100%"
height=
"100%"
/>
</div>
</div>
</div>
</AnalysisBox>
</AnalysisBox>
</div>
</div>
...
@@ -141,28 +147,28 @@
...
@@ -141,28 +147,28 @@
</div>
</div>
<div
class=
"box4-main"
>
<div
class=
"box4-main"
>
<div
class=
"box4-main-main"
>
<div
class=
"box4-main-main"
>
<div
class=
"box4-item"
v-for=
"(item, index) in filteredOpinions"
:key=
"index"
>
<div
class=
"box4-item"
v-for=
"(item, index) in filteredOpinions"
:key=
"item.id != null ? item.id : index"
>
<div
class=
"top-row"
>
<div
class=
"top-row"
>
<div
class=
"left"
>
<div
class=
"left"
>
{{ index + 1 }}
{{ index + 1 }}
</div>
</div>
<div
class=
"center"
>
<div
class=
"center"
>
<div
class=
"title"
>
{{ item.titleZh }}
</div>
<div
class=
"title"
v-html=
"highlightOpinionText(item.titleZh)"
>
</div>
<div>
<div>
<img
src=
"../images/image-open.png"
alt=
""
class=
"center-image"
<img
src=
"../images/image-open.png"
alt=
""
class=
"center-image"
@
click=
"handleOpenReportOriginal(item)"
/>
@
click=
"handleOpenReportOriginal(item)"
/>
</div>
</div>
<div>
<div>
<img
v-if=
"
expandedIndex !== index"
src=
"../images/image-down.png"
alt=
""
class=
"center-image
"
<img
v-if=
"
!isOpinionExpanded(item, index)"
src=
"../images/image-down.png"
alt=
"
"
@
click=
"toggleOpinion(
index)"
/>
class=
"center-image"
@
click=
"toggleOpinion(item,
index)"
/>
<img
v-else
src=
"../images/image-up.png"
alt=
""
class=
"center-image"
<img
v-else
src=
"../images/image-up.png"
alt=
""
class=
"center-image"
@
click=
"toggleOpinion(index)"
/>
@
click=
"toggleOpinion(item, index)"
/>
</div>
</div>
</div>
</div>
</div>
<div
v-if=
"expandedIndex === index"
class=
"desc"
>
{{ item.contentZh }}
</div>
</div>
<div
v-if=
"isOpinionExpanded(item, index)"
class=
"desc"
v-html=
"highlightOpinionText(item.contentZh)"
></div>
<!-- <div class="right"> -->
<!-- <div class="right"> -->
<!-- <div class="tag" v-for="(val, idx) in item.hylyList" :key="idx">
<!-- <div class="tag" v-for="(val, idx) in item.hylyList" :key="idx">
{{ val }}
{{ val }}
...
@@ -193,11 +199,12 @@
...
@@ -193,11 +199,12 @@
</template>
</template>
<
script
setup
>
<
script
setup
>
import
DefaultIcon1
from
'@/assets/icons/default-icon1.png'
import
WarningPane
from
"@/components/base/WarningPane/index.vue"
import
WarningPane
from
"@/components/base/WarningPane/index.vue"
import
WordCloudChart
from
"@/components/base/WordCloundChart/index.vue"
import
SearchContainer
from
"@/components/SearchContainer.vue"
;
import
SearchContainer
from
"@/components/SearchContainer.vue"
;
import
{
ref
,
onMounted
,
computed
,
defineProps
,
nextTick
}
from
"vue"
;
import
{
ref
,
onMounted
,
computed
,
defineProps
}
from
"vue"
;
import
setChart
from
"@/utils/setChart"
;
import
{
ElMessage
}
from
"element-plus"
;
import
getWordCloudChart
from
"./utils/worldCloudChart"
;
import
{
import
{
getThinkTankReportAbstract
,
getThinkTankReportAbstract
,
getThinkTankReportContent
,
getThinkTankReportContent
,
...
@@ -209,6 +216,7 @@ import { getChartAnalysis } from "@/api/aiAnalysis/index";
...
@@ -209,6 +216,7 @@ import { getChartAnalysis } from "@/api/aiAnalysis/index";
import
{
useRouter
}
from
"vue-router"
;
import
{
useRouter
}
from
"vue-router"
;
import
"echarts-wordcloud"
;
import
"echarts-wordcloud"
;
import
AiSummary
from
'@/components/base/Ai/AiSummary/index.vue'
import
AiSummary
from
'@/components/base/Ai/AiSummary/index.vue'
import
{
getPersonSummaryInfo
}
from
"@/api/common/index"
;
const
router
=
useRouter
();
const
router
=
useRouter
();
...
@@ -235,24 +243,59 @@ const props = defineProps({
...
@@ -235,24 +243,59 @@ const props = defineProps({
const
searchOpinions
=
ref
(
''
);
const
searchOpinions
=
ref
(
''
);
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
highlightOpinionText
=
(
text
)
=>
{
const
safeText
=
escapeHtml
(
text
);
const
keyword
=
(
searchOpinions
.
value
||
""
).
trim
();
if
(
!
keyword
)
return
safeText
;
const
pattern
=
new
RegExp
(
`(
${
escapeRegExp
(
keyword
)}
)`
,
"gi"
);
return
safeText
.
replace
(
pattern
,
`<span class="opinion-keyword-highlight">$1</span>`
);
};
const
handleSearchOpinions
=
()
=>
{
const
handleSearchOpinions
=
()
=>
{
currentPage
.
value
=
1
;
currentPage
.
value
=
1
;
handleGetThinkTankReportViewpoint
();
handleGetThinkTankReportViewpoint
();
};
};
/
/ 当前展开的核心论点下标(同一时刻只展开一条)
/
** 可同时展开多条;用 id 区分项,避免翻页后索引与展开状态错位 */
const
expanded
Index
=
ref
(
null
);
const
expanded
OpinionKeys
=
ref
(
new
Set
()
);
const
filteredOpinions
=
computed
(()
=>
majorOpinions
.
value
);
const
filteredOpinions
=
computed
(()
=>
majorOpinions
.
value
);
const
opinionsTotal
=
computed
(()
=>
total
.
value
);
const
opinionsTotal
=
computed
(()
=>
total
.
value
);
const
toggleOpinion
=
index
=>
{
const
getOpinionExpandKey
=
(
item
,
index
)
=>
{
if
(
expandedIndex
.
value
===
index
)
{
if
(
item
!=
null
&&
item
.
id
!=
null
&&
item
.
id
!==
""
)
{
expandedIndex
.
value
=
null
;
return
String
(
item
.
id
);
}
return
`idx-
${
index
}
`
;
};
const
isOpinionExpanded
=
(
item
,
index
)
=>
{
return
expandedOpinionKeys
.
value
.
has
(
getOpinionExpandKey
(
item
,
index
));
};
const
toggleOpinion
=
(
item
,
index
)
=>
{
const
key
=
getOpinionExpandKey
(
item
,
index
);
const
next
=
new
Set
(
expandedOpinionKeys
.
value
);
if
(
next
.
has
(
key
))
{
next
.
delete
(
key
);
}
else
{
}
else
{
expandedIndex
.
value
=
index
;
next
.
add
(
key
)
;
}
}
expandedOpinionKeys
.
value
=
next
;
};
};
const
publishTime
=
computed
(()
=>
{
const
publishTime
=
computed
(()
=>
{
...
@@ -277,6 +320,62 @@ const reportAuthors = computed(() => {
...
@@ -277,6 +320,62 @@ const reportAuthors = computed(() => {
}
}
return
[];
return
[];
});
});
// 点击报告作者头像,跳转到人物主页
// 与核心研究人员逻辑一致:核心依赖 personId,本页面依赖作者的 id(作为 personId 传入)
const
handleClickReportAuthor
=
async
(
author
)
=>
{
const
personId
=
author
?.
id
;
if
(
!
personId
)
return
;
let
personTypeList
=
[];
try
{
personTypeList
=
JSON
.
parse
(
window
.
sessionStorage
.
getItem
(
"personTypeList"
)
||
"[]"
);
}
catch
(
e
)
{
personTypeList
=
[];
}
const
params
=
{
personId
};
try
{
const
res
=
await
getPersonSummaryInfo
(
params
);
if
(
res
.
code
!==
200
||
!
res
.
data
)
return
;
const
arr
=
personTypeList
.
filter
((
t
)
=>
{
const
typeIdNum
=
Number
(
t
.
typeId
);
const
personTypeNum
=
Number
(
res
.
data
.
personType
);
return
!
Number
.
isNaN
(
typeIdNum
)
&&
!
Number
.
isNaN
(
personTypeNum
)
&&
typeIdNum
===
personTypeNum
;
});
if
(
!
arr
.
length
)
{
ElMessage
.
warning
(
"找不到当前人员的类型值!"
);
return
;
}
const
personTypeName
=
arr
[
0
]?.
typeName
||
""
;
let
type
=
0
;
if
(
personTypeName
===
"科技企业领袖"
)
{
type
=
1
;
}
else
if
(
personTypeName
===
"国会议员"
)
{
type
=
2
;
}
else
if
(
personTypeName
===
"智库研究人员"
)
{
type
=
3
;
}
else
{
ElMessage
.
warning
(
"找不到当前人员的类型值!"
);
return
;
}
window
.
sessionStorage
.
setItem
(
"curTabName"
,
author
?.
name
||
""
);
const
route
=
router
.
resolve
({
path
:
"/characterPage"
,
query
:
{
type
,
personId
}
});
window
.
open
(
route
.
href
,
"_blank"
);
}
catch
(
error
)
{
console
.
error
(
"点击报告作者头像跳转失败"
,
error
);
}
};
const
riskSignal
=
computed
(()
=>
{
const
riskSignal
=
computed
(()
=>
{
const
info
=
props
.
thinkInfo
||
{};
const
info
=
props
.
thinkInfo
||
{};
return
info
.
riskSignal
;
return
info
.
riskSignal
;
...
@@ -345,6 +444,8 @@ const box2Data = ref([
...
@@ -345,6 +444,8 @@ const box2Data = ref([
]);
]);
// 报告关键词云
// 报告关键词云
const
box5Data
=
ref
([]);
const
box5Data
=
ref
([]);
/** 词云子组件不 watch 数据,每次接口成功有数据时递增 key,强制重新挂载以触发 onMounted */
const
box5WordCloudKey
=
ref
(
0
);
//获取科技领域词云
//获取科技领域词云
const
handleGetThinkTankReportIndustryCloud
=
async
()
=>
{
const
handleGetThinkTankReportIndustryCloud
=
async
()
=>
{
...
@@ -362,10 +463,9 @@ const handleGetThinkTankReportIndustryCloud = async () => {
...
@@ -362,10 +463,9 @@ const handleGetThinkTankReportIndustryCloud = async () => {
}));
}));
// 该接口数据用于「报告关键词云」
// 该接口数据用于「报告关键词云」
box5Data
.
value
=
data
;
box5Data
.
value
=
data
;
if
(
data
.
length
)
{
await
nextTick
();
box5WordCloudKey
.
value
+=
1
;
const
box5Chart
=
getWordCloudChart
(
box5Data
.
value
);
}
setChart
(
box5Chart
,
"box5Chart"
);
}
else
{
}
else
{
box5Data
.
value
=
[];
box5Data
.
value
=
[];
}
}
...
@@ -462,7 +562,7 @@ const handleGetThinkTankReportViewpoint = async () => {
...
@@ -462,7 +562,7 @@ const handleGetThinkTankReportViewpoint = async () => {
total
.
value
=
res
.
data
.
totalElements
||
0
;
total
.
value
=
res
.
data
.
totalElements
||
0
;
// 重置展开状态
// 重置展开状态
expanded
Index
.
value
=
null
;
expanded
OpinionKeys
.
value
=
new
Set
()
;
}
}
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"获取主要观点error"
,
error
);
console
.
error
(
"获取主要观点error"
,
error
);
...
@@ -492,7 +592,7 @@ onMounted(() => {
...
@@ -492,7 +592,7 @@ onMounted(() => {
.wrap
{
.wrap
{
display
:
flex
;
display
:
flex
;
justify-content
:
center
;
justify-content
:
center
;
gap
:
16px
;
padding-bottom
:
16px
;
padding-bottom
:
16px
;
flex-direction
:
column
;
flex-direction
:
column
;
...
@@ -520,7 +620,7 @@ onMounted(() => {
...
@@ -520,7 +620,7 @@ onMounted(() => {
.box1
{
.box1
{
margin-top
:
17px
;
margin-top
:
17px
;
width
:
480px
;
width
:
480px
;
height
:
486px
;
// border: 1px solid rgba(234, 236, 238, 1);
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// border-radius: 10px;
...
@@ -528,7 +628,7 @@ onMounted(() => {
...
@@ -528,7 +628,7 @@ onMounted(() => {
// background: rgba(255, 255, 255, 1);
// background: rgba(255, 255, 255, 1);
.box1-main
{
.box1-main
{
width
:
480px
;
width
:
480px
;
height
:
438px
;
.text-box
{
.text-box
{
width
:
437px
;
width
:
437px
;
...
@@ -635,8 +735,8 @@ onMounted(() => {
...
@@ -635,8 +735,8 @@ onMounted(() => {
.author-box
{
.author-box
{
width
:
437px
;
width
:
437px
;
height
:
auto
;
height
:
auto
;
/* 改为自适应高度,不要固定 220px */
max-height
:
220px
;
margin-top
:
34px
;
margin-top
:
34px
;
margin-left
:
18px
;
margin-left
:
18px
;
display
:
grid
;
display
:
grid
;
...
@@ -646,6 +746,7 @@ onMounted(() => {
...
@@ -646,6 +746,7 @@ onMounted(() => {
/* 左右间距(同一行) */
/* 左右间距(同一行) */
row-gap
:
8px
;
row-gap
:
8px
;
/* 上下间距(同一列) */
/* 上下间距(同一列) */
margin-bottom
:
38px
;
.author-item
{
.author-item
{
width
:
213px
;
width
:
213px
;
...
@@ -659,6 +760,7 @@ onMounted(() => {
...
@@ -659,6 +760,7 @@ onMounted(() => {
margin-top
:
3px
;
margin-top
:
3px
;
margin-left
:
3px
;
margin-left
:
3px
;
display
:
inline-block
;
display
:
inline-block
;
cursor
:
pointer
;
img
{
img
{
...
@@ -726,9 +828,9 @@ onMounted(() => {
...
@@ -726,9 +828,9 @@ onMounted(() => {
box-sizing
:
border-box
;
box-sizing
:
border-box
;
overflow
:
hidden
;
overflow
:
hidden
;
#
box5Chart
{
.
box5Chart
{
width
:
100%
;
width
:
418px
;
height
:
100%
;
height
:
292px
;
margin
:
0
auto
;
margin
:
0
auto
;
overflow
:
hidden
;
overflow
:
hidden
;
}
}
...
@@ -738,7 +840,7 @@ onMounted(() => {
...
@@ -738,7 +840,7 @@ onMounted(() => {
.box2
{
.box2
{
width
:
480px
;
width
:
480px
;
height
:
648px
;
// border: 1px solid rgba(234, 236, 238, 1);
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
...
@@ -749,6 +851,7 @@ onMounted(() => {
...
@@ -749,6 +851,7 @@ onMounted(() => {
margin-top
:
5px
;
margin-top
:
5px
;
margin-left
:
23px
;
margin-left
:
23px
;
.box2-item
{
.box2-item
{
height
:
103px
;
height
:
103px
;
width
:
100%
;
width
:
100%
;
...
@@ -834,6 +937,7 @@ onMounted(() => {
...
@@ -834,6 +937,7 @@ onMounted(() => {
.box2-btn
{
.box2-btn
{
margin-top
:
16px
;
margin-top
:
16px
;
margin-bottom
:
21px
;
margin-left
:
23px
;
margin-left
:
23px
;
width
:
436px
;
width
:
436px
;
height
:
36px
;
height
:
36px
;
...
@@ -880,7 +984,7 @@ onMounted(() => {
...
@@ -880,7 +984,7 @@ onMounted(() => {
.box3
{
.box3
{
width
:
1103px
;
width
:
1103px
;
height
:
890px
;
// border: 1px solid rgba(234, 236, 238, 1);
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// border-radius: 10px;
...
@@ -888,13 +992,24 @@ onMounted(() => {
...
@@ -888,13 +992,24 @@ onMounted(() => {
// background: rgba(255, 255, 255, 1);
// background: rgba(255, 255, 255, 1);
.box3-main
{
.box3-main
{
width
:
1058px
;
width
:
1058px
;
height
:
830px
;
margin-top
:
3px
;
margin-top
:
3px
;
margin-left
:
22px
;
margin-left
:
22px
;
padding-bottom
:
22px
;
padding-bottom
:
22px
;
:deep
(
.summary-main
)
{
margin-bottom
:
25px
;
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
/* Regular 常规 */
font-size
:
16px
;
line-height
:
30px
;
letter-spacing
:
0px
;
text-align
:
justify
;
color
:
rgb
(
59
,
65
,
75
);
/* 两端对齐 */
}
.box3-top
{
.box3-top
{
width
:
1058px
;
width
:
1058px
;
...
@@ -943,7 +1058,8 @@ onMounted(() => {
...
@@ -943,7 +1058,8 @@ onMounted(() => {
.box4
{
.box4
{
width
:
1103px
;
width
:
1103px
;
height
:
965px
;
height
:
auto
;
// border: 1px solid rgba(234, 236, 238, 1);
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
...
@@ -984,12 +1100,13 @@ onMounted(() => {
...
@@ -984,12 +1100,13 @@ onMounted(() => {
.box4-main
{
.box4-main
{
width
:
1057px
;
width
:
1057px
;
height
:
auto
;
margin
:
0
auto
;
margin
:
0
auto
;
.box4-main-main
{
.box4-main-main
{
height
:
767px
;
height
:
auto
;
overflow
:
hidden
;
overflow
:
visible
;
.box4-item
{
.box4-item
{
width
:
1057px
;
width
:
1057px
;
...
@@ -998,6 +1115,11 @@ onMounted(() => {
...
@@ -998,6 +1115,11 @@ onMounted(() => {
display
:
flex
;
display
:
flex
;
flex-direction
:
column
;
flex-direction
:
column
;
position
:
relative
;
position
:
relative
;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
&
:first-child
{
border-top
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
}
.top-row
{
.top-row
{
display
:
flex
;
display
:
flex
;
...
@@ -1005,7 +1127,7 @@ onMounted(() => {
...
@@ -1005,7 +1127,7 @@ onMounted(() => {
}
}
.left
{
.left
{
margin-top
:
24
px
;
margin-top
:
19
px
;
margin-left
:
15px
;
margin-left
:
15px
;
width
:
24px
;
width
:
24px
;
height
:
24px
;
height
:
24px
;
...
@@ -1021,9 +1143,9 @@ onMounted(() => {
...
@@ -1021,9 +1143,9 @@ onMounted(() => {
}
}
.center
{
.center
{
min-height
:
6
4
px
;
min-height
:
6
2
px
;
margin-left
:
1
3
px
;
margin-left
:
1
8
px
;
display
:
flex
;
display
:
flex
;
align-items
:
center
;
align-items
:
center
;
// overflow: hidden;
// overflow: hidden;
...
@@ -1031,14 +1153,14 @@ onMounted(() => {
...
@@ -1031,14 +1153,14 @@ onMounted(() => {
// white-space: nowrap;
// white-space: nowrap;
.title
{
.title
{
margin-top
:
12px
;
width
:
918px
;
width
:
918px
;
// height: 55px;
// height: 55px;
color
:
rgba
(
59
,
65
,
75
,
1
);
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-size
:
16px
;
font-weight
:
700
;
font-weight
:
700
;
line-height
:
26
px
;
line-height
:
30
px
;
letter-spacing
:
0px
;
letter-spacing
:
0px
;
text-align
:
left
;
text-align
:
left
;
overflow
:
hidden
;
overflow
:
hidden
;
...
@@ -1101,9 +1223,10 @@ onMounted(() => {
...
@@ -1101,9 +1223,10 @@ onMounted(() => {
}
}
.desc
{
.desc
{
width
:
950px
;
margin-top
:
22px
;
padding-top
:
22px
;
margin-left
:
52px
;
// 24(left) + 13(center margin) + 一点间距
padding-bottom
:
23px
;
padding-left
:
56px
;
// 24(left) + 13(center margin) + 一点间距
color
:
rgb
(
59
,
65
,
75
);
color
:
rgb
(
59
,
65
,
75
);
font-family
:
"Source Han Sans CN"
;
font-family
:
"Source Han Sans CN"
;
...
@@ -1114,13 +1237,13 @@ onMounted(() => {
...
@@ -1114,13 +1237,13 @@ onMounted(() => {
letter-spacing
:
0px
;
letter-spacing
:
0px
;
text-align
:
justify
;
text-align
:
justify
;
/* 两端对齐 */
/* 两端对齐 */
}
}
.box4-item
{
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
border-top
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
border-top
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
}
.title
:deep
(
.opinion-keyword-highlight
),
.desc
:deep
(
.opinion-keyword-highlight
)
{
background-color
:
#fff59d
;
}
}
}
}
}
...
...
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
浏览文件 @
c675e799
...
@@ -120,15 +120,7 @@
...
@@ -120,15 +120,7 @@
</
template
>
</
template
>
<
template
v-else
>
<
template
v-else
>
<div
class=
"box3-chart-row"
>
<div
class=
"box3-chart-row"
>
<button
v-if=
"box3LegendShowNav"
type=
"button"
class=
"box3-legend-nav-btn"
:disabled=
"!box3LegendCanPrev"
aria-label=
"上一页图例"
@
click=
"handleBox3LegendPrev"
>
‹
</button>
<div
id=
"box3Chart"
class=
"box3-chart-canvas"
></div>
<div
id=
"box3Chart"
class=
"box3-chart-canvas"
></div>
<button
v-if=
"box3LegendShowNav"
type=
"button"
class=
"box3-legend-nav-btn"
:disabled=
"!box3LegendCanNext"
aria-label=
"下一页图例"
@
click=
"handleBox3LegendNext"
>
›
</button>
</div>
</div>
<div
class=
"source"
>
<div
class=
"source"
>
<TipTab
:text=
"POLICY_TRACKING_TIP_BOX3"
/>
<TipTab
:text=
"POLICY_TRACKING_TIP_BOX3"
/>
...
@@ -188,17 +180,11 @@
...
@@ -188,17 +180,11 @@
<div
class=
"title"
>
{{ "科技领域" }}
</div>
<div
class=
"title"
>
{{ "科技领域" }}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<el-checkbox-group
<el-checkbox-group
class=
"checkbox-group"
v-model=
"selectedAreaList"
@
change=
"handleChangePolicyAreaList"
>
class=
"checkbox-group"
v-model=
"selectedAreaList"
@
change=
"handleChangePolicyAreaList"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"POLICY_FILTER_ALL_AREA"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"POLICY_FILTER_ALL_AREA"
>
{{ POLICY_FILTER_ALL_AREA }}
{{ POLICY_FILTER_ALL_AREA }}
</el-checkbox>
</el-checkbox>
<el-checkbox
<el-checkbox
class=
"filter-checkbox"
v-for=
"research in areaList"
:key=
"research.id"
class=
"filter-checkbox"
v-for=
"research in areaList"
:key=
"research.id"
:label=
"research.id"
>
:label=
"research.id"
>
{{ research.name }}
{{ research.name }}
</el-checkbox>
</el-checkbox>
...
@@ -211,17 +197,12 @@
...
@@ -211,17 +197,12 @@
<div
class=
"title"
>
{{ "涉及部门" }}
</div>
<div
class=
"title"
>
{{ "涉及部门" }}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<el-checkbox-group
<el-checkbox-group
class=
"checkbox-group"
v-model=
"selectedDepartmentList"
class=
"checkbox-group"
v-model=
"selectedDepartmentList"
@
change=
"handleChangePolicyDepartmentList"
>
@
change=
"handleChangePolicyDepartmentList"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"POLICY_FILTER_ALL_DEPT"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"POLICY_FILTER_ALL_DEPT"
>
{{ POLICY_FILTER_ALL_DEPT }}
{{ POLICY_FILTER_ALL_DEPT }}
</el-checkbox>
</el-checkbox>
<el-checkbox
<el-checkbox
class=
"filter-checkbox"
v-for=
"department in selectableDepartment"
:key=
"department.id"
class=
"filter-checkbox"
v-for=
"department in selectableDepartment"
:key=
"department.id"
:label=
"department.id"
>
:label=
"department.id"
>
{{ department.name }}
{{ department.name }}
</el-checkbox>
</el-checkbox>
...
@@ -234,18 +215,11 @@
...
@@ -234,18 +215,11 @@
<div
class=
"title"
>
{{ "发布时间" }}
</div>
<div
class=
"title"
>
{{ "发布时间" }}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<el-checkbox-group
<el-checkbox-group
class=
"checkbox-group"
v-model=
"selectedYearList"
@
change=
"handleChangePolicyYearList"
>
class=
"checkbox-group"
v-model=
"selectedYearList"
@
change=
"handleChangePolicyYearList"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"POLICY_FILTER_ALL_TIME"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"POLICY_FILTER_ALL_TIME"
>
{{ POLICY_FILTER_ALL_TIME }}
{{ POLICY_FILTER_ALL_TIME }}
</el-checkbox>
</el-checkbox>
<el-checkbox
<el-checkbox
class=
"filter-checkbox"
v-for=
"year in selectableYears"
:key=
"year"
:label=
"year"
>
class=
"filter-checkbox"
v-for=
"year in selectableYears"
:key=
"year"
:label=
"year"
>
{{ year }}
{{ year }}
</el-checkbox>
</el-checkbox>
</el-checkbox-group>
</el-checkbox-group>
...
@@ -260,10 +234,10 @@
...
@@ -260,10 +234,10 @@
</div>
</div>
<div
class=
"item-right"
>
<div
class=
"item-right"
>
<div>
<div>
<div
class=
"title"
>
{{ item.titleZh }}
</div>
<div
class=
"title"
v-html=
"highlightPolicyText(item.titleZh)"
>
</div>
<div
class=
"info"
>
<div
class=
"info"
>
<div
class=
"text"
>
<div
class=
"text"
>
{{ item.reportDateDisplay }}·{{ item.contentZh }}
<span
v-html=
"highlightPolicyText(`${item.reportDateDisplay}·${item.contentZh}`)"
></span>
</div>
</div>
<div
class=
"more"
@
click=
"toDetail(item)"
>
<div
class=
"more"
@
click=
"toDetail(item)"
>
...
@@ -313,7 +287,7 @@
...
@@ -313,7 +287,7 @@
import
{
ref
,
watch
,
onMounted
,
computed
,
nextTick
}
from
"vue"
;
import
{
ref
,
watch
,
onMounted
,
computed
,
nextTick
}
from
"vue"
;
import
setChart
from
"@/utils/setChart"
;
import
setChart
from
"@/utils/setChart"
;
import
getPieChart
from
"./utils/piechart"
;
import
getPieChart
from
"./utils/piechart"
;
import
getMultiLineChart
,
{
POLICY_TRACKING_LEGEND_PAGE_SIZE
}
from
"./utils/multiLineChart"
;
import
getMultiLineChart
from
"./utils/multiLineChart"
;
import
{
import
{
getThinkPolicyIndustry
,
getThinkPolicyIndustry
,
getThinkPolicyIndustryTotal
,
getThinkPolicyIndustryTotal
,
...
@@ -594,41 +568,7 @@ const hasBox3ChartData = computed(() => {
...
@@ -594,41 +568,7 @@ const hasBox3ChartData = computed(() => {
);
);
});
});
const
box3LegendPageIndex
=
ref
(
0
);
/** 研究领域变化趋势:图例不分页,超多时最多三行显示 */
const
box3LegendPageCount
=
computed
(()
=>
{
const
n
=
box3Data
.
value
?.
data
?.
length
??
0
;
return
Math
.
max
(
1
,
Math
.
ceil
(
n
/
POLICY_TRACKING_LEGEND_PAGE_SIZE
));
});
const
box3LegendShowNav
=
computed
(()
=>
{
const
n
=
box3Data
.
value
?.
data
?.
length
??
0
;
return
n
>
POLICY_TRACKING_LEGEND_PAGE_SIZE
;
});
const
box3LegendCanPrev
=
computed
(()
=>
box3LegendPageIndex
.
value
>
0
);
const
box3LegendCanNext
=
computed
(
()
=>
box3LegendPageIndex
.
value
<
box3LegendPageCount
.
value
-
1
);
const
handleBox3LegendPrev
=
()
=>
{
if
(
box3LegendPageIndex
.
value
<=
0
)
{
return
;
}
box3LegendPageIndex
.
value
-=
1
;
renderBox3Chart
();
};
const
handleBox3LegendNext
=
()
=>
{
if
(
box3LegendPageIndex
.
value
>=
box3LegendPageCount
.
value
-
1
)
{
return
;
}
box3LegendPageIndex
.
value
+=
1
;
renderBox3Chart
();
};
/** 研究领域变化趋势:图例分页与概览「数量变化趋势」一致 */
const
renderBox3Chart
=
async
()
=>
{
const
renderBox3Chart
=
async
()
=>
{
const
v
=
box3Data
.
value
;
const
v
=
box3Data
.
value
;
if
(
if
(
...
@@ -640,22 +580,8 @@ const renderBox3Chart = async () => {
...
@@ -640,22 +580,8 @@ const renderBox3Chart = async () => {
)
{
)
{
return
;
return
;
}
}
const
totalNames
=
v
.
data
.
length
;
const
pages
=
Math
.
max
(
1
,
Math
.
ceil
(
totalNames
/
POLICY_TRACKING_LEGEND_PAGE_SIZE
));
if
(
box3LegendPageIndex
.
value
>=
pages
)
{
box3LegendPageIndex
.
value
=
pages
-
1
;
}
if
(
box3LegendPageIndex
.
value
<
0
)
{
box3LegendPageIndex
.
value
=
0
;
}
await
nextTick
();
await
nextTick
();
const
box3Chart
=
getMultiLineChart
(
const
box3Chart
=
getMultiLineChart
({
title
:
v
.
title
,
data
:
v
.
data
});
{
title
:
v
.
title
,
data
:
v
.
data
},
{
legendShowCount
:
POLICY_TRACKING_LEGEND_PAGE_SIZE
,
legendPageIndex
:
box3LegendPageIndex
.
value
}
);
setChart
(
box3Chart
,
"box3Chart"
);
setChart
(
box3Chart
,
"box3Chart"
);
};
};
...
@@ -729,7 +655,6 @@ const handleGetThinkPolicyIndustryChange = async () => {
...
@@ -729,7 +655,6 @@ const handleGetThinkPolicyIndustryChange = async () => {
return
;
return
;
}
}
box3Data
.
value
=
frontendData
;
box3Data
.
value
=
frontendData
;
box3LegendPageIndex
.
value
=
0
;
await
renderBox3Chart
();
await
renderBox3Chart
();
}
else
{
}
else
{
box3Data
.
value
=
{
title
:
[],
data
:
[]
};
box3Data
.
value
=
{
title
:
[],
data
:
[]
};
...
@@ -740,6 +665,19 @@ const handleGetThinkPolicyIndustryChange = async () => {
...
@@ -740,6 +665,19 @@ const handleGetThinkPolicyIndustryChange = async () => {
}
}
};
};
/** 兼容 getChartAnalysis 返回对象:从 data[0] 提取「解读」文本 */
const
getInterpretationTextFromChartResponse
=
(
res
)
=>
{
const
list
=
res
?.
data
;
const
first
=
Array
.
isArray
(
list
)
?
list
[
0
]
:
null
;
return
(
first
?.[
"解读"
]
||
first
?.[
"interpretation"
]
||
first
?.[
"analysis"
]
||
first
?.[
"content"
]
||
""
);
};
/** 政策追踪-领域分布饼图解读(与概览页 box6 入参一致) */
/** 政策追踪-领域分布饼图解读(与概览页 box6 入参一致) */
const
fetchPolicyPtBox1ChartInterpretation
=
async
()
=>
{
const
fetchPolicyPtBox1ChartInterpretation
=
async
()
=>
{
const
list
=
Array
.
isArray
(
box1Data
.
value
)
?
box1Data
.
value
:
[];
const
list
=
Array
.
isArray
(
box1Data
.
value
)
?
box1Data
.
value
:
[];
...
@@ -767,17 +705,9 @@ const fetchPolicyPtBox1ChartInterpretation = async () => {
...
@@ -767,17 +705,9 @@ const fetchPolicyPtBox1ChartInterpretation = async () => {
}))
}))
};
};
try
{
try
{
const
text
=
await
getChartAnalysis
(
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
{
text
:
JSON
.
stringify
(
chartPayload
)
},
const
text
=
getInterpretationTextFromChartResponse
(
res
);
{
aiContentPolicyPt1
.
value
=
text
||
aiContentPolicyPt1
.
value
||
"未返回有效解读内容"
;
onInterpretationDelta
:
(
chunk
)
=>
{
if
(
chunk
)
{
aiContentPolicyPt1
.
value
=
chunk
;
}
}
}
);
aiContentPolicyPt1
.
value
=
text
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"政策追踪领域分布图表解读请求失败"
,
error
);
console
.
error
(
"政策追踪领域分布图表解读请求失败"
,
error
);
aiContentPolicyPt1
.
value
=
"解读加载失败"
;
aiContentPolicyPt1
.
value
=
"解读加载失败"
;
...
@@ -813,17 +743,9 @@ const fetchPolicyPtBox2ChartInterpretation = async () => {
...
@@ -813,17 +743,9 @@ const fetchPolicyPtBox2ChartInterpretation = async () => {
}))
}))
};
};
try
{
try
{
const
text
=
await
getChartAnalysis
(
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
{
text
:
JSON
.
stringify
(
chartPayload
)
},
const
text
=
getInterpretationTextFromChartResponse
(
res
);
{
aiContentPolicyPt2
.
value
=
text
||
aiContentPolicyPt2
.
value
||
"未返回有效解读内容"
;
onInterpretationDelta
:
(
chunk
)
=>
{
if
(
chunk
)
{
aiContentPolicyPt2
.
value
=
chunk
;
}
}
}
);
aiContentPolicyPt2
.
value
=
text
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"政策追踪部门分布图表解读请求失败"
,
error
);
console
.
error
(
"政策追踪部门分布图表解读请求失败"
,
error
);
aiContentPolicyPt2
.
value
=
"解读加载失败"
;
aiContentPolicyPt2
.
value
=
"解读加载失败"
;
...
@@ -867,17 +789,9 @@ const fetchPolicyPtBox3ChartInterpretation = async () => {
...
@@ -867,17 +789,9 @@ const fetchPolicyPtBox3ChartInterpretation = async () => {
})
})
};
};
try
{
try
{
const
text
=
await
getChartAnalysis
(
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
{
text
:
JSON
.
stringify
(
chartPayload
)
},
const
text
=
getInterpretationTextFromChartResponse
(
res
);
{
aiContentPolicyPt3
.
value
=
text
||
aiContentPolicyPt3
.
value
||
"未返回有效解读内容"
;
onInterpretationDelta
:
(
chunk
)
=>
{
if
(
chunk
)
{
aiContentPolicyPt3
.
value
=
chunk
;
}
}
}
);
aiContentPolicyPt3
.
value
=
text
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"政策追踪研究领域趋势图表解读请求失败"
,
error
);
console
.
error
(
"政策追踪研究领域趋势图表解读请求失败"
,
error
);
aiContentPolicyPt3
.
value
=
"解读加载失败"
;
aiContentPolicyPt3
.
value
=
"解读加载失败"
;
...
@@ -889,6 +803,27 @@ const fetchPolicyPtBox3ChartInterpretation = async () => {
...
@@ -889,6 +803,27 @@ const fetchPolicyPtBox3ChartInterpretation = async () => {
// 搜索政策建议
// 搜索政策建议
const
searchPolicy
=
ref
(
""
);
const
searchPolicy
=
ref
(
""
);
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
highlightPolicyText
=
(
text
)
=>
{
const
safeText
=
escapeHtml
(
text
);
const
keyword
=
(
searchPolicy
.
value
||
""
).
trim
();
if
(
!
keyword
)
return
safeText
;
const
pattern
=
new
RegExp
(
`(
${
escapeRegExp
(
keyword
)}
)`
,
"gi"
);
return
safeText
.
replace
(
pattern
,
'<span class="keyword-highlight">$1</span>'
);
};
const
yearList
=
ref
([
const
yearList
=
ref
([
{
{
label
:
"近一年发布"
,
label
:
"近一年发布"
,
...
@@ -1507,40 +1442,10 @@ onMounted(() => {
...
@@ -1507,40 +1442,10 @@ onMounted(() => {
display
:
flex
;
display
:
flex
;
flex-direction
:
row
;
flex-direction
:
row
;
align-items
:
flex-start
;
align-items
:
flex-start
;
gap
:
8px
;
width
:
100%
;
width
:
100%
;
box-sizing
:
border-box
;
box-sizing
:
border-box
;
}
}
.box3-legend-nav-btn
{
flex-shrink
:
0
;
width
:
32px
;
height
:
32px
;
margin-top
:
6px
;
padding
:
0
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
220
,
223
,
230
,
1
);
border-radius
:
6px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
color
:
rgba
(
59
,
65
,
75
,
1
);
font-size
:
20px
;
line-height
:
1
;
cursor
:
pointer
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.box3-legend-nav-btn
:hover:not
(
:disabled
)
{
border-color
:
var
(
--
color-main-primary
,
#055fc2
);
color
:
var
(
--
color-main-primary
,
#055fc2
);
}
.box3-legend-nav-btn
:disabled
{
opacity
:
0
.35
;
cursor
:
not
-
allowed
;
}
.box3-chart-canvas
{
.box3-chart-canvas
{
flex
:
1
;
flex
:
1
;
min-width
:
0
;
min-width
:
0
;
...
@@ -2128,6 +2033,10 @@ onMounted(() => {
...
@@ -2128,6 +2033,10 @@ onMounted(() => {
}
}
}
}
:deep
(
.keyword-highlight
)
{
background-color
:
#fff59d
;
}
.tag-box
{
.tag-box
{
margin-top
:
7px
;
margin-top
:
7px
;
display
:
flex
;
display
:
flex
;
...
@@ -2237,4 +2146,8 @@ onMounted(() => {
...
@@ -2237,4 +2146,8 @@ onMounted(() => {
height
:
8px
!
important
;
height
:
8px
!
important
;
margin-right
:
4px
;
margin-right
:
4px
;
}
}
:deep
(
.el-checkbox__label
)
{
font-size
:
16px
;
}
</
style
>
</
style
>
src/views/thinkTank/ThinkTankDetail/PolicyTracking/utils/multiLineChart.js
浏览文件 @
c675e799
import
*
as
echarts
from
'echarts'
import
*
as
echarts
from
'echarts'
/** 政策追踪「研究领域变化趋势」图例分页:每页条数(与概览数量变化趋势逻辑一致,条数按产品要求为 4) */
export
const
POLICY_TRACKING_LEGEND_PAGE_SIZE
=
4
const
colorList
=
[
const
colorList
=
[
'rgba(5, 95, 194, 1)'
,
'rgba(5, 95, 194, 1)'
,
'rgba(19, 168, 168, 1)'
,
'rgba(19, 168, 168, 1)'
,
...
@@ -35,22 +32,16 @@ const parseRgba = (colorStr) => {
...
@@ -35,22 +32,16 @@ const parseRgba = (colorStr) => {
/**
/**
* @param {{ title: unknown[], data: Array<{ name: string, value: unknown[], color?: string }> }} chartInput
* @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
title
=
chartInput
.
title
const
series
=
chartInput
.
data
||
[]
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
allNames
=
series
.
map
((
item
)
=>
item
.
name
)
const
pageCount
=
Math
.
max
(
1
,
Math
.
ceil
(
allNames
.
length
/
legendShowCount
)
)
const
lineSize
=
Math
.
ceil
(
allNames
.
length
/
3
)
const
legend
PageIndex
=
Math
.
min
(
Math
.
max
(
0
,
rawPageIndex
),
pageCount
-
1
)
const
legend
Line1
=
allNames
.
slice
(
0
,
lineSize
)
const
legend
Start
=
legendPageIndex
*
legendShowCount
const
legend
Line2
=
allNames
.
slice
(
lineSize
,
lineSize
*
2
)
const
legend
Data
=
allNames
.
slice
(
legendStart
,
legendStart
+
legendShowCount
)
const
legend
Line3
=
allNames
.
slice
(
lineSize
*
2
)
const
xCount
=
Array
.
isArray
(
title
)
?
title
.
length
:
0
const
xCount
=
Array
.
isArray
(
title
)
?
title
.
length
:
0
const
labelFontSize
=
xCount
>
8
?
10
:
xCount
>
5
?
11
:
12
const
labelFontSize
=
xCount
>
8
?
10
:
xCount
>
5
?
11
:
12
...
@@ -91,16 +82,17 @@ const getMultiLineChart = (chartInput, options = {}) => {
...
@@ -91,16 +82,17 @@ const getMultiLineChart = (chartInput, options = {}) => {
},
},
/* 贴满 #box3Chart:四边 0,由 containLabel 在网格内为轴文字留位,避免左侧/底部大块留白 */
/* 贴满 #box3Chart:四边 0,由 containLabel 在网格内为轴文字留位,避免左侧/底部大块留白 */
grid
:
{
grid
:
{
top
:
50
,
top
:
92
,
right
:
10
,
right
:
10
,
bottom
:
0
,
bottom
:
0
,
left
:
20
,
left
:
20
,
containLabel
:
true
containLabel
:
true
},
},
legend
:
{
legend
:
[
{
show
:
true
,
show
:
true
,
type
:
'plain'
,
type
:
'plain'
,
data
:
legendData
,
data
:
legendLine1
,
top
:
4
,
top
:
4
,
left
:
'center'
,
left
:
'center'
,
icon
:
'circle'
,
icon
:
'circle'
,
...
@@ -116,6 +108,45 @@ const getMultiLineChart = (chartInput, options = {}) => {
...
@@ -116,6 +108,45 @@ const getMultiLineChart = (chartInput, options = {}) => {
itemWidth
:
12
,
itemWidth
:
12
,
itemHeight
:
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
,
color
:
colorList
,
xAxis
:
[
xAxis
:
[
{
{
...
@@ -142,6 +173,16 @@ const getMultiLineChart = (chartInput, options = {}) => {
...
@@ -142,6 +173,16 @@ const getMultiLineChart = (chartInput, options = {}) => {
yAxis
:
[
yAxis
:
[
{
{
type
:
'value'
,
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
,
splitNumber
:
4
,
axisLabel
:
{
axisLabel
:
{
color
:
'rgb(132, 136, 142)'
,
color
:
'rgb(132, 136, 142)'
,
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/CongressHearing/index.vue
浏览文件 @
c675e799
...
@@ -21,16 +21,17 @@
...
@@ -21,16 +21,17 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTypeIds)"
class=
"checkbox-group"
@
change=
"val => handleToggleAll(val, researchTypeIds)"
>
:model-value=
"selectedResearchIds"
全部领域
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
v-model=
"selectedResearchIds"
:label=
"type.id"
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
class=
"filter-checkbox"
@
change=
"handleGetThinkDynamicsReport()"
>
{{
type
.
name
}}
{{
type
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
</div>
<div
class=
"select-time-box"
>
<div
class=
"select-time-box"
>
...
@@ -39,16 +40,17 @@
...
@@ -39,16 +40,17 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTimeIds)"
class=
"checkbox-group"
@
change=
"val => handleToggleAll(val, researchTimeIds)"
>
:model-value=
"selectedResearchTimeIds"
全部时间
@
change=
"handleTimeGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
v-model=
"selectedResearchTimeIds"
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleGetThinkDynamicsReport()"
>
{{
type
.
name
}}
{{
type
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
<!--
<div
class=
"input-main"
>
<!--
<div
class=
"input-main"
>
<el-input
placeholder=
"输入作者名字"
v-model=
"author"
@
input=
"handleGetThinkDynamicsReport()"
/>
<el-input
placeholder=
"输入作者名字"
v-model=
"author"
@
input=
"handleGetThinkDynamicsReport()"
/>
...
@@ -60,16 +62,17 @@
...
@@ -60,16 +62,17 @@
<div
class=
"title"
>
{{
"听证会部门"
}}
</div>
<div
class=
"title"
>
{{
"听证会部门"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchHearingIds)"
class=
"checkbox-group"
@
change=
"val => handleToggleAll(val, researchHearingIds)"
>
:model-value=
"selectedResearchHearingIds"
全部部门
@
change=
"handleDeptGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_DEPT"
>
{{
RESOURCE_FILTER_ALL_DEPT
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
v-for=
"type in researchHearingList"
:key=
"type.id"
v-model=
"selectedResearchHearingIds"
<el-checkbox
v-for=
"type in researchHearingList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleGetThinkDynamicsReport()"
>
{{
type
.
name
}}
{{
type
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
<!--
<div
class=
"input-main"
>
<!--
<div
class=
"input-main"
>
<el-input
placeholder=
"输入作者名字"
v-model=
"author"
@
input=
"handleGetThinkDynamicsReport()"
/>
<el-input
placeholder=
"输入作者名字"
v-model=
"author"
@
input=
"handleGetThinkDynamicsReport()"
/>
...
@@ -84,11 +87,10 @@
...
@@ -84,11 +87,10 @@
<img
class=
"card-item-img"
src=
"../images/img congress.png"
alt=
"report image"
/>
<img
class=
"card-item-img"
src=
"../images/img congress.png"
alt=
"report image"
/>
<div
class=
"card-item-text"
>
<div
class=
"card-item-text"
>
<div
class=
"card-item-title"
>
<div
class=
"card-item-title"
>
<span
v-html=
"highlightText(item.title)"
></span>
{{
item
.
title
}}
</div>
</div>
<div
class=
"card-item-time"
>
<div
class=
"card-item-time"
>
{{
item
.
time
+
' · '
+
item
.
content
}}
<span
v-html=
"highlightText(item.time + ' · ' + item.content)"
></span>
<img
src=
"../images/image open.png"
alt=
"open icon"
class=
"card-open-image"
/>
<img
src=
"../images/image open.png"
alt=
"open icon"
class=
"card-open-image"
/>
</div>
</div>
<div
class=
"card-item-category"
>
{{
item
.
category
}}
</div>
<div
class=
"card-item-category"
>
{{
item
.
category
}}
</div>
...
@@ -113,6 +115,17 @@
...
@@ -113,6 +115,17 @@
</
template
>
</
template
>
<
script
setup
>
<
script
setup
>
import
{
ref
,
toRefs
,
watch
,
computed
}
from
"vue"
;
import
{
ref
,
toRefs
,
watch
,
computed
}
from
"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
({
const
props
=
defineProps
({
researchTypeList
:
{
researchTypeList
:
{
...
@@ -154,6 +167,10 @@ const props = defineProps({
...
@@ -154,6 +167,10 @@ const props = defineProps({
currentPage
:
{
currentPage
:
{
type
:
Number
,
type
:
Number
,
default
:
1
default
:
1
},
searchKeyword
:
{
type
:
String
,
default
:
""
}
}
});
});
...
@@ -180,69 +197,96 @@ function parseChineseDate(dateStr) {
...
@@ -180,69 +197,96 @@ function parseChineseDate(dateStr) {
return
new
Date
(
Number
(
match
[
1
]),
Number
(
match
[
2
])
-
1
,
Number
(
match
[
3
]));
return
new
Date
(
Number
(
match
[
1
]),
Number
(
match
[
2
])
-
1
,
Number
(
match
[
3
]));
}
}
const
selectedResearchIds
=
ref
([]);
const
selectedResearchIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedResearchTimeIds
=
ref
([]);
const
selectedResearchTimeIds
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
const
selectedResearchHearingIds
=
ref
([]);
const
selectedResearchHearingIds
=
ref
([
RESOURCE_FILTER_ALL_DEPT
]);
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
(
watch
(
()
=>
props
.
selectedFilters
,
()
=>
props
.
selectedFilters
,
val
=>
{
val
=>
{
selectedResearchIds
.
value
=
val
?.
researchTypeIds
?
[...
val
.
researchTypeIds
]
:
[];
selectedResearchIds
.
value
=
selectedResearchTimeIds
.
value
=
val
?.
researchTimeIds
?
[...
val
.
researchTimeIds
]
:
[];
val
?.
researchTypeIds
?.
length
>
0
selectedResearchHearingIds
.
value
=
val
?.
researchHearingIds
?
[...
val
.
researchHearingIds
]
:
[];
?
[...
val
.
researchTypeIds
]
:
[
RESOURCE_FILTER_ALL_AREA
];
selectedResearchTimeIds
.
value
=
val
?.
researchTimeIds
?.
length
>
0
?
[...
val
.
researchTimeIds
]
:
[
RESOURCE_FILTER_ALL_TIME
];
selectedResearchHearingIds
.
value
=
val
?.
researchHearingIds
?.
length
>
0
?
[...
val
.
researchHearingIds
]
:
[
RESOURCE_FILTER_ALL_DEPT
];
},
},
{
immediate
:
true
,
deep
:
true
}
{
immediate
:
true
,
deep
:
true
}
);
);
const
buildSelectedFiltersPayload
=
()
=>
({
const
buildSelectedFiltersPayload
=
()
=>
({
researchTypeIds
:
[...
selectedResearchIds
.
value
]
,
researchTypeIds
:
stripAllAreaForRequest
(
selectedResearchIds
.
value
)
,
researchTimeIds
:
[...
selectedResearchTimeIds
.
value
]
,
researchTimeIds
:
stripAllTimeForRequest
(
selectedResearchTimeIds
.
value
)
,
researchHearingIds
:
[...
selectedResearchHearingIds
.
value
]
researchHearingIds
:
stripAllDeptForRequest
(
selectedResearchHearingIds
.
value
)
});
});
const
researchTypeIds
=
computed
(()
=>
(
researchTypeList
.
value
||
[]).
map
(
item
=>
item
.
id
));
const
handleAreaGroupChange
=
(
val
)
=>
{
const
researchTimeIds
=
computed
(()
=>
(
researchTimeList
.
value
||
[]).
map
(
item
=>
item
.
id
));
selectedResearchIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
);
const
researchHearingIds
=
computed
(()
=>
(
researchHearingList
.
value
||
[]).
map
(
item
=>
item
.
id
));
handleGetThinkDynamicsReport
();
const
getTargetSelection
=
ids
=>
{
if
(
ids
===
researchTypeIds
.
value
)
return
selectedResearchIds
;
if
(
ids
===
researchTimeIds
.
value
)
return
selectedResearchTimeIds
;
return
selectedResearchHearingIds
;
};
};
const
isGroupAllSelected
=
ids
=>
const
handleTimeGroupChange
=
(
val
)
=>
{
ids
.
length
>
0
&&
ids
.
every
(
id
=>
getTargetSelection
(
ids
).
value
.
includes
(
id
));
selectedResearchTimeIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
);
handleGetThinkDynamicsReport
();
};
const
handleToggleAll
=
(
checked
,
ids
)
=>
{
const
handleDeptGroupChange
=
(
val
)
=>
{
if
(
!
ids
.
length
)
return
;
selectedResearchHearingIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_DEPT
);
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
];
handleGetThinkDynamicsReport
();
handleGetThinkDynamicsReport
();
};
};
const
filteredHearingData
=
computed
(()
=>
{
const
filteredHearingData
=
computed
(()
=>
{
const
areaSel
=
stripAllAreaForRequest
(
selectedResearchIds
.
value
);
const
timeSel
=
stripAllTimeForRequest
(
selectedResearchTimeIds
.
value
);
const
deptSel
=
stripAllDeptForRequest
(
selectedResearchHearingIds
.
value
);
const
rangeStart
=
getDateYearsAgo
(
selectedYear
.
value
||
1
);
const
rangeStart
=
getDateYearsAgo
(
selectedYear
.
value
||
1
);
return
(
hearingData
.
value
||
[]).
filter
(
item
=>
{
return
(
hearingData
.
value
||
[]).
filter
(
item
=>
{
const
itemDate
=
parseChineseDate
(
item
.
time
);
const
itemDate
=
parseChineseDate
(
item
.
time
);
const
matchTopRange
=
itemDate
?
itemDate
>=
rangeStart
:
true
;
const
matchTopRange
=
itemDate
?
itemDate
>=
rangeStart
:
true
;
const
matchYear
=
const
matchYear
=
selectedResearchTimeIds
.
value
.
length
===
0
||
timeSel
.
length
===
0
||
selectedResearchTimeIds
.
value
.
some
(
year
=>
String
(
item
.
time
||
""
).
startsWith
(
year
));
timeSel
.
some
(
sel
=>
{
if
(
sel
===
RESOURCE_FILTER_EARLIER
)
{
return
matchesEarlierChineseDate
(
item
.
time
);
}
return
String
(
item
.
time
||
""
).
startsWith
(
String
(
sel
));
});
const
matchDepartment
=
const
matchDepartment
=
selectedResearchHearingIds
.
value
.
length
===
0
||
deptSel
.
length
===
0
||
selectedResearchHearingIds
.
value
.
some
(
department
=>
deptSel
.
some
(
department
=>
String
(
item
.
content
||
""
).
includes
(
department
)
||
String
(
item
.
title
||
""
).
includes
(
department
)
String
(
item
.
content
||
""
).
includes
(
department
)
||
String
(
item
.
title
||
""
).
includes
(
department
)
);
);
const
matchType
=
const
matchType
=
selectedResearchIds
.
value
.
length
===
0
||
areaSel
.
length
===
0
||
selectedResearchIds
.
value
.
some
(
typeId
=>
areaSel
.
some
(
typeId
=>
String
(
item
.
category
||
""
).
includes
(
typeId
)
||
String
(
item
.
title
||
""
).
includes
(
typeId
)
String
(
item
.
category
||
""
).
includes
(
String
(
typeId
))
||
String
(
item
.
title
||
""
).
includes
(
String
(
typeId
)
)
);
);
return
matchTopRange
&&
matchYear
&&
matchDepartment
&&
matchType
;
return
matchTopRange
&&
matchYear
&&
matchDepartment
&&
matchType
;
});
});
...
@@ -466,6 +510,10 @@ const handleToReportDetail = item => {
...
@@ -466,6 +510,10 @@ const handleToReportDetail = item => {
}
}
}
}
:deep
(
.keyword-highlight
)
{
background-color
:
#fff59d
;
}
.right
{
.right
{
width
:
1224px
;
width
:
1224px
;
height
:
1377px
;
height
:
1377px
;
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/SurveyForm/index.vue
浏览文件 @
c675e799
...
@@ -21,16 +21,17 @@
...
@@ -21,16 +21,17 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTypeIds)"
class=
"checkbox-group"
@
change=
"val => handleToggleAll(val, researchTypeIds)"
>
:model-value=
"selectedResearchIds"
全部领域
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
v-model=
"selectedResearchIds"
:label=
"type.id"
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
class=
"filter-checkbox"
@
change=
"handleGetThinkDynamicsReport()"
>
{{
type
.
name
}}
{{
type
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
</div>
<div
class=
"select-time-box"
>
<div
class=
"select-time-box"
>
...
@@ -39,16 +40,17 @@
...
@@ -39,16 +40,17 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTimeIds)"
class=
"checkbox-group"
@
change=
"val => handleToggleAll(val, researchTimeIds)"
>
:model-value=
"selectedResearchTimeIds"
全部时间
@
change=
"handleTimeGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
v-model=
"selectedResearchTimeIds"
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleGetThinkDynamicsReport()"
>
{{
type
.
name
}}
{{
type
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
<!--
<div
class=
"input-main"
>
<!--
<div
class=
"input-main"
>
<el-input
placeholder=
"输入作者名字"
v-model=
"author"
@
input=
"handleGetThinkDynamicsReport()"
/>
<el-input
placeholder=
"输入作者名字"
v-model=
"author"
@
input=
"handleGetThinkDynamicsReport()"
/>
...
@@ -63,7 +65,7 @@
...
@@ -63,7 +65,7 @@
<img
:src=
"item.imageUrl"
alt=
""
/>
<img
:src=
"item.imageUrl"
alt=
""
/>
</div>
</div>
<div
class=
"footer-card-title"
>
<div
class=
"footer-card-title"
>
{{
item
.
name
}}
<span
v-html=
"highlightText(item.name)"
></span>
</div>
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
item
.
times
}}
</div>
<div
class=
"time"
>
{{
item
.
times
}}
</div>
...
@@ -84,7 +86,14 @@
...
@@ -84,7 +86,14 @@
</div>
</div>
</
template
>
</
template
>
<
script
setup
>
<
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
({
const
props
=
defineProps
({
researchTypeList
:
{
researchTypeList
:
{
...
@@ -114,6 +123,10 @@ const props = defineProps({
...
@@ -114,6 +123,10 @@ const props = defineProps({
currentPage
:
{
currentPage
:
{
type
:
Number
,
type
:
Number
,
default
:
1
default
:
1
},
searchKeyword
:
{
type
:
String
,
default
:
""
}
}
});
});
...
@@ -127,43 +140,58 @@ const emit = defineEmits([
...
@@ -127,43 +140,58 @@ const emit = defineEmits([
// 解构 props,保持模板里变量名不变
// 解构 props,保持模板里变量名不变
const
{
researchTypeList
,
researchTimeList
,
curFooterList
,
total
,
currentPage
}
=
toRefs
(
props
);
const
{
researchTypeList
,
researchTimeList
,
curFooterList
,
total
,
currentPage
}
=
toRefs
(
props
);
const
selectedResearchIds
=
ref
([]);
const
selectedResearchIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedResearchTimeIds
=
ref
([]);
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
(
watch
(
()
=>
props
.
selectedFilters
,
()
=>
props
.
selectedFilters
,
val
=>
{
val
=>
{
selectedResearchIds
.
value
=
val
?.
researchTypeIds
?
[...
val
.
researchTypeIds
]
:
[];
selectedResearchIds
.
value
=
selectedResearchTimeIds
.
value
=
val
?.
researchTimeIds
?
[...
val
.
researchTimeIds
]
:
[];
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
}
{
immediate
:
true
,
deep
:
true
}
);
);
const
buildSelectedFiltersPayload
=
()
=>
({
const
buildSelectedFiltersPayload
=
()
=>
({
researchTypeIds
:
[...
selectedResearchIds
.
value
]
,
researchTypeIds
:
stripAllAreaForRequest
(
selectedResearchIds
.
value
)
,
researchTimeIds
:
[...
selectedResearchTimeIds
.
value
]
,
researchTimeIds
:
stripAllTimeForRequest
(
selectedResearchTimeIds
.
value
)
,
researchHearingIds
:
[]
researchHearingIds
:
[]
});
});
const
researchTypeIds
=
computed
(()
=>
(
researchTypeList
.
value
||
[]).
map
(
item
=>
item
.
id
));
const
handleAreaGroupChange
=
(
val
)
=>
{
const
researchTimeIds
=
computed
(()
=>
(
researchTimeList
.
value
||
[]).
map
(
item
=>
item
.
id
));
selectedResearchIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
);
handleGetThinkDynamicsReport
();
const
getTargetSelection
=
ids
=>
(
ids
===
researchTypeIds
.
value
?
selectedResearchIds
:
selectedResearchTimeIds
);
};
const
isGroupAllSelected
=
ids
=>
ids
.
length
>
0
&&
ids
.
every
(
id
=>
getTargetSelection
(
ids
).
value
.
includes
(
id
));
const
handleToggleAll
=
(
checked
,
ids
)
=>
{
const
handleTimeGroupChange
=
(
val
)
=>
{
if
(
!
ids
.
length
)
return
;
selectedResearchTimeIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
);
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
];
handleGetThinkDynamicsReport
();
handleGetThinkDynamicsReport
();
};
};
...
@@ -326,6 +354,10 @@ const handleToReportDetail = item => {
...
@@ -326,6 +354,10 @@ const handleToReportDetail = item => {
}
}
}
}
:deep
(
.keyword-highlight
)
{
background-color
:
#fff59d
;
}
.right
{
.right
{
width
:
1284px
;
width
:
1284px
;
height
:
1377px
;
height
:
1377px
;
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/ThinkTankReport/index.vue
浏览文件 @
c675e799
...
@@ -21,16 +21,17 @@
...
@@ -21,16 +21,17 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTypeIds)"
class=
"checkbox-group"
@
change=
"val => handleToggleAll(val, researchTypeIds)"
>
:model-value=
"selectedResearchIds"
全部领域
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
v-model=
"selectedResearchIds"
:label=
"type.id"
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
class=
"filter-checkbox"
@
change=
"handleGetThinkDynamicsReport()"
>
{{
type
.
name
}}
{{
type
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
</div>
<div
class=
"select-time-box"
>
<div
class=
"select-time-box"
>
...
@@ -39,16 +40,17 @@
...
@@ -39,16 +40,17 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTimeIds)"
class=
"checkbox-group"
@
change=
"val => handleToggleAll(val, researchTimeIds)"
>
:model-value=
"selectedResearchTimeIds"
全部时间
@
change=
"handleTimeGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
v-model=
"selectedResearchTimeIds"
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleGetThinkDynamicsReport()"
>
{{
type
.
name
}}
{{
type
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
<!--
<div
class=
"input-main"
>
<!--
<div
class=
"input-main"
>
<el-input
placeholder=
"输入作者名字"
v-model=
"author"
@
input=
"handleGetThinkDynamicsReport()"
/>
<el-input
placeholder=
"输入作者名字"
v-model=
"author"
@
input=
"handleGetThinkDynamicsReport()"
/>
...
@@ -63,7 +65,7 @@
...
@@ -63,7 +65,7 @@
<img
:src=
"item.imageUrl"
alt=
""
/>
<img
:src=
"item.imageUrl"
alt=
""
/>
</div>
</div>
<div
class=
"footer-card-title"
>
<div
class=
"footer-card-title"
>
{{
item
.
name
}}
<span
v-html=
"highlightText(item.name)"
></span>
</div>
</div>
<div
class=
"footer-card-footer"
>
<div
class=
"footer-card-footer"
>
<div
class=
"time"
>
{{
item
.
times
}}
</div>
<div
class=
"time"
>
{{
item
.
times
}}
</div>
...
@@ -84,7 +86,14 @@
...
@@ -84,7 +86,14 @@
</div>
</div>
</
template
>
</
template
>
<
script
setup
>
<
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
({
const
props
=
defineProps
({
researchTypeList
:
{
researchTypeList
:
{
...
@@ -114,6 +123,10 @@ const props = defineProps({
...
@@ -114,6 +123,10 @@ const props = defineProps({
currentPage
:
{
currentPage
:
{
type
:
Number
,
type
:
Number
,
default
:
1
default
:
1
},
searchKeyword
:
{
type
:
String
,
default
:
""
}
}
});
});
...
@@ -127,43 +140,59 @@ const emit = defineEmits([
...
@@ -127,43 +140,59 @@ const emit = defineEmits([
// 解构 props,保持模板里变量名不变
// 解构 props,保持模板里变量名不变
const
{
researchTypeList
,
researchTimeList
,
curFooterList
,
total
,
currentPage
}
=
toRefs
(
props
);
const
{
researchTypeList
,
researchTimeList
,
curFooterList
,
total
,
currentPage
}
=
toRefs
(
props
);
const
selectedResearchIds
=
ref
([]);
const
selectedResearchIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedResearchTimeIds
=
ref
([]);
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
(
watch
(
()
=>
props
.
selectedFilters
,
()
=>
props
.
selectedFilters
,
val
=>
{
val
=>
{
selectedResearchIds
.
value
=
val
?.
researchTypeIds
?
[...
val
.
researchTypeIds
]
:
[];
selectedResearchIds
.
value
=
selectedResearchTimeIds
.
value
=
val
?.
researchTimeIds
?
[...
val
.
researchTimeIds
]
:
[];
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
}
{
immediate
:
true
,
deep
:
true
}
);
);
const
buildSelectedFiltersPayload
=
()
=>
({
const
buildSelectedFiltersPayload
=
()
=>
({
researchTypeIds
:
[...
selectedResearchIds
.
value
]
,
researchTypeIds
:
stripAllAreaForRequest
(
selectedResearchIds
.
value
)
,
researchTimeIds
:
[...
selectedResearchTimeIds
.
value
]
,
researchTimeIds
:
stripAllTimeForRequest
(
selectedResearchTimeIds
.
value
)
,
researchHearingIds
:
[]
researchHearingIds
:
[]
});
});
const
researchTypeIds
=
computed
(()
=>
(
researchTypeList
.
value
||
[]).
map
(
item
=>
item
.
id
));
const
handleAreaGroupChange
=
(
val
)
=>
{
const
researchTimeIds
=
computed
(()
=>
(
researchTimeList
.
value
||
[]).
map
(
item
=>
item
.
id
));
selectedResearchIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
);
handleGetThinkDynamicsReport
();
const
getTargetSelection
=
ids
=>
(
ids
===
researchTypeIds
.
value
?
selectedResearchIds
:
selectedResearchTimeIds
);
};
const
isGroupAllSelected
=
ids
=>
ids
.
length
>
0
&&
ids
.
every
(
id
=>
getTargetSelection
(
ids
).
value
.
includes
(
id
));
const
handleToggleAll
=
(
checked
,
ids
)
=>
{
const
handleTimeGroupChange
=
(
val
)
=>
{
if
(
!
ids
.
length
)
return
;
selectedResearchTimeIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
);
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
];
handleGetThinkDynamicsReport
();
handleGetThinkDynamicsReport
();
};
};
...
@@ -326,6 +355,10 @@ const handleToReportDetail = item => {
...
@@ -326,6 +355,10 @@ const handleToReportDetail = item => {
}
}
}
}
:deep
(
.keyword-highlight
)
{
background-color
:
#fff59d
;
}
.right
{
.right
{
width
:
1284px
;
width
:
1284px
;
height
:
1377px
;
height
:
1377px
;
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/index.vue
浏览文件 @
c675e799
...
@@ -3,7 +3,8 @@
...
@@ -3,7 +3,8 @@
<div
class=
"main-header"
>
<div
class=
"main-header"
>
<div
class=
"search-box"
>
<div
class=
"search-box"
>
<el-input
placeholder=
"搜索智库动态"
v-model=
"searchReport"
style=
"width: 270px"
/>
<el-input
placeholder=
"搜索智库动态"
v-model=
"searchReport"
style=
"width: 270px"
@
keyup
.
enter=
"handleGetThinkDynamicsReport()"
/>
<div
class=
"icon"
>
<div
class=
"icon"
>
<img
src=
"./images/search-icon.png"
alt=
""
@
click=
"handleGetThinkDynamicsReport()"
/>
<img
src=
"./images/search-icon.png"
alt=
""
@
click=
"handleGetThinkDynamicsReport()"
/>
</div>
</div>
...
@@ -16,31 +17,9 @@
...
@@ -16,31 +17,9 @@
</div>
</div>
<div>
<div>
<div
class=
"select-box"
>
<div
class=
"select-box"
>
<div
class=
"select-box-time"
>
<el-select
v-model=
"selectedYear"
placeholder=
"近一年以来"
style=
"width: 120px"
:teleported=
"true"
:placement=
"'bottom-start'"
:popper-options=
"
{
modifiers: [
{
name: 'preventOverflow', // 禁用自动翻转逻辑
options: {
mainAxis: false, // 禁用垂直方向的自动调整
altAxis: false, // 禁用水平方向的自动调整
}
},
{
name: 'flip', // 完全禁用翻转功能
enabled: false
}
]
}">
<el-option
@
click=
"handleGetThinkDynamicsReport()"
v-for=
"item in yearList"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
</div>
<div
class=
"select-box-sort"
>
<div
class=
"select-box-sort"
>
<el-select
v-model=
"sort"
placeholder=
"
倒序
"
style=
"width: 120px"
:teleported=
"true"
<el-select
v-model=
"sort"
placeholder=
"
发布时间
"
style=
"width: 120px"
:teleported=
"true"
:placement=
"'bottom-start'"
:popper-options=
"
{
:placement=
"'bottom-start'"
:popper-options=
"
{
modifiers: [
modifiers: [
{
{
...
@@ -57,10 +36,9 @@
...
@@ -57,10 +36,9 @@
]
]
}">
}">
<template
#
prefix
>
<template
#
prefix
>
<img
src=
"./images/image down.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSortAndFetch()"
<img
v-if=
"sort !== true"
src=
"./images/image down.png"
class=
"select-prefix-img"
alt=
""
:key=
"true"
label=
"正序"
:value=
"true"
v-if=
"!sort"
/>
@
click
.
stop=
"toggleSortAndFetch()"
/>
<img
src=
"./images/image up.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSortAndFetch()"
<img
v-else
src=
"./images/image up.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSortAndFetch()"
/>
:key=
"true"
label=
"正序"
:value=
"true"
v-if=
"sort"
/>
</
template
>
</
template
>
<el-option
@
click=
"handleGetThinkDynamicsReport()"
:key=
"true"
label=
"正序"
:value=
"true"
/>
<el-option
@
click=
"handleGetThinkDynamicsReport()"
:key=
"true"
label=
"正序"
:value=
"true"
/>
<el-option
@
click=
"handleGetThinkDynamicsReport()"
:key=
"false"
label=
"倒序"
:value=
"false"
/>
<el-option
@
click=
"handleGetThinkDynamicsReport()"
:key=
"false"
label=
"倒序"
:value=
"false"
/>
...
@@ -72,7 +50,7 @@
...
@@ -72,7 +50,7 @@
<div
v-if=
"isThinkTankReport"
>
<div
v-if=
"isThinkTankReport"
>
<ThinkTankReport
:research-type-list=
"researchTypeList"
:research-time-list=
"researchTimeList"
<ThinkTankReport
:research-type-list=
"researchTypeList"
:research-time-list=
"researchTimeList"
:key=
"`智库报告-${tabResetKey}`"
:selected-filters=
"selectedFilters"
:cur-footer-list=
"curFooterList"
:total=
"total"
:key=
"`智库报告-${tabResetKey}`"
:selected-filters=
"selectedFilters"
:cur-footer-list=
"curFooterList"
:total=
"total"
:current-page=
"currentPage"
@
update:selected-filters=
"handleSelectedFiltersUpdate"
:current-page=
"currentPage"
:search-keyword=
"searchReport"
@
update:selected-filters=
"handleSelectedFiltersUpdate"
@
filter-change=
"(payload) => handleGetThinkDynamicsReport(payload)"
@
page-change=
"handleCurrentChange"
@
filter-change=
"(payload) => handleGetThinkDynamicsReport(payload)"
@
page-change=
"handleCurrentChange"
@
report-click=
"handleToReportDetail"
/>
@
report-click=
"handleToReportDetail"
/>
</div>
</div>
...
@@ -80,6 +58,7 @@
...
@@ -80,6 +58,7 @@
<CongressHearing
:research-type-list=
"researchTypeList"
:research-time-list=
"researchTimeList"
<CongressHearing
:research-type-list=
"researchTypeList"
:research-time-list=
"researchTimeList"
:key=
"`国会听证会-${tabResetKey}`"
:research-hearing-list=
"researchHearingList"
:selected-filters=
"selectedFilters"
:key=
"`国会听证会-${tabResetKey}`"
:research-hearing-list=
"researchHearingList"
:selected-filters=
"selectedFilters"
:selected-year=
"selectedYear"
:cur-footer-list=
"curFooterList"
:total=
"total"
:current-page=
"currentPage"
:selected-year=
"selectedYear"
:cur-footer-list=
"curFooterList"
:total=
"total"
:current-page=
"currentPage"
:search-keyword=
"searchReport"
:hearing-data=
"hearingData"
@
update:selected-filters=
"handleSelectedFiltersUpdate"
:hearing-data=
"hearingData"
@
update:selected-filters=
"handleSelectedFiltersUpdate"
@
filter-change=
"(payload) => handleGetThinkDynamicsReport(payload)"
@
page-change=
"handleCurrentChange"
@
filter-change=
"(payload) => handleGetThinkDynamicsReport(payload)"
@
page-change=
"handleCurrentChange"
@
report-click=
"handleToReportDetail"
/>
@
report-click=
"handleToReportDetail"
/>
...
@@ -87,7 +66,7 @@
...
@@ -87,7 +66,7 @@
<div>
<div>
<SurveyForm
v-if=
"isSurveyForm"
:research-type-list=
"researchTypeList"
:research-time-list=
"researchTimeList"
<SurveyForm
v-if=
"isSurveyForm"
:research-type-list=
"researchTypeList"
:research-time-list=
"researchTimeList"
:key=
"`调查项目-${tabResetKey}`"
:selected-filters=
"selectedFilters"
:cur-footer-list=
"curFooterList"
:total=
"total"
:key=
"`调查项目-${tabResetKey}`"
:selected-filters=
"selectedFilters"
:cur-footer-list=
"curFooterList"
:total=
"total"
:current-page=
"currentPage"
@
update:selected-filters=
"handleSelectedFiltersUpdate"
:current-page=
"currentPage"
:search-keyword=
"searchReport"
@
update:selected-filters=
"handleSelectedFiltersUpdate"
@
filter-change=
"(payload) => handleGetThinkDynamicsReport(payload)"
@
page-change=
"handleCurrentChange"
@
filter-change=
"(payload) => handleGetThinkDynamicsReport(payload)"
@
page-change=
"handleCurrentChange"
@
report-click=
"handleToReportDetail"
/>
@
report-click=
"handleToReportDetail"
/>
</div>
</div>
...
@@ -113,8 +92,14 @@ import SurveyForm from "./SurveyForm/index.vue"
...
@@ -113,8 +92,14 @@ import SurveyForm from "./SurveyForm/index.vue"
import
{
import
{
getThinkDynamicsReportType
,
getThinkDynamicsReportType
,
getHylyList
,
getHylyList
,
getThink
Dynamics
Report
getThink
Tank
Report
}
from
"@/api/thinkTank/overview"
;
}
from
"@/api/thinkTank/overview"
;
import
{
RESOURCE_FILTER_EARLIER
,
stripAllTimeForRequest
,
isSelectionCoveringAllOptions
,
getResourceLibraryReportDateRangeFromTimeSelection
}
from
"../../utils/resourceLibraryFilters"
;
import
{
useRouter
}
from
"vue-router"
;
import
{
useRouter
}
from
"vue-router"
;
import
ThinkTankReport
from
"./ThinkTankReport/index.vue"
;
import
ThinkTankReport
from
"./ThinkTankReport/index.vue"
;
import
CongressHearing
from
"./CongressHearing/index.vue"
;
import
CongressHearing
from
"./CongressHearing/index.vue"
;
...
@@ -323,7 +308,7 @@ const createDefaultSelectedFilters = () => ({
...
@@ -323,7 +308,7 @@ const createDefaultSelectedFilters = () => ({
const
resetThinkDynamicsState
=
()
=>
{
const
resetThinkDynamicsState
=
()
=>
{
searchReport
.
value
=
''
searchReport
.
value
=
''
selectedYear
.
value
=
1
selectedYear
.
value
=
1
sort
.
value
=
false
sort
.
value
=
null
author
.
value
=
''
author
.
value
=
''
currentPage
.
value
=
1
currentPage
.
value
=
1
selectedFilters
.
value
=
createDefaultSelectedFilters
()
selectedFilters
.
value
=
createDefaultSelectedFilters
()
...
@@ -375,8 +360,8 @@ const researchTimeList = ref([
...
@@ -375,8 +360,8 @@ const researchTimeList = ref([
name
:
'2021年'
,
name
:
'2021年'
,
},
},
{
{
id
:
'更早以前'
,
id
:
RESOURCE_FILTER_EARLIER
,
name
:
'更早以前'
,
name
:
RESOURCE_FILTER_EARLIER
,
}
}
])
])
const
researchHearingList
=
ref
([
const
researchHearingList
=
ref
([
...
@@ -492,35 +477,11 @@ const curFooterList = ref([
...
@@ -492,35 +477,11 @@ const curFooterList = ref([
// }
// }
]);
]);
const
yearList
=
ref
([
{
label
:
"近一年发布"
,
value
:
1
},
{
label
:
"近两年发布"
,
value
:
2
},
{
label
:
"近三年发布"
,
value
:
3
}
]);
const
selectedYear
=
ref
(
1
);
const
selectedYear
=
ref
(
1
);
const
sort
=
ref
(
false
);
/** null:界面显示「发布时间」占位且默认按发布时间倒序;true 正序;false 倒序(显式选中),与政策追踪一致 */
//获取当前时间x年前的日期
const
sort
=
ref
(
null
);
function
getDateYearsAgo
(
years
)
{
// 获取当前日期
const
currentDate
=
new
Date
();
// 计算指定年数之前的日期
const
pastDate
=
new
Date
(
currentDate
.
getFullYear
()
-
years
,
currentDate
.
getMonth
(),
currentDate
.
getDate
());
// 格式化日期为 "YYYY-MM-DD" 的形式
const
year
=
pastDate
.
getFullYear
();
const
month
=
String
(
pastDate
.
getMonth
()
+
1
).
padStart
(
2
,
"0"
);
// 月份从0开始,需要加1
const
day
=
String
(
pastDate
.
getDate
()).
padStart
(
2
,
"0"
);
return
`
${
year
}
-
${
month
}
-
${
day
}
`
;
}
//获取智库研究类型
//获取智库研究类型
const
handleGetThinkDynamicsReportType
=
async
()
=>
{
const
handleGetThinkDynamicsReportType
=
async
()
=>
{
try
{
try
{
...
@@ -550,9 +511,9 @@ const handleGetHylyList = async () => {
...
@@ -550,9 +511,9 @@ const handleGetHylyList = async () => {
};
};
const
toggleSortAndFetch
=
async
()
=>
{
const
toggleSortAndFetch
=
async
()
=>
{
sort
.
value
=
!
sort
.
value
sort
.
value
=
sort
.
value
===
true
?
false
:
true
;
await
handleGetThinkDynamicsReport
()
await
handleGetThinkDynamicsReport
()
;
}
}
;
const
currentPage
=
ref
(
1
);
const
currentPage
=
ref
(
1
);
// 处理页码改变事件
// 处理页码改变事件
const
handleCurrentChange
=
page
=>
{
const
handleCurrentChange
=
page
=>
{
...
@@ -569,50 +530,62 @@ function arrayToString(arr) {
...
@@ -569,50 +530,62 @@ function arrayToString(arr) {
},
""
);
},
""
);
}
}
function
buildThinkDynamicsYears
(
selectedYears
)
{
const
allYearIds
=
researchTimeList
.
value
.
map
(
item
=>
item
.
id
)
if
(
!
selectedYears
?.
length
||
selectedYears
.
length
===
allYearIds
.
length
)
{
return
null
}
return
arrayToString
(
selectedYears
)
}
// 获取智库动态报告,payload 为子组件筛选变更时传入的当前选中值,避免时序导致拿到旧值
// 获取智库动态报告,payload 为子组件筛选变更时传入的当前选中值,避免时序导致拿到旧值
const
handleGetThinkDynamicsReport
=
async
(
payload
)
=>
{
const
handleGetThinkDynamicsReport
=
async
(
payload
)
=>
{
const
nextFilters
=
payload
&&
typeof
payload
===
'object'
const
nextFilters
=
payload
&&
typeof
payload
===
"object"
?
{
?
{
researchTypeIds
:
payload
.
researchTypeIds
?
[...
payload
.
researchTypeIds
]
:
[],
researchTypeIds
:
payload
.
researchTypeIds
?
[...
payload
.
researchTypeIds
]
:
[],
researchTimeIds
:
payload
.
researchTimeIds
?
[...
payload
.
researchTimeIds
]
:
[],
researchTimeIds
:
payload
.
researchTimeIds
?
[...
payload
.
researchTimeIds
]
:
[],
researchHearingIds
:
payload
.
researchHearingIds
?
[...
payload
.
researchHearingIds
]
:
[]
researchHearingIds
:
payload
.
researchHearingIds
?
[...
payload
.
researchHearingIds
]
:
[]
}
}
:
selectedFilters
.
value
;
:
selectedFilters
.
value
;
if
(
payload
&&
typeof
payload
===
'object'
)
{
if
(
payload
&&
typeof
payload
===
"object"
)
{
selectedFilters
.
value
=
nextFilters
;
selectedFilters
.
value
=
nextFilters
;
currentPage
.
value
=
1
;
currentPage
.
value
=
1
;
}
}
// 国会听证会走本地数据,与资源库 /thinkTankOverview/report 无关
if
(
!
isThinkTankReport
.
value
&&
!
isSurveyForm
.
value
)
{
return
;
}
try
{
try
{
const
parmas
=
{
const
strippedTime
=
stripAllTimeForRequest
(
nextFilters
.
researchTimeIds
||
[]);
id
:
router
.
currentRoute
.
_value
.
params
.
id
,
const
allTimeIds
=
(
researchTimeList
.
value
||
[]).
map
((
x
)
=>
x
.
id
);
startDate
:
getDateYearsAgo
(
selectedYear
.
value
??
1
),
const
{
startDate
,
endDate
}
=
getResourceLibraryReportDateRangeFromTimeSelection
(
parmas
:
{
strippedTime
,
searchText
:
searchReport
.
value
,
allTimeIds
authorName
:
author
.
value
?
author
.
value
:
null
,
);
sortFun
:
sort
.
value
??
true
,
const
areas
=
nextFilters
.
researchTypeIds
||
[];
currentPage
:
currentPage
.
value
-
1
,
const
allAreaIds
=
(
researchTypeList
.
value
||
[]).
map
((
a
)
=>
a
.
id
);
const
domainIds
=
isSelectionCoveringAllOptions
(
areas
,
allAreaIds
)
?
""
:
arrayToString
(
areas
);
const
keyword
=
(
searchReport
.
value
||
""
).
trim
();
const
thinkTankId
=
router
.
currentRoute
.
_value
.
params
?.
id
;
const
params
=
{
pageNum
:
currentPage
.
value
,
pageSize
:
12
,
pageSize
:
12
,
researchTypeIds
:
arrayToString
(
nextFilters
.
researchTypeIds
)
===
''
?
null
:
arrayToString
(
nextFilters
.
researchTypeIds
),
sortFun
:
sort
.
value
===
true
,
years
:
buildThinkDynamicsYears
(
nextFilters
.
researchTimeIds
)
domainIds
,
startDate
,
endDate
};
if
(
isSurveyForm
.
value
)
{
params
.
category
=
"调查项目"
;
}
}
if
(
thinkTankId
)
{
params
.
thinkTankId
=
thinkTankId
;
}
if
(
keyword
)
{
params
.
keyword
=
keyword
;
}
}
const
res
=
await
getThink
DynamicsReport
(
parma
s
);
const
res
=
await
getThink
TankReport
(
param
s
);
console
.
log
(
"智库动态报告"
,
res
);
console
.
log
(
"智库动态报告"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
if
(
res
.
code
===
200
&&
res
.
data
)
{
curFooterList
.
value
=
res
.
data
.
content
curFooterList
.
value
=
res
.
data
.
content
;
total
.
value
=
res
.
data
.
totalElements
total
.
value
=
res
.
data
.
totalElements
;
}
else
if
(
res
.
code
===
500
&&
res
.
message
===
'未找到对应筛选的报告'
)
{
}
else
if
(
res
.
code
===
500
&&
res
.
message
===
"未找到对应筛选的报告"
)
{
curFooterList
.
value
=
[]
curFooterList
.
value
=
[]
;
total
.
value
=
0
total
.
value
=
0
;
}
}
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"获取智库动态报告error"
,
error
);
console
.
error
(
"获取智库动态报告error"
,
error
);
...
...
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
...
@@ -201,7 +201,8 @@
...
@@ -201,7 +201,8 @@
<div
class=
"box3-main-right"
>
<div
class=
"box3-main-right"
>
<div
class=
"box3-right-item"
v-for=
"(item, index) in box3RightData"
:key=
"index"
>
<div
class=
"box3-right-item"
v-for=
"(item, index) in box3RightData"
:key=
"index"
>
<div
class=
"icon"
@
click=
"handleClickPerson(item)"
>
<div
class=
"icon"
@
click=
"handleClickPerson(item)"
>
<img
:src=
"item.imageUrl ? item.imageUrl : DefaultIcon1"
alt=
""
/>
<img
:src=
"item.imageUrl ? item.imageUrl : DefaultIcon1"
alt=
""
@
error=
"() =>
{ if (item.imageUrl) item.imageUrl = null; }" />
</div>
</div>
<div
class=
"info"
>
<div
class=
"info"
>
<div
class=
"info-header"
>
<div
class=
"info-header"
>
...
@@ -223,17 +224,26 @@
...
@@ -223,17 +224,26 @@
<div
class=
"info"
><img
src=
"./images/image-exclamation.png"
></div>
<div
class=
"info"
><img
src=
"./images/image-exclamation.png"
></div>
<div
class=
"text"
>
数据来源:美国国会官网,数据时间:2015.1至2025.12
</div>
<div
class=
"text"
>
数据来源:美国国会官网,数据时间:2015.1至2025.12
</div>
</div>
</div>
<div
class=
"middle"
>
<div
class=
"middle-text"
>
{{
"共"
}}{{
personTotal
}}{{
"名核心研究人员"
}}
</div>
<div
class=
"page"
>
<div
class=
"image"
@
click=
"handlePersonPrevPage"
><img
src=
"./images/left-page-btn.png"
alt=
""
/></div>
<div
class=
"page-text"
>
{{
personPageText
}}
</div>
<div
class=
"image"
@
click=
"handlePersonNextPage"
><img
src=
"./images/right-page-btn.png"
alt=
""
/></div>
</div>
</div>
<div
class=
"box3-main-bottom"
>
<div
class=
"box3-main-bottom"
>
<div
class=
"box3-right-item"
v-for=
"(item, index) in
box3RightData
"
:key=
"index"
>
<div
class=
"box3-right-item"
v-for=
"(item, index) in
personList
"
:key=
"index"
>
<div
class=
"icon"
@
click=
"handleClickPerson(item)"
>
<div
class=
"icon"
@
click=
"handleClickPerson(item)"
>
<img
:src=
"item.imageUrl ? item.imageUrl : DefaultIcon1"
alt=
""
/>
<img
:src=
"item.avatarUrl ? item.avatarUrl : DefaultIcon1"
alt=
""
@
error=
"() =>
{ if (item.avatarUrl) item.avatarUrl = null; }" />
</div>
</div>
<div
class=
"info"
>
<div
class=
"info"
>
<div
class=
"info-header"
>
<div
class=
"info-header"
>
<div
class=
"name"
>
{{
item
.
name
}}
</div>
<div
class=
"name"
>
{{
item
.
name
}}
</div>
<div
class=
"position"
>
{{
item
.
positionTitle
??
'全球研究人才'
}}
</div>
<div
class=
"position"
>
{{
item
.
positionTitle
??
'全球研究人才'
}}
</div>
</div>
</div>
<div
class=
"info-footer"
>
{{
item
.
describe
}}
</div>
<div
class=
"info-footer"
>
{{
item
.
introduction
}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -245,14 +255,14 @@
...
@@ -245,14 +255,14 @@
</
template
>
</
template
>
<
script
setup
>
<
script
setup
>
import
{
ref
,
onMounted
,
nextTick
}
from
"vue"
;
import
{
ref
,
onMounted
,
nextTick
,
computed
}
from
"vue"
;
import
setChart
from
"@/utils/setChart"
;
import
setChart
from
"@/utils/setChart"
;
import
getPieChart
from
"./utils/piechart"
;
import
getPieChart
from
"./utils/piechart"
;
import
getTreeMapChart
from
"./utils/treeMapChart"
;
import
getTreeMapChart
from
"./utils/treeMapChart"
;
import
{
import
{
getThinkTankInfoBasic
,
getThinkTankInfoBasic
,
getThinkTankInfoBranch
,
getThinkTankInfoBranch
,
getThinkTankFundsTotal
,
getThinkTankFundsSource
,
getThinkTankFundsSource
,
getThinkTankResearchAreae
,
getThinkTankResearchAreae
,
getThinkTankPerson
getThinkTankPerson
...
@@ -262,7 +272,7 @@ import { useRouter } from "vue-router";
...
@@ -262,7 +272,7 @@ import { useRouter } from "vue-router";
import
DefaultIcon1
from
'@/assets/icons/default-icon1.png'
import
DefaultIcon1
from
'@/assets/icons/default-icon1.png'
import
{
getPersonSummaryInfo
}
from
"@/api/common/index"
;
import
{
getPersonSummaryInfo
}
from
"@/api/common/index"
;
const
totalPersonNumber
=
ref
(
0
)
const
router
=
useRouter
();
const
router
=
useRouter
();
import
InfoImg
from
"./images/img.png"
;
import
InfoImg
from
"./images/img.png"
;
import
Icon1
from
"./images/icon1.png"
;
import
Icon1
from
"./images/icon1.png"
;
...
@@ -341,9 +351,9 @@ const handleGetThinkTankInfoBranch = async () => {
...
@@ -341,9 +351,9 @@ const handleGetThinkTankInfoBranch = async () => {
// 经费来源
// 经费来源
const
box1LeftData
=
ref
({
const
box1LeftData
=
ref
({
total
:
"4.358亿美元"
,
total
Je
:
0
,
gov
:
"3.271亿美元"
,
zfJe
:
0
,
other
:
"1.087亿美元"
other
J
:
0
});
});
const
box1ChartData
=
ref
([
const
box1ChartData
=
ref
([
...
@@ -387,10 +397,19 @@ const box1ChartData = ref([
...
@@ -387,10 +397,19 @@ const box1ChartData = ref([
const
handleGetThinkTankFundsTotal
=
async
()
=>
{
const
handleGetThinkTankFundsTotal
=
async
()
=>
{
try
{
try
{
const
res
=
await
getThinkTankFunds
Total
(
router
.
currentRoute
.
_value
.
params
.
id
);
const
res
=
await
getThinkTankFunds
Source
(
router
.
currentRoute
.
_value
.
params
.
id
);
console
.
log
(
"获取经费来源统计"
,
res
);
console
.
log
(
"获取经费来源统计"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
if
(
res
.
code
===
200
&&
res
.
data
)
{
box1LeftData
.
value
=
res
.
data
// 后端字段已更新:totalAmount/usGovernmentAmount/pentagonContractorAmount
// 前端卡片仍使用 totalJe/zfJe/otherJ 字段名,因此做兼容映射(不改样式)
const
totalJe
=
Number
(
res
.
data
.
totalAmount
??
res
.
data
.
totalJe
??
0
);
const
zfJe
=
Number
(
res
.
data
.
usGovernmentAmount
??
res
.
data
.
zfJe
??
0
);
const
otherJ
=
Number
(
res
.
data
.
pentagonContractorAmount
??
res
.
data
.
otherJ
??
0
);
return
box1LeftData
.
value
=
{
totalJe
:
totalJe
||
0
,
// 为空就用 0
zfJe
:
zfJe
||
0
,
// 为空就用 0
otherJ
:
otherJ
||
0
// 为空就用 0
};
}
}
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"获取经费来源统计error"
,
error
);
console
.
error
(
"获取经费来源统计error"
,
error
);
...
@@ -404,10 +423,11 @@ const handleGetThinkTankFundsSource = async () => {
...
@@ -404,10 +423,11 @@ const handleGetThinkTankFundsSource = async () => {
console
.
log
(
"获取经费来源"
,
res
);
console
.
log
(
"获取经费来源"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
if
(
res
.
code
===
200
&&
res
.
data
)
{
const
topList
=
Array
.
isArray
(
res
.
data
)
?
res
.
data
.
slice
(
0
,
7
)
:
[]
// 后端字段已改为 sources;机构名优先 institution,其次 secondInstitution
const
topList
=
Array
.
isArray
(
res
.
data
.
sources
)
?
res
.
data
.
sources
:
[]
const
data
=
topList
.
map
(
item
=>
({
const
data
=
topList
.
map
(
item
=>
({
name
:
item
.
institution
,
name
:
item
.
institution
||
item
.
secondInstitution
||
"未知机构"
,
value
:
item
.
amount
,
value
:
Number
(
item
.
amount
)
||
0
,
}))
}))
box1ChartData
.
value
=
data
box1ChartData
.
value
=
data
const
box1Chart
=
getPieChart
(
box1ChartData
.
value
);
const
box1Chart
=
getPieChart
(
box1ChartData
.
value
);
...
@@ -423,24 +443,30 @@ const handleGetThinkTankFundsSource = async () => {
...
@@ -423,24 +443,30 @@ const handleGetThinkTankFundsSource = async () => {
const
LABEL_OFFSET_UP
=
3
+
LABEL_BOX_HEIGHT
/
2
const
LABEL_OFFSET_UP
=
3
+
LABEL_BOX_HEIGHT
/
2
const
dataList
=
box1ChartData
.
value
const
dataList
=
box1ChartData
.
value
const
total
=
dataList
.
reduce
((
s
,
d
)
=>
s
+
(
d
.
value
||
0
),
0
)
||
1
const
total
=
dataList
.
reduce
((
s
,
d
)
=>
s
+
(
d
.
value
||
0
),
0
)
||
1
const
formatDeptName
=
(
name
)
=>
{
const
text
=
String
(
name
||
""
);
const
chars
=
Array
.
from
(
text
);
return
chars
.
length
>
9
?
`
${
chars
.
slice
(
0
,
9
).
join
(
""
)}
...`
:
text
;
};
box1Chart
.
series
[
0
].
label
=
{
box1Chart
.
series
[
0
].
label
=
{
...
box1Chart
.
series
[
0
].
label
,
...
box1Chart
.
series
[
0
].
label
,
alignTo
:
'edge'
,
alignTo
:
'edge'
,
offset
:
[
0
,
-
LABEL_OFFSET_UP
],
offset
:
[
0
,
-
LABEL_OFFSET_UP
],
formatter
(
params
)
{
formatter
(
params
)
{
const
valueYi
=
(
params
.
data
.
value
||
0
)
/
1000000
const
valueYi
=
(
params
.
data
.
value
||
0
)
/
1000000
00
const
percent
=
Math
.
round
(
params
.
percent
||
0
)
const
percent
=
params
.
percent
||
0
const
valueStr
=
`
${
Math
.
round
(
valueYi
)}
亿
${
percent
}
%`
const
valueStr
=
`
${
valueYi
.
toFixed
(
3
)}
亿
${
percent
}
%`
let
cumulative
=
0
let
cumulative
=
0
for
(
let
i
=
0
;
i
<
params
.
dataIndex
;
i
++
)
cumulative
+=
dataList
[
i
].
value
||
0
for
(
let
i
=
0
;
i
<
params
.
dataIndex
;
i
++
)
cumulative
+=
dataList
[
i
].
value
||
0
const
centerAngle
=
90
+
((
cumulative
+
(
params
.
data
.
value
||
0
)
/
2
)
/
total
)
*
360
const
centerAngle
=
90
+
((
cumulative
+
(
params
.
data
.
value
||
0
)
/
2
)
/
total
)
*
360
const
angleNorm
=
((
centerAngle
%
360
)
+
360
)
%
360
const
angleNorm
=
((
centerAngle
%
360
)
+
360
)
%
360
// 左侧仅 270–360° 及跨圈:部门左、经费右;其余(含 180–270° 右下两条):经费左、部门右
// 左侧仅 270–360° 及跨圈:部门左、经费右;其余(含 180–270° 右下两条):经费左、部门右
const
isLeftLabel
=
(
angleNorm
>=
270
&&
angleNorm
<
360
)
||
centerAngle
>=
360
const
isLeftLabel
=
(
angleNorm
>=
270
&&
angleNorm
<
360
)
||
centerAngle
>=
360
const
deptName
=
formatDeptName
(
params
.
data
.
name
);
if
(
isLeftLabel
)
{
if
(
isLeftLabel
)
{
return
`{nameL|
${
params
.
data
.
n
ame
}
}{valueR|
${
valueStr
}
}`
return
`{nameL|
${
deptN
ame
}
}{valueR|
${
valueStr
}
}`
}
}
return
`{valueL|
${
valueStr
}
}{nameR|
${
params
.
data
.
n
ame
}
}`
return
`{valueL|
${
valueStr
}
}{nameR|
${
deptN
ame
}
}`
},
},
lineHeight
:
LABEL_BOX_HEIGHT
,
lineHeight
:
LABEL_BOX_HEIGHT
,
rich
:
{
rich
:
{
...
@@ -604,8 +630,8 @@ const handleBox3Chart = () => {
...
@@ -604,8 +630,8 @@ const handleBox3Chart = () => {
delete
box3Chart
.
series
[
0
].
itemStyle
;
delete
box3Chart
.
series
[
0
].
itemStyle
;
box3Chart
.
series
[
0
].
sort
=
false
;
box3Chart
.
series
[
0
].
sort
=
false
;
// 图表充满 box3-main-top,左右各留 12px
// 图表充满 box3-main-top,左右各留 12px
box3Chart
.
series
[
0
].
left
=
12
;
box3Chart
.
series
[
0
].
left
=
0
;
box3Chart
.
series
[
0
].
right
=
12
;
box3Chart
.
series
[
0
].
right
=
0
;
box3Chart
.
series
[
0
].
top
=
0
;
box3Chart
.
series
[
0
].
top
=
0
;
box3Chart
.
series
[
0
].
bottom
=
0
;
box3Chart
.
series
[
0
].
bottom
=
0
;
// 方块内文字与区域标题统一为黑色,方块内文字加 1px 白色描边(等效 CSS text-stroke: 1px #fff)
// 方块内文字与区域标题统一为黑色,方块内文字加 1px 白色描边(等效 CSS text-stroke: 1px #fff)
...
@@ -621,57 +647,62 @@ const handleBox3Chart = () => {
...
@@ -621,57 +647,62 @@ const handleBox3Chart = () => {
};
};
box3Chart
.
series
[
0
].
upperLabel
=
{
box3Chart
.
series
[
0
].
upperLabel
=
{
...
box3Chart
.
series
[
0
].
upperLabel
,
...
box3Chart
.
series
[
0
].
upperLabel
,
color
:
'rgb(51,51,51)'
color
:
'rgb(51,51,51)'
,
fontSize
:
16
,
fontWeight
:
700
,
backgroundColor
:
'rgb(247,248,249)'
,
lineHeight
:
16
,
height
:
16
,
padding
:
0
};
};
setChart
(
box3Chart
,
"box3Chart"
);
setChart
(
box3Chart
,
"box3Chart"
);
};
};
const
box3RightData
=
ref
([
const
PERSON_PAGE_SIZE
=
10
;
{
const
personCurrentPage
=
ref
(
1
);
img
:
Icon1
,
const
box3RightData
=
ref
({
name
:
"杰森·马西尼 "
,
content
:
[],
postion
:
"兰德公司总裁兼首席执行官"
,
totalElements
:
0
desc
:
"曾任职于哈佛大学经济学院、美国财政部"
});
},
const
personList
=
computed
(()
=>
(
Array
.
isArray
(
box3RightData
.
value
?.
content
)
?
box3RightData
.
value
.
content
:
[]));
{
const
personTotal
=
computed
(()
=>
Number
(
box3RightData
.
value
?.
totalElements
||
0
));
img
:
Icon2
,
const
personTotalPages
=
computed
(()
=>
(
personTotal
.
value
>
0
?
Math
.
ceil
(
personTotal
.
value
/
PERSON_PAGE_SIZE
)
:
1
));
name
:
"安德鲁·R·霍恩 "
,
const
personPageText
=
computed
(()
=>
`
${
personCurrentPage
.
value
}
/
${
personTotalPages
.
value
}
`
);
postion
:
"高级副总裁,研究与分析"
,
desc
:
"曾任职于白宫科技政策顾问办公室"
},
{
img
:
Icon3
,
name
:
"杰森·马西尼 "
,
postion
:
"兰德公司总裁兼首席执行官"
,
desc
:
"日籍研究院,毕业于斯坦福大学"
},
{
img
:
Icon4
,
name
:
"梅丽莎·罗 "
,
postion
:
"副总裁,全球研究人才"
,
desc
:
"曾任职于美国国防部"
},
{
img
:
Icon5
,
name
:
"安妮塔·钱德拉 "
,
postion
:
"副总裁兼主任,兰德社会和经济福祉"
,
desc
:
"曾任职于哈佛大学经济学院、美国商务部"
}
]);
const
handleGetThinkPerson
=
async
()
=>
{
const
handleGetThinkPerson
=
async
(
page
=
personCurrentPage
.
value
)
=>
{
try
{
try
{
const
safePage
=
Math
.
max
(
1
,
Number
(
page
)
||
1
);
const
params
=
{
currentPage
:
safePage
,
pageSize
:
PERSON_PAGE_SIZE
,
thinkTankId
:
router
.
currentRoute
.
_value
.
params
.
id
}
const
res
=
await
getThinkTankPerson
(
params
);
const
res
=
await
getThinkTankPerson
(
router
.
currentRoute
.
_value
.
params
.
id
);
console
.
log
(
"核心研究人员"
,
res
);
console
.
log
(
"核心研究人员"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
if
(
res
.
code
===
200
&&
res
.
data
)
{
box3RightData
.
value
=
res
.
data
box3RightData
.
value
=
{
content
:
Array
.
isArray
(
res
.
data
.
content
)
?
res
.
data
.
content
:
[],
totalElements
:
Number
(
res
.
data
.
totalElements
||
0
)
}
// 以后端返回页码为准;未返回时使用请求页码
personCurrentPage
.
value
=
Number
(
res
.
data
.
currentPage
||
res
.
data
.
pageNum
||
safePage
);
}
}
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"获取核心研究人员error"
,
error
);
console
.
error
(
"获取核心研究人员error"
,
error
);
}
}
};
};
const
handlePersonPrevPage
=
()
=>
{
if
(
personCurrentPage
.
value
<=
1
)
return
;
handleGetThinkPerson
(
personCurrentPage
.
value
-
1
);
};
const
handlePersonNextPage
=
()
=>
{
if
(
personCurrentPage
.
value
>=
personTotalPages
.
value
)
return
;
handleGetThinkPerson
(
personCurrentPage
.
value
+
1
);
};
// 点击人物头像,跳转到人物主页
// 点击人物头像,跳转到人物主页
const
handleClickPerson
=
async
item
=>
{
const
handleClickPerson
=
async
item
=>
{
...
@@ -1238,10 +1269,11 @@ onMounted(() => {
...
@@ -1238,10 +1269,11 @@ onMounted(() => {
.box3-main-top
{
.box3-main-top
{
width
:
1100px
;
width
:
1100px
;
height
:
37
2
px
;
height
:
37
9
px
;
box-sizing
:
border-box
;
box-sizing
:
border-box
;
padding-left
:
35px
;
padding-left
:
35px
;
padding-top
:
7px
;
#box3Chart
{
#box3Chart
{
width
:
1035px
;
width
:
1035px
;
...
@@ -1287,15 +1319,67 @@ onMounted(() => {
...
@@ -1287,15 +1319,67 @@ onMounted(() => {
}
}
}
}
.middle
{
width
:
1027px
;
height
:
32px
;
margin-left
:
43px
;
display
:
flex
;
justify-content
:
space-between
;
margin-top
:
24px
;
.middle-text
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
16px
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
left
;
color
:
rgb
(
95
,
101
,
108
);
}
.page
{
height
:
24px
;
display
:
flex
;
margin-top
:
4px
;
.page-text
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
14px
;
line-height
:
22px
;
letter-spacing
:
0px
;
text-align
:
center
;
color
:
rgb
(
5
,
95
,
194
);
margin
:
1px
8px
;
}
.image
{
width
:
24px
;
height
:
24px
;
img
{
width
:
100%
;
height
:
100%
;
display
:
block
;
}
}
}
}
.box3-main-bottom
{
.box3-main-bottom
{
margin-left
:
28
px
;
margin-left
:
35
px
;
margin-top
:
24px
;
margin-top
:
24px
;
width
:
536px
;
width
:
1044px
;
height
:
326px
;
height
:
352px
;
display
:
flex
;
flex-wrap
:
wrap
;
column-gap
:
44px
;
row-gap
:
8px
;
align-content
:
flex-start
;
.box3-right-item
{
.box3-right-item
{
display
:
flex
;
display
:
flex
;
width
:
5
2
0px
;
width
:
5
0
0px
;
height
:
64px
;
height
:
64px
;
align-items
:
center
;
align-items
:
center
;
...
@@ -1314,7 +1398,7 @@ onMounted(() => {
...
@@ -1314,7 +1398,7 @@ onMounted(() => {
}
}
.info
{
.info
{
width
:
459px
;
width
:
calc
(
100%
-
61px
)
;
margin-left
:
13px
;
margin-left
:
13px
;
.info-header
{
.info-header
{
...
@@ -1322,6 +1406,7 @@ onMounted(() => {
...
@@ -1322,6 +1406,7 @@ onMounted(() => {
justify-content
:
space-between
;
justify-content
:
space-between
;
.name
{
.name
{
width
:
219px
;
height
:
24px
;
height
:
24px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
"Source Han Sans CN"
;
font-family
:
"Source Han Sans CN"
;
...
@@ -1329,9 +1414,13 @@ onMounted(() => {
...
@@ -1329,9 +1414,13 @@ onMounted(() => {
font-weight
:
700
;
font-weight
:
700
;
line-height
:
24px
;
line-height
:
24px
;
letter-spacing
:
1px
;
letter-spacing
:
1px
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
}
.position
{
.position
{
width
:
219px
;
height
:
22px
;
height
:
22px
;
color
:
var
(
--
color-main-active
);
color
:
var
(
--
color-main-active
);
font-family
:
"Source Han Sans CN"
;
font-family
:
"Source Han Sans CN"
;
...
@@ -1340,6 +1429,9 @@ onMounted(() => {
...
@@ -1340,6 +1429,9 @@ onMounted(() => {
line-height
:
22px
;
line-height
:
22px
;
letter-spacing
:
0px
;
letter-spacing
:
0px
;
text-align
:
right
;
text-align
:
right
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
}
}
}
...
...
src/views/thinkTank/allThinkTank/index.vue
浏览文件 @
c675e799
...
@@ -51,7 +51,7 @@
...
@@ -51,7 +51,7 @@
}"
>
}"
>
<
template
#
prefix
>
<
template
#
prefix
>
<img
src=
"../assets/images/sort-asc.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSort()"
<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()"
<img
src=
"../assets/images/sort-desc.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSort()"
:key=
"false"
label=
"倒序"
:value=
"false"
v-if=
"sort === false"
/>
:key=
"false"
label=
"倒序"
:value=
"false"
v-if=
"sort === false"
/>
</
template
>
</
template
>
...
@@ -191,7 +191,7 @@ const handleGetThinkTankList = async () => {
...
@@ -191,7 +191,7 @@ const handleGetThinkTankList = async () => {
total
.
value
=
0
;
total
.
value
=
0
;
}
}
};
};
//
初始为 null:el-select 显示 placeholder;但排序仍按“正序”规则(见 sortedCardList)
//
默认显示 placeholder「报告数量」,但前缀图标显示正序
const
sort
=
ref
(
null
);
const
sort
=
ref
(
null
);
const
toggleSort
=
()
=>
{
const
toggleSort
=
()
=>
{
sort
.
value
=
sort
.
value
===
false
?
true
:
false
sort
.
value
=
sort
.
value
===
false
?
true
:
false
...
...
src/views/thinkTank/components/HomeMainFooterMain.vue
浏览文件 @
c675e799
...
@@ -7,15 +7,21 @@
...
@@ -7,15 +7,21 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
v-model=
"checkAllModel"
class=
"all-checkbox"
@
change=
"emit('check-all-change', $event)"
>
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>
<el-checkbox
v-for=
"research in areaList"
:key=
"research.id"
v-model=
"selectedAreaListModel"
<el-checkbox
:label=
"research.id"
@
change=
"emit('checked-area-change')"
class=
"filter-checkbox"
>
v-for=
"research in areaList"
:key=
"research.id"
class=
"filter-checkbox"
:label=
"research.id"
>
{{
research
.
name
}}
{{
research
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
</div>
...
@@ -25,17 +31,21 @@
...
@@ -25,17 +31,21 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
v-model=
"checkAllTimeModel"
class=
"all-checkbox"
class=
"checkbox-group"
@
change=
"emit('check-all-time-change', $event)"
>
: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>
<el-checkbox
v-for=
"time in pubTimeList"
:key=
"time.id"
v-model=
"selectedPubTimeListModel"
:label=
"time.id"
<el-checkbox
class=
"filter-checkbox"
@
change=
"emit('checked-area-time-change')"
>
v-for=
"time in pubTimeList"
:key=
"time.id"
class=
"filter-checkbox"
:label=
"time.id"
>
{{
time
.
name
}}
{{
time
.
name
}}
</el-checkbox>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -68,56 +78,39 @@
...
@@ -68,56 +78,39 @@
</
template
>
</
template
>
<
script
setup
>
<
script
setup
>
import
{
computed
}
from
"vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
normalizeExclusiveAllOption
}
from
"../utils/resourceLibraryFilters"
;
const
props
=
defineProps
({
defineProps
({
checkAll
:
{
type
:
Boolean
,
default
:
false
},
isIndeterminate
:
{
type
:
Boolean
,
default
:
false
},
areaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
areaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedAreaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedAreaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
checkAllTime
:
{
type
:
Boolean
,
default
:
false
},
isIndeterminateTime
:
{
type
:
Boolean
,
default
:
false
},
pubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
pubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedPubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedPubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
curFooterList
:
{
type
:
Array
,
default
:
()
=>
[]
},
curFooterList
:
{
type
:
Array
,
default
:
()
=>
[]
},
total
:
{
type
:
Number
,
default
:
0
},
total
:
{
type
:
Number
,
default
:
0
},
currentPage
:
{
type
:
Number
,
default
:
1
}
currentPage
:
{
type
:
Number
,
default
:
1
}
});
});
const
emit
=
defineEmits
([
const
emit
=
defineEmits
([
"update:checkAll"
,
"update:selectedAreaList"
,
"update:selectedAreaList"
,
"check-all-change"
,
"checked-area-change"
,
"update:checkAllTime"
,
"update:selectedPubTimeList"
,
"update:selectedPubTimeList"
,
"check-all-time-change"
,
"filter-change"
,
"checked-area-time-change"
,
"report-click"
,
"report-click"
,
"page-change"
"page-change"
]);
]);
const
checkAllModel
=
computed
(
{
const
handleAreaGroupChange
=
(
val
)
=>
{
get
:
()
=>
props
.
checkAll
,
emit
(
"update:selectedAreaList"
,
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
));
set
:
val
=>
emit
(
"update:checkAll"
,
val
)
emit
(
"filter-change"
);
}
)
;
};
const
selectedAreaListModel
=
computed
({
const
handleTimeGroupChange
=
(
val
)
=>
{
get
:
()
=>
props
.
selectedAreaList
,
emit
(
"update:selectedPubTimeList"
,
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
));
set
:
val
=>
emit
(
"update:selectedAreaList"
,
val
)
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
)
});
</
script
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
>
...
...
src/views/thinkTank/components/HomeMainFooterSurvey.vue
浏览文件 @
c675e799
<
template
>
<
template
>
<!-- 调查项目:
结构/样式与智库报告一致,但组件独立,避免互相影响
-->
<!-- 调查项目:
与智库报告相同的「全部」互斥逻辑
-->
<div
class=
"home-main-footer-main"
>
<div
class=
"home-main-footer-main"
>
<div
class=
"left"
>
<div
class=
"left"
>
<div
class=
"select-box"
>
<div
class=
"select-box"
>
...
@@ -8,15 +8,21 @@
...
@@ -8,15 +8,21 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
v-model=
"checkAllModel"
class=
"all-checkbox"
@
change=
"emit('check-all-change', $event)"
>
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>
<el-checkbox
v-for=
"research in areaList"
:key=
"research.id"
v-model=
"selectedAreaListModel"
<el-checkbox
:label=
"research.id"
@
change=
"emit('checked-area-change')"
class=
"filter-checkbox"
>
v-for=
"research in areaList"
:key=
"research.id"
class=
"filter-checkbox"
:label=
"research.id"
>
{{
research
.
name
}}
{{
research
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
</div>
...
@@ -26,16 +32,21 @@
...
@@ -26,16 +32,21 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
v-model=
"checkAllTimeModel"
class=
"all-checkbox"
class=
"checkbox-group"
@
change=
"emit('check-all-time-change', $event)"
>
: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>
<el-checkbox
v-model=
"selectedPubTimeListModel"
v-for=
"time in pubTimeList"
:key=
"time.id"
:label=
"time.id"
<el-checkbox
class=
"filter-checkbox"
@
change=
"emit('checked-area-time-change')"
>
v-for=
"time in pubTimeList"
:key=
"time.id"
class=
"filter-checkbox"
:label=
"time.id"
>
{{
time
.
name
}}
{{
time
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -68,56 +79,39 @@
...
@@ -68,56 +79,39 @@
</
template
>
</
template
>
<
script
setup
>
<
script
setup
>
import
{
computed
}
from
"vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
normalizeExclusiveAllOption
}
from
"../utils/resourceLibraryFilters"
;
const
props
=
defineProps
({
defineProps
({
checkAll
:
{
type
:
Boolean
,
default
:
false
},
isIndeterminate
:
{
type
:
Boolean
,
default
:
false
},
areaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
areaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedAreaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedAreaList
:
{
type
:
Array
,
default
:
()
=>
[]
},
checkAllTime
:
{
type
:
Boolean
,
default
:
false
},
isIndeterminateTime
:
{
type
:
Boolean
,
default
:
false
},
pubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
pubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedPubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedPubTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
curFooterList
:
{
type
:
Array
,
default
:
()
=>
[]
},
curFooterList
:
{
type
:
Array
,
default
:
()
=>
[]
},
total
:
{
type
:
Number
,
default
:
0
},
total
:
{
type
:
Number
,
default
:
0
},
currentPage
:
{
type
:
Number
,
default
:
1
}
currentPage
:
{
type
:
Number
,
default
:
1
}
});
});
const
emit
=
defineEmits
([
const
emit
=
defineEmits
([
"update:checkAll"
,
"update:selectedAreaList"
,
"update:selectedAreaList"
,
"check-all-change"
,
"checked-area-change"
,
"update:checkAllTime"
,
"update:selectedPubTimeList"
,
"update:selectedPubTimeList"
,
"check-all-time-change"
,
"filter-change"
,
"checked-area-time-change"
,
"report-click"
,
"report-click"
,
"page-change"
"page-change"
]);
]);
const
checkAllModel
=
computed
({
const
handleAreaGroupChange
=
(
val
)
=>
{
get
:
()
=>
props
.
checkAll
,
emit
(
"update:selectedAreaList"
,
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
));
set
:
val
=>
emit
(
"update:checkAll"
,
val
)
emit
(
"filter-change"
);
});
};
const
selectedAreaListModel
=
computed
({
get
:
()
=>
props
.
selectedAreaList
,
set
:
val
=>
emit
(
"update:selectedAreaList"
,
val
)
});
const
checkAllTimeModel
=
computed
({
const
handleTimeGroupChange
=
(
val
)
=>
{
get
:
()
=>
props
.
checkAllTime
,
emit
(
"update:selectedPubTimeList"
,
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
));
set
:
val
=>
emit
(
"update:checkAllTime"
,
val
)
emit
(
"filter-change"
);
});
};
const
selectedPubTimeListModel
=
computed
({
get
:
()
=>
props
.
selectedPubTimeList
,
set
:
val
=>
emit
(
"update:selectedPubTimeList"
,
val
)
});
</
script
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
>
...
...
src/views/thinkTank/components/ThinkTankCongressHearingOverview.vue
浏览文件 @
c675e799
...
@@ -7,16 +7,21 @@
...
@@ -7,16 +7,21 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTypeIds)"
class=
"checkbox-group"
@
change=
"val => handleToggleAll(val, researchTypeIds)"
>
:model-value=
"selectedResearchIds"
全部领域
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
v-for=
"type in researchTypeList"
:key=
"type.id"
v-model=
"selectedResearchIds"
:label=
"type.id"
<el-checkbox
class=
"filter-checkbox"
@
change=
"handleFilterChange"
>
v-for=
"type in researchTypeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
{{
type
.
name
}}
{{
type
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
</div>
...
@@ -26,16 +31,21 @@
...
@@ -26,16 +31,21 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchTimeIds)"
class=
"checkbox-group"
@
change=
"val => handleToggleAll(val, researchTimeIds)"
>
:model-value=
"selectedResearchTimeIds"
全部时间
@
change=
"handleTimeGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
v-for=
"type in researchTimeList"
:key=
"type.id"
v-model=
"selectedResearchTimeIds"
<el-checkbox
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleFilterChange"
>
v-for=
"type in researchTimeList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
{{
type
.
name
}}
{{
type
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
</div>
...
@@ -45,16 +55,21 @@
...
@@ -45,16 +55,21 @@
<div
class=
"title"
>
{{
"听证会部门"
}}
</div>
<div
class=
"title"
>
{{
"听证会部门"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group hearing-grid"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isGroupAllSelected(researchHearingIds)"
class=
"checkbox-group hearing-grid"
@
change=
"val => handleToggleAll(val, researchHearingIds)"
>
:model-value=
"selectedResearchHearingIds"
全部部门
@
change=
"handleDeptGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_DEPT"
>
{{
RESOURCE_FILTER_ALL_DEPT
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
v-for=
"type in researchHearingList"
:key=
"type.id"
v-model=
"selectedResearchHearingIds"
<el-checkbox
:label=
"type.id"
class=
"filter-checkbox"
@
change=
"handleFilterChange"
>
v-for=
"type in researchHearingList"
:key=
"type.id"
class=
"filter-checkbox"
:label=
"type.id"
>
{{
type
.
name
}}
{{
type
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -76,8 +91,7 @@
...
@@ -76,8 +91,7 @@
class=
"card-open-image"
/>
class=
"card-open-image"
/>
</div>
</div>
<div
class=
"card-item-category"
>
<div
class=
"card-item-category"
>
<AreaTag
:key=
"index"
:tagName=
"item.category"
>
<AreaTag
:key=
"`cat-$
{item.id}`" :tagName="item.category" />
</AreaTag>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -101,6 +115,18 @@
...
@@ -101,6 +115,18 @@
<
script
setup
>
<
script
setup
>
import
{
computed
,
ref
}
from
"vue"
;
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
({
const
props
=
defineProps
({
researchTypeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
researchTypeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
...
@@ -112,11 +138,10 @@ const emit = defineEmits(["report-click"]);
...
@@ -112,11 +138,10 @@ const emit = defineEmits(["report-click"]);
const
pageSize
=
10
;
const
pageSize
=
10
;
const
currentPage
=
ref
(
1
);
const
currentPage
=
ref
(
1
);
const
selectedResearchIds
=
ref
([]);
const
selectedResearchIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedResearchTimeIds
=
ref
([]);
const
selectedResearchTimeIds
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
const
selectedResearchHearingIds
=
ref
([]);
const
selectedResearchHearingIds
=
ref
([
RESOURCE_FILTER_ALL_DEPT
]);
// 概览页暂无独立接口时,先使用一份静态数据(结构与智库动态保持一致)
const
hearingData
=
ref
([
const
hearingData
=
ref
([
{
id
:
1
,
title
:
"美国国会听证会:人工智能与国家安全"
,
content
:
"美中经济与安全审查委员会"
,
category
:
"人工智能"
,
time
:
"2025年7月8日"
},
{
id
:
1
,
title
:
"美国国会听证会:人工智能与国家安全"
,
content
:
"美中经济与安全审查委员会"
,
category
:
"人工智能"
,
time
:
"2025年7月8日"
},
{
id
:
2
,
title
:
"美国国会听证会:先进制造供应链韧性"
,
content
:
"国会-行政部门中国委员会"
,
category
:
"先进制造"
,
time
:
"2025年6月15日"
},
{
id
:
2
,
title
:
"美国国会听证会:先进制造供应链韧性"
,
content
:
"国会-行政部门中国委员会"
,
category
:
"先进制造"
,
time
:
"2025年6月15日"
},
...
@@ -138,46 +163,44 @@ const researchHearingList = ref([
...
@@ -138,46 +163,44 @@ const researchHearingList = ref([
{
id
:
"美国商务部"
,
name
:
"美国商务部"
},
{
id
:
"美国商务部"
,
name
:
"美国商务部"
},
]);
]);
const
researchTypeIds
=
computed
(()
=>
(
props
.
researchTypeList
||
[]).
map
(
item
=>
item
.
id
));
const
handleAreaGroupChange
=
(
val
)
=>
{
const
researchTimeIds
=
computed
(()
=>
(
props
.
researchTimeList
||
[]).
map
(
item
=>
item
.
id
));
selectedResearchIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_AREA
);
const
researchHearingIds
=
computed
(()
=>
(
researchHearingList
.
value
||
[]).
map
(
item
=>
item
.
id
));
currentPage
.
value
=
1
;
};
const
getTargetSelection
=
ids
=>
{
const
handleTimeGroupChange
=
(
val
)
=>
{
if
(
ids
===
researchTypeIds
.
value
)
return
selectedResearchIds
;
selectedResearchTimeIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_TIME
);
if
(
ids
===
researchTimeIds
.
value
)
return
selectedResearchTimeIds
;
currentPage
.
value
=
1
;
return
selectedResearchHearingIds
;
};
};
const
isGroupAllSelected
=
ids
=>
const
handleDeptGroupChange
=
(
val
)
=>
{
ids
.
length
>
0
&&
ids
.
every
(
id
=>
getTargetSelection
(
ids
).
value
.
includes
(
id
));
selectedResearchHearingIds
.
value
=
normalizeExclusiveAllOption
(
val
,
RESOURCE_FILTER_ALL_DEPT
);
currentPage
.
value
=
1
;
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
filteredHearingData
=
computed
(()
=>
{
const
filteredHearingData
=
computed
(()
=>
{
const
areaSel
=
stripAllAreaForRequest
(
selectedResearchIds
.
value
);
const
timeSel
=
stripAllTimeForRequest
(
selectedResearchTimeIds
.
value
);
const
deptSel
=
stripAllDeptForRequest
(
selectedResearchHearingIds
.
value
);
return
(
hearingData
.
value
||
[]).
filter
(
item
=>
{
return
(
hearingData
.
value
||
[]).
filter
(
item
=>
{
const
matchYear
=
const
matchYear
=
selectedResearchTimeIds
.
value
.
length
===
0
||
timeSel
.
length
===
0
||
selectedResearchTimeIds
.
value
.
some
(
year
=>
String
(
item
.
time
||
""
).
startsWith
(
year
));
timeSel
.
some
(
sel
=>
{
if
(
sel
===
RESOURCE_FILTER_EARLIER
)
{
return
matchesEarlierChineseDate
(
item
.
time
);
}
return
String
(
item
.
time
||
""
).
startsWith
(
String
(
sel
));
});
const
matchDepartment
=
const
matchDepartment
=
selectedResearchHearingIds
.
value
.
length
===
0
||
deptSel
.
length
===
0
||
selectedResearchHearingIds
.
value
.
some
(
department
=>
deptSel
.
some
(
department
=>
String
(
item
.
content
||
""
).
includes
(
department
)
||
String
(
item
.
title
||
""
).
includes
(
department
)
String
(
item
.
content
||
""
).
includes
(
department
)
||
String
(
item
.
title
||
""
).
includes
(
department
)
);
);
const
matchType
=
const
matchType
=
selectedResearchIds
.
value
.
length
===
0
||
areaSel
.
length
===
0
||
selectedResearchIds
.
value
.
some
(
typeId
=>
areaSel
.
some
(
typeId
=>
String
(
item
.
category
||
""
).
includes
(
typeId
)
||
String
(
item
.
title
||
""
).
includes
(
typeId
)
String
(
item
.
category
||
""
).
includes
(
String
(
typeId
))
||
String
(
item
.
title
||
""
).
includes
(
String
(
typeId
)
)
);
);
return
matchYear
&&
matchDepartment
&&
matchType
;
return
matchYear
&&
matchDepartment
&&
matchType
;
});
});
...
@@ -189,10 +212,6 @@ const displayList = computed(() => {
...
@@ -189,10 +212,6 @@ const displayList = computed(() => {
return
list
.
slice
(
start
,
start
+
pageSize
);
return
list
.
slice
(
start
,
start
+
pageSize
);
});
});
const
handleFilterChange
=
()
=>
{
currentPage
.
value
=
1
;
};
const
handlePageChange
=
page
=>
{
const
handlePageChange
=
page
=>
{
currentPage
.
value
=
page
;
currentPage
.
value
=
page
;
};
};
...
...
src/views/thinkTank/components/ThinkTankPolicyAdviceOverview.vue
浏览文件 @
c675e799
...
@@ -7,16 +7,21 @@
...
@@ -7,16 +7,21 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isAllSelected(typeIds)"
class=
"checkbox-group"
@
change=
"val => toggleAll(val, typeIds)"
>
:model-value=
"selectedTypeIds"
全部领域
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
class=
"filter-checkbox"
v-for=
"t in (researchTypeList || [])"
:key=
"t.id"
<el-checkbox
v-model=
"selectedTypeIds"
:label=
"t.id"
@
change=
"emitChange"
>
class=
"filter-checkbox"
v-for=
"t in (researchTypeList || [])"
:key=
"t.id"
:label=
"t.id"
>
{{
t
.
name
}}
{{
t
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
</div>
...
@@ -26,16 +31,21 @@
...
@@ -26,16 +31,21 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
</div>
<div
class=
"select-main"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox-group
<el-checkbox
class=
"filter-checkbox"
:model-value=
"isAllSelected(yearIds)"
class=
"checkbox-group"
@
change=
"val => toggleAll(val, yearIds)"
>
:model-value=
"selectedYearIds"
全部时间
@
change=
"handleYearGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
</el-checkbox>
<el-checkbox
class=
"filter-checkbox"
v-for=
"y in (researchTimeList || [])"
:key=
"y.id"
<el-checkbox
v-model=
"selectedYearIds"
:label=
"y.id"
@
change=
"emitChange"
>
class=
"filter-checkbox"
v-for=
"y in (researchTimeList || [])"
:key=
"y.id"
:label=
"y.id"
>
{{
y
.
name
}}
{{
y
.
name
}}
</el-checkbox>
</el-checkbox>
</
div
>
</
el-checkbox-group
>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -95,10 +105,17 @@
...
@@ -95,10 +105,17 @@
</
template
>
</
template
>
<
script
setup
>
<
script
setup
>
import
{
computed
,
ref
}
from
"vue"
;
import
{
ref
}
from
"vue"
;
import
AreaTag
from
"@/components/base/AreaTag/index.vue"
;
import
AreaTag
from
"@/components/base/AreaTag/index.vue"
;
import
{
const
props
=
defineProps
({
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
normalizeExclusiveAllOption
,
stripAllAreaForRequest
,
stripAllTimeForRequest
}
from
"../utils/resourceLibraryFilters"
;
defineProps
({
researchTypeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
researchTypeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
researchTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
researchTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
list
:
{
type
:
Array
,
default
:
()
=>
[]
},
list
:
{
type
:
Array
,
default
:
()
=>
[]
},
...
@@ -109,29 +126,25 @@ const props = defineProps({
...
@@ -109,29 +126,25 @@ const props = defineProps({
const
emit
=
defineEmits
([
"filter-change"
,
"page-change"
,
"item-click"
]);
const
emit
=
defineEmits
([
"filter-change"
,
"page-change"
,
"item-click"
]);
const
selectedTypeIds
=
ref
([]);
const
selectedTypeIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedYearIds
=
ref
([]);
const
selectedYearIds
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
const
typeIds
=
computed
(()
=>
(
props
.
researchTypeList
||
[]).
map
(
i
=>
i
.
id
));
const
emitFilterToParent
=
()
=>
{
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
=
()
=>
{
emit
(
"filter-change"
,
{
emit
(
"filter-change"
,
{
researchTypeIds
:
[...
selectedTypeIds
.
value
],
researchTypeIds
:
stripAllAreaForRequest
(
selectedTypeIds
.
value
),
researchTimeIds
:
[...
selectedYearIds
.
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
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
>
...
...
src/views/thinkTank/index.vue
浏览文件 @
c675e799
...
@@ -197,15 +197,7 @@
...
@@ -197,15 +197,7 @@
</
template
>
</
template
>
<
template
v-else
>
<
template
v-else
>
<div
class=
"box5-chart-row"
>
<div
class=
"box5-chart-row"
>
<button
v-if=
"box5LegendShowNav"
type=
"button"
class=
"box5-legend-nav-btn"
:disabled=
"!box5LegendCanPrev"
aria-label=
"上一页图例"
@
click=
"handleBox5LegendPrev"
>
‹
</button>
<div
id=
"box5Chart"
class=
"box5-chart-canvas"
></div>
<div
id=
"box5Chart"
class=
"box5-chart-canvas"
></div>
<button
v-if=
"box5LegendShowNav"
type=
"button"
class=
"box5-legend-nav-btn"
:disabled=
"!box5LegendCanNext"
aria-label=
"下一页图例"
@
click=
"handleBox5LegendNext"
>
›
</button>
</div>
</div>
<div
class=
"source"
>
<div
class=
"source"
>
<TipTab
:text=
"'智库报告数量变化趋势,数据来源:美国各智库官网'"
/>
<TipTab
:text=
"'智库报告数量变化趋势,数据来源:美国各智库官网'"
/>
...
@@ -342,17 +334,19 @@
...
@@ -342,17 +334,19 @@
</div>
</div>
</div>
</div>
<div
class=
"select-box"
>
<div
class=
"select-box"
>
<div
class=
"paixu-btn"
@
click=
"handleSwithSort()"
>
<el-select
v-model=
"resourceLibrarySortModel"
class=
"resource-library-sort-select"
placeholder=
"发布时间"
<div
class=
"icon1"
>
style=
"width: 120px"
:teleported=
"true"
placement=
"bottom-start"
<img
v-if=
"activeSort"
src=
"@/assets/icons/shengxu1.png"
alt=
""
/>
:popper-options=
"resourceLibrarySortPopperOptions"
@
change=
"handleResourceLibrarySortChange"
>
<img
v-else
src=
"@/assets/icons/jiangxu1.png"
alt=
""
/>
<
template
#
prefix
>
</div>
<img
v-if=
"resourceLibrarySortModel !== true"
<div
class=
"text"
>
{{ "发布时间" }}
</div>
src=
"@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image down.png"
<div
class=
"icon2"
>
class=
"resource-library-sort-prefix-img"
alt=
""
@
click
.
stop=
"toggleResourceLibrarySortPrefix"
/>
<img
v-if=
"activeSort"
src=
"@/assets/icons/shengxu2.png"
alt=
""
/>
<img
v-else
src=
"@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image up.png"
<img
v-else
src=
"@/assets/icons/jiangxu2.png"
alt=
""
/>
class=
"resource-library-sort-prefix-img"
alt=
""
@
click
.
stop=
"toggleResourceLibrarySortPrefix"
/>
</div>
</
template
>
</div>
<el-option
:key=
"'resource-lib-sort-asc'"
label=
"正序"
:value=
"true"
/>
<el-option
:key=
"'resource-lib-sort-desc'"
label=
"倒序"
:value=
"false"
/>
</el-select>
</div>
</div>
<!-- <el-select v-model="sort" placeholder="发布时间" style="width: 120px; margin-left: 8px">
<!-- <el-select v-model="sort" placeholder="发布时间" style="width: 120px; margin-left: 8px">
<el-option @click="handleGetetThinkTankReport()" :key="true" label="正序" :value="true" />
<el-option @click="handleGetetThinkTankReport()" :key="true" label="正序" :value="true" />
...
@@ -360,25 +354,17 @@
...
@@ -360,25 +354,17 @@
</el-select> -->
</el-select> -->
</div>
</div>
<div
class=
"resource-library-content"
>
<div
class=
"resource-library-content"
>
<HomeMainFooterMain
v-if=
"activeCate === '智库报告'"
v-model:checkAll=
"checkAll"
<HomeMainFooterMain
v-if=
"activeCate === '智库报告'"
:area-list=
"areaList"
:is-indeterminate=
"isIndeterminate"
:area-list=
"areaList"
v-model:selectedAreaList=
"selectedAreaList"
v-model:selectedAreaList=
"selectedAreaList"
:pub-time-list=
"pubTimeList"
@
check-all-change=
"handleCheckAllChange"
@
checked-area-change=
"handleCheckedAreaChange()"
v-model:selectedPubTimeList=
"selectedPubTimeList"
@
filter-change=
"handleThinkTankReportFilterChange"
v-model:checkAllTime=
"checkAllTime"
:is-indeterminate-time=
"isIndeterminateTime"
:pub-time-list=
"pubTimeList"
v-model:selectedPubTimeList=
"selectedPubTimeList"
@
check-all-time-change=
"handleCheckAllChangeTime"
@
checked-area-time-change=
"handleCheckedAreaChangeTime()"
:cur-footer-list=
"curFooterList"
:total=
"total"
:current-page=
"currentPage"
:cur-footer-list=
"curFooterList"
:total=
"total"
:current-page=
"currentPage"
@
report-click=
"handleToReportDetail"
@
page-change=
"handleCurrentChange"
/>
@
report-click=
"handleToReportDetail"
@
page-change=
"handleCurrentChange"
/>
<HomeMainFooterSurvey
v-else-if=
"activeCate === '调查项目'"
v-model:checkAll=
"surveyCheckAll"
<HomeMainFooterSurvey
v-else-if=
"activeCate === '调查项目'"
:area-list=
"areaList"
:is-indeterminate=
"surveyIsIndeterminate"
:area-list=
"areaList"
v-model:selectedAreaList=
"surveySelectedAreaList"
:pub-time-list=
"pubTimeList"
v-model:selectedAreaList=
"surveySelectedAreaList"
@
check-all-change=
"handleSurveyCheckAllChange"
v-model:selectedPubTimeList=
"surveySelectedPubTimeList"
@
filter-change=
"handleSurveyFilterChange"
@
checked-area-change=
"handleSurveyCheckedAreaChange()"
v-model:checkAllTime=
"surveyCheckAllTime"
:cur-footer-list=
"surveyFooterList"
:total=
"surveyTotal"
:current-page=
"surveyCurrentPage"
:is-indeterminate-time=
"surveyIsIndeterminateTime"
:pub-time-list=
"pubTimeList"
@
report-click=
"handleToReportDetail"
@
page-change=
"handleSurveyCurrentChange"
/>
v-model:selectedPubTimeList=
"surveySelectedPubTimeList"
@
check-all-time-change=
"handleSurveyCheckAllChangeTime"
@
checked-area-time-change=
"handleSurveyCheckedAreaChangeTime()"
:cur-footer-list=
"surveyFooterList"
:total=
"surveyTotal"
:current-page=
"surveyCurrentPage"
@
report-click=
"handleToReportDetail"
@
page-change=
"handleSurveyCurrentChange"
/>
<ThinkTankCongressHearingOverview
v-else-if=
"activeCate === '国会听证会'"
:key=
"`congress-${resourceTabResetKey}`"
<ThinkTankCongressHearingOverview
v-else-if=
"activeCate === '国会听证会'"
:key=
"`congress-${resourceTabResetKey}`"
:research-type-list=
"areaList"
:research-time-list=
"pubTimeList"
@
report-click=
"handleToReportDetail"
/>
:research-type-list=
"areaList"
:research-time-list=
"pubTimeList"
@
report-click=
"handleToReportDetail"
/>
...
@@ -423,7 +409,7 @@ import {
...
@@ -423,7 +409,7 @@ import {
getThinkTankReportRemarks
getThinkTankReportRemarks
}
from
"@/api/thinkTank/overview"
;
}
from
"@/api/thinkTank/overview"
;
import
{
getPersonSummaryInfo
}
from
"@/api/common/index"
;
import
{
getPersonSummaryInfo
}
from
"@/api/common/index"
;
import
getMultiLineChart
,
{
MULTILINE_LEGEND_SHOW_COUNT
}
from
"./utils/multiLineChart"
;
import
getMultiLineChart
from
"./utils/multiLineChart"
;
import
getPieChart
from
"./utils/piechart"
;
import
getPieChart
from
"./utils/piechart"
;
import
getSankeyChart
from
"./utils/sankey"
;
import
getSankeyChart
from
"./utils/sankey"
;
import
{
getChartAnalysis
}
from
"@/api/aiAnalysis/index"
;
import
{
getChartAnalysis
}
from
"@/api/aiAnalysis/index"
;
...
@@ -437,6 +423,16 @@ import News5 from "./assets/images/news5.png";
...
@@ -437,6 +423,16 @@ import News5 from "./assets/images/news5.png";
import
AiButton
from
'@/components/base/Ai/AiButton/index.vue'
import
AiButton
from
'@/components/base/Ai/AiButton/index.vue'
import
AiPane
from
'@/components/base/Ai/AiPane/index.vue'
import
AiPane
from
'@/components/base/Ai/AiPane/index.vue'
import
TipTab
from
"./TipTab/index.vue"
;
import
TipTab
from
"./TipTab/index.vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
RESOURCE_FILTER_ALL_TIME
,
RESOURCE_FILTER_EARLIER
,
stripAllAreaForRequest
,
stripAllTimeForRequest
,
buildNumericYearsQueryString
,
isSelectionCoveringAllOptions
,
getResourceLibraryReportDateRangeFromTimeSelection
}
from
"./utils/resourceLibraryFilters"
;
import
Message1
from
"./assets/images/message-icon1.png"
;
import
Message1
from
"./assets/images/message-icon1.png"
;
import
Message2
from
"./assets/images/message-icon2.png"
;
import
Message2
from
"./assets/images/message-icon2.png"
;
...
@@ -1009,63 +1005,17 @@ const box5ChartDisplayData = computed(() => {
...
@@ -1009,63 +1005,17 @@ const box5ChartDisplayData = computed(() => {
};
};
});
});
const
box5LegendPageIndex
=
ref
(
0
);
const
box5LegendPageCount
=
computed
(()
=>
{
const
n
=
box5ChartDisplayData
.
value
?.
data
?.
length
??
0
;
return
Math
.
max
(
1
,
Math
.
ceil
(
n
/
MULTILINE_LEGEND_SHOW_COUNT
));
});
const
box5LegendShowNav
=
computed
(()
=>
{
const
n
=
box5ChartDisplayData
.
value
?.
data
?.
length
??
0
;
return
n
>
MULTILINE_LEGEND_SHOW_COUNT
;
});
const
box5LegendCanPrev
=
computed
(()
=>
box5LegendPageIndex
.
value
>
0
);
const
box5LegendCanNext
=
computed
(
()
=>
box5LegendPageIndex
.
value
<
box5LegendPageCount
.
value
-
1
);
const
handleBox5LegendPrev
=
()
=>
{
if
(
box5LegendPageIndex
.
value
<=
0
)
{
return
;
}
box5LegendPageIndex
.
value
-=
1
;
renderBox5Chart
();
};
const
handleBox5LegendNext
=
()
=>
{
if
(
box5LegendPageIndex
.
value
>=
box5LegendPageCount
.
value
-
1
)
{
return
;
}
box5LegendPageIndex
.
value
+=
1
;
renderBox5Chart
();
};
const
renderBox5Chart
=
()
=>
{
const
renderBox5Chart
=
()
=>
{
const
chartInput
=
box5ChartDisplayData
.
value
;
const
chartInput
=
box5ChartDisplayData
.
value
;
if
(
!
chartInput
.
title
.
length
||
!
chartInput
.
data
.
length
)
{
if
(
!
chartInput
.
title
.
length
||
!
chartInput
.
data
.
length
)
{
return
;
return
;
}
}
const
totalNames
=
chartInput
.
data
.
length
;
const
box5Chart
=
getMultiLineChart
(
chartInput
);
const
pages
=
Math
.
max
(
1
,
Math
.
ceil
(
totalNames
/
MULTILINE_LEGEND_SHOW_COUNT
));
if
(
box5LegendPageIndex
.
value
>=
pages
)
{
box5LegendPageIndex
.
value
=
pages
-
1
;
}
if
(
box5LegendPageIndex
.
value
<
0
)
{
box5LegendPageIndex
.
value
=
0
;
}
const
box5Chart
=
getMultiLineChart
(
chartInput
,
{
legendShowCount
:
MULTILINE_LEGEND_SHOW_COUNT
,
legendPageIndex
:
box5LegendPageIndex
.
value
});
setChart
(
box5Chart
,
"box5Chart"
);
setChart
(
box5Chart
,
"box5Chart"
);
};
};
const
handleBox5AreaChange
=
()
=>
{
const
handleBox5AreaChange
=
()
=>
{
aiContentBox5
.
value
=
""
;
aiContentBox5
.
value
=
""
;
box5LegendPageIndex
.
value
=
0
;
renderBox5Chart
();
renderBox5Chart
();
};
};
...
@@ -1073,12 +1023,24 @@ const handleBox5 = async year => {
...
@@ -1073,12 +1023,24 @@ const handleBox5 = async year => {
const
y
=
year
!=
null
?
Number
(
year
)
:
box5selectetedYear
.
value
;
const
y
=
year
!=
null
?
Number
(
year
)
:
box5selectetedYear
.
value
;
box5selectetedYear
.
value
=
y
;
box5selectetedYear
.
value
=
y
;
box5selectetedArea
.
value
=
"全部领域"
;
box5selectetedArea
.
value
=
"全部领域"
;
box5LegendPageIndex
.
value
=
0
;
await
handleGetThinkTankPolicyIndustryChange
(
getBox5YearDateRange
(
y
));
await
handleGetThinkTankPolicyIndustryChange
(
getBox5YearDateRange
(
y
));
renderBox5Chart
();
renderBox5Chart
();
aiContentBox5
.
value
=
""
;
aiContentBox5
.
value
=
""
;
};
};
/** 兼容 getChartAnalysis 返回对象:从 data[0] 提取「解读」文本 */
const
getInterpretationTextFromChartResponse
=
(
res
)
=>
{
const
list
=
res
?.
data
;
const
first
=
Array
.
isArray
(
list
)
?
list
[
0
]
:
null
;
return
(
first
?.[
"解读"
]
||
first
?.[
"interpretation"
]
||
first
?.[
"analysis"
]
||
first
?.[
"content"
]
||
""
);
};
/** 请求 box5 折线图解读并更新 aiContentBox5(支持流式 SSE) */
/** 请求 box5 折线图解读并更新 aiContentBox5(支持流式 SSE) */
const
fetchBox5ChartInterpretation
=
async
()
=>
{
const
fetchBox5ChartInterpretation
=
async
()
=>
{
const
v
=
box5ChartDisplayData
.
value
;
const
v
=
box5ChartDisplayData
.
value
;
...
@@ -1108,17 +1070,9 @@ const fetchBox5ChartInterpretation = async () => {
...
@@ -1108,17 +1070,9 @@ const fetchBox5ChartInterpretation = async () => {
})
})
};
};
try
{
try
{
const
text
=
await
getChartAnalysis
(
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
{
text
:
JSON
.
stringify
(
chartPayload
)
},
const
text
=
getInterpretationTextFromChartResponse
(
res
);
{
aiContentBox5
.
value
=
text
||
aiContentBox5
.
value
||
"未返回有效解读内容"
;
onInterpretationDelta
:
(
chunk
)
=>
{
if
(
chunk
)
{
aiContentBox5
.
value
=
chunk
;
}
}
}
);
aiContentBox5
.
value
=
text
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"图表解读请求失败"
,
error
);
console
.
error
(
"图表解读请求失败"
,
error
);
aiContentBox5
.
value
=
"解读加载失败"
;
aiContentBox5
.
value
=
"解读加载失败"
;
...
@@ -1247,17 +1201,9 @@ const fetchBox6ChartInterpretation = async () => {
...
@@ -1247,17 +1201,9 @@ const fetchBox6ChartInterpretation = async () => {
}))
}))
};
};
try
{
try
{
const
text
=
await
getChartAnalysis
(
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
{
text
:
JSON
.
stringify
(
chartPayload
)
},
const
text
=
getInterpretationTextFromChartResponse
(
res
);
{
aiContentBox6
.
value
=
text
||
aiContentBox6
.
value
||
"未返回有效解读内容"
;
onInterpretationDelta
:
(
chunk
)
=>
{
if
(
chunk
)
{
aiContentBox6
.
value
=
chunk
;
}
}
}
);
aiContentBox6
.
value
=
text
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"领域分布图表解读请求失败"
,
error
);
console
.
error
(
"领域分布图表解读请求失败"
,
error
);
aiContentBox6
.
value
=
"解读加载失败"
;
aiContentBox6
.
value
=
"解读加载失败"
;
...
@@ -1473,17 +1419,9 @@ const fetchBox7ChartInterpretation = async () => {
...
@@ -1473,17 +1419,9 @@ const fetchBox7ChartInterpretation = async () => {
}))
}))
};
};
try
{
try
{
const
text
=
await
getChartAnalysis
(
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
{
text
:
JSON
.
stringify
(
chartPayload
)
},
const
text
=
getInterpretationTextFromChartResponse
(
res
);
{
aiContentBox7
.
value
=
text
||
aiContentBox7
.
value
||
"未返回有效解读内容"
;
onInterpretationDelta
:
(
chunk
)
=>
{
if
(
chunk
)
{
aiContentBox7
.
value
=
chunk
;
}
}
}
);
aiContentBox7
.
value
=
text
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"智库资金流向图表解读请求失败"
,
error
);
console
.
error
(
"智库资金流向图表解读请求失败"
,
error
);
aiContentBox7
.
value
=
"解读加载失败"
;
aiContentBox7
.
value
=
"解读加载失败"
;
...
@@ -1578,46 +1516,37 @@ const resourceTabResetKey = ref(0)
...
@@ -1578,46 +1516,37 @@ const resourceTabResetKey = ref(0)
const
resetResourceTabCommon
=
()
=>
{
const
resetResourceTabCommon
=
()
=>
{
// 统一回到“刷新后刚加载出来的状态”
// 统一回到“刷新后刚加载出来的状态”
// 分页归 1,排序
归默认(false)
// 分页归 1,排序
占位「发布时间」且默认倒序(null),与智库动态一致
currentPage
.
value
=
1
currentPage
.
value
=
1
sort
.
value
=
false
sort
.
value
=
null
surveyCurrentPage
.
value
=
1
surveyCurrentPage
.
value
=
1
surveySort
.
value
=
false
surveySort
.
value
=
null
policyCurrentPage
.
value
=
1
policyCurrentPage
.
value
=
1
policySort
.
value
=
false
policySort
.
value
=
null
congressResourceSort
.
value
=
null
// 强制重置那些在子组件内部维护状态的组件(国会听证会/政策建议筛选)
// 强制重置那些在子组件内部维护状态的组件(国会听证会/政策建议筛选)
resourceTabResetKey
.
value
+=
1
resourceTabResetKey
.
value
+=
1
}
}
const
resetThinkTankReportFilters
=
()
=>
{
const
resetThinkTankReportFilters
=
()
=>
{
checkAll
.
value
=
false
selectedAreaList
.
value
=
[
RESOURCE_FILTER_ALL_AREA
];
isIndeterminate
.
value
=
true
selectedPubTimeList
.
value
=
[
RESOURCE_FILTER_ALL_TIME
];
selectedAreaList
.
value
=
[]
};
checkAllTime
.
value
=
false
isIndeterminateTime
.
value
=
true
selectedPubTimeList
.
value
=
[
""
]
}
const
resetSurveyFilters
=
()
=>
{
const
resetSurveyFilters
=
()
=>
{
surveyCheckAll
.
value
=
false
surveySelectedAreaList
.
value
=
[
RESOURCE_FILTER_ALL_AREA
];
surveyIsIndeterminate
.
value
=
true
surveySelectedPubTimeList
.
value
=
[
RESOURCE_FILTER_ALL_TIME
];
surveySelectedAreaList
.
value
=
[]
};
surveyCheckAllTime
.
value
=
false
surveyIsIndeterminateTime
.
value
=
true
surveySelectedPubTimeList
.
value
=
[
""
]
}
const
resetPolicyFilters
=
()
=>
{
const
resetPolicyFilters
=
()
=>
{
// 默认“不过滤领域”:domainIds 传 null,由后端返回全部领域
// 与资源库勾选一致:子组件 remount 后为「全部领域 + 全部时间」,此处用空数组表示不按具体 id 过滤(接口侧走全量/默认年)
policySelectedTypeIds
.
value
=
[]
policySelectedTypeIds
.
value
=
[];
// 默认选择最新一年,保证 /thinkTankOverview/policy 能返回数据(后端通常需要 years)
policySelectedYearIds
.
value
=
[];
policySelectedYearIds
.
value
=
pubTimeList
.
value
?.
length
?
[
pubTimeList
.
value
[
0
].
id
]
:
[]
};
}
const
handleClickCate
=
cate
=>
{
const
handleClickCate
=
cate
=>
{
activeCate
.
value
=
cate
;
activeCate
.
value
=
cate
;
...
@@ -1672,30 +1601,11 @@ const handleToSocialDetail = item => {
...
@@ -1672,30 +1601,11 @@ const handleToSocialDetail = item => {
});
});
window
.
open
(
route
.
href
,
"_blank"
);
window
.
open
(
route
.
href
,
"_blank"
);
};
};
const
checkAll
=
ref
(
false
);
const
selectedAreaList
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
isIndeterminate
=
ref
(
true
);
const
selectedAreaList
=
ref
([]);
const
handleCheckAllChange
=
val
=>
{
/** 资源库-智库报告:筛选变更(与政策追踪相同的「全部」互斥后)重置页码并请求 */
// console.log(val, "handleCheckAllChange");
const
handleThinkTankReportFilterChange
=
()
=>
{
if
(
val
)
{
currentPage
.
value
=
1
;
isIndeterminate
.
value
=
false
;
selectedAreaList
.
value
.
length
!==
areaList
.
value
.
length
?
(
selectedAreaList
.
value
=
areaList
.
value
.
map
(
obj
=>
obj
.
id
))
:
""
;
}
else
{
selectedAreaList
.
value
=
[];
}
// selectedAreaList.value = val ? areaList : []
// isIndeterminate.value = false
handleGetetThinkTankReport
();
};
const
handleCheckedAreaChange
=
()
=>
{
// console.log(selectedAreaList.value, "handleCheckedAreaChange");
console
.
log
(
selectedAreaList
.
value
,
"当前选中的领域"
);
selectedAreaList
.
value
.
length
!==
areaList
.
value
.
length
?
(
isIndeterminate
.
value
=
true
)
:
((
checkAll
.
value
=
true
),
(
isIndeterminate
.
value
=
false
));
handleGetetThinkTankReport
();
handleGetetThinkTankReport
();
};
};
...
@@ -1719,58 +1629,77 @@ const pubTimeList = ref([
...
@@ -1719,58 +1629,77 @@ const pubTimeList = ref([
{
{
id
:
2021
,
id
:
2021
,
name
:
"2021"
name
:
"2021"
},
{
id
:
RESOURCE_FILTER_EARLIER
,
name
:
RESOURCE_FILTER_EARLIER
}
}
// {
// id: "更早时间",
// name: "更早时间"
// }
]);
]);
const
selectedPubTimeList
=
ref
([
""
]);
const
selectedPubTimeList
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
const
checkAllTime
=
ref
(
false
);
/** null:占位「发布时间」且默认倒序;true 正序;false 倒序(显式),与智库动态一致 */
const
isIndeterminateTime
=
ref
(
true
);
const
sort
=
ref
(
null
);
const
sort
=
ref
(
false
);
const
activeSort
=
computed
(()
=>
{
/** 国会听证会无列表接口排序,仅保持与动态相同的排序控件展示与交互 */
if
(
activeCate
.
value
===
"调查项目"
)
return
surveySort
.
value
;
const
congressResourceSort
=
ref
(
null
);
if
(
activeCate
.
value
===
"政策建议"
)
return
policySort
.
value
;
// 国会听证会目前不走排序;默认使用智库报告排序状态
const
resourceLibrarySortPopperOptions
=
{
modifiers
:
[
{
name
:
"preventOverflow"
,
options
:
{
mainAxis
:
false
,
altAxis
:
false
}
},
{
name
:
"flip"
,
enabled
:
false
}
]
};
const
resourceLibrarySortModel
=
computed
({
get
()
{
if
(
activeCate
.
value
===
"调查项目"
)
{
return
surveySort
.
value
;
}
if
(
activeCate
.
value
===
"政策建议"
)
{
return
policySort
.
value
;
}
if
(
activeCate
.
value
===
"国会听证会"
)
{
return
congressResourceSort
.
value
;
}
return
sort
.
value
;
return
sort
.
value
;
},
set
(
v
)
{
if
(
activeCate
.
value
===
"调查项目"
)
{
surveySort
.
value
=
v
;
}
else
if
(
activeCate
.
value
===
"政策建议"
)
{
policySort
.
value
=
v
;
}
else
if
(
activeCate
.
value
===
"国会听证会"
)
{
congressResourceSort
.
value
=
v
;
}
else
{
sort
.
value
=
v
;
}
}
});
});
const
handleSwithSort
=
()
=>
{
const
handleResourceLibrarySortChange
=
()
=>
{
if
(
activeCate
.
value
===
"调查项目"
)
{
if
(
activeCate
.
value
===
"调查项目"
)
{
surveySort
.
value
=
!
surveySort
.
value
;
handleGetThinkTankSurvey
();
handleGetThinkTankSurvey
();
return
;
}
else
if
(
activeCate
.
value
===
"政策建议"
)
{
}
if
(
activeCate
.
value
===
"政策建议"
)
{
policySort
.
value
=
!
policySort
.
value
;
handleGetThinkTankPolicyAdvice
();
handleGetThinkTankPolicyAdvice
();
}
else
if
(
activeCate
.
value
===
"国会听证会"
)
{
return
;
return
;
}
}
else
{
sort
.
value
=
!
sort
.
value
;
handleGetetThinkTankReport
();
handleGetetThinkTankReport
();
}
};
};
const
handleCheckAllChangeTime
=
val
=>
{
const
toggleResourceLibrarySortPrefix
=
()
=>
{
// console.log(val, "handleCheckAllChange");
if
(
activeCate
.
value
===
"调查项目"
)
{
if
(
val
)
{
surveySort
.
value
=
surveySort
.
value
===
true
?
false
:
true
;
isIndeterminateTime
.
value
=
false
;
handleGetThinkTankSurvey
();
selectedPubTimeList
.
value
.
length
!==
pubTimeList
.
value
.
length
}
else
if
(
activeCate
.
value
===
"政策建议"
)
{
?
(
selectedPubTimeList
.
value
=
pubTimeList
.
value
.
map
(
obj
=>
obj
.
id
))
policySort
.
value
=
policySort
.
value
===
true
?
false
:
true
;
:
""
;
handleGetThinkTankPolicyAdvice
();
}
else
if
(
activeCate
.
value
===
"国会听证会"
)
{
congressResourceSort
.
value
=
congressResourceSort
.
value
===
true
?
false
:
true
;
}
else
{
}
else
{
selectedPubTimeList
.
value
=
[];
sort
.
value
=
sort
.
value
===
true
?
false
:
true
;
}
// selectedAreaList.value = val ? areaList : []
// isIndeterminate.value = false
handleGetetThinkTankReport
();
};
const
handleCheckedAreaChangeTime
=
()
=>
{
// console.log(selectedAreaList.value, "handleCheckedAreaChange");
console
.
log
(
selectedPubTimeList
.
value
,
"当前选中的时间"
);
selectedPubTimeList
.
value
.
length
!==
pubTimeList
.
value
.
length
?
(
isIndeterminateTime
.
value
=
true
)
:
((
checkAllTime
.
value
=
true
),
(
isIndeterminateTime
.
value
=
false
));
handleGetetThinkTankReport
();
handleGetetThinkTankReport
();
}
};
};
const
curFooterList
=
ref
([
const
curFooterList
=
ref
([
...
@@ -1851,52 +1780,15 @@ const currentPage = ref(1);
...
@@ -1851,52 +1780,15 @@ const currentPage = ref(1);
const
total
=
ref
(
0
);
const
total
=
ref
(
0
);
// ===== 调查项目:独立状态(不影响智库报告)=====
// ===== 调查项目:独立状态(不影响智库报告)=====
const
surveyCheckAll
=
ref
(
false
);
const
surveySelectedAreaList
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
surveyIsIndeterminate
=
ref
(
true
);
const
surveySelectedPubTimeList
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
const
surveySelectedAreaList
=
ref
([]);
const
surveySort
=
ref
(
null
);
const
surveySelectedPubTimeList
=
ref
([
""
]);
const
surveyCheckAllTime
=
ref
(
false
);
const
surveyIsIndeterminateTime
=
ref
(
true
);
const
surveySort
=
ref
(
false
);
const
surveyFooterList
=
ref
([]);
const
surveyFooterList
=
ref
([]);
const
surveyCurrentPage
=
ref
(
1
);
const
surveyCurrentPage
=
ref
(
1
);
const
surveyTotal
=
ref
(
0
);
const
surveyTotal
=
ref
(
0
);
const
handleSurveyCheckAllChange
=
val
=>
{
const
handleSurveyFilterChange
=
()
=>
{
if
(
val
)
{
surveyCurrentPage
.
value
=
1
;
surveyIsIndeterminate
.
value
=
false
;
surveySelectedAreaList
.
value
.
length
!==
areaList
.
value
.
length
?
(
surveySelectedAreaList
.
value
=
areaList
.
value
.
map
(
obj
=>
obj
.
id
))
:
""
;
}
else
{
surveySelectedAreaList
.
value
=
[];
}
handleGetThinkTankSurvey
();
};
const
handleSurveyCheckedAreaChange
=
()
=>
{
surveySelectedAreaList
.
value
.
length
!==
areaList
.
value
.
length
?
(
surveyIsIndeterminate
.
value
=
true
)
:
((
surveyCheckAll
.
value
=
true
),
(
surveyIsIndeterminate
.
value
=
false
));
handleGetThinkTankSurvey
();
};
const
handleSurveyCheckAllChangeTime
=
val
=>
{
if
(
val
)
{
surveyIsIndeterminateTime
.
value
=
false
;
surveySelectedPubTimeList
.
value
.
length
!==
pubTimeList
.
value
.
length
?
(
surveySelectedPubTimeList
.
value
=
pubTimeList
.
value
.
map
(
obj
=>
obj
.
id
))
:
""
;
}
else
{
surveySelectedPubTimeList
.
value
=
[];
}
handleGetThinkTankSurvey
();
};
const
handleSurveyCheckedAreaChangeTime
=
()
=>
{
surveySelectedPubTimeList
.
value
.
length
!==
pubTimeList
.
value
.
length
?
(
surveyIsIndeterminateTime
.
value
=
true
)
:
((
surveyCheckAllTime
.
value
=
true
),
(
surveyIsIndeterminateTime
.
value
=
false
));
handleGetThinkTankSurvey
();
handleGetThinkTankSurvey
();
};
};
...
@@ -1906,12 +1798,24 @@ const handleSurveyCurrentChange = page => {
...
@@ -1906,12 +1798,24 @@ const handleSurveyCurrentChange = page => {
};
};
const
handleGetThinkTankSurvey
=
async
()
=>
{
const
handleGetThinkTankSurvey
=
async
()
=>
{
const
{
startDate
,
endDate
}
=
getResourceLibraryReportDateRangeFromTimeSelection
(
stripAllTimeForRequest
(
surveySelectedPubTimeList
.
value
),
(
pubTimeList
.
value
||
[]).
map
((
x
)
=>
x
.
id
)
);
const
params
=
{
const
params
=
{
pageNum
:
surveyCurrentPage
.
value
,
pageNum
:
surveyCurrentPage
.
value
,
pageSize
:
12
,
pageSize
:
12
,
sortFun
:
surveySort
.
value
,
sortFun
:
surveySort
.
value
===
true
,
researchTypeIds
:
arrayToString
(
surveySelectedAreaList
.
value
),
domainIds
:
(()
=>
{
years
:
arrayToString
(
surveySelectedPubTimeList
.
value
),
const
areas
=
stripAllAreaForRequest
(
surveySelectedAreaList
.
value
);
const
allAreaIds
=
(
areaList
.
value
||
[]).
map
((
a
)
=>
a
.
id
);
if
(
isSelectionCoveringAllOptions
(
areas
,
allAreaIds
))
{
return
""
;
}
return
arrayToString
(
areas
);
})(),
startDate
,
endDate
,
category
:
"调查项目"
category
:
"调查项目"
};
};
try
{
try
{
...
@@ -1932,12 +1836,14 @@ const handleGetThinkTankSurvey = async () => {
...
@@ -1932,12 +1836,14 @@ const handleGetThinkTankSurvey = async () => {
const
policyFooterList
=
ref
([]);
const
policyFooterList
=
ref
([]);
const
policyCurrentPage
=
ref
(
1
);
const
policyCurrentPage
=
ref
(
1
);
const
policyTotal
=
ref
(
0
);
const
policyTotal
=
ref
(
0
);
const
policySort
=
ref
(
false
);
const
policySort
=
ref
(
null
);
const
policySelectedTypeIds
=
ref
([]);
const
policySelectedTypeIds
=
ref
([]);
const
policySelectedYearIds
=
ref
([]);
const
policySelectedYearIds
=
ref
([]);
const
handlePolicyFilterChange
=
payload
=>
{
const
handlePolicyFilterChange
=
payload
=>
{
policySelectedTypeIds
.
value
=
payload
?.
researchTypeIds
?
[...
payload
.
researchTypeIds
]
:
[];
// 子组件 ThinkTankPolicyAdviceOverview 传 researchTypeIds(与智库报告筛选一致)
const
typeIds
=
payload
?.
researchTypeIds
??
payload
?.
domainIds
;
policySelectedTypeIds
.
value
=
Array
.
isArray
(
typeIds
)
?
[...
typeIds
]
:
[];
policySelectedYearIds
.
value
=
payload
?.
researchTimeIds
?
[...
payload
.
researchTimeIds
]
:
[];
policySelectedYearIds
.
value
=
payload
?.
researchTimeIds
?
[...
payload
.
researchTimeIds
]
:
[];
policyCurrentPage
.
value
=
1
;
policyCurrentPage
.
value
=
1
;
handleGetThinkTankPolicyAdvice
();
handleGetThinkTankPolicyAdvice
();
...
@@ -1983,21 +1889,32 @@ const handleGetThinkTankPolicyAdvice = async () => {
...
@@ -1983,21 +1889,32 @@ const handleGetThinkTankPolicyAdvice = async () => {
policyFooterList
.
value
=
mock
.
slice
(
start
,
start
+
pageSize
);
policyFooterList
.
value
=
mock
.
slice
(
start
,
start
+
pageSize
);
};
};
const
fallbackYear
=
pubTimeList
.
value
?.
length
?
String
(
pubTimeList
.
value
[
0
].
id
)
:
null
const
strippedPolicyYears
=
stripAllTimeForRequest
(
policySelectedYearIds
.
value
);
const
yearsStr
=
arrayToString
(
policySelectedYearIds
.
value
)
const
allPubTimeIds
=
(
pubTimeList
.
value
||
[]).
map
((
x
)
=>
x
.
id
);
// 领域:如果用户还没选领域,则默认使用“全部领域”的 id 列表
/** 与智库报告一致:仅「全部时间」或选满所有具体年份 → 不按 years 狭义过滤 */
const
effectiveDomainIds
=
policySelectedTypeIds
.
value
.
length
const
isPolicyAllTime
=
?
policySelectedTypeIds
.
value
strippedPolicyYears
.
length
===
0
||
:
(
areaList
.
value
||
[]).
map
(
obj
=>
obj
.
id
)
(
allPubTimeIds
.
length
>
0
&&
isSelectionCoveringAllOptions
(
strippedPolicyYears
,
allPubTimeIds
));
const
domainIdsStr
=
arrayToString
(
effectiveDomainIds
)
const
typeIdsForApi
=
stripAllAreaForRequest
(
policySelectedTypeIds
.
value
);
// 领域:未选具体领域(仅「全部领域」)时传全量 id 串
const
effectiveDomainIds
=
typeIdsForApi
.
length
?
typeIdsForApi
:
(
areaList
.
value
||
[]).
map
(
obj
=>
obj
.
id
);
const
domainIdsStr
=
arrayToString
(
effectiveDomainIds
);
const
params
=
{
const
params
=
{
currentPage
:
policyCurrentPage
.
value
-
1
,
currentPage
:
policyCurrentPage
.
value
-
1
,
pageSize
:
7
,
pageSize
:
7
,
sortFun
:
policySort
.
value
,
sortFun
:
policySort
.
value
===
true
years
:
yearsStr
===
""
?
fallbackYear
:
yearsStr
};
};
// 全部时间:不传 years,避免误用首项年份(如仅查 2025)
if
(
!
isPolicyAllTime
)
{
const
yearsStr
=
buildNumericYearsQueryString
(
strippedPolicyYears
);
if
(
yearsStr
)
{
params
.
years
=
yearsStr
;
}
}
// 领域:始终传 domainIds;如果用户没选,就传“全部领域”的 id 串
// 领域:始终传 domainIds;如果用户没选,就传“全部领域”的 id 串
params
.
domainIds
=
domainIdsStr
||
null
params
.
domainIds
=
domainIdsStr
||
null
;
try
{
try
{
const
res
=
await
getThinkTankOverviewPolicy
(
params
);
const
res
=
await
getThinkTankOverviewPolicy
(
params
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
if
(
res
.
code
===
200
&&
res
.
data
)
{
...
@@ -2050,12 +1967,24 @@ function arrayToString(arr) {
...
@@ -2050,12 +1967,24 @@ function arrayToString(arr) {
//获取智库报告
//获取智库报告
const
handleGetetThinkTankReport
=
async
()
=>
{
const
handleGetetThinkTankReport
=
async
()
=>
{
const
{
startDate
,
endDate
}
=
getResourceLibraryReportDateRangeFromTimeSelection
(
stripAllTimeForRequest
(
selectedPubTimeList
.
value
),
(
pubTimeList
.
value
||
[]).
map
((
x
)
=>
x
.
id
)
);
const
params
=
{
const
params
=
{
pageNum
:
currentPage
.
value
,
pageNum
:
currentPage
.
value
,
pageSize
:
12
,
pageSize
:
12
,
sortFun
:
sort
.
value
,
sortFun
:
sort
.
value
===
true
,
researchTypeIds
:
arrayToString
(
selectedAreaList
.
value
),
domainIds
:
(()
=>
{
years
:
arrayToString
(
selectedPubTimeList
.
value
)
const
areas
=
stripAllAreaForRequest
(
selectedAreaList
.
value
);
const
allAreaIds
=
(
areaList
.
value
||
[]).
map
((
a
)
=>
a
.
id
);
if
(
isSelectionCoveringAllOptions
(
areas
,
allAreaIds
))
{
return
""
;
}
return
arrayToString
(
areas
);
})(),
startDate
,
endDate
};
};
try
{
try
{
const
res
=
await
getThinkTankReport
(
params
);
const
res
=
await
getThinkTankReport
(
params
);
...
@@ -2505,7 +2434,7 @@ onMounted(async () => {
...
@@ -2505,7 +2434,7 @@ onMounted(async () => {
justify-content
:
space-between
;
justify-content
:
space-between
;
gap
:
16px
;
gap
:
16px
;
position
:
relative
;
position
:
relative
;
z-index
:
9999999
;
overflow
:
visible
;
overflow
:
visible
;
.card
{
.card
{
...
@@ -2519,12 +2448,12 @@ onMounted(async () => {
...
@@ -2519,12 +2448,12 @@ onMounted(async () => {
transition
:
all
0
.3s
;
transition
:
all
0
.3s
;
cursor
:
pointer
;
cursor
:
pointer
;
position
:
relative
;
// 让 red-info 按当前 card 自身定位
position
:
relative
;
// 让 red-info 按当前 card 自身定位
z-index
:
9999998
;
&
:hover
{
&
:hover
{
transform
:
translateY
(
-3px
);
transform
:
translateY
(
-3px
);
box-shadow
:
0
4px
16px
rgba
(
0
,
0
,
0
,
0
.15
);
box-shadow
:
0
4px
16px
rgba
(
0
,
0
,
0
,
0
.15
);
z-index
:
9
999999
;
z-index
:
9
;
}
}
.red-info
{
.red-info
{
...
@@ -2549,7 +2478,7 @@ onMounted(async () => {
...
@@ -2549,7 +2478,7 @@ onMounted(async () => {
padding
:
2px
8px
;
padding
:
2px
8px
;
/* 左右留空隙,更美观 */
/* 左右留空隙,更美观 */
white-space
:
nowrap
;
white-space
:
nowrap
;
z-index
:
10
000000
;
z-index
:
10
;
}
}
.card-header
{
.card-header
{
...
@@ -3602,40 +3531,10 @@ onMounted(async () => {
...
@@ -3602,40 +3531,10 @@ onMounted(async () => {
display
:
flex
;
display
:
flex
;
flex-direction
:
row
;
flex-direction
:
row
;
align-items
:
flex-start
;
align-items
:
flex-start
;
gap
:
8px
;
width
:
100%
;
width
:
100%
;
box-sizing
:
border-box
;
box-sizing
:
border-box
;
}
}
.box5-legend-nav-btn
{
flex-shrink
:
0
;
width
:
32px
;
height
:
32px
;
margin-top
:
6px
;
padding
:
0
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
220
,
223
,
230
,
1
);
border-radius
:
6px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
color
:
rgba
(
59
,
65
,
75
,
1
);
font-size
:
20px
;
line-height
:
1
;
cursor
:
pointer
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.box5-legend-nav-btn
:hover:not
(
:disabled
)
{
border-color
:
var
(
--
color-main-primary
,
#055fc2
);
color
:
var
(
--
color-main-primary
,
#055fc2
);
}
.box5-legend-nav-btn
:disabled
{
opacity
:
0
.35
;
cursor
:
not
-
allowed
;
}
.box5-chart-canvas
{
.box5-chart-canvas
{
flex
:
1
;
flex
:
1
;
min-width
:
0
;
min-width
:
0
;
...
@@ -4220,56 +4119,29 @@ onMounted(async () => {
...
@@ -4220,56 +4119,29 @@ onMounted(async () => {
box-sizing
:
border-box
;
box-sizing
:
border-box
;
padding
:
5px
0
;
padding
:
5px
0
;
.paixu-btn
{
.resource-library-sort-select
{
display
:
flex
;
width
:
120px
;
height
:
32px
;
height
:
32px
;
box-s
izing
:
border-box
;
box-s
hadow
:
0px
0px
20px
0px
rgba
(
94
,
95
,
95
,
0
.1
)
;
border
:
1px
solid
rgb
a
(
230
,
231
,
232
,
1
);
border
:
1px
solid
rgb
(
230
,
231
,
232
);
border-radius
:
4px
;
border-radius
:
4px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
background
:
rgb
(
255
,
255
,
255
);
&
:hover
{
background
:
var
(
--
color-bg-hover
);
}
.resource-library-sort-prefix-img
{
width
:
8px
;
height
:
8px
;
margin-right
:
4px
;
cursor
:
pointer
;
cursor
:
pointer
;
.icon1
{
width
:
11px
;
height
:
14px
;
margin-top
:
10px
;
margin-left
:
9px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
}
.text
{
:deep
(
.el-select__wrapper
)
{
height
:
19px
;
min-height
:
30px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
box-shadow
:
none
;
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
line-height
:
18px
;
letter-spacing
:
0px
;
text-align
:
left
;
margin-top
:
7px
;
margin-left
:
9px
;
}
}
.icon2
{
:deep
(
.el-select-dropdown
)
{
width
:
10px
;
left
:
0
!
important
;
height
:
5px
;
top
:
100%
!
important
;
margin-top
:
5px
;
transform
:
none
!
important
;
margin-left
:
13px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
}
}
}
}
}
...
...
src/views/thinkTank/utils/multiLineChart.js
浏览文件 @
c675e799
import
*
as
echarts
from
'echarts'
;
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 {{ 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
title
=
data
.
title
const
series
=
data
.
data
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
allNames
=
series
.
map
((
item
)
=>
item
.
name
)
const
pageCount
=
Math
.
max
(
1
,
Math
.
ceil
(
allNames
.
length
/
legendShowCount
))
const
legendSplitAt
=
Math
.
ceil
(
allNames
.
length
/
2
)
const
legendPageIndex
=
Math
.
min
(
Math
.
max
(
0
,
rawPageIndex
),
pageCount
-
1
)
const
legendFirstLine
=
allNames
.
slice
(
0
,
legendSplitAt
)
const
legendStart
=
legendPageIndex
*
legendShowCount
const
legendSecondLine
=
allNames
.
slice
(
legendSplitAt
)
const
legendData
=
allNames
.
slice
(
legendStart
,
legendStart
+
legendShowCount
)
// 定义配色数组
// 定义配色数组
const
colorList
=
[
const
colorList
=
[
...
@@ -101,28 +91,46 @@ const getMultiLineChart = (data, options = {}) => {
...
@@ -101,28 +91,46 @@ const getMultiLineChart = (data, options = {}) => {
},
},
/* 顶部预留足够空间:多行图例 + Y 轴标题「数量」在纵轴顶端,避免与图例重合 */
/* 顶部预留足够空间:多行图例 + Y 轴标题「数量」在纵轴顶端,避免与图例重合 */
grid
:
{
grid
:
{
top
:
'
28
%'
,
top
:
'
34
%'
,
right
:
'
5
%'
,
right
:
'
3
%'
,
bottom
:
'5%'
,
bottom
:
'5%'
,
left
:
'
5
%'
,
left
:
'
2
%'
,
containLabel
:
true
containLabel
:
true
},
},
legend
:
{
legend
:
[
{
show
:
true
,
show
:
true
,
type
:
'plain'
,
type
:
'plain'
,
data
:
legendData
,
data
:
legendFirstLine
,
top
:
8
,
top
:
8
,
left
:
'center'
,
left
:
'center'
,
icon
:
'circle'
,
icon
:
'circle'
,
textStyle
:
{
textStyle
:
{
fontFamily
:
'Source Han Sans CN'
,
// 字体
fontFamily
:
'Source Han Sans CN'
,
fontWeight
:
400
,
// 字重值(Regular对应400)
fontWeight
:
400
,
fontSize
:
14
,
// 字号
fontSize
:
14
,
lineHeight
:
24
,
// 行高
lineHeight
:
24
,
letterSpacing
:
0
,
// 字间距
letterSpacing
:
0
,
align
:
'left'
// 文本左对齐
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
,
// 使用预设的配色数组
color
:
colorList
,
// 使用预设的配色数组
xAxis
:
[
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) => {
...
@@ -6,7 +6,7 @@ const getSankeyChart = (nodes, links) => {
type
:
'sankey'
,
type
:
'sankey'
,
layout
:
'none'
,
layout
:
'none'
,
left
:
'2%'
,
left
:
'2%'
,
right
:
'1
8
%'
,
right
:
'1
5
%'
,
top
:
'5%'
,
top
:
'5%'
,
bottom
:
'5%'
,
bottom
:
'5%'
,
emphasis
:
{
emphasis
:
{
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论