Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
de9b61c0
提交
de9b61c0
authored
3月 12, 2026
作者:
刘宇琪
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
刘宇琪 更新智库人物页面
上级
592d75b3
全部展开
显示空白字符变更
内嵌
并排
正在显示
11 个修改的文件
包含
1166 行增加
和
1 行删除
+1166
-1
Resume.vue
...cterPage/components/thinkTankPerson/components/Resume.vue
+275
-0
index.vue
...inkTankPerson/components/characterRelationships/index.vue
+1
-1
news.js
...thinkTankPerson/components/historicalProposal/api/news.js
+42
-0
img.png
...nkTankPerson/components/historicalProposal/assets/img.png
+0
-0
NewsCard.vue
...son/components/historicalProposal/components/NewsCard.vue
+144
-0
NewsPagination.vue
...mponents/historicalProposal/components/NewsPagination.vue
+118
-0
NewsSidebar.vue
.../components/historicalProposal/components/NewsSidebar.vue
+152
-0
NewsTopBar.vue
...n/components/historicalProposal/components/NewsTopBar.vue
+243
-0
NewsTracker.vue
.../components/historicalProposal/components/NewsTracker.vue
+191
-0
index-old.vue
...inkTankPerson/components/historicalProposal/index-old.vue
+0
-0
index.vue
src/views/characterPage/components/thinkTankPerson/index.vue
+0
-0
没有找到文件。
src/views/characterPage/components/thinkTankPerson/components/Resume.vue
0 → 100644
浏览文件 @
de9b61c0
<
template
>
<div
class=
"resume-card"
>
<div
class=
"resume-card__header"
>
<div
class=
"resume-card__indicator"
></div>
<div
class=
"resume-card__title"
>
{{
title
}}
</div>
<div
class=
"resume-card__tabs"
v-if=
"tabs.length > 0"
>
<div
v-for=
"(tab, i) in tabs"
:key=
"tab.key"
class=
"resume-card__tab"
:class=
"
{ 'resume-card__tab--active': activeTab === tab.key }"
@click="switchTab(tab.key)"
>
{{
tab
.
label
}}
</div>
</div>
<div
class=
"resume-card__actions"
>
<img
:src=
"downloadIcon"
alt=
"下载"
class=
"resume-card__action-btn"
@
click=
"$emit('download')"
/>
<img
:src=
"collectIcon"
alt=
"收藏"
class=
"resume-card__action-btn"
@
click=
"$emit('collect')"
/>
</div>
</div>
<div
class=
"resume-card__body"
>
<div
v-for=
"(item, index) in currentList"
:key=
"index"
class=
"resume-item"
>
<img
:src=
"timelineIcon"
alt=
""
class=
"resume-item__icon"
/>
<div
class=
"resume-item__time"
>
{{
item
.
startTime
+
'-'
+
item
.
endTime
}}
</div>
<div
class=
"resume-item__title"
>
{{
item
.
orgName
+
'|'
+
item
.
jobName
}}
</div>
<div
class=
"resume-item__content"
>
{{
item
.
content
}}
</div>
<div
class=
"resume-item__door"
v-if=
"item.door"
>
<img
:src=
"doorIcon"
alt=
""
/>
<span>
{{
item
.
door
}}
</span>
</div>
</div>
</div>
</div>
</
template
>
<
script
>
import
defaultDownloadIcon
from
"../assets/下载按钮.png"
;
import
defaultCollectIcon
from
"../assets/收藏按钮.png"
;
import
defaultTimelineIcon
from
"../assets/icon01.png"
;
import
defaultDoorIcon
from
"../assets/icon02.png"
;
export
default
{
name
:
"ResumeCard"
,
props
:
{
/** 卡片标题 */
title
:
{
type
:
String
,
default
:
"生涯履历"
,
},
/**
* Tab 配置数组,示例:
* [{ key: "career", label: "职业履历" }, { key: "edu", label: "教育履历" }]
* 不传则无 tab,直接显示 list 数据
*/
tabs
:
{
type
:
Array
,
default
:
()
=>
[],
},
/**
* 无 tab 时的数据数组;有 tab 时忽略此 prop,使用 dataMap
*/
list
:
{
type
:
Array
,
default
:
()
=>
[],
},
/**
* 有 tab 时的数据映射,key 对应 tabs[].key,示例:
* { career: [...], edu: [...] }
*/
dataMap
:
{
type
:
Object
,
default
:
()
=>
({}),
},
/** 自定义图标路径(可选) */
downloadIcon
:
{
type
:
String
,
default
:
defaultDownloadIcon
,
},
collectIcon
:
{
type
:
String
,
default
:
defaultCollectIcon
,
},
timelineIcon
:
{
type
:
String
,
default
:
defaultTimelineIcon
,
},
doorIcon
:
{
type
:
String
,
default
:
defaultDoorIcon
,
},
},
emits
:
[
"download"
,
"collect"
,
"tab-change"
],
data
()
{
return
{
activeTab
:
this
.
tabs
.
length
>
0
?
this
.
tabs
[
0
].
key
:
""
,
};
},
computed
:
{
/** 当前展示的列表数据 */
currentList
()
{
if
(
this
.
tabs
.
length
>
0
&&
this
.
activeTab
)
{
return
this
.
dataMap
[
this
.
activeTab
]
||
[];
}
return
this
.
list
;
},
},
methods
:
{
switchTab
(
key
)
{
this
.
activeTab
=
key
;
this
.
$emit
(
"tab-change"
,
key
);
},
},
};
</
script
>
<
style
scoped
lang=
"scss"
>
.resume-card
{
width
:
520px
;
min-height
:
200px
;
background-color
:
rgba
(
255
,
255
,
255
,
1
);
border-radius
:
10px
;
box-shadow
:
0px
0px
20px
rgba
(
25
,
69
,
130
,
0
.1
);
}
.resume-card__header
{
width
:
100%
;
height
:
80px
;
display
:
flex
;
align-items
:
center
;
padding
:
14px
12px
40px
0
;
}
.resume-card__indicator
{
width
:
8px
;
height
:
20px
;
background-color
:
rgb
(
5
,
95
,
194
);
border-bottom-right-radius
:
4px
;
border-top-right-radius
:
4px
;
margin-right
:
14px
;
}
.resume-card__title
{
font-size
:
20px
;
font-weight
:
700
;
font-family
:
"Microsoft YaHei"
;
line-height
:
26px
;
color
:
rgb
(
5
,
95
,
194
);
}
.resume-card__tabs
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
margin-left
:
20px
;
}
.resume-card__tab
{
font-size
:
16px
;
font-weight
:
400
;
font-family
:
"Microsoft YaHei"
;
line-height
:
24px
;
color
:
rgb
(
95
,
101
,
108
);
padding
:
2px
12px
;
border-radius
:
4px
;
border
:
1px
solid
transparent
;
cursor
:
pointer
;
transition
:
all
0
.2s
;
&
:hover
{
color
:
rgb
(
5
,
95
,
194
);
}
&
--active
{
color
:
rgb
(
5
,
95
,
194
);
font-weight
:
700
;
border-color
:
rgb
(
5
,
95
,
194
);
background-color
:
rgba
(
5
,
95
,
194
,
0
.05
);
}
}
.resume-card__actions
{
margin-left
:
auto
;
display
:
flex
;
align-items
:
center
;
gap
:
4px
;
}
.resume-card__action-btn
{
width
:
28px
;
height
:
28px
;
cursor
:
pointer
;
}
.resume-card__body
{
width
:
480px
;
margin-left
:
16px
;
padding-bottom
:
20px
;
}
.resume-item
{
width
:
454px
;
// margin-bottom: 60px;
margin-left
:
26px
;
position
:
relative
;
}
.resume-item__icon
{
width
:
14px
;
height
:
12
.13px
;
position
:
absolute
;
top
:
8px
;
left
:
-26px
;
}
.resume-item__time
{
font-size
:
16px
;
font-weight
:
700
;
font-family
:
"Microsoft YaHei"
;
line-height
:
30px
;
color
:
rgb
(
5
,
95
,
194
);
margin-bottom
:
8px
;
}
.resume-item__title
{
font-size
:
16px
;
font-weight
:
700
;
font-family
:
"Microsoft YaHei"
;
line-height
:
30px
;
color
:
rgb
(
59
,
65
,
75
);
margin-bottom
:
8px
;
}
.resume-item__content
{
font-size
:
16px
;
font-weight
:
400
;
font-family
:
"Microsoft YaHei"
;
line-height
:
24px
;
color
:
rgb
(
95
,
101
,
108
);
margin-bottom
:
8px
;
}
.resume-item__door
{
width
:
300px
;
height
:
32px
;
display
:
flex
;
align-items
:
center
;
padding
:
4px
0
4px
11px
;
border-radius
:
4px
;
background-color
:
rgba
(
255
,
246
,
240
,
1
);
border
:
1px
solid
rgba
(
250
,
140
,
22
,
0
.4
);
img
{
width
:
20px
;
height
:
24px
;
margin-right
:
10px
;
}
span
{
font-size
:
16px
;
font-weight
:
400
;
font-family
:
"Microsoft YaHei"
;
line-height
:
24px
;
color
:
rgba
(
255
,
149
,
77
,
1
);
}
}
</
style
>
src/views/characterPage/components/thinkTankPerson/components/characterRelationships/index.vue
浏览文件 @
de9b61c0
...
@@ -118,7 +118,7 @@ const getCharacterGlobalInfoFn = async () => {
...
@@ -118,7 +118,7 @@ const getCharacterGlobalInfoFn = async () => {
const
res
=
await
getCharacterGlobalInfo
(
params
);
const
res
=
await
getCharacterGlobalInfo
(
params
);
if
(
res
.
code
===
200
)
{
if
(
res
.
code
===
200
)
{
console
.
log
(
"人物全局信息"
,
res
);
console
.
log
(
"人物全局信息
4
"
,
res
);
if
(
res
.
data
)
{
if
(
res
.
data
)
{
characterInfo
.
value
=
res
.
data
;
characterInfo
.
value
=
res
.
data
;
}
}
...
...
src/views/characterPage/components/thinkTankPerson/components/historicalProposal/api/news.js
0 → 100644
浏览文件 @
de9b61c0
/**
* 新闻动态模块 Mock API
*/
const
delay
=
(
ms
)
=>
new
Promise
((
resolve
)
=>
setTimeout
(
resolve
,
ms
))
const
NEWS_TITLES
=
[
'解决春节回家难的问题'
,
'全国新能源汽车产销双增长'
,
'人工智能大模型发展新趋势'
,
'量子计算芯片突破性进展'
,
'深海资源开发战略规划出台'
,
'航空航天技术再创新高'
,
'生物医药产业集群加速形成'
,
'新材料研发助力制造升级'
,
'半导体产业链自主可控提速'
,
'清洁能源装机容量再创纪录'
,
'5G通信网络覆盖全面铺开'
,
'极地科考取得重要发现'
,
]
const
NEWS_SOURCES
=
[
'河南新闻'
,
'新华社'
,
'人民日报'
,
'中新网'
,
'科技日报'
,
'央视新闻'
]
/**
* 获取新闻列表
*/
export
async
function
fetchNewsList
(
params
=
{})
{
await
delay
(
300
)
const
page
=
params
.
page
||
1
const
pageSize
=
params
.
pageSize
||
12
const
total
=
1059
const
list
=
Array
.
from
({
length
:
pageSize
},
(
_
,
i
)
=>
({
id
:
(
page
-
1
)
*
pageSize
+
i
+
1
,
title
:
NEWS_TITLES
[
i
%
NEWS_TITLES
.
length
],
date
:
'2025年6月26日'
,
source
:
NEWS_SOURCES
[
i
%
NEWS_SOURCES
.
length
],
image
:
null
,
}))
return
{
code
:
200
,
data
:
{
list
,
total
}
}
}
src/views/characterPage/components/thinkTankPerson/components/historicalProposal/assets/img.png
deleted
100644 → 0
浏览文件 @
592d75b3
5.5 KB
src/views/characterPage/components/thinkTankPerson/components/historicalProposal/components/NewsCard.vue
0 → 100644
浏览文件 @
de9b61c0
<
template
>
<div
class=
"news-card"
>
<div
class=
"news-card-image"
>
<img
v-if=
"item.imageUrl"
:src=
"item.imageUrl"
:alt=
"item.name"
/>
<div
v-else
class=
"news-card-placeholder"
>
<svg
width=
"48"
height=
"48"
viewBox=
"0 0 48 48"
fill=
"none"
>
<rect
width=
"48"
height=
"48"
rx=
"6"
fill=
"rgba(5,95,194,0.06)"
/>
<path
d=
"M16 32l6-8 4 5 6-8 6 11H16z"
fill=
"rgba(5,95,194,0.15)"
/>
<circle
cx=
"20"
cy=
"20"
r=
"3"
fill=
"rgba(5,95,194,0.15)"
/>
</svg>
</div>
</div>
<div
class=
"news-card-content"
>
<p
class=
"news-card-title"
>
{{
item
.
name
}}
</p>
<div
class=
"news-card-meta"
>
<span
class=
"news-card-date"
>
{{
item
.
time
}}
</span>
<span
class=
"news-card-source"
>
{{
item
.
sourceName
}}
</span>
</div>
</div>
</div>
</
template
>
<
script
setup
>
defineProps
({
item
:
{
type
:
Object
,
required
:
true
,
},
})
</
script
>
<
style
scoped
>
.news-card
{
width
:
398px
;
height
:
300px
;
border-radius
:
10px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0.1
);
overflow
:
hidden
;
position
:
relative
;
cursor
:
pointer
;
transition
:
box-shadow
0.2s
;
}
.news-card
:hover
{
box-shadow
:
0px
0px
24px
0px
rgba
(
25
,
69
,
130
,
0.16
);
}
.news-card-image
{
position
:
absolute
;
left
:
17px
;
right
:
17px
;
top
:
15px
;
bottom
:
105px
;
border-radius
:
6px
;
overflow
:
hidden
;
background
:
rgba
(
247
,
248
,
249
,
1
);
}
.news-card-image
img
{
width
:
100%
;
height
:
100%
;
object-fit
:
cover
;
display
:
block
;
}
.news-card-placeholder
{
width
:
100%
;
height
:
100%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
background
:
linear-gradient
(
135deg
,
rgba
(
5
,
95
,
194
,
0.04
)
0%
,
rgba
(
5
,
95
,
194
,
0.08
)
100%
);
}
.news-card-content
{
position
:
absolute
;
left
:
21px
;
right
:
17px
;
bottom
:
15px
;
height
:
78px
;
}
.news-card-source
{
position
:
absolute
;
right
:
0
;
right
:
0
;
top
:
0
;
font-size
:
18px
;
font-family
:
'Source Han Sans CN'
,
sans-serif
;
font-weight
:
700
;
line-height
:
24px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
text-align
:
justify
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
margin
:
0
;
}
.news-card-title
{
position
:
absolute
;
left
:
0
;
right
:
0
;
top
:
0
;
font-size
:
18px
;
font-family
:
'Source Han Sans CN'
,
sans-serif
;
font-weight
:
700
;
line-height
:
24px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
text-align
:
justify
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
margin
:
0
;
}
.news-card-meta
{
position
:
absolute
;
left
:
0
;
right
:
28px
;
bottom
:
0
;
display
:
flex
;
justify-content
:
space-between
;
}
.news-card-date
{
font-size
:
14px
;
font-family
:
'Source Han Sans CN'
,
sans-serif
;
font-weight
:
400
;
line-height
:
22px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
}
.news-card-source
{
font-size
:
14px
;
font-family
:
'Source Han Sans CN'
,
sans-serif
;
font-weight
:
400
;
line-height
:
22px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
position
:
absolute
;
right
:
-28px
;
bottom
:
0
;
}
</
style
>
src/views/characterPage/components/thinkTankPerson/components/historicalProposal/components/NewsPagination.vue
0 → 100644
浏览文件 @
de9b61c0
<
template
>
<div
class=
"news-pagination-bar"
>
<span
class=
"news-pagination-total"
>
共
{{
total
}}
篇新闻报告
</span>
<div
class=
"news-pagination"
>
<button
class=
"pg-btn"
:disabled=
"currentPage
<
=
1
"
@
click=
"$emit('update:currentPage', currentPage - 1)"
>
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
>
<path
d=
"M10 3l-5 5 5 5"
stroke=
"currentColor"
stroke-width=
"1.2"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</svg>
</button>
<template
v-for=
"p in pageList"
:key=
"p"
>
<span
v-if=
"p === '...'"
class=
"pg-ellipsis"
>
...
</span>
<button
v-else
:class=
"['pg-num',
{ active: p === currentPage }]"
@click="$emit('update:currentPage', p)"
>
{{
p
}}
</button>
</
template
>
<button
class=
"pg-btn"
:disabled=
"currentPage >= totalPages"
@
click=
"$emit('update:currentPage', currentPage + 1)"
>
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
>
<path
d=
"M6 3l5 5-5 5"
stroke=
"currentColor"
stroke-width=
"1.2"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</svg>
</button>
</div>
</div>
</template>
<
script
setup
>
import
{
computed
}
from
'vue'
const
props
=
defineProps
({
total
:
{
type
:
Number
,
default
:
0
},
currentPage
:
{
type
:
Number
,
default
:
1
},
pageSize
:
{
type
:
Number
,
default
:
12
},
})
defineEmits
([
'update:currentPage'
])
const
totalPages
=
computed
(()
=>
Math
.
ceil
(
props
.
total
/
props
.
pageSize
)
||
1
)
const
pageList
=
computed
(()
=>
{
const
tp
=
totalPages
.
value
const
cp
=
props
.
currentPage
if
(
tp
<=
7
)
return
Array
.
from
({
length
:
tp
},
(
_
,
i
)
=>
i
+
1
)
const
pages
=
[]
pages
.
push
(
1
)
if
(
cp
>
3
)
pages
.
push
(
'...'
)
const
start
=
Math
.
max
(
2
,
cp
-
1
)
const
end
=
Math
.
min
(
tp
-
1
,
cp
+
1
)
for
(
let
i
=
start
;
i
<=
end
;
i
++
)
pages
.
push
(
i
)
if
(
cp
<
tp
-
2
)
pages
.
push
(
'...'
)
pages
.
push
(
tp
)
return
pages
})
</
script
>
<
style
scoped
>
.news-pagination-bar
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
16px
0
0
0
;
}
.news-pagination-total
{
font-size
:
14px
;
font-family
:
'Microsoft YaHei'
,
sans-serif
;
font-weight
:
400
;
color
:
rgba
(
132
,
136
,
142
,
1
);
}
.news-pagination
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
}
.pg-btn
{
width
:
32px
;
height
:
32px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
6px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
color
:
rgba
(
95
,
101
,
108
,
1
);
cursor
:
pointer
;
transition
:
all
0.15s
;
}
.pg-btn
:disabled
{
opacity
:
0.4
;
cursor
:
not-allowed
;
}
.pg-num
{
min-width
:
32px
;
height
:
32px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
6px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
font-size
:
14px
;
font-family
:
'Microsoft YaHei'
,
sans-serif
;
font-weight
:
400
;
color
:
rgba
(
59
,
65
,
75
,
1
);
cursor
:
pointer
;
transition
:
all
0.15s
;
padding
:
0
4px
;
}
.pg-num.active
{
background
:
rgba
(
5
,
95
,
194
,
1
);
border-color
:
rgba
(
5
,
95
,
194
,
1
);
color
:
#fff
;
}
.pg-ellipsis
{
font-size
:
14px
;
color
:
rgba
(
132
,
136
,
142
,
1
);
width
:
32px
;
text-align
:
center
;
}
</
style
>
src/views/characterPage/components/thinkTankPerson/components/historicalProposal/components/NewsSidebar.vue
0 → 100644
浏览文件 @
de9b61c0
<
template
>
<div
class=
"news-sidebar"
>
<!-- Section: 科技领域 -->
<div
class=
"ns-section"
>
<div
class=
"ns-section-header"
>
<span
class=
"ns-header-bar"
></span>
<span
class=
"ns-header-title"
>
科技领域
</span>
</div>
<div
class=
"ns-options"
>
<label
v-for=
"opt in domainOptions"
:key=
"opt.id"
class=
"ns-option"
>
<span
:class=
"['ns-checkbox',
{ checked: selectedDomains.includes(opt.id) }]"
@click.prevent="$emit('toggle-domain', opt.id)"
>
<svg
v-if=
"selectedDomains.includes(opt.id)"
width=
"14"
height=
"14"
viewBox=
"0 0 14 14"
fill=
"none"
>
<rect
width=
"14"
height=
"14"
rx=
"4"
fill=
"rgba(5,95,194,1)"
/>
<path
d=
"M3.5 7l2.5 2.5 4.5-5"
stroke=
"#fff"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
fill=
"none"
/>
</svg>
</span>
<span
class=
"ns-option-label"
@
click=
"$emit('toggle-domain', opt.id)"
>
{{
opt
.
label
}}
</span>
</label>
</div>
</div>
<div
class=
"ns-section"
>
<div
class=
"ns-section-header"
>
<span
class=
"ns-header-bar"
></span>
<span
class=
"ns-header-title"
>
发布时间
</span>
</div>
<div
class=
"ns-options"
>
<label
v-for=
"opt in timeOptions"
:key=
"opt.id"
class=
"ns-option"
>
<span
:class=
"['ns-checkbox',
{ checked: selectedTimes.includes(opt.id) }]"
@click.prevent="$emit('toggle-time', opt.id)"
>
<svg
v-if=
"selectedTimes.includes(opt.id)"
width=
"14"
height=
"14"
viewBox=
"0 0 14 14"
fill=
"none"
>
<rect
width=
"14"
height=
"14"
rx=
"4"
fill=
"rgba(5,95,194,1)"
/>
<path
d=
"M3.5 7l2.5 2.5 4.5-5"
stroke=
"#fff"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
fill=
"none"
/>
</svg>
</span>
<span
class=
"ns-option-label"
@
click=
"$emit('toggle-time', opt.id)"
>
{{
opt
.
label
}}
</span>
</label>
</div>
</div>
</div>
</
template
>
<
script
setup
>
defineProps
({
domainOptions
:
{
type
:
Array
,
default
:
()
=>
[]
},
timeOptions
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedDomains
:
{
type
:
Array
,
default
:
()
=>
[
'all'
]
},
selectedTimes
:
{
type
:
Array
,
default
:
()
=>
[
'all'
]
},
})
defineEmits
([
'toggle-domain'
,
'toggle-time'
])
</
script
>
<
style
scoped
>
.news-sidebar
{
width
:
360px
;
flex-shrink
:
0
;
background
:
rgba
(
255
,
255
,
255
,
1
);
border-radius
:
10px
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0.1
);
padding
:
19px
0
24px
0
;
display
:
flex
;
flex-direction
:
column
;
gap
:
16px
;
overflow
:
hidden
;
}
.ns-section
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.ns-section-header
{
display
:
flex
;
flex-direction
:
row
;
gap
:
17px
;
align-items
:
center
;
}
.ns-header-bar
{
width
:
8px
;
height
:
16px
;
background
:
rgba
(
5
,
95
,
194
,
1
);
border-radius
:
0
4px
4px
0
;
flex-shrink
:
0
;
}
.ns-header-title
{
font-size
:
16px
;
font-family
:
'Source Han Sans CN'
,
sans-serif
;
font-weight
:
700
;
letter-spacing
:
1px
;
line-height
:
24px
;
color
:
rgba
(
5
,
95
,
194
,
1
);
}
.ns-options
{
display
:
flex
;
flex-direction
:
row
;
flex-wrap
:
wrap
;
gap
:
8px
4px
;
padding
:
0
0
0
24px
;
}
.ns-option
{
width
:
160px
;
display
:
flex
;
flex-direction
:
row
;
gap
:
8px
;
align-items
:
center
;
cursor
:
pointer
;
}
.ns-checkbox
{
width
:
14px
;
height
:
14px
;
flex-shrink
:
0
;
border-radius
:
4px
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
background
:
transparent
;
}
.ns-checkbox.checked
{
border
:
none
;
}
.ns-checkbox.checked
svg
{
display
:
block
;
}
.ns-option-label
{
font-size
:
16px
;
font-family
:
'Source Han Sans CN'
,
sans-serif
;
font-weight
:
400
;
line-height
:
24px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
user-select
:
none
;
}
</
style
>
src/views/characterPage/components/thinkTankPerson/components/historicalProposal/components/NewsTopBar.vue
0 → 100644
浏览文件 @
de9b61c0
<
template
>
<div
class=
"news-topbar"
>
<div
class=
"news-search-wrapper"
>
<div
class=
"news-search-box"
>
<input
type=
"text"
class=
"news-search-input"
:placeholder=
"'搜索新闻动态'"
:value=
"searchText"
@
input=
"$emit('update:searchText', $event.target.value)"
/>
<svg
class=
"news-search-icon"
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
>
<circle
cx=
"7"
cy=
"7"
r=
"5.5"
stroke=
"rgba(132,136,142,1)"
stroke-width=
"1.2"
/>
<line
x1=
"11"
y1=
"11"
x2=
"14"
y2=
"14"
stroke=
"rgba(132,136,142,1)"
stroke-width=
"1.2"
stroke-linecap=
"round"
/>
</svg>
</div>
</div>
<div
class=
"news-tabs"
>
<button
:class=
"['news-tab',
{ active: activeTab === 'local' }]"
@click="$emit('update:activeTab', 'local')"
>智库报告
</button>
<button
:class=
"['news-tab',
{ active: activeTab === 'capital' }]"
@click="$emit('update:activeTab', 'capital')"
>调查项目
</button>
</div>
<div
class=
"news-sort"
ref=
"sortDropdownRef"
>
<button
class=
"news-sort-btn"
@
click=
"showSortDropdown = !showSortDropdown"
>
<svg
class=
"news-sort-icon"
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
>
<rect
x=
"4"
y=
"3"
width=
"2"
height=
"10"
rx=
"1"
fill=
"rgba(95,101,108,1)"
/>
<rect
x=
"10"
y=
"6"
width=
"2"
height=
"7"
rx=
"1"
fill=
"rgba(95,101,108,1)"
/>
</svg>
<span
class=
"news-sort-label"
>
{{
currentSortLabel
}}
</span>
<svg
class=
"news-sort-arrow"
:class=
"
{ open: showSortDropdown }" width="11" height="6" viewBox="0 0 11 6" fill="none">
<path
d=
"M1 1l4.5 4L10 1"
stroke=
"rgba(95,101,108,1)"
stroke-width=
"1.2"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</svg>
</button>
<div
v-if=
"showSortDropdown"
class=
"news-sort-dropdown"
>
<button
v-for=
"opt in sortOptions"
:key=
"opt.value"
@
click=
"selectSort(opt.value)"
class=
"news-sort-option"
:class=
"
{ active: sortBy === opt.value }"
>
{{
opt
.
label
}}
</button>
</div>
</div>
</div>
</
template
>
<
script
setup
>
import
{
ref
,
computed
,
onMounted
,
onUnmounted
}
from
'vue'
const
props
=
defineProps
({
searchText
:
{
type
:
String
,
default
:
''
},
activeTab
:
{
type
:
String
,
default
:
'local'
},
sortBy
:
{
type
:
String
,
default
:
'publishTimeDesc'
},
sortOptions
:
{
type
:
Array
,
default
:
()
=>
[
{
value
:
'publishTimeDesc'
,
label
:
'发布时间倒序'
},
{
value
:
'publishTimeAsc'
,
label
:
'发布时间正序'
},
],
},
})
const
emit
=
defineEmits
([
'update:searchText'
,
'update:activeTab'
,
'update:sort'
])
const
showSortDropdown
=
ref
(
false
)
const
sortDropdownRef
=
ref
(
null
)
const
currentSortLabel
=
computed
(()
=>
{
const
opt
=
props
.
sortOptions
.
find
((
o
)
=>
o
.
value
===
props
.
sortBy
)
return
opt
?
opt
.
label
:
'发布时间倒序'
})
function
selectSort
(
value
)
{
emit
(
'update:sort'
,
value
)
showSortDropdown
.
value
=
false
}
function
handleClickOutside
(
e
)
{
if
(
sortDropdownRef
.
value
&&
!
sortDropdownRef
.
value
.
contains
(
e
.
target
))
{
showSortDropdown
.
value
=
false
}
}
onMounted
(()
=>
document
.
addEventListener
(
'click'
,
handleClickOutside
))
onUnmounted
(()
=>
document
.
removeEventListener
(
'click'
,
handleClickOutside
))
</
script
>
<
style
scoped
>
.news-topbar
{
display
:
flex
;
align-items
:
center
;
gap
:
16px
;
width
:
100%
;
height
:
32px
;
margin-bottom
:
16px
;
}
.news-search-wrapper
{
width
:
360px
;
height
:
32px
;
flex-shrink
:
0
;
}
.news-search-box
{
width
:
100%
;
height
:
100%
;
display
:
flex
;
flex-direction
:
row
;
align-items
:
center
;
gap
:
12px
;
padding
:
5px
8px
5px
12px
;
border-radius
:
4px
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
}
.news-search-input
{
flex
:
1
;
border
:
none
;
outline
:
none
;
background
:
transparent
;
font-size
:
14px
;
font-family
:
'Microsoft YaHei'
,
sans-serif
;
font-weight
:
400
;
line-height
:
22px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
}
.news-search-input
::placeholder
{
color
:
rgba
(
132
,
136
,
142
,
1
);
}
.news-search-icon
{
flex-shrink
:
0
;
}
.news-tabs
{
display
:
flex
;
flex-direction
:
row
;
gap
:
8px
;
justify-content
:
center
;
align-items
:
flex-start
;
}
.news-tab
{
width
:
120px
;
height
:
32px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
padding
:
4px
24px
;
border-radius
:
21px
;
font-size
:
16px
;
font-family
:
'Source Han Sans CN'
,
sans-serif
;
font-weight
:
400
;
line-height
:
24px
;
cursor
:
pointer
;
transition
:
all
0.2s
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
color
:
rgba
(
95
,
101
,
108
,
1
);
}
.news-tab.active
{
background
:
rgba
(
5
,
95
,
194
,
1
);
border-color
:
rgba
(
5
,
95
,
194
,
1
);
color
:
rgba
(
255
,
255
,
255
,
1
);
font-weight
:
700
;
}
.news-sort
{
margin-left
:
auto
;
position
:
relative
;
flex-shrink
:
0
;
}
.news-sort-btn
{
width
:
120px
;
height
:
32px
;
display
:
flex
;
align-items
:
center
;
padding
:
0
12px
0
7px
;
gap
:
0
;
border-radius
:
4px
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
cursor
:
pointer
;
box-sizing
:
border-box
;
}
.news-sort-btn
:hover
{
border-color
:
rgba
(
5
,
95
,
194
,
0.4
);
}
.news-sort-icon
{
flex-shrink
:
0
;
margin-right
:
6px
;
}
.news-sort-label
{
flex
:
1
;
font-size
:
14px
;
font-family
:
'Microsoft YaHei'
,
sans-serif
;
font-weight
:
400
;
color
:
rgba
(
95
,
101
,
108
,
1
);
white-space
:
nowrap
;
}
.news-sort-arrow
{
flex-shrink
:
0
;
transition
:
transform
0.2s
;
}
.news-sort-arrow.open
{
transform
:
rotate
(
180deg
);
}
.news-sort-dropdown
{
position
:
absolute
;
right
:
0
;
top
:
100%
;
margin-top
:
4px
;
min-width
:
120px
;
background
:
#fff
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
border-radius
:
8px
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0.1
);
padding
:
4px
0
;
z-index
:
20
;
}
.news-sort-option
{
display
:
block
;
width
:
100%
;
text-align
:
left
;
padding
:
8px
12px
;
font-size
:
14px
;
font-family
:
'Microsoft YaHei'
,
sans-serif
;
background
:
none
;
border
:
none
;
cursor
:
pointer
;
color
:
rgba
(
95
,
101
,
108
,
1
);
}
.news-sort-option
:hover
{
background
:
rgba
(
247
,
248
,
249
,
1
);
}
.news-sort-option.active
{
color
:
rgba
(
5
,
95
,
194
,
1
);
font-weight
:
500
;
}
</
style
>
src/views/characterPage/components/thinkTankPerson/components/historicalProposal/components/NewsTracker.vue
0 → 100644
浏览文件 @
de9b61c0
<
template
>
<div
class=
"news-tracker"
>
<NewsTopBar
v-model:searchText=
"searchText"
v-model:activeTab=
"activeTab"
:sort-by=
"sortBy"
@
update:sort=
"updateSort"
/>
<div
class=
"news-body"
>
<NewsSidebar
:domain-options=
"domainOptions"
:time-options=
"timeOptions"
:selected-domains=
"selectedDomains"
:selected-times=
"selectedTimes"
@
toggle-domain=
"toggleDomain"
@
toggle-time=
"toggleTime"
/>
<div
class=
"news-main"
>
<div
class=
"news-grid"
>
<NewsCard
v-for=
"item in newsList"
:key=
"item.reportId"
:item=
"item"
/>
</div>
<NewsPagination
:total=
"totalNews"
v-model:current-page=
"currentPage"
:page-size=
"pageSize"
/>
</div>
</div>
</div>
</
template
>
<
script
setup
>
import
{
ref
,
computed
,
onMounted
,
watch
}
from
'vue'
import
{
useRoute
}
from
'vue-router'
import
NewsTopBar
from
'./NewsTopBar.vue'
import
NewsSidebar
from
'./NewsSidebar.vue'
import
NewsCard
from
'./NewsCard.vue'
import
NewsPagination
from
'./NewsPagination.vue'
import
{
getIndustryKeyList
}
from
'@/api/bill/billHome.js'
import
{
getFindingsReport
}
from
'@/api/characterPage/characterPage.js'
const
route
=
useRoute
()
const
personId
=
computed
(()
=>
route
.
params
.
personId
||
route
.
query
.
personId
||
''
)
const
searchText
=
ref
(
''
)
const
activeTab
=
ref
(
'local'
)
const
currentPage
=
ref
(
1
)
const
pageSize
=
12
const
totalNews
=
ref
(
0
)
const
newsList
=
ref
([])
const
loading
=
ref
(
false
)
const
sortBy
=
ref
(
'publishTimeDesc'
)
const
domainOptions
=
ref
([])
const
timeOptions
=
ref
([])
const
selectedDomains
=
ref
([
'all'
])
const
selectedTimes
=
ref
([
'all'
])
async
function
loadFilterOptions
()
{
const
res
=
await
getIndustryKeyList
()
if
(
res
.
code
===
200
&&
res
.
data
)
{
domainOptions
.
value
=
[
{
id
:
'all'
,
label
:
'全部领域'
},
...
res
.
data
.
map
(
item
=>
({
id
:
item
.
id
,
label
:
item
.
name
})),
]
}
const
currentYear
=
new
Date
().
getFullYear
()
timeOptions
.
value
=
[
{
id
:
'all'
,
label
:
'全部时间'
},
...
Array
.
from
({
length
:
5
},
(
_
,
i
)
=>
{
const
year
=
currentYear
-
i
return
{
id
:
String
(
year
),
label
:
`
${
year
}
年`
}
}),
]
}
function
buildParams
()
{
const
params
=
{
currentPage
:
currentPage
.
value
-
1
,
pageSize
,
sortFun
:
sortBy
.
value
===
'publishTimeAsc'
,
}
if
(
!
selectedDomains
.
value
.
includes
(
'all'
))
{
params
.
industryIds
=
selectedDomains
.
value
}
if
(
!
selectedTimes
.
value
.
includes
(
'all'
))
{
params
.
years
=
selectedTimes
.
value
}
return
params
}
function
updateSort
(
value
)
{
sortBy
.
value
=
value
currentPage
.
value
=
1
loadNews
()
}
async
function
loadNews
()
{
if
(
!
personId
.
value
)
return
loading
.
value
=
true
try
{
const
params
=
buildParams
()
const
res
=
await
getFindingsReport
(
personId
.
value
,
params
)
if
(
res
.
code
===
200
&&
res
.
data
)
{
newsList
.
value
=
res
.
data
.
content
||
[]
totalNews
.
value
=
res
.
data
.
totalElements
||
0
}
else
{
newsList
.
value
=
[]
totalNews
.
value
=
0
}
}
finally
{
loading
.
value
=
false
}
}
function
toggleDomain
(
id
)
{
if
(
id
===
'all'
)
{
selectedDomains
.
value
=
[
'all'
]
}
else
{
const
without
=
selectedDomains
.
value
.
filter
(
d
=>
d
!==
'all'
)
const
idx
=
without
.
indexOf
(
id
)
if
(
idx
>
-
1
)
without
.
splice
(
idx
,
1
)
else
without
.
push
(
id
)
selectedDomains
.
value
=
without
.
length
>
0
?
without
:
[
'all'
]
}
}
function
toggleTime
(
id
)
{
if
(
id
===
'all'
)
{
selectedTimes
.
value
=
[
'all'
]
}
else
{
const
without
=
selectedTimes
.
value
.
filter
(
d
=>
d
!==
'all'
)
const
idx
=
without
.
indexOf
(
id
)
if
(
idx
>
-
1
)
without
.
splice
(
idx
,
1
)
else
without
.
push
(
id
)
selectedTimes
.
value
=
without
.
length
>
0
?
without
:
[
'all'
]
}
}
watch
(
()
=>
[
selectedDomains
.
value
,
selectedTimes
.
value
],
()
=>
{
currentPage
.
value
=
1
loadNews
()
},
{
deep
:
true
}
)
watch
(
currentPage
,
()
=>
{
loadNews
()
})
onMounted
(
async
()
=>
{
await
loadFilterOptions
()
await
loadNews
()
})
</
script
>
<
style
scoped
>
.news-tracker
{
width
:
100%
;
background
:
rgba
(
247
,
248
,
249
,
1
);
padding
:
0
;
}
.news-body
{
display
:
flex
;
flex-direction
:
row
;
gap
:
14px
;
align-items
:
flex-start
;
}
.news-main
{
flex
:
1
;
min-width
:
0
;
}
.news-grid
{
display
:
flex
;
flex-direction
:
row
;
flex-wrap
:
wrap
;
gap
:
16px
;
align-content
:
flex-start
;
align-items
:
flex-start
;
}
</
style
>
src/views/characterPage/components/thinkTankPerson/components/historicalProposal/index.vue
→
src/views/characterPage/components/thinkTankPerson/components/historicalProposal/index
-old
.vue
浏览文件 @
de9b61c0
File moved
src/views/characterPage/components/thinkTankPerson/index.vue
浏览文件 @
de9b61c0
差异被折叠。
点击展开。
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论