Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
82f15736
提交
82f15736
authored
3月 18, 2026
作者:
朱政
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat:查看全部智库,智库报告-报告原文,智库报告-政策追踪样式与功能开发
上级
20ebbfed
隐藏空白字符变更
内嵌
并排
正在显示
23 个修改的文件
包含
1870 行增加
和
852 行删除
+1870
-852
overview.js
src/api/thinkTank/overview.js
+27
-3
thinktank.js
src/router/modules/thinktank.js
+9
-0
index.vue
src/views/bill/billHome/index.vue
+16
-28
index.vue
src/views/exportControl/index.vue
+1
-0
image-open2.png
src/views/thinkTank/ReportDetail/images/image-open2.png
+0
-0
image-pdf.png
src/views/thinkTank/ReportDetail/images/image-pdf.png
+0
-0
image-right.png
src/views/thinkTank/ReportDetail/images/image-right.png
+0
-0
image-translate.png
src/views/thinkTank/ReportDetail/images/image-translate.png
+0
-0
index.vue
src/views/thinkTank/ReportDetail/index.vue
+52
-2
index.vue
src/views/thinkTank/ReportDetail/policyTracking/index.vue
+674
-597
index.vue
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
+5
-0
index.vue
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
+3
-4
multiLineChart.js
...nk/ThinkTankDetail/PolicyTracking/utils/multiLineChart.js
+8
-27
index.vue
src/views/thinkTank/allThinkTank/index.vue
+562
-0
Line_Search.png
src/views/thinkTank/assets/images/Line_Search.png
+0
-0
arrow-left.png
src/views/thinkTank/assets/images/arrow-left.png
+0
-0
blue-right.png
src/views/thinkTank/assets/images/blue-right.png
+0
-0
info.png
src/views/thinkTank/assets/images/info.png
+0
-0
sort-asc.png
src/views/thinkTank/assets/images/sort-asc.png
+0
-0
sort-desc.png
src/views/thinkTank/assets/images/sort-desc.png
+0
-0
index.vue
src/views/thinkTank/index.vue
+126
-74
index.vue
src/views/thinkTank/reportOriginal/index.vue
+300
-96
pdf.vue
src/views/thinkTank/reportOriginal/pdf.vue
+87
-21
没有找到文件。
src/api/thinkTank/overview.js
浏览文件 @
82f15736
...
...
@@ -10,6 +10,13 @@ export function getThinkTankList() {
})
}
export
function
getAllThinkTankList
(
params
)
{
return
request
({
method
:
'GET'
,
url
:
'/api/thinkTankOverview/thinkTanks/page'
,
params
:
params
})
}
//智库概览:获取智库发布
export
function
getNewReport
()
{
return
request
({
...
...
@@ -299,9 +306,26 @@ export function getThinkTankReportPolicy(params) {
//获取相关政策动态
export
function
getThinkTankReportPolicyAction
(
params
)
{
return
request
({
method
:
'GET'
,
url
:
`/api/thinkTankReport/policyAction/
${
params
}
`
,
const
{
reportId
,
currentPage
,
pageSize
,
keyword
=
""
,
orgIds
=
""
,
// 新增:按科技领域 / 标签过滤
industryName
=
""
}
=
params
;
return
request
({
method
:
'GET'
,
url
:
`/api/thinkTankReport/policyDetail/
${
reportId
}
`
,
params
:
{
currentPage
,
pageSize
,
keyword
,
// 后端按标签过滤使用的字段
industryName
,
orgIds
,
}
})
}
...
...
src/router/modules/thinktank.js
浏览文件 @
82f15736
...
...
@@ -3,6 +3,7 @@ const thinkTank = () => import('@/views/thinkTank/index.vue')
const
ThinkTankDetail
=
()
=>
import
(
'@/views/thinkTank/ThinkTankDetail/index.vue'
)
const
ReportDetail
=
()
=>
import
(
'@/views/thinkTank/ReportDetail/index.vue'
)
const
ReportOriginal
=
()
=>
import
(
'@/views/thinkTank/reportOriginal/index.vue'
)
const
allThinkTank
=
()
=>
import
(
'@/views/thinkTank/allThinkTank/index.vue'
)
const
thinktankRoutes
=
[
// 智库系统的主要路由
...
...
@@ -40,6 +41,14 @@ const thinktankRoutes = [
// title: "报告原文"
// }
},
{
path
:
"/thinkTank/allThinkTank"
,
name
:
"allThinkTank"
,
component
:
allThinkTank
,
// meta: {
// title: "报告原文"
// }
},
]
...
...
src/views/bill/billHome/index.vue
浏览文件 @
82f15736
...
...
@@ -5,9 +5,8 @@
<div
class=
"home-content"
>
<div
class=
"home-content-header"
>
<SearchContainer
style=
"margin-bottom: 0; height: fit-content"
v-if=
"containerRef"
placeholder=
"搜索科技法案"
:containerRef=
"containerRef"
areaName=
"法案"
:enableBillTypeSwitch=
"true"
defaultBillSearchType=
"federal"
/>
<SearchContainer
style=
"margin-bottom: 0; height: fit-content"
v-if=
"containerRef"
placeholder=
"搜索科技法案"
:containerRef=
"containerRef"
areaName=
"法案"
:enableBillTypeSwitch=
"true"
defaultBillSearchType=
"federal"
/>
</div>
<DivideHeader
id=
"position1"
class=
"divide1"
:titleText=
"'最新动态'"
></DivideHeader>
...
...
@@ -28,8 +27,8 @@
</div>
</div>
<div
class=
"box1-main"
style=
"display: block"
>
<el-carousel
ref=
"carouselRef"
height=
"354px"
:autoplay=
"true"
:interval=
"3000"
arrow=
"never"
indicator-position=
"none"
@
change=
"handleCarouselChange"
>
<el-carousel
ref=
"carouselRef"
height=
"354px"
:autoplay=
"true"
:interval=
"3000"
arrow=
"never"
indicator-position=
"none"
@
change=
"handleCarouselChange"
>
<el-carousel-item
v-for=
"(bill, billIndex) in hotBillList"
:key=
"billIndex"
>
<div
class=
"carousel-content"
style=
"display: flex; height: 100%"
>
<div
class=
"box1-main-left"
>
...
...
@@ -37,8 +36,7 @@
{{ bill.billName }}
</div>
<div
class=
"box1-main-left-info"
>
<AreaTag
v-for=
"(item, index) in bill.hylyList"
:key=
"index"
:tagName=
"item.industryName"
>
<AreaTag
v-for=
"(item, index) in bill.hylyList"
:key=
"index"
:tagName=
"item.industryName"
>
</AreaTag>
</div>
<div
class=
"box1-main-left-info1"
>
...
...
@@ -56,18 +54,15 @@
</div>
</div>
<div
class=
"box1-main-left-info2"
>
<div
class=
"info2-item"
v-for=
"(item, index) in bill.dyqkList"
:key=
"index"
>
<div
class=
"time-line"
v-if=
"index !== bill.dyqkList.length - 1"
></div>
<div
class=
"info2-item"
v-for=
"(item, index) in bill.dyqkList"
:key=
"index"
>
<div
class=
"time-line"
v-if=
"index !== bill.dyqkList.length - 1"
></div>
<div
class=
"item-icon"
>
<img
src=
"./assets/images/info2-icon.png"
alt=
""
/>
</div>
<div
class=
"item-time"
:class=
"{ itemTimeActive: index === 0 }"
>
{{ item.actionDate }}
</div>
<div
class=
"item-title"
:class=
"{ itemTitleActive: index === 0 }"
>
<div
class=
"item-title"
:class=
"{ itemTitleActive: index === 0 }"
>
{{ item.actionContentCn }}
</div>
</div>
...
...
@@ -94,25 +89,17 @@
</el-carousel>
</div>
</overviewMainBox>
<RiskSignal
:list=
"warningList"
@
more-click=
"handleToMoreRiskSignal"
@
item-click=
"handleClickToDetailO"
riskLevel=
"signalLevel"
postDate=
"signalTime"
name=
"signalTitle"
/>
<RiskSignal
:list=
"warningList"
@
more-click=
"handleToMoreRiskSignal"
@
item-click=
"handleClickToDetailO"
riskLevel=
"signalLevel"
postDate=
"signalTime"
name=
"signalTitle"
/>
</div>
<DivideHeader
id=
"position2"
class=
"divide2"
:titleText=
"'资讯要闻'"
></DivideHeader>
<div
class=
"center-center"
>
<NewsList
:newsList=
"newsList"
img=
"newsImage"
title=
"newsTitle"
from=
"from"
content=
"newsContent"
/>
<MessageBubble
:messageList=
"messageList"
imageUrl=
"personImage"
@
more-click=
"handleToSocialDetail"
@
person-click=
"handleClickToCharacter"
name=
"personName"
content=
"remarks"
source=
"orgName"
/>
<NewsList
:newsList=
"newsList"
img=
"newsImage"
title=
"newsTitle"
from=
"from"
content=
"newsContent"
/>
<MessageBubble
:messageList=
"messageList"
imageUrl=
"personImage"
@
more-click=
"handleToSocialDetail"
@
person-click=
"handleClickToCharacter"
name=
"personName"
content=
"remarks"
source=
"orgName"
/>
</div>
<DivideHeader
id=
"position3"
class=
"divide3"
:titleText=
"'数据总览'"
></DivideHeader>
<div
class=
"center-footer"
>
<OverviewCard
class=
"overview-card--double box5"
title=
"涉华法案数量变化趋势"
:icon=
"box5HeaderIcon"
>
...
...
@@ -1783,7 +1770,7 @@ onUnmounted(() => {
margin-top
:
21px
;
height
:
450px
;
display
:
flex
;
gap
:
16px
;
gap
:
16px
;
.box3
{
width
:
792px
;
...
...
@@ -2074,6 +2061,7 @@ onUnmounted(() => {
.overview-card--double
{
width
:
calc
(
#{
$overview-single-width
}
*
2
+
#{
$overview-card-gap
}
);
}
.box5-main
,
.box6-main
,
.box7-main
,
...
...
src/views/exportControl/index.vue
浏览文件 @
82f15736
...
...
@@ -591,6 +591,7 @@
</template>
<
script
setup
>
//这是一个备注
import
NewsList
from
"@/components/base/newsList/index.vue"
;
import
RiskSignal
from
"@/components/base/RiskSignal/index.vue"
;
import
{
onMounted
,
ref
,
computed
,
reactive
,
shallowRef
,
watch
,
nextTick
}
from
"vue"
;
...
...
src/views/thinkTank/ReportDetail/images/image-open2.png
0 → 100644
浏览文件 @
82f15736
22.7 KB
src/views/thinkTank/ReportDetail/images/image-pdf.png
0 → 100644
浏览文件 @
82f15736
26.7 KB
src/views/thinkTank/ReportDetail/images/image-right.png
0 → 100644
浏览文件 @
82f15736
21.3 KB
src/views/thinkTank/ReportDetail/images/image-translate.png
0 → 100644
浏览文件 @
82f15736
25.4 KB
src/views/thinkTank/ReportDetail/index.vue
浏览文件 @
82f15736
...
...
@@ -62,7 +62,7 @@
</div>
<div
class=
"text"
@
click=
"toReport()"
>
{{
"报告原文"
}}
</div>
</div>
<div
class=
"btn"
>
<div
class=
"btn"
@
click=
"handleDownloadDocument"
>
<div
class=
"icon"
>
<img
src=
"./images/btn-icon3.png"
alt=
""
/>
</div>
...
...
@@ -89,7 +89,7 @@ import { ref, onMounted } from "vue";
import
ReportAnalysis
from
"./reportAnalysis/index.vue"
;
import
PolicyTracking
from
"./policyTracking/index.vue"
;
import
{
getThinkTankReportSummary
,
getThinkTankReportRelated
}
from
"@/api/thinkTank/overview"
;
import
{
getThinkTankReportSummary
,
getThinkTankReportRelated
,
getThinkTankReportcontentUrl
}
from
"@/api/thinkTank/overview"
;
import
{
useRoute
,
useRouter
}
from
"vue-router"
;
const
router
=
useRouter
();
const
route
=
useRoute
();
...
...
@@ -163,6 +163,56 @@ const goToOfficialWebsite = () => {
window
.
open
(
url
,
"_blank"
);
};
// 文档下载:先拉取 PDF 地址,再中英文都下载(与报告原文页相同方式)
const
downloadOnePdf
=
async
(
url
,
filename
)
=>
{
const
response
=
await
fetch
(
url
,
{
method
:
"GET"
,
headers
:
{
"Content-Type"
:
"application/pdf"
},
});
if
(
!
response
.
ok
)
throw
new
Error
(
`HTTP error! status:
${
response
.
status
}
`
);
const
blob
=
await
response
.
blob
();
const
blobUrl
=
window
.
URL
.
createObjectURL
(
blob
);
const
link
=
document
.
createElement
(
"a"
);
link
.
href
=
blobUrl
;
link
.
download
=
filename
;
document
.
body
.
appendChild
(
link
);
link
.
click
();
document
.
body
.
removeChild
(
link
);
window
.
URL
.
revokeObjectURL
(
blobUrl
);
};
const
handleDownloadDocument
=
async
()
=>
{
try
{
const
{
ElMessage
}
=
await
import
(
"element-plus"
);
const
res
=
await
getThinkTankReportcontentUrl
(
router
.
currentRoute
.
_value
.
params
.
id
);
if
(
res
.
code
!==
200
||
!
res
.
data
)
{
ElMessage
.
warning
(
"暂无下载链接"
);
return
;
}
const
urlZh
=
res
.
data
.
content
?
String
(
res
.
data
.
content
).
split
(
"#"
)[
0
]
:
""
;
const
urlEn
=
res
.
data
.
contentEn
?
String
(
res
.
data
.
contentEn
).
split
(
"#"
)[
0
]
:
""
;
if
(
!
urlZh
&&
!
urlEn
)
{
ElMessage
.
warning
(
"暂无下载链接"
);
return
;
}
const
baseName
=
(
thinkInfo
.
value
?.
name
||
"报告原文"
).
replace
(
/
[/\\
?%*:|"<>
]
/g
,
"-"
);
if
(
urlZh
)
{
await
downloadOnePdf
(
urlZh
,
`
${
baseName
}
_中文.pdf`
);
}
if
(
urlEn
)
{
if
(
urlZh
)
await
new
Promise
(
r
=>
setTimeout
(
r
,
300
));
await
downloadOnePdf
(
urlEn
,
`
${
baseName
}
_英文.pdf`
);
}
ElMessage
.
success
(
urlZh
&&
urlEn
?
"已下载中文、英文两份 PDF"
:
"下载成功"
);
}
catch
(
error
)
{
console
.
error
(
"文档下载失败:"
,
error
);
try
{
const
{
ElMessage
}
=
await
import
(
"element-plus"
);
ElMessage
.
error
(
"PDF 下载失败,请稍后重试"
);
}
catch
(
_
)
{
}
}
};
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
src/views/thinkTank/ReportDetail/policyTracking/index.vue
浏览文件 @
82f15736
<
template
>
<div
class=
"wrap"
>
<div
class=
"left"
>
<div
class=
"box1"
>
<AnalysisBox
title=
"政策建议相关情况"
:showAllBtn=
"false"
>
<div
class=
"box1-main"
>
<div
class=
"box1-item"
v-for=
"(item, index) in box1Data"
:key=
"index"
>
<div
class=
"left"
>
{{
index
+
1
}}
<div
class=
"wrap"
ref=
"wrapRef"
>
<div
class=
"box1"
>
<AnalysisBox
title=
"政策建议相关情况"
:showAllBtn=
"true"
>
<div
class=
"box1-main"
>
<div
class=
"left"
>
<div
class=
"search-box"
>
<el-input
placeholder=
"搜索政策建议"
v-model=
"searchOpinions"
@
keyup
.
enter=
"handleSearchOpinions"
/>
<div
class=
"icon"
>
<img
src=
"../images/Line_Search.png"
alt=
""
@
click=
"handleSearchOpinions"
/>
</div>
<div
class=
"center"
>
<div
class=
"title"
>
{{
item
.
content
}}
</div>
<div
class=
"tag-box"
>
<div
class=
"tag"
v-for=
"(val, idx) in item.tagList"
:key=
"idx"
>
{{
val
}}
</div>
<div
class=
"tag-box"
>
<span
class=
"tag"
:class=
"
{ 'tag-active': !activeTag }" @click="handleTagClick('')">
<div
class=
"tag-text"
>
{{
"全部"
}}
</div>
</span>
<span
class=
"tag"
v-for=
"(tag, index) in allTags"
:key=
"tag + index"
:class=
"
{ 'tag-active': activeTag === tag }" @click="handleTagClick(tag)">
<span
class=
"tag-text"
>
{{
tag
}}
(
{{
tagCountMap
[
tag
]
||
0
}}
项)
</span>
</span>
</div>
<div
class=
"item-box"
>
<div
class=
"item"
v-for=
"(item, index) in filteredOpinions"
:key=
"item.id || index"
:class=
"
{ 'item-active': index === activeItemIndex }" @click="
() => {
activeItemIndex = index;
box1DataItem = item;
console.log('当前选中政策建议:', item);
}
">
<!-- 序号 -->
<div
class=
"item-left"
>
{{
index
+
1
}}
</div>
<!-- 标题中与搜索关键词相同的文字高亮显示 -->
<div
class=
"item-right"
v-html=
"highlightKeyword(item.titleZh)"
></div>
<!-- 仅在选中项时显示右侧竖线 -->
<div
class=
"item-right-solid"
v-if=
"index === activeItemIndex"
></div>
</div>
</div>
<div
class=
"box4-main-footer"
>
<div
class=
"info"
>
共
{{
opinionsTotal
}}
条核心论点
</div>
<div
class=
"page-box"
>
<el-pagination
:page-size=
"pageSize"
background
layout=
"prev, pager, next"
:total=
"opinionsTotal"
@
current-change=
"handleCurrentChange"
:current-page=
"currentPage"
/>
</div>
</div>
</div>
<div
class=
"right"
v-if=
"box1DataItem"
>
<div
class=
"right-header"
>
<div
class=
"header-text"
>
<div
class=
"left-text"
>
{{
"政策建议详情"
}}
</div>
<div
class=
"right-text"
>
<div
class=
"text"
>
{{
"跳转原文"
}}
</div>
<div
class=
"right-header-image"
>
<img
src=
"../images/image-open2.png"
alt=
""
@
click=
"toReport()"
/>
</div>
</div>
<div
class=
"file-box"
>
<div
class=
"file-item"
v-for=
"(vall, idxx) in item.fileList"
:key=
"idxx"
>
<div
class=
"file-item-left"
>
法案
</div>
<div
class=
"file-item-center"
>
{{
vall
.
relationBillsList
}}
</div>
<div
class=
"file-item-right"
>
<img
src=
"@/assets/images/icon-right-circle.png"
alt=
""
/>
</div>
</div>
<div
class=
"right-content"
v-html=
"box1DataItem ? highlightKeyword(box1DataItem.contentZh || '') : ''"
>
</div>
<div
class=
"info-group"
>
<div
class=
"info-content"
>
<div
class=
"info-item"
>
<div
class=
"info-text"
>
{{
"相关领域:"
}}
</div>
<div
class=
"info-right"
v-if=
"box1DataItem && box1DataItem.domains"
>
<div
class=
"tag-box"
>
<div
class=
"tag"
v-for=
"(item, index) in box1DataItem.domains"
:key=
"index"
v-show=
"item"
>
{{
item
.
industryName
}}
</div>
</div>
</div>
<div
class=
"file-item"
v-for=
"(vall, idxx) in item.fileList"
:key=
"idxx"
>
<div
class=
"file-item-left"
>
政令
</div>
<div
class=
"file-item-center"
>
{{
vall
.
relationAdList
}}
</div>
<div
class=
"file-item-right"
>
<img
src=
"@/assets/images/icon-right-circle.png"
alt=
""
/>
</div>
<div
class=
"info-item"
v-if=
"box1DataItem"
>
<div
class=
"info-text"
>
{{
"遏制手段:"
}}
</div>
<div
class=
"info-right"
>
<div
class=
"tag-box"
>
<div
class=
"tag"
>
{{
"移民政策"
}}
</div>
</div>
</div>
</div>
<div
class=
"info-item"
>
<div
class=
"info-text"
>
{{
"涉及部门:"
}}
</div>
<div
class=
"info-right"
v-for=
"(item, index) in box1DataItem.organizations"
:key=
"index"
v-show=
"item"
>
<div
class=
"info-right-image"
>
<img
:src=
"item.logoUrl"
/>
</div>
<div
class=
"info-right-text"
>
{{
item
.
orgName
}}
</div>
</div>
</div>
</div>
<!--
<div
class=
"right"
>
<div
class=
"text"
>
{{
"查看智库原文"
}}
</div>
<div
class=
"icon"
>
<img
src=
"@/assets/icons/open.png"
alt=
""
/>
</div>
</div>
-->
</div>
</div>
</AnalysisBox>
</div>
</div>
<div
class=
"box2"
>
<AnalysisBox
title=
"相关政策动态"
:showAllBtn=
"false"
>
<div
class=
"box2-main"
>
<div
class=
"box2-item"
v-for=
"(item, index) in box2Data"
:key=
"index"
@
click=
"handleToNewsAnalysis(item)"
>
<div
class=
"box2-item-left"
>
<div
class=
"point"
>
<img
src=
"@/assets/images/dot.png"
alt=
""
/>
</div>
<div
class=
"line"
v-if=
"index !== box2Data.length - 1"
></div>
</div>
<div
class=
"box2-item-center"
>
<div
class=
"title"
>
{{
item
.
newsTitle
}}
</div>
<div
class=
"content"
>
{{
item
.
newsContent
}}
</div>
<div
class=
"right-footer-title"
>
<div
class=
"footer-title-text"
>
{{
"相关政策"
}}
</div>
</div>
<div
class=
"box2-item-right"
>
<div
class=
"time"
>
{{
item
.
newsDate
}}
</div>
<div
class=
"img-box"
>
<img
:src=
"item.newsImage?item.newsImage:DefaultNewsImg"
alt=
""
/>
<div
class=
"right-footer-box"
>
<div
class=
"relatedBills"
v-for=
"(item, index) in box1DataItem.relatedBills"
:key=
"index"
v-show=
"item"
>
<div
class=
"tag"
>
{{
"政令"
}}
</div>
<div
class=
"tag"
>
{{
"科技领域相似"
}}
</div>
<div
class=
"relatedBills-content"
>
{{
item
.
name
}}
</div>
<div
class=
"footer-image"
>
<img
src=
"../images/image-right.png"
alt=
""
/></div>
</div>
<div
class=
"relatedAdministrativeOrders"
v-for=
"(item, index) in box1DataItem.relatedAdministrativeOrders"
v-show=
"item"
:key=
"index"
>
<div
class=
"tag"
>
{{
"政令"
}}
</div>
<div
class=
"tag"
>
{{
"科技领域相似"
}}
</div>
<div
class=
"relatedAdministrativeOrders-content"
>
{{
item
.
content
}}
</div>
<div
class=
"footer-image"
>
<img
src=
"../images/image-right.png"
alt=
""
/></div>
</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
</
template
>
<
script
setup
>
import
{
ref
,
onMounted
}
from
"vue"
;
import
{
ref
,
onMounted
,
computed
,
nextTick
}
from
"vue"
;
import
{
getThinkTankReportPolicy
,
getThinkTankReportPolicyAction
...
...
@@ -83,238 +136,185 @@ import {
import
{
useRouter
}
from
"vue-router"
;
import
DefaultNewsImg
from
'@/assets/images/default-icon-news.png'
const
router
=
useRouter
();
// 政策建议相关情况
const
box1Data
=
ref
([
{
id
:
1
,
title
:
"示例标题1"
,
tagList
:
[
"人才交流"
,
"移民政策"
],
fileList
:
[
{
type
:
"法案"
,
name
:
"2024《重塑美国人口结构法案》"
},
{
type
:
"法案"
,
name
:
"2025《开放人才法案》"
}
]
},
{
id
:
2
,
title
:
"示例标题2"
,
tagList
:
[
"人才交流"
,
"移民政策"
],
fileList
:
[
{
type
:
"法案"
,
name
:
"2024《重塑美国人口结构法案》"
},
{
type
:
"法案"
,
name
:
"2025《开放人才法案》"
}
]
},
{
id
:
3
,
title
:
"示例标题3"
,
tagList
:
[
"人才交流"
,
"移民政策"
],
fileList
:
[
{
type
:
"法案"
,
name
:
"2024《重塑美国人口结构法案》"
},
{
type
:
"法案"
,
name
:
"2025《开放人才法案》"
}
]
},
{
id
:
4
,
title
:
"示例标题4"
,
tagList
:
[
"人才交流"
,
"移民政策"
],
fileList
:
[
{
type
:
"法案"
,
name
:
"2024《重塑美国人口结构法案》"
},
{
type
:
"法案"
,
name
:
"2025《开放人才法案》"
}
]
},
{
id
:
5
,
title
:
"示例标题5"
,
tagList
:
[
"人才交流"
,
"移民政策"
],
fileList
:
[
{
type
:
"法案"
,
name
:
"2024《重塑美国人口结构法案》"
},
{
type
:
"法案"
,
name
:
"2025《开放人才法案》"
}
]
},
{
id
:
6
,
title
:
"示例标题6"
,
tagList
:
[
"人才交流"
,
"移民政策"
],
fileList
:
[
{
type
:
"法案"
,
name
:
"2024《重塑美国人口结构法案》"
},
{
type
:
"法案"
,
name
:
"2025《开放人才法案》"
}
]
},
{
id
:
7
,
title
:
"示例标题7"
,
tagList
:
[
"人才交流"
,
"移民政策"
],
fileList
:
[
{
type
:
"法案"
,
name
:
"2024《重塑美国人口结构法案》"
},
{
type
:
"法案"
,
name
:
"2025《开放人才法案》"
}
]
},
{
id
:
8
,
title
:
"示例标题8"
,
tagList
:
[
"人才交流"
,
"移民政策"
],
fileList
:
[
{
type
:
"法案"
,
name
:
"2024《重塑美国人口结构法案》"
},
{
type
:
"法案"
,
name
:
"2025《开放人才法案》"
}
]
},
{
id
:
9
,
title
:
"示例标题9"
,
tagList
:
[
"人才交流"
,
"移民政策"
],
fileList
:
[
{
type
:
"法案"
,
name
:
"2024《重塑美国人口结构法案》"
},
{
type
:
"法案"
,
name
:
"2025《开放人才法案》"
}
]
},
{
id
:
10
,
title
:
"示例标题10"
,
tagList
:
[
"人才交流"
,
"移民政策"
],
fileList
:
[
{
type
:
"法案"
,
name
:
"2024《重塑美国人口结构法案》"
},
{
type
:
"法案"
,
name
:
"2025《开放人才法案》"
}
]
const
searchOpinions
=
ref
(
""
);
// 政策建议相关情况(当前页/当前筛选展示的数据)
const
box1Data
=
ref
([]);
// 全量数据(用于标签统计与前端筛选分页)
const
allBox1Data
=
ref
([]);
// 每个标签在全部数据中的出现次数(基于全部数据,非当前页)
const
tagCountMap
=
ref
({});
// 标签列表(基于当前全量数据 + 搜索条件)
const
allTags
=
ref
([]);
// 当前选中的标签(空字符串表示“全部”)
const
activeTag
=
ref
(
""
);
// 当前选中的条目索引(相对于当前页,默认选中第一个)
const
activeItemIndex
=
ref
(
0
);
const
pageSize
=
ref
(
10
);
// 当前选中 item 的数据
const
box1DataItem
=
ref
(
null
);
// 整个页面容器,用于分页后滚回 wrap 顶部
const
wrapRef
=
ref
(
null
);
const
reportUrl
=
ref
(
""
);
// 总条数基于当前筛选条件(activeTag)
const
opinionsTotal
=
computed
(()
=>
{
if
(
!
activeTag
.
value
)
{
return
allBox1Data
.
value
.
length
;
}
]);
const
handleGetThinkTankReportPolicy
=
async
()
=>
{
try
{
const
res
=
await
getThinkTankReportPolicy
(
router
.
currentRoute
.
_value
.
params
.
id
);
console
.
log
(
"政策建议相关情况"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
box1Data
.
value
=
res
.
data
return
allBox1Data
.
value
.
filter
(
item
=>
(
item
.
domains
||
[]).
some
(
d
=>
d
?.
industryName
===
activeTag
.
value
)
).
length
;
});
const
currentPage
=
ref
(
1
);
const
handleCurrentChange
=
page
=>
{
currentPage
.
value
=
page
;
activeItemIndex
.
value
=
0
;
updatePageData
();
// 使用页面滚动轴滚动到最顶部
nextTick
(()
=>
{
scrollToWrapTop
()
});
};
const
scrollToWrapTop
=
()
=>
{
const
el
=
wrapRef
.
value
;
if
(
!
el
)
return
;
// 向上查找最近的可滚动父元素
let
node
=
el
.
parentElement
;
while
(
node
&&
node
!==
document
.
body
&&
node
!==
document
.
documentElement
)
{
if
(
node
.
scrollHeight
>
node
.
clientHeight
+
1
)
{
const
top
=
node
.
scrollTop
+
el
.
getBoundingClientRect
().
top
-
node
.
getBoundingClientRect
().
top
-
400
;
node
.
scrollTo
({
top
,
behavior
:
"smooth"
});
return
;
}
}
catch
(
error
)
{
console
.
error
(
"获取政策建议相关情况rror"
,
error
);
node
=
node
.
parentElement
;
}
// 兜底:全局滚动
window
.
scrollTo
({
top
:
el
.
getBoundingClientRect
().
top
+
window
.
scrollY
-
40
,
behavior
:
"smooth"
});
};
const
toReport
=
()
=>
{
// 相关政策动态
const
box2Data
=
ref
([
// {
// title: "这是示例标题a",
// content: "这是示例内容",
// time: "2025-08-30",
// img: 1
// },
// {
// title: "这是示例标题a",
// content: "这是示例内容",
// time: "2025-08-30",
// img: 1
// },
// {
// title: "这是示例标题a",
// content: "这是示例内容",
// time: "2025-08-30",
// img: 1
// },
// {
// title: "这是示例标题a",
// content: "这是示例内容",
// time: "2025-08-30",
// img: 1
// },
// {
// title: "这是示例标题a",
// content: "这是示例内容",
// time: "2025-08-30",
// img: 1
// },
// {
// title: "这是示例标题a",
// content: "这是示例内容",
// time: "2025-08-30",
// img: 1
// }
]);
// 点击新闻条目,跳转到新闻分析页
const
handleToNewsAnalysis
=
news
=>
{
console
.
log
(
news
)
const
route
=
router
.
resolve
({
path
:
"/newsAnalysis
"
,
query
:
{
newsId
:
news
.
newsI
d
name
:
"ReportOriginal
"
,
params
:
{
id
:
router
.
currentRoute
.
_value
.
params
.
i
d
}
});
window
.
open
(
route
.
href
,
"_blank"
);
};
// 当前页展示的数据
const
filteredOpinions
=
computed
(()
=>
box1Data
.
value
);
// 处理标签点击(通过接口按标签过滤)
const
handleTagClick
=
tag
=>
{
activeTag
.
value
=
tag
;
currentPage
.
value
=
1
;
// 切换标签只在前端重算分页,并重置选中行
activeItemIndex
.
value
=
0
;
updatePageData
();
};
const
handleSearchOpinions
=
()
=>
{
// 搜索时默认切回“全部”标签
activeTag
.
value
=
""
;
currentPage
.
value
=
1
;
activeItemIndex
.
value
=
0
;
handleGetThinkTankReportPolicyAction
();
};
// 高亮标题中与搜索关键词相同的文字
const
highlightKeyword
=
text
=>
{
if
(
!
text
)
return
""
;
const
keyword
=
(
searchOpinions
.
value
||
""
).
trim
();
if
(
!
keyword
)
return
text
;
// 转义正则特殊字符
const
escaped
=
keyword
.
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
"
\\
$&"
);
const
reg
=
new
RegExp
(
escaped
,
"gi"
);
return
text
.
replace
(
reg
,
match
=>
`<span class="highlight-keyword">
${
match
}
</span>`
);
};
// 根据当前全量数据更新 tag 及数量
const
updateTagsFromAllData
=
()
=>
{
const
set
=
new
Set
();
const
map
=
{};
allBox1Data
.
value
.
forEach
(
item
=>
{
(
item
.
domains
||
[]).
forEach
(
d
=>
{
if
(
!
d
?.
industryName
)
return
;
const
name
=
d
.
industryName
;
set
.
add
(
name
);
map
[
name
]
=
(
map
[
name
]
||
0
)
+
1
;
});
});
allTags
.
value
=
Array
.
from
(
set
);
tagCountMap
.
value
=
map
;
};
const
handleGetThinkTankReportPolicyAction
=
async
()
=>
{
try
{
const
res
=
await
getThinkTankReportPolicyAction
(
router
.
currentRoute
.
_value
.
params
.
id
);
const
params
=
{
reportId
:
router
.
currentRoute
.
_value
.
params
.
id
,
// 这里请求全量数据,前端自行分页
currentPage
:
0
,
pageSize
:
1000
,
keyword
:
(
searchOpinions
.
value
||
""
).
trim
(),
// 仍将标签传给后端,若后端支持则为额外兜底过滤
industryName
:
activeTag
.
value
||
undefined
};
const
res
=
await
getThinkTankReportPolicyAction
(
params
);
console
.
log
(
"相关政策动态"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
box2Data
.
value
=
res
.
data
// 每次请求先清空全量与当前页数据
allBox1Data
.
value
=
[];
box1Data
.
value
=
[];
if
(
res
&&
res
.
code
===
200
&&
res
.
data
&&
Array
.
isArray
(
res
.
data
.
content
))
{
const
content
=
res
.
data
.
content
;
allBox1Data
.
value
=
content
;
console
.
log
(
allBox1Data
.
value
,
"allBox1Data"
);
// 基于当前搜索结果更新 tag 和数量
updateTagsFromAllData
();
// 拉取到全量后,根据当前筛选与页码更新本页数据,默认选中第一页第一条
activeItemIndex
.
value
=
0
;
updatePageData
();
}
else
{
// 非 200 / 数据结构异常时,清空标签和数量
allTags
.
value
=
[];
tagCountMap
.
value
=
{};
console
.
warn
(
"相关政策动态接口返回非 200:"
,
res
&&
res
.
code
);
}
}
catch
(
error
)
{
console
.
error
(
"获取相关政策动态rror"
,
error
);
console
.
error
(
"获取相关政策动态 error"
,
error
);
allBox1Data
.
value
=
[];
box1Data
.
value
=
[];
allTags
.
value
=
[];
tagCountMap
.
value
=
{};
}
};
// 按当前标签与页码从全量数据中截取一页
const
updatePageData
=
()
=>
{
const
start
=
(
currentPage
.
value
-
1
)
*
pageSize
.
value
;
const
end
=
start
+
pageSize
.
value
;
let
list
=
allBox1Data
.
value
;
if
(
activeTag
.
value
)
{
list
=
list
.
filter
(
item
=>
(
item
.
domains
||
[]).
some
(
d
=>
d
?.
industryName
===
activeTag
.
value
)
);
}
const
pageList
=
list
.
slice
(
start
,
end
);
box1Data
.
value
=
pageList
;
// 如当前页有数据且当前选中索引超出范围,则默认选中当前页第一条
if
(
pageList
.
length
>
0
)
{
if
(
activeItemIndex
.
value
<
0
||
activeItemIndex
.
value
>=
pageList
.
length
)
{
activeItemIndex
.
value
=
0
;
}
// 同步当前选中 item 数据
box1DataItem
.
value
=
pageList
[
activeItemIndex
.
value
];
console
.
log
(
"当前选中政策建议:"
,
box1DataItem
.
value
);
}
else
{
activeItemIndex
.
value
=
-
1
;
box1DataItem
.
value
=
null
;
}
};
onMounted
(
async
()
=>
{
handleGetThinkTankReportPolicy
()
handleGetThinkTankReportPolicyAction
()
});
</
script
>
...
...
@@ -324,241 +324,406 @@ onMounted(async () => {
display
:
flex
;
justify-content
:
center
;
gap
:
16px
;
height
:
1
00%
;
height
:
1
573px
;
padding-bottom
:
16px
;
.box
-header
{
width
:
100%
;
height
:
5
0px
;
display
:
fle
x
;
position
:
relative
;
.header-left
{
margin-top
:
18
px
;
width
:
8
px
;
height
:
2
0px
;
border-radius
:
0
4px
4px
0
;
background
:
var
(
--
color-main-active
)
;
}
.box
1
{
margin-top
:
19px
;
width
:
160
0px
;
height
:
1173p
x
;
.box1-main
{
margin-top
:
8px
;
height
:
1097
px
;
padding-left
:
21
px
;
padding-right
:
5
0px
;
padding-bottom
:
21px
;
gap
:
55px
;
display
:
flex
;
.title
{
margin-left
:
14px
;
margin-top
:
14px
;
height
:
26px
;
line-height
:
26px
;
color
:
var
(
--
color-main-active
);
font-family
:
Microsoft
YaHei
;
font-size
:
20px
;
font-weight
:
700
;
}
.left
{
width
:
506px
;
gap
:
12px
;
display
:
flex
;
flex-direction
:
column
;
.header-btn-box
{
position
:
absolute
;
top
:
15px
;
right
:
83px
;
display
:
flex
;
justify-content
:
flex-end
;
gap
:
8px
;
.btn
{
height
:
28px
;
padding
:
0
8px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
4px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
28px
;
}
.search-box
{
display
:
flex
;
width
:
100%
;
height
:
32px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
4px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
position
:
relative
;
.btnActive
{
color
:
var
(
--
color-main-active
);
border
:
1px
solid
var
(
--
color-main-active
);
}
}
.header-right
{
position
:
absolute
;
top
:
14px
;
right
:
12px
;
display
:
flex
;
justify-content
:
flex-end
;
gap
:
4px
;
.icon
{
width
:
16px
;
height
:
16px
;
.icon
{
width
:
28px
;
height
:
28px
;
cursor
:
pointer
;
position
:
absolute
;
right
:
8px
;
top
:
8px
;
img
{
width
:
100%
;
height
:
100%
;
display
:
flex
;
justify-content
:
flex-end
;
img
{
width
:
100%
;
height
:
100%
;
}
}
}
}
}
}
.left
{
height
:
100%
;
.box1
{
margin-top
:
16px
;
width
:
1104px
;
height
:
100%
;
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background: rgba(255, 255, 255, 1);
.box1-main
{
width
:
1056px
;
min-height
:
738px
;
max-height
:
1280px
;
padding-bottom
:
20px
;
margin
:
0
auto
;
overflow
:
hidden
;
overflow-y
:
auto
;
.box1-item
{
align-items
:
center
;
// height: 128px;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
.tag-box
{
width
:
506px
;
gap
:
8px
;
display
:
flex
;
position
:
relative
;
height
:
28px
;
.left
{
width
:
28px
;
.tag
{
height
:
28px
;
border-radius
:
14px
;
background
:
rgba
(
231
,
243
,
255
,
1
);
margin-left
:
16px
;
margin-top
:
16px
;
text-align
:
center
;
line-height
:
28px
;
color
:
var
(
--
color-main-active
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
letter-spacing
:
0px
;
border
:
1px
solid
rgb
(
230
,
231
,
232
);
border-radius
:
4px
;
padding
:
0px
8px
;
display
:
flex
;
cursor
:
pointer
;
&
.tag-active
{
border-color
:
rgb
(
5
,
95
,
194
);
background-color
:
rgb
(
231
,
243
,
255
);
.tag-text
{
color
:
rgb
(
5
,
95
,
194
);
}
}
.tag-text
{
font-size
:
16px
;
line-height
:
28px
;
letter-spacing
:
0px
;
text-align
:
left
;
font-family
:
"Source Han Sans CN"
;
color
:
rgb
(
59
,
65
,
75
);
font-weight
:
400
;
align-items
:
center
;
display
:
flex
;
}
}
}
.item-box
{
width
:
506px
;
height
:
100%
;
border-top
:
1px
solid
rgb
(
234
,
236
,
238
);
.item
{
width
:
506px
;
display
:
flex
;
gap
:
18px
;
padding-top
:
16px
;
padding-left
:
15px
;
padding-right
:
22px
;
padding-bottom
:
16px
;
border-bottom
:
1px
solid
rgb
(
234
,
236
,
238
);
cursor
:
pointer
;
position
:
relative
;
&
.item-active
{
background-color
:
rgb
(
246
,
250
,
255
);
border
:
1px
solid
rgb
(
185
,
220
,
255
);
.item-right
{
color
:
rgb
(
5
,
95
,
194
);
font-weight
:
700
;
}
}
.center
{
width
:
850px
;
margin-left
:
18px
;
.item-left
{
margin-top
:
3px
;
width
:
24px
;
height
:
27px
;
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
border-radius
:
50%
;
background
:
rgba
(
231
,
243
,
255
,
1
);
color
:
var
(
--
color-main-active
);
font-family
:
"Source Han Sans CN"
;
font-size
:
14px
;
font-weight
:
400
;
letter-spacing
:
0px
;
/* 关键:不要被 flex 压缩 */
flex
:
0
0
27px
;
}
.
title
{
margin-top
:
16px
;
// height: 24px
;
.
item-right
{
flex
:
1
;
width
:
auto
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
1
8
px
;
font-weight
:
7
00
;
line-height
:
24
px
;
font-family
:
"Source Han Sans CN"
;
font-size
:
1
6
px
;
font-weight
:
4
00
;
line-height
:
30
px
;
letter-spacing
:
0px
;
text-align
:
left
;
}
.tag-box
{
.item-right-solid
{
position
:
absolute
;
right
:
0
;
top
:
16px
;
bottom
:
16px
;
width
:
4px
;
background-color
:
rgb
(
5
,
95
,
194
);
}
}
}
.box4-main-footer
{
display
:
flex
;
justify-content
:
space-between
;
}
}
.right
{
width
:
968px
;
.right-header
{
height
:
54px
;
width
:
968px
;
display
:
flex
;
border-top
:
1px
solid
rgb
(
234
,
236
,
238
);
border-bottom
:
1px
solid
rgb
(
234
,
236
,
238
);
margin-bottom
:
16px
;
.header-text
{
width
:
968px
;
height
:
24px
;
margin-top
:
16px
;
justify-content
:
space-between
;
display
:
flex
;
.right-text
{
height
:
24px
;
display
:
flex
;
margin-top
:
7px
;
gap
:
8px
;
.tag
{
height
:
22px
;
padding
:
0
8px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
4px
;
background
:
rgba
(
247
,
248
,
249
,
1
);
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
.text
{
color
:
rgb
(
5
,
95
,
194
);
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
/* Regular */
font-size
:
16px
;
line-height
:
24px
;
letter-spacing
:
0
;
text-align
:
justify
;
/* 两端对齐 */
}
.right-header-image
{
width
:
12px
;
height
:
12px
;
display
:
inline-block
;
margin-left
:
6px
;
margin-top
:
4
.5px
;
img
{
width
:
100%
;
height
:
100%
;
object-fit
:
contain
;
/* 保证图片不变形 */
letter-spacing
:
0px
;
text-align
:
left
;
}
}
}
.file-box
{
margin-top
:
7px
;
.left-text
{
height
:
24px
;
color
:
rgb
(
59
,
65
,
75
);
font-family
:
"Source Han Sans CN"
;
font-weight
:
700
;
/* Bold 粗体 */
font-size
:
18px
;
line-height
:
24px
;
letter-spacing
:
0
;
text-align
:
left
;
}
}
}
.right-content
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
/* Regular */
font-size
:
16px
;
line-height
:
30px
;
letter-spacing
:
0
;
text-align
:
left
;
color
:
rgb
(
59
,
65
,
75
);
margin-bottom
:
16px
;
}
.info-group
{
width
:
968px
;
height
:
160px
;
border-top
:
1px
solid
rgb
(
234
,
236
,
238
);
border-bottom
:
1px
solid
rgb
(
234
,
236
,
238
);
.info-content
{
width
:
968px
;
height
:
128px
;
margin-top
:
16px
;
display
:
flex
;
flex-direction
:
column
;
gap
:
16px
;
.info-item
{
height
:
32px
;
display
:
flex
;
gap
:
8px
;
align-items
:
center
;
justify-content
:
flex-start
;
.info-text
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
16px
;
line-height
:
24px
;
letter-spacing
:
0
;
text-align
:
justify
;
}
.file-item
{
height
:
32px
;
padding
:
0
8px
;
border-radius
:
4px
;
background
:
rgba
(
246
,
250
,
255
,
1
);
.info-right
{
height
:
24px
;
margin-left
:
8px
;
display
:
flex
;
align-items
:
center
;
.file-item-left
{
height
:
22px
;
padding
:
0
4px
;
border-radius
:
4px
;
background
:
rgba
(
231
,
243
,
255
,
1
);
line-height
:
22px
;
text-align
:
center
;
color
:
var
(
--
color-main-active
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
letter-spacing
:
0px
;
text-align
:
left
;
}
.file-item-center
{
margin-left
:
12px
;
color
:
var
(
--
color-main-active
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
left
;
.tag-box
{
height
:
24px
;
display
:
flex
;
flex-direction
:
row
;
flex-wrap
:
nowrap
;
gap
:
8px
;
.tag
{
border-radius
:
4px
;
height
:
24px
;
background-color
:
rgb
(
231
,
243
,
255
);
padding
:
1px
8px
;
font-family
:
"Source Han Sans CN"
,
sans-serif
;
font-weight
:
400
;
font-size
:
14px
;
line-height
:
22px
;
letter-spacing
:
0
;
text-align
:
left
;
color
:
rgb
(
5
,
95
,
194
);
}
}
.file-item-right
{
margin-left
:
12px
;
width
:
20px
;
.info-right-image
{
height
:
20px
;
width
:
20px
;
margin-right
:
4px
;
margin-top
:
2px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.info-right-text
{
color
:
rgb
(
95
,
101
,
108
);
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
16px
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
left
;
}
}
}
}
.right
{
position
:
absolute
;
top
:
16px
;
right
:
11px
;
display
:
flex
;
}
.right-footer-title
{
width
:
968px
;
height
:
56px
;
border-bottom
:
1px
solid
rgb
(
234
,
236
,
238
);
display
:
flex
;
.footer-title-text
{
height
:
24px
;
justify-content
:
flex-end
;
align-items
:
center
;
gap
:
9px
;
margin-top
:
16px
;
color
:
rgb
(
59
,
65
,
75
);
font-family
:
"Source Han Sans CN"
;
font-weight
:
700
;
font-size
:
18px
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
justify
;
}
}
.right-footer-box
{
width
:
968px
;
gap
:
13px
;
display
:
flex
;
flex-direction
:
column
;
.text
{
.relatedBills
{
width
:
968px
;
height
:
48px
;
border
:
1px
solid
rgb
(
231
,
243
,
255
);
background-color
:
rgb
(
246
,
250
,
255
);
padding
:
11px
16px
;
display
:
flex
;
.tag
{
border-radius
:
4px
;
height
:
24px
;
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
color
:
var
(
--
color-main-active
);
background-color
:
rgb
(
231
,
243
,
255
);
padding
:
1px
8px
;
font-family
:
"Source Han Sans CN"
,
sans-serif
;
font-weight
:
400
;
font-size
:
14px
;
line-height
:
22px
;
letter-spacing
:
0
;
text-align
:
left
;
color
:
rgb
(
5
,
95
,
194
);
margin-right
:
4px
;
}
.relatedBills-content
{
height
:
24px
;
margin-left
:
8px
;
margin-left
:
8px
;
font-family
:
"Source Han Sans CN"
,
sans-serif
;
font-weight
:
400
;
/* Regular 对应字重 */
font-size
:
16px
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
right
;
letter-spacing
:
0
;
text-align
:
left
;
color
:
rgb
(
5
,
95
,
194
);
}
.icon
{
width
:
16px
;
height
:
16px
;
.footer-image
{
margin-top
:
2px
;
width
:
20px
;
height
:
20px
;
margin-left
:
auto
;
img
{
width
:
100%
;
...
...
@@ -566,167 +731,78 @@ onMounted(async () => {
}
}
}
}
}
.box1-footer
{
height
:
50px
;
display
:
flex
;
justify-content
:
space-between
;
box-sizing
:
border-box
;
padding
:
20px
;
.info
{
color
:
rgba
(
132
,
136
,
142
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
line-height
:
18px
;
letter-spacing
:
0px
;
text-align
:
left
;
}
}
}
}
.box2
{
margin-top
:
16px
;
width
:
480px
;
height
:
790px
;
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background: rgba(255, 255, 255, 1);
.box2-main
{
margin
:
0
auto
;
margin-top
:
15px
;
width
:
459px
;
height
:
654px
;
overflow
:
hidden
;
.box2-item
{
height
:
109px
;
display
:
flex
;
.relatedAdministrativeOrders
{
width
:
968px
;
height
:
48px
;
border
:
1px
solid
rgb
(
231
,
243
,
255
);
background-color
:
rgb
(
246
,
250
,
255
);
padding
:
11px
16px
;
display
:
flex
;
.box2-item-left
{
.point
{
margin-left
:
14px
;
width
:
10px
;
height
:
10px
;
img
{
width
:
100%
;
height
:
100%
;
.relatedAdministrativeOrders-content
{
height
:
24px
;
margin-left
:
8px
;
font-family
:
"Source Han Sans CN"
,
sans-serif
;
font-weight
:
400
;
/* Regular 对应字重 */
font-size
:
16px
;
line-height
:
24px
;
letter-spacing
:
0
;
text-align
:
left
;
color
:
rgb
(
5
,
95
,
194
);
}
}
.line
{
width
:
2px
;
height
:
109px
;
background
:
#ccc
;
margin-left
:
18px
;
}
}
.box2-item-center
{
margin-left
:
11px
;
.title
{
width
:
314px
;
height
:
24px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
24px
;
letter-spacing
:
1px
;
text-align
:
left
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.content
{
width
:
314px
;
height
:
48px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
justify
;
overflow
:
hidden
;
}
}
.box2-item-right
{
margin-left
:
18px
;
.time
{
height
:
22px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
line-height
:
22px
;
letter-spacing
:
0px
;
text-align
:
right
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.tag
{
border-radius
:
4px
;
height
:
24px
;
background-color
:
rgb
(
231
,
243
,
255
);
padding
:
1px
8px
;
font-family
:
"Source Han Sans CN"
,
sans-serif
;
font-weight
:
400
;
font-size
:
14px
;
line-height
:
22px
;
letter-spacing
:
0
;
text-align
:
left
;
color
:
rgb
(
5
,
95
,
194
);
margin-right
:
4px
;
}
.img-box
{
margin-top
:
14
px
;
width
:
78
px
;
height
:
5
0px
;
border-radius
:
2px
;
.footer-image
{
margin-top
:
2
px
;
width
:
20
px
;
height
:
2
0px
;
margin-left
:
auto
;
img
{
width
:
100%
;
height
:
100%
;
img
{
width
:
100%
;
height
:
100%
;
}
}
}
}
}
}
.box2-footer
{
margin
:
10px
auto
;
width
:
108px
;
height
:
32px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
4px
;
cursor
:
pointer
;
&
:hover
{
background
:
var
(
--
color-bg-hover
);
}
}
.text
{
color
:
var
(
--
color-main-active
);
height
:
22px
;
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
line-height
:
22px
;
letter-spacing
:
0px
;
}
.icon
{
width
:
16px
;
height
:
16px
;
}
img
{
width
:
100%
;
height
:
100%
;
}
}
}
/* 搜索关键词高亮(v-html 渲染,需要 :deep 才能作用到插入的 DOM) */
:deep
(
.highlight-keyword
)
{
background-color
:
#fff3b0
;
}
:deep
(
.analysis-box-wrapper
.wrapper-header
)
{
height
:
54px
!
important
;
display
:
flex
;
align-items
:
center
;
.header-title
>
div
{
line-height
:
54px
;
}
}
</
style
>
\ No newline at end of file
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
浏览文件 @
82f15736
...
...
@@ -1129,5 +1129,10 @@ onMounted(() => {
:deep
(
.analysis-box-wrapper
.wrapper-header
)
{
height
:
54px
!
important
;
display
:
flex
;
align-items
:
center
;
.header-title
>
div
{
line-height
:
54px
;
}
}
</
style
>
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
浏览文件 @
82f15736
...
...
@@ -456,11 +456,10 @@ const handleGetThinkPolicyIndustryChange = async () => {
frontendData
.
data
.
push
(
industryData
);
});
box3Data
.
value
=
frontendData
;
let
box3Chart
=
getMultiLineChart
(
// 传入全部行业数据,支持 13 条线同时展示
const
box3Chart
=
getMultiLineChart
(
box3Data
.
value
.
title
,
box3Data
.
value
.
data
[
0
].
value
,
box3Data
.
value
.
data
[
1
].
value
,
box3Data
.
value
.
data
[
2
].
value
box3Data
.
value
.
data
);
setChart
(
box3Chart
,
"box3Chart"
);
}
...
...
src/views/thinkTank/ThinkTankDetail/PolicyTracking/utils/multiLineChart.js
浏览文件 @
82f15736
import
*
as
echarts
from
'echarts'
import
{
size
,
split
}
from
'lodash'
const
getMultiLineChart
=
(
dataX
,
dataY1
,
dataY2
,
dataY3
)
=>
{
// data: [{ name: string, value: number[] }, ...]
const
getMultiLineChart
=
(
dataX
,
seriesData
)
=>
{
return
{
tooltip
:
{
trigger
:
'axis'
,
...
...
@@ -71,32 +72,12 @@ const getMultiLineChart = (dataX, dataY1, dataY2, dataY3) => {
}
}
],
series
:
[
{
name
:
'人工智能'
,
type
:
'line'
,
emphasis
:
{
focus
:
'series'
},
data
:
dataY1
},
{
name
:
'集成电路'
,
type
:
'line'
,
emphasis
:
{
focus
:
'series'
},
data
:
dataY2
},
{
name
:
'量子科技'
,
type
:
'line'
,
emphasis
:
{
focus
:
'series'
},
data
:
dataY3
}
]
series
:
(
seriesData
||
[]).
map
(
item
=>
({
name
:
item
.
name
,
type
:
'line'
,
emphasis
:
{
focus
:
'series'
},
data
:
item
.
value
}))
}
}
...
...
src/views/thinkTank/allThinkTank/index.vue
0 → 100644
浏览文件 @
82f15736
<
template
>
<div
class=
"home-wrapper"
>
<div
class=
"home-main"
ref=
"containerRef"
>
<div
class=
"home-top-bg"
></div>
<div
class=
"to-back"
>
<div
class=
"to-back-btn"
@
click=
"goToAllThinkTank"
>
<div
class=
"back-image"
>
<img
src=
"../assets/images/arrow-left.png"
/>
</div>
<div
class=
"back-text"
>
{{
"返回"
}}
</div>
</div>
</div>
<div
class=
"item-box"
>
<div
class=
"box-header"
>
<div
class=
"box-header-text"
>
<div
class=
"box-title"
>
{{
"美国科技智库"
}}
</div>
<div
class=
"number-btn"
>
<div
class=
"number-text"
>
{{
total
}}{{
"家"
}}
</div>
</div>
</div>
<div
class=
"select-box"
>
<div
class=
"search-box"
>
<el-input
placeholder=
"搜索政策建议"
v-model=
"searchPolicy"
>
<template
#
suffix
>
<img
src=
"../assets/images/Line_Search.png"
class=
"search-icon"
alt=
"搜索"
>
</
template
>
</el-input>
</div>
<div
class=
"select-box-sort"
>
<el-select
v-model=
"sort"
placeholder=
"报告数量"
:teleported=
"true"
:placement=
"'bottom-start'"
:popper-options=
"{
modifiers: [
{
name: 'preventOverflow', // 禁用自动翻转逻辑
options: {
mainAxis: false, // 禁用垂直方向的自动调整
altAxis: false, // 禁用水平方向的自动调整
}
},
{
name: 'flip', // 完全禁用翻转功能
enabled: false
}
]
}"
>
<
template
#
prefix
>
<img
src=
"../assets/images/sort-asc.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSortAndFetch()"
:key=
"true"
label=
"正序"
:value=
"true"
v-if=
"sort"
/>
<img
src=
"../assets/images/sort-desc.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSortAndFetch()"
:key=
"true"
label=
"倒序"
:value=
"true"
v-if=
"!sort"
/>
</
template
>
<el-option
@
click=
"handleGetThinkDynamicsReport()"
:key=
"true"
label=
"正序"
:value=
"true"
/>
<el-option
@
click=
"handleGetThinkDynamicsReport()"
:key=
"false"
label=
"倒序"
:value=
"false"
/>
</el-select>
</div>
</div>
</div>
<div
class=
"title-info-bar"
>
<div
class=
"title-info"
>
<div
class=
"info"
>
<img
src=
"../assets/images/info.png"
/>
</div>
<div
class=
"title"
>
{{ "近期美国智库机构发布涉华报告数量汇总" }}
</div>
</div>
</div>
<div
class=
"all-item"
>
<div
class=
"item-card"
v-for=
"(item, index) in sortedCardList"
:key=
"item.id || index"
@
click=
"handleClick(item)"
>
<div
class=
"item-header"
>
<div
class=
"item-header-image"
>
<img
:src=
item.logo
alt=
""
/>
</div>
<div
class=
"item-header-text"
>
<div
class=
"text"
>
{{ item.reportNumber }}{{ "篇报告" }}
</div>
</div>
</div>
<div
class=
"item-middle"
>
<div
class=
"think-tank-name"
>
{{ item.name }}
</div>
<div
class=
"country"
>
{{ item.country }}
</div>
</div>
<div
class=
"item-content"
>
{{ item.desc }}
</div>
<div
class=
"tag-box"
>
<AreaTag
v-for=
"(val, idx) in item.tagList"
:key=
"idx"
:tagName=
"val.industryName"
></AreaTag>
</div>
</div>
</div>
<div
class=
"page-box"
>
<div
class=
"page-change"
>
<el-pagination
v-model:current-page=
"currentPage"
:page-size=
"pageSize"
:total=
"total"
layout=
"prev, pager, next"
background
@
current-change=
"handleCurrentChange"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<
script
setup
>
import
{
ref
,
reactive
,
computed
,
onMounted
}
from
"vue"
;
import
{
getAllThinkTankList
}
from
"@/api/thinkTank/overview"
import
{
useRouter
}
from
'vue-router'
;
import
router
from
"@/router"
;
const
cardList
=
ref
([]);
// 按 reportNumber 从大到小排序,保证从左到右从上到下排列
const
sortedCardList
=
computed
(()
=>
{
return
[...
cardList
.
value
].
sort
((
a
,
b
)
=>
{
const
an
=
Number
(
a
?.
reportNumber
??
0
);
const
bn
=
Number
(
b
?.
reportNumber
??
0
);
return
bn
-
an
;
});
});
// el-pagination 是 1-based
const
currentPage
=
ref
(
1
)
const
pageSize
=
ref
(
15
)
const
total
=
ref
(
0
)
const
handleCurrentChange
=
page
=>
{
currentPage
.
value
=
page
;
handleGetThinkTankList
()
};
const
routerTo
=
useRouter
()
// 跳转到全部智库页面
const
goToAllThinkTank
=
()
=>
{
// 替换为你的实际路由路径
routerTo
.
push
(
'/thinkTank'
);
};
const
handleGetThinkTankList
=
async
()
=>
{
try
{
const
res
=
await
getAllThinkTankList
({
// 后端通常是 0-based,这里做一次转换
currentPage
:
currentPage
.
value
-
1
,
pageSize
:
pageSize
.
value
});
console
.
log
(
"智库列表"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
const
list
=
res
.
data
?.
content
||
[];
total
.
value
=
res
.
data
.
totalElements
;
cardList
.
value
=
list
.
map
(
item
=>
({
...
item
,
id
:
item
.
id
,
logo
:
item
.
imageUrl
,
name
:
item
.
name
,
tagList
:
item
.
tags
,
country
:
item
.
country
,
desc
:
item
.
describe
,
rank
:
item
.
id
,
reportNumber
:
item
.
reportNumber
}));
}
}
catch
(
error
)
{
console
.
error
(
"获取智库列表error"
,
error
);
}
};
const
sort
=
ref
(
null
);
const
searchPolicy
=
ref
(
""
);
const
handleClick
=
tank
=>
{
console
.
log
(
tank
);
window
.
sessionStorage
.
setItem
(
"curTabName"
,
tank
.
name
+
"概览"
);
// router.push({ name: "ThinkTankDetail", params: { id: tank.id } });
if
(
!
tank
.
id
)
{
ElMessage
.
warning
(
"当前智库id为空,无法进入详情页"
);
return
;
}
const
curRoute
=
router
.
resolve
({
name
:
"ThinkTankDetail"
,
params
:
{
id
:
tank
.
id
,
name
:
tank
.
name
}
});
window
.
open
(
curRoute
.
href
,
"_blank"
);
};
onMounted
(
async
()
=>
{
handleGetThinkTankList
();
});
</
script
>
<
style
lang=
"scss"
scoped
>
.home-wrapper
{
width
:
100%
;
height
:
100%
;
position
:
relative
;
overflow-y
:
hidden
;
.home-main
{
position
:
relative
;
width
:
100%
;
height
:
100%
;
overflow-y
:
auto
;
background-size
:
100%
100%
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
.home-top-bg
{
background
:
url("../assets/images/background.png")
,
linear-gradient
(
180deg
,
rgba
(
229
,
241
,
254
,
1
)
0%
,
rgba
(
246
,
251
,
255
,
0
)
30%
);
background-size
:
100%
100%
;
position
:
absolute
;
width
:
100%
;
height
:
100%
;
z-index
:
-100
;
top
:
-64px
;
}
.to-back
{
width
:
100%
;
height
:
80px
;
box-sizing
:
border-box
;
min-height
:
80px
;
position
:
relative
;
.to-back-btn
{
position
:
absolute
;
left
:
calc
(
50%
-
76px
-
1600px
/
2
-
20px
-
33px
);
/* 核心公式 */
width
:
92px
;
height
:
38px
;
border-radius
:
32px
;
background-color
:
rgba
(
255
,
255
,
255
,
0
.65
);
border
:
1px
solid
rgba
(
25
,
69
,
130
,
0
.1
);
display
:
flex
;
padding-left
:
20px
;
margin-top
:
24px
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
.back-text
{
height
:
30px
;
margin-top
:
3px
;
font-family
:
"Source Han Sans CN"
,
sans-serif
;
font-weight
:
400
;
font-size
:
16px
;
line-height
:
30px
;
letter-spacing
:
0
;
text-align
:
center
;
color
:
rgb
(
95
,
101
,
108
);
}
.back-image
{
width
:
16px
;
height
:
16px
;
margin-top
:
11px
;
margin-right
:
4px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
}
}
.item-box
{
width
:
1600px
;
height
:
962px
;
box-sizing
:
border-box
;
min-height
:
962px
;
.box-header
{
width
:
1600px
;
height
:
62px
;
display
:
flex
;
justify-content
:
space-between
;
.box-header-text
{
height
:
62px
;
display
:
flex
;
.box-title
{
font-family
:
"YouSheBiaoTiHei"
;
font-weight
:
400
;
font-size
:
48px
;
line-height
:
62px
;
letter-spacing
:
0
;
text-align
:
left
;
color
:
rgb
(
34
,
41
,
52
);
margin-left
:
2px
;
}
.number-btn
{
height
:
35px
;
border-radius
:
50px
;
background-color
:
rgb
(
5
,
95
,
194
);
display
:
flex
;
padding
:
2px
16px
;
margin-top
:
13
.5px
;
margin-left
:
12px
;
.number-text
{
font-family
:
"YouSheBiaoTiHei"
;
font-weight
:
400
;
font-size
:
24px
;
line-height
:
31px
;
letter-spacing
:
0
;
text-align
:
left
;
color
:
rgb
(
255
,
255
,
255
);
}
}
}
.select-box
{
height
:
32px
;
margin-top
:
15px
;
display
:
flex
;
gap
:
12px
;
.search-box
{
width
:
160px
;
height
:
32px
;
border
:
1px
solid
rgb
(
230
,
231
,
232
);
background-color
:
rgb
(
255
,
255
,
255
);
border-radius
:
4px
;
:deep
(
.el-input__wrapper
)
{
height
:
32px
;
border
:
1px
solid
rgb
(
230
,
231
,
232
);
background-color
:
#fff
;
border-radius
:
4px
;
box-shadow
:
none
;
padding
:
0
10px
;
}
.search-icon
{
width
:
16px
;
height
:
16px
;
cursor
:
pointer
;
}
}
.select-box-sort
{
.select-prefix-img
{
width
:
6
.72px
;
height
:
14px
;
margin-right
:
10px
;
}
}
:deep
(
.el-select-dropdown
)
{
left
:
0
!
important
;
/* 强制下拉框左对齐选择框 */
top
:
100%
!
important
;
/* 强制下拉框在选择框正下方 */
transform
:
none
!
important
;
/* 禁用默认的位移变换 */
}
}
}
.title-info-bar
{
width
:
1600px
;
height
:
36px
;
margin-top
:
14px
;
display
:
flex
;
.title-info
{
height
:
24px
;
margin-top
:
6px
;
display
:
flex
;
.info
{
width
:
16px
;
height
:
16px
;
margin-top
:
4px
;
margin-right
:
8px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.title
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
18px
;
line-height
:
24px
;
letter-spacing
:
0
;
text-align
:
left
;
color
:
rgb
(
59
,
65
,
75
);
}
}
}
.all-item
{
width
:
1600px
;
height
:
716px
;
margin-top
:
30px
;
display
:
flex
;
gap
:
16px
;
flex-wrap
:
wrap
;
/* 👉 加上这一行就自动换行了! */
.item-card
{
width
:
307px
;
height
:
228px
;
box-shadow
:
0
0
20px
0
rgba
(
25
,
69
,
130
,
0
.1
);
background-color
:
rgba
(
255
,
255
,
255
,
0
.65
);
border
:
1px
solid
rgb
(
255
,
255
,
255
);
border-radius
:
10px
;
padding-left
:
20px
;
padding-top
:
17px
;
padding-bottom
:
21px
;
.item-header
{
width
:
287px
;
height
:
36px
;
display
:
flex
;
.item-header-image
{
width
:
36px
;
height
:
36px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.item-header-text
{
height
:
32px
;
width
:
180px
;
margin-top
:
2px
;
margin-left
:
71px
;
background
:
linear-gradient
(
270deg
,
rgba
(
5
,
95
,
194
,
0
.1
)
,
rgba
(
5
,
95
,
194
,
0
)
100%
);
.text
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
700
;
font-size
:
18px
;
line-height
:
24px
;
letter-spacing
:
0
;
text-align
:
right
;
color
:
rgb
(
5
,
95
,
194
);
width
:
136px
;
height
:
24px
;
margin-top
:
4px
;
margin-left
:
26px
;
}
}
}
.item-middle
{
height
:
24px
;
display
:
flex
;
justify-content
:
space-between
;
margin-top
:
11px
;
.think-tank-name
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
700
;
font-size
:
16px
;
line-height
:
24px
;
letter-spacing
:
1px
;
text-align
:
left
;
color
:
rgb
(
59
,
65
,
75
);
height
:
24px
;
}
.country
{
color
:
rgb
(
132
,
136
,
142
);
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
14px
;
line-height
:
22px
;
letter-spacing
:
0
;
text-align
:
right
;
height
:
22px
;
margin-right
:
21px
;
}
}
.item-content
{
width
:
266px
;
height
:
66px
;
margin-top
:
13px
;
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
14px
;
line-height
:
22px
;
letter-spacing
:
0
;
text-align
:
justify
;
color
:
rgb
(
95
,
101
,
108
);
/* 👇 下面这 4 行是核心:控制 3 行溢出显示 ... */
display
:
-
webkit-box
;
-webkit-line-clamp
:
3
;
/* 限制 3 行 */
-webkit-box-orient
:
vertical
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
.tag-box
{
margin-top
:
14px
;
margin-right
:
21px
;
display
:
flex
;
gap
:
8px
;
height
:
24px
;
display
:
flex
;
flex-wrap
:
wrap
;
/* 允许换行 */
gap
:
8px
;
overflow
:
auto
;
/* 根据内容自动调整高度 */
// max-width: calc(3 * 40px + 2 * 8px);
/* 每行 3 列,每个 tag 最大宽度 200px,gap 8px */
}
}
}
.page-box
{
height
:
32px
;
margin-top
:
36px
;
display
:
flex
;
justify-content
:
center
;
}
}
}
}
:deep
(
.el-select
)
{
width
:
160px
;
height
:
32px
;
}
</
style
>
\ No newline at end of file
src/views/thinkTank/assets/images/Line_Search.png
0 → 100644
浏览文件 @
82f15736
21.4 KB
src/views/thinkTank/assets/images/arrow-left.png
0 → 100644
浏览文件 @
82f15736
13.9 KB
src/views/thinkTank/assets/images/blue-right.png
0 → 100644
浏览文件 @
82f15736
16.4 KB
src/views/thinkTank/assets/images/info.png
0 → 100644
浏览文件 @
82f15736
20.8 KB
src/views/thinkTank/assets/images/sort-asc.png
0 → 100644
浏览文件 @
82f15736
5.1 KB
src/views/thinkTank/assets/images/sort-desc.png
0 → 100644
浏览文件 @
82f15736
22.1 KB
src/views/thinkTank/index.vue
浏览文件 @
82f15736
...
...
@@ -42,9 +42,18 @@
<div
class=
"item-footer"
>
热点科技领域
</div>
</div>
</div>
-->
<div
class=
"title-info-bar"
>
<div
class=
"title-info"
>
<div
class=
"info"
>
<img
src=
"./assets/images/info.png"
/>
</div>
<div
class=
"title"
>
{{
"近期美国智库机构发布涉华报告数量汇总"
}}
</div>
</div>
</div>
<div
class=
"home-main-header-card-box"
>
<div
class=
"card"
v-for=
"(item, index) in
c
ardList"
:key=
"index"
@
click=
"handleClick(item)"
>
<div
class=
"card"
v-for=
"(item, index) in
sortedC
ardList"
:key=
"index"
@
click=
"handleClick(item)"
>
<div
class=
"card-header"
>
<div
class=
"icon"
>
<img
:src=
"item.logo"
alt=
""
/>
...
...
@@ -52,9 +61,8 @@
<!--
<div
class=
"rank"
:class=
"
{ rank1: item.rank === 1, rank2: item.rank === 2, rank3: item.rank === 3 }">
{{
"No."
+
(
index
+
1
)
}}
</div>
-->
<div
class=
"rank"
:class=
"
{ 'rank-number-one': index === 0, 'rank-number-two': index === 1, 'rank-number-three': index === 2, 'rank-number-four': index === 3, 'rank-number-five': index === 4 }">
<div
class=
"number"
>
{{
"No."
+
(
index
+
1
)
}}
</div>
<div
class=
"rank"
>
<div
class=
" number"
>
{{
item
.
reportNumber
}}
{{
"篇报告"
}}
</div>
</div>
</div>
...
...
@@ -74,9 +82,12 @@
<AreaTag
v-for=
"(val, idx) in item.tagList"
:key=
"idx"
:tagName=
"val.industryName"
></AreaTag>
</div>
</div>
<div
class=
"card"
>
<div
class=
"card"
@
click=
"goToAllThinkTank()"
>
<div
class=
"more"
>
{{ "查看全部智库 >" }}
{{ "查看全部智库" }}{{ "(" }}{{ totalAllItem }}{{ ")" }}{{ "家" }}
<div
class=
"blue-right"
>
<img
src=
"./assets/images/blue-right.png"
alt=
""
/>
</div>
</div>
</div>
</div>
...
...
@@ -170,7 +181,8 @@
</div>
</div>
<div
class=
"box5-main"
>
<div
id=
"box5Chart"
></div>
<el-empty
v-if=
"!hasBox5ChartData"
description=
"暂无数据"
:image-size=
"100"
/>
<div
id=
"box5Chart"
v-else
></div>
<div
class=
"source"
>
<div
class=
"info"
><img
src=
"./assets/images/image-exclamation.png"
></div>
<div
class=
"text"
>
数据来源:美国国会官网,数据时间:2015.1至2025.12
</div>
...
...
@@ -331,6 +343,7 @@ import {
getThinkTankPolicyIndustryChange
,
getThinkTankPolicyIndustry
,
getThinkTankDonation
,
getAllThinkTankList
,
getThinkTankHot
,
getNewReport
,
getHylyList
,
...
...
@@ -378,8 +391,34 @@ import Box1Img from "./assets/images/box1-img.png";
import
Box1Logo
from
"./assets/images/box1-logo.png"
;
import
{
setCanvasCreator
}
from
"echarts/core"
;
import
{
ElMessage
}
from
"element-plus"
;
import
{
useRouter
}
from
'vue-router'
;
const
containerRef
=
ref
(
null
);
const
statCountInfo
=
ref
([]);
const
pageSize
=
ref
(
15
)
const
totalAllItem
=
ref
(
0
)
const
handleGetAllThinkTankList
=
async
()
=>
{
try
{
const
res
=
await
getAllThinkTankList
({
// 后端通常是 0-based,这里做一次转换
currentPage
:
currentPage
.
value
-
1
,
pageSize
:
pageSize
.
value
});
console
.
log
(
"智库列表"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
totalAllItem
.
value
=
res
.
data
.
totalElements
;
}
}
catch
(
error
)
{
console
.
error
(
"获取智库列表error"
,
error
);
}
};
const
routerTo
=
useRouter
()
// 跳转到全部智库页面
const
goToAllThinkTank
=
()
=>
{
// 替换为你的实际路由路径
routerTo
.
push
(
'/thinkTank/allThinkTank'
);
};
const
getStatCountInfo
=
async
()
=>
{
...
...
@@ -446,6 +485,13 @@ const cardList = ref([
// ]
// }
]);
const
sortedCardList
=
computed
(()
=>
{
return
[...
cardList
.
value
].
sort
((
a
,
b
)
=>
{
const
an
=
Number
(
a
?.
reportNumber
??
0
);
const
bn
=
Number
(
b
?.
reportNumber
??
0
);
return
bn
-
an
;
});
});
// 获取智库列表
const
handleGetThinkTankList
=
async
()
=>
{
try
{
...
...
@@ -729,6 +775,11 @@ const box5Data = ref({
// }
// ]
});
const
hasBox5ChartData
=
computed
(()
=>
{
const
v
=
box5Data
.
value
;
if
(
!
v
||
typeof
v
!==
"object"
)
return
false
;
return
Array
.
isArray
(
v
.
title
)
&&
v
.
title
.
length
>
0
&&
Array
.
isArray
(
v
.
data
)
&&
v
.
data
.
length
>
0
;
});
//获取当前时间x年前的日期
function
getDateYearsAgo
(
years
)
{
...
...
@@ -814,7 +865,7 @@ const handleGetThinkTankPolicyIndustryChange = async date => {
});
box5Data
.
value
=
result
;
}
else
{
box5Data
.
value
=
[]
;
box5Data
.
value
=
{
title
:
[],
data
:
[]
}
;
}
}
catch
(
error
)
{
console
.
error
(
"获取政策建议趋势分布error"
,
error
);
...
...
@@ -1739,6 +1790,7 @@ const handleSearch = () => {
onMounted
(
async
()
=>
{
handleGetThinkTankList
();
handleGetAllThinkTankList
()
await
getStatCountInfo
();
// 定义一个定时器,每隔2秒轮播一次
setInterval
(()
=>
{
...
...
@@ -2009,8 +2061,43 @@ onMounted(async () => {
}
}
.home-main-header-card-box
{
.title-info-bar
{
width
:
1600px
;
height
:
36px
;
margin-top
:
64px
;
display
:
flex
;
.title-info
{
height
:
24px
;
margin-top
:
6px
;
display
:
flex
;
.info
{
width
:
16px
;
height
:
16px
;
margin-top
:
4px
;
margin-right
:
8px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.title
{
font-family
:
"Source Han Sans CN"
;
font-weight
:
400
;
font-size
:
18px
;
line-height
:
24px
;
letter-spacing
:
0
;
text-align
:
left
;
color
:
rgb
(
59
,
65
,
75
);
}
}
}
.home-main-header-card-box
{
margin-top
:
30px
;
display
:
flex
;
justify-content
:
space-between
;
...
...
@@ -2050,77 +2137,26 @@ onMounted(async () => {
}
.rank
{
width
:
1
0
0px
;
width
:
1
8
0px
;
height
:
32px
;
background
:
linear-gradient
(
270deg
,
rgba
(
5
,
95
,
194
,
0
.1
)
,
rgba
(
5
,
95
,
194
,
0
)
100%
);
.number
{
line-height
:
32px
;
text-align
:
left
;
font-family
:
"YouSheBiaoTiHei"
;
font-size
:
24px
;
font-weight
:
400
;
line-height
:
31px
;
letter-spacing
:
0px
;
overflow
:
hidden
;
height
:
31px
;
font-family
:
"Source Han Sans CN"
;
font-weight
:
700
;
font-size
:
18px
;
line-height
:
24px
;
letter-spacing
:
0
;
text-align
:
right
;
width
:
136px
;
margin-top
:
4px
;
margin-left
:
26px
;
color
:
rgb
(
5
,
95
,
194
);
}
}
// text-overflow: ellipsis;
// white-space: nowrap;
.rank-number-one
{
background
:
linear-gradient
(
90deg
,
rgba
(
206
,
79
,
81
,
0
)
,
rgba
(
206
,
79
,
81
,
0
.3
)
100%
);
color
:
rgb
(
206
,
79
,
81
);
padding-left
:
33px
;
}
.rank-number-two
{
background
:
linear-gradient
(
270deg
,
rgba
(
255
,
172
,
77
,
0
.3
)
,
rgba
(
255
,
172
,
77
,
0
)
100%
);
color
:
rgb
(
255
,
149
,
77
);
padding-left
:
23px
;
}
.rank-number-three
{
background
:
linear-gradient
(
270deg
,
rgba
(
255
,
197
,
61
,
0
.3
)
,
rgba
(
255
,
197
,
61
,
0
)
100%
);
color
:
rgba
(
255
,
197
,
61
,
1
);
padding-left
:
23px
;
}
.rank-number-four
{
background
:
linear-gradient
(
270deg
,
rgba
(
10
,
87
,
166
,
0
.3
)
,
rgba
(
10
,
87
,
166
,
0
)
100%
);
color
:
rgba
(
5
,
95
,
194
,
1
);
padding-left
:
23px
;
}
.rank-number-five
{
background
:
linear-gradient
(
270deg
,
rgba
(
10
,
87
,
166
,
0
.3
)
,
rgba
(
10
,
87
,
166
,
0
)
100%
);
color
:
rgba
(
5
,
95
,
194
,
1
);
padding-left
:
23px
;
}
.rank1
{
background
:
linear-gradient
(
270deg
,
rgba
(
206
,
79
,
81
,
0
.3
)
,
rgba
(
206
,
79
,
81
,
0
)
100%
);
color
:
rgba
(
206
,
79
,
81
,
1
);
}
.rank2
{
background
:
linear-gradient
(
270deg
,
rgba
(
255
,
172
,
77
,
0
.3
)
,
rgba
(
255
,
172
,
77
,
0
)
100%
);
color
:
rgba
(
255
,
149
,
77
,
1
);
}
.rank3
{
background
:
linear-gradient
(
270deg
,
rgba
(
255
,
197
,
61
,
0
.3
)
,
rgba
(
255
,
197
,
61
,
0
)
100%
);
color
:
rgba
(
255
,
197
,
61
,
1
);
}
}
.card-title
{
...
...
@@ -2252,7 +2288,7 @@ onMounted(async () => {
.more
{
margin
:
103px
auto
;
height
:
2
2
px
;
height
:
2
4
px
;
color
:
rgba
(
5
,
95
,
194
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
...
...
@@ -2261,6 +2297,22 @@ onMounted(async () => {
letter-spacing
:
1px
;
text-align
:
center
;
cursor
:
pointer
;
display
:
flex
;
justify-content
:
center
;
/* 👈 水平居中 */
.blue-right
{
width
:
16px
;
height
:
16px
;
margin-top
:
5px
;
margin-left
:
8px
;
img
{
width
:
100%
;
height
:
100%
;
display
:
block
;
}
}
}
}
}
...
...
src/views/thinkTank/reportOriginal/index.vue
浏览文件 @
82f15736
...
...
@@ -3,23 +3,20 @@
<div
class=
"header"
>
<div
class=
"header-top"
>
<div
class=
"header-top-left"
>
<img
:src=
"thinkInfo.
content
Url"
alt=
""
/>
<img
:src=
"thinkInfo.
image
Url"
alt=
""
/>
<div>
<div
class=
"title"
>
{{
thinkInfo
.
name
}}
</div>
<div
class=
"en-title"
>
{{
thinkInfo
.
ename
}}
</div>
<div
style=
"display: flex;"
>
<div
class=
"tag-box"
v-for=
"value in thinkInfo.tags"
>
<div
class=
"tag"
>
{{
value
.
industryName
}}
</div>
</div>
</div>
</div>
</div>
<div
class=
"header-top-right"
>
<div
class=
"name"
>
{{
thinkInfo
.
thinkTankName
}}
</div>
<div
class=
"image-name-box"
>
<div
class=
"image"
>
<img
:src=
thinkInfo.thinkTankLogoUrl
alt=
""
/></div>
<div
class=
"name"
>
{{
thinkInfo
.
thinkTankName
}}
</div>
</div>
<div
class=
"time"
>
{{
thinkInfo
.
times
}}
</div>
</div>
</div>
...
...
@@ -30,19 +27,55 @@
<div
style=
" margin-top: 17px;"
>
智库报告原文
</div>
<div
class=
"btn-box"
>
<div
class=
"translate"
>
<div
class=
"switch"
>
<el-switch
v-model=
"valueSwitch"
/>
</div>
<div
class=
"translate-image"
>
<img
class=
"translate-icon"
src=
"../ReportDetail/images/image-translate.png"
alt=
""
style=
"width: 16px; height: 16px; max-width: 16px; max-height: 16px; display: block; object-fit: contain;"
/>
</div>
<div
class=
"translate-text"
>
{{
"显示原文"
}}
</div>
</div>
<div
class=
"btn"
@
click=
"handleDownload"
>
<div
class=
"icon"
>
<img
src=
"../ReportDetail/images/image-pdf.png"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
"下载"
}}
</div>
</div>
<div
class=
"btn search-btn"
@
click=
"showSearchInput = !showSearchInput"
>
<div
class=
"icon"
>
<img
src=
"../ReportDetail/images/Line_Search.png"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
"查找"
}}
</div>
</div>
<div
v-if=
"showSearchInput"
class=
"search-input-wrap"
>
<el-input
v-model=
"searchKeywordText"
placeholder=
"输入关键词查找页码"
clearable
size=
"small"
style=
"width: 200px;"
@
keyup
.
enter=
"handleSearchInPdf"
/>
<el-button
type=
"primary"
size=
"small"
@
click=
"handleSearchInPdf"
>
查找
</el-button>
</div>
</div>
</div>
<div
class=
"report-box"
>
<iframe
:src=
"reportUrlWithPage"
width=
"50%"
height=
"100%"
>
</iframe>
<iframe
:src=
"reportUrlEnWithPage"
width=
"50%"
height=
"100%"
>
</iframe>
<!--
<pdf
:pdfUrl=
"reportUrl"
style=
"width: 48%;"
/>
<pdf
:pdfUrl=
"reportUrlEn"
style=
"width: 48%;"
/>
-->
<!--
<iframe
src=
"https://www.rand.org/pubs/research_reports/RRA3572-1.html"
width=
"100%"
height=
"600px"
frameborder=
"0"
allowfullscreen
></iframe>
-->
<pdf
v-if=
"valueSwitch && reportUrlEnWithPage"
ref=
"leftPdfRef"
:pdfUrl=
"reportUrlEnWithPage"
class=
"pdf-pane"
/>
<pdf
v-if=
"reportUrlWithPage"
ref=
"rightPdfRef"
:pdfUrl=
"reportUrlWithPage"
:class=
"['pdf-pane',
{ 'pdf-pane-full': !valueSwitch }]"
/>
</div>
</div>
</div>
...
...
@@ -78,7 +111,83 @@ const buildPdfPageUrl = url => {
const
reportUrlWithPage
=
computed
(()
=>
buildPdfPageUrl
(
reportUrl
.
value
))
const
reportUrlEnWithPage
=
computed
(()
=>
buildPdfPageUrl
(
reportUrlEn
.
value
))
const
valueSwitch
=
ref
(
true
)
const
showSearchInput
=
ref
(
false
)
const
searchKeywordText
=
ref
(
''
)
const
leftPdfRef
=
ref
(
null
)
const
rightPdfRef
=
ref
(
null
)
const
handleSearchInPdf
=
async
()
=>
{
const
keyword
=
searchKeywordText
.
value
?.
trim
()
if
(
!
keyword
)
return
const
leftPdf
=
leftPdfRef
.
value
const
rightPdf
=
rightPdfRef
.
value
let
page
=
0
let
targetRef
=
null
if
(
leftPdf
&&
typeof
leftPdf
.
searchKeyword
===
'function'
)
{
page
=
await
leftPdf
.
searchKeyword
(
keyword
)
if
(
page
)
targetRef
=
leftPdf
}
if
(
!
page
&&
rightPdf
&&
typeof
rightPdf
.
searchKeyword
===
'function'
)
{
page
=
await
rightPdf
.
searchKeyword
(
keyword
)
if
(
page
)
targetRef
=
rightPdf
}
if
(
page
&&
targetRef
&&
typeof
targetRef
.
goToPage
===
'function'
)
{
targetRef
.
goToPage
(
page
)
}
else
{
try
{
const
{
ElMessage
}
=
await
import
(
'element-plus'
)
ElMessage
.
warning
(
'未找到包含该关键词的页面'
)
}
catch
(
_
)
{}
}
}
// 下载:中英文都下载,与政令原文页相同的 fetch → blob → a 标签触发下载
const
downloadOnePdf
=
async
(
url
,
filename
)
=>
{
const
response
=
await
fetch
(
url
,
{
method
:
'GET'
,
headers
:
{
'Content-Type'
:
'application/pdf'
},
})
if
(
!
response
.
ok
)
throw
new
Error
(
`HTTP error! status:
${
response
.
status
}
`
)
const
blob
=
await
response
.
blob
()
const
blobUrl
=
window
.
URL
.
createObjectURL
(
blob
)
const
link
=
document
.
createElement
(
'a'
)
link
.
href
=
blobUrl
link
.
download
=
filename
document
.
body
.
appendChild
(
link
)
link
.
click
()
document
.
body
.
removeChild
(
link
)
window
.
URL
.
revokeObjectURL
(
blobUrl
)
}
const
handleDownload
=
async
()
=>
{
const
urlZh
=
reportUrl
.
value
?
String
(
reportUrl
.
value
).
split
(
'#'
)[
0
]
:
''
const
urlEn
=
reportUrlEn
.
value
?
String
(
reportUrlEn
.
value
).
split
(
'#'
)[
0
]
:
''
if
(
!
urlZh
&&
!
urlEn
)
{
try
{
const
{
ElMessage
}
=
await
import
(
'element-plus'
)
ElMessage
.
warning
(
'暂无下载链接'
)
}
catch
(
_
)
{}
return
}
const
baseName
=
(
thinkInfo
.
value
?.
name
||
'报告原文'
).
replace
(
/
[/\\
?%*:|"<>
]
/g
,
'-'
)
const
{
ElMessage
}
=
await
import
(
'element-plus'
)
try
{
if
(
urlZh
)
{
await
downloadOnePdf
(
urlZh
,
`
${
baseName
}
_中文.pdf`
)
}
if
(
urlEn
)
{
if
(
urlZh
)
await
new
Promise
(
r
=>
setTimeout
(
r
,
300
))
await
downloadOnePdf
(
urlEn
,
`
${
baseName
}
_英文.pdf`
)
}
if
(
urlZh
||
urlEn
)
{
ElMessage
.
success
(
urlZh
&&
urlEn
?
'已下载中文、英文两份 PDF'
:
'下载成功'
)
}
}
catch
(
error
)
{
console
.
error
(
'下载失败:'
,
error
)
ElMessage
.
error
(
'PDF 下载失败,请稍后重试'
)
}
}
// 获取报告全局信息
const
handleGetThinkTankReportSummary
=
async
()
=>
{
...
...
@@ -121,36 +230,34 @@ onMounted(async () => {
<
style
lang=
"scss"
scoped
>
.wrap
{
width
:
100%
;
height
:
100%
;
overflow
:
hidden
;
overflow-y
:
auto
;
.header
{
width
:
100%
;
height
:
88px
;
box-sizing
:
border-box
;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
border-top
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
position
:
sticky
;
top
:
0
;
z-index
:
99
;
z-index
:
99999
;
overflow
:
hidden
;
.header-top
{
margin
:
0
auto
;
margin-top
:
20px
;
margin-left
:
160px
;
margin-bottom
:
20px
;
width
:
1600px
;
display
:
flex
;
justify-content
:
space-between
;
margin-right
:
160px
;
.header-top-left
{
display
:
flex
;
img
{
width
:
72
px
;
height
:
88
px
;
width
:
44
px
;
height
:
54
px
;
}
.title
{
...
...
@@ -166,6 +273,7 @@ onMounted(async () => {
}
.en-title
{
margin-top
:
4px
;
margin-left
:
20px
;
height
:
24px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
...
...
@@ -182,35 +290,50 @@ onMounted(async () => {
display
:
flex
;
gap
:
8px
;
margin-left
:
20px
;
}
}
.header-top-right
{
display
:
flex
;
flex-direction
:
column
;
text-align
:
right
;
align-items
:
flex-end
;
.image-name-box
{
width
:
118px
;
height
:
24px
;
gap
:
6px
;
.tag
{
height
:
26px
;
padding
:
0
8px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
231
,
243
,
255
,
1
);
border-radius
:
4px
;
background
:
rgba
(
246
,
250
,
255
,
1
);
color
:
var
(
--
color-main-active
);
text-align
:
right
;
display
:
flex
;
justify-content
:
flex-end
;
.name
{
height
:
24px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
left
;
text-align
:
right
;
}
}
}
.header-top-right
{
.name
{
height
:
24px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
right
;
.image
{
width
:
16px
;
height
:
16px
;
margin-top
:
5px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
}
.time
{
...
...
@@ -281,25 +404,118 @@ onMounted(async () => {
}
}
}
}
.main
{
margin
:
0
auto
;
background
:
rgb
(
255
,
255
,
255
);
width
:
1600px
;
height
:
928px
;
overflow
:
hidden
;
border
:
1px
,
solid
,
rgb
(
234
,
236
,
238
);
box-shadow
:
0
0
20px
0
rgba
(
25
,
69
,
130
,
0
.1
);
.main-header
{
height
:
64px
;
/* box-sizing: border-box; */
border-bottom
:
1px
solid
rgb
(
234
,
236
,
238
);
background
:
rgb
(
255
,
255
,
255
);
margin
:
0
70px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
"Source Han Sans CN"
;
font-style
:
Bold
;
font-size
:
20px
;
font-weight
:
700
;
line-height
:
26px
;
letter-spacing
:
0px
;
width
:
1456px
;
text-align
:
left
;
display
:
flex
;
justify-content
:
space-between
;
.btn-box
{
display
:
flex
;
gap
:
12px
;
align-items
:
center
;
gap
:
8px
;
.translate
{
display
:
flex
;
flex-wrap
:
nowrap
;
align-items
:
center
;
height
:
24px
;
margin-right
:
16px
;
:deep
(
.el-switch
)
{
width
:
22px
!
important
;
height
:
14px
!
important
;
margin-bottom
:
5px
;
margin-right
:
8px
;
}
:deep
(
.el-switch__core
)
{
width
:
22px
!
important
;
height
:
14px
!
important
;
min-width
:
22px
!
important
;
}
:deep
(
.el-switch__button
),
:deep
(
.el-switch__action
)
{
width
:
10px
!
important
;
height
:
10px
!
important
;
}
/* 打开时圆球从左边移到最右边:轨道 22px - 圆球 10px = 12px */
:deep
(
.el-switch.is-checked
.el-switch__button
),
:deep
(
.el-switch.is-checked
.el-switch__action
)
{
transform
:
translateX
(
6px
)
!
important
;
}
.translate-image
{
display
:
flex
;
width
:
16px
;
height
:
16px
;
overflow
:
hidden
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.translate-text
{
font-size
:
14px
;
font-weight
:
400
;
line-height
:
22px
;
}
}
.btn
{
width
:
120px
;
height
:
36px
;
width
:
88px
;
height
:
32px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
6px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
8px
;
.icon
{
width
:
16px
;
height
:
16px
;
display
:
inline-flex
;
margin-top
:
8px
;
margin-left
:
16px
;
img
{
width
:
100%
;
...
...
@@ -308,61 +524,49 @@ onMounted(async () => {
}
.text
{
width
:
66px
;
height
:
22px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
margin-top
:
4px
;
width
:
32px
;
height
:
24px
;
color
:
rgb
(
59
,
65
,
75
);
font-family
:
"Source Han Sans CN"
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
2
2
px
;
line-height
:
2
4
px
;
letter-spacing
:
0px
;
text-align
:
center
;
text-align
:
left
;
}
}
.
btn1
{
border-radius
:
6px
;
background
:
var
(
--
color-main-active
);
.
search-btn
{
cursor
:
pointer
;
}
.text
{
color
:
rgba
(
255
,
255
,
255
,
1
);
}
.search-input-wrap
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
8px
;
margin-left
:
4px
;
}
}
}
}
.main
{
margin
:
0
auto
;
background
:
#ffffff
;
width
:
1600px
;
height
:
1025px
;
overflow
:
hidden
;
.report-box
{
margin-left
:
70px
;
width
:
1456px
;
height
:
881px
;
display
:
flex
;
overflow-y
:
auto
;
/* 右侧统一滚动条,控制两侧原文+译文一起滚动 */
overflow-x
:
hidden
;
}
.main-header
{
height
:
64px
;
/* box-sizing: border-box; */
border-bottom
:
1px
solid
rgb
(
234
,
236
,
238
);
border-top
:
1px
solid
rgb
(
234
,
236
,
238
);
/* box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); */
background
:
rgb
(
255
,
255
,
255
);
margin
:
0
70px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
line-height
:
64px
;
font-family
:
Microsoft
YaHei
;
font-style
:
Bold
;
font-size
:
20px
;
font-weight
:
700
;
line-height
:
26px
;
letter-spacing
:
0px
;
text-align
:
left
;
.pdf-pane
{
width
:
50%
;
height
:
100%
;
}
.
report-box
{
.
pdf-pane-full
{
width
:
100%
;
height
:
750px
;
overflow
:
auto
;
display
:
flex
;
}
}
}
...
...
src/views/thinkTank/reportOriginal/pdf.vue
浏览文件 @
82f15736
<
template
>
<div
class=
"pdf-viewer"
>
<canvas
ref=
"pdfCanvas"
></canvas>
<canvas
v-for=
"page in pageCount"
:key=
"page"
:ref=
"el => setCanvasRef(page, el)"
></canvas>
<div
v-if=
"loading"
class=
"loading"
>
加载中...
</div>
</div>
</
template
>
<
script
>
import
{
ref
,
onMounted
}
from
'vue'
;
import
{
ref
,
onMounted
,
nextTick
}
from
'vue'
;
import
*
as
pdfjsLib
from
'pdfjs-dist'
;
...
...
@@ -24,37 +28,98 @@ export default {
}
},
setup
(
props
)
{
const
pdfCanvas
=
ref
(
null
);
// 非响应式的 canvas 映射,避免触发布局递归更新
const
canvasMap
=
{};
const
pageCount
=
ref
(
0
);
const
loading
=
ref
(
true
);
const
pdfDocRef
=
ref
(
null
);
onMounted
(
async
()
=>
{
const
setCanvasRef
=
(
page
,
el
)
=>
{
if
(
!
el
)
return
;
canvasMap
[
page
]
=
el
;
};
const
parsePdfUrl
=
(
pdfUrl
)
=>
{
if
(
!
pdfUrl
||
typeof
pdfUrl
!==
'string'
)
return
''
;
const
[
urlPart
]
=
pdfUrl
.
split
(
'#'
);
return
urlPart
;
}
const
renderPdf
=
async
(
pdfUrl
)
=>
{
const
url
=
parsePdfUrl
(
pdfUrl
)
if
(
!
url
)
return
loading
.
value
=
true
pdfDocRef
.
value
=
null
try
{
const
loadingTask
=
pdfjsLib
.
getDocument
(
props
.
pdfU
rl
);
const
loadingTask
=
pdfjsLib
.
getDocument
(
u
rl
);
const
pdf
=
await
loadingTask
.
promise
;
const
page
=
await
pdf
.
getPage
(
1
);
// 加载第一页
pdfDocRef
.
value
=
pdf
pageCount
.
value
=
pdf
.
numPages
;
const
viewport
=
page
.
getViewport
({
scale
:
1.5
});
const
context
=
pdfCanvas
.
value
.
getContext
(
'2d'
);
const
renderContext
=
{
canvasContext
:
context
,
viewport
:
viewport
};
// 等待 canvas 按 pageCount 渲染出来
await
nextTick
();
pdfCanvas
.
value
.
width
=
viewport
.
width
;
pdfCanvas
.
value
.
height
=
viewport
.
height
;
for
(
let
p
=
1
;
p
<=
pdf
.
numPages
;
p
++
)
{
const
pdfPage
=
await
pdf
.
getPage
(
p
);
const
viewport
=
pdfPage
.
getViewport
({
scale
:
1.5
});
const
canvas
=
canvasMap
[
p
];
if
(
!
canvas
)
continue
;
const
context
=
canvas
.
getContext
(
'2d'
);
const
renderContext
=
{
canvasContext
:
context
,
viewport
:
viewport
};
await
page
.
render
(
renderContext
).
promise
;
canvas
.
width
=
viewport
.
width
;
canvas
.
height
=
viewport
.
height
;
await
pdfPage
.
render
(
renderContext
).
promise
;
}
}
catch
(
error
)
{
console
.
error
(
'加载 PDF 出错:'
,
error
);
}
finally
{
loading
.
value
=
false
;
}
});
}
/** 在 PDF 中查找关键词,返回首次出现的页码(1-based),未找到返回 0 */
const
searchKeyword
=
async
(
keyword
)
=>
{
const
doc
=
pdfDocRef
.
value
if
(
!
doc
||
!
keyword
||
!
String
(
keyword
).
trim
())
return
0
const
k
=
String
(
keyword
).
trim
()
const
num
=
doc
.
numPages
for
(
let
p
=
1
;
p
<=
num
;
p
++
)
{
const
page
=
await
doc
.
getPage
(
p
)
const
content
=
await
page
.
getTextContent
()
const
text
=
(
content
.
items
||
[]).
map
(
it
=>
it
.
str
||
''
).
join
(
''
)
if
(
text
.
includes
(
k
))
return
p
}
return
0
}
/** 滚动到指定页码(1-based)对应的 canvas */
const
goToPage
=
(
pageNum
)
=>
{
const
canvas
=
canvasMap
[
pageNum
]
if
(
canvas
&&
typeof
canvas
.
scrollIntoView
===
'function'
)
{
canvas
.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'start'
})
}
}
// 首次挂载后再根据当前 url 渲染,避免 canvas 还没准备好
onMounted
(()
=>
{
if
(
props
.
pdfUrl
)
{
renderPdf
(
props
.
pdfUrl
)
}
})
return
{
pdfCanvas
,
loading
};
pageCount
,
setCanvasRef
,
loading
,
searchKeyword
,
goToPage
}
}
};
</
script
>
...
...
@@ -63,12 +128,13 @@ export default {
.pdf-viewer
{
position
:
relative
;
width
:
100%
;
height
:
800px
;
/* 高度由内容决定,让外层容器控制滚动 */
}
canvas
{
width
:
100%
;
height
:
100%
;
height
:
auto
;
display
:
block
;
}
.loading
{
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论