Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
592d75b3
提交
592d75b3
authored
3月 12, 2026
作者:
刘宇琪
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
刘宇琪 人物详情页修改 科技巨头
上级
2d832f9c
全部展开
隐藏空白字符变更
内嵌
并排
正在显示
28 个修改的文件
包含
2711 行增加
和
3 行删除
+2711
-3
billHome.js
src/api/bill/billHome.js
+98
-2
characterPage.js
src/api/characterPage/characterPage.js
+1
-1
close-icon.svg
...terPage/components/memberOfCongress/assets/close-icon.svg
+6
-0
icon-circle.svg
.../components/characterRelationships/assets/icon-circle.svg
+4
-0
icon-download.svg
...omponents/characterRelationships/assets/icon-download.svg
+14
-0
icon-expand.svg
.../components/characterRelationships/assets/icon-expand.svg
+12
-0
icon-force.svg
...s/components/characterRelationships/assets/icon-force.svg
+4
-0
icon-star.svg
...ss/components/characterRelationships/assets/icon-star.svg
+4
-0
icon-tree.svg
...ss/components/characterRelationships/assets/icon-tree.svg
+4
-0
index.vue
...berOfCongress/components/characterRelationships/index.vue
+0
-0
bills.js
...mberOfCongress/components/historicalProposal/api/bills.js
+286
-0
img.png
...erOfCongress/components/historicalProposal/assets/img.png
+0
-0
BillCard.vue
...ess/components/historicalProposal/components/BillCard.vue
+243
-0
BillList.vue
...ess/components/historicalProposal/components/BillList.vue
+172
-0
BillTracker.vue
.../components/historicalProposal/components/BillTracker.vue
+108
-0
DocumentPreview.vue
...ponents/historicalProposal/components/DocumentPreview.vue
+151
-0
ImportanceBadge.vue
...ponents/historicalProposal/components/ImportanceBadge.vue
+76
-0
PotentialNews.vue
...omponents/historicalProposal/components/PotentialNews.vue
+93
-0
PotentialNewsAnalysis.vue
...s/historicalProposal/components/PotentialNewsAnalysis.vue
+176
-0
PotentialNewsList.vue
...nents/historicalProposal/components/PotentialNewsList.vue
+370
-0
ProgressBar.vue
.../components/historicalProposal/components/ProgressBar.vue
+114
-0
SidebarFilter.vue
...omponents/historicalProposal/components/SidebarFilter.vue
+165
-0
TagBadge.vue
...ess/components/historicalProposal/components/TagBadge.vue
+88
-0
TopBar.vue
...gress/components/historicalProposal/components/TopBar.vue
+234
-0
UserAvatar.vue
...s/components/historicalProposal/components/UserAvatar.vue
+57
-0
useBills.js
...ess/components/historicalProposal/composables/useBills.js
+231
-0
index.vue
.../memberOfCongress/components/historicalProposal/index.vue
+0
-0
index.vue
...views/characterPage/components/memberOfCongress/index.vue
+0
-0
没有找到文件。
src/api/bill/billHome.js
浏览文件 @
592d75b3
...
...
@@ -149,4 +149,100 @@ export function getPostMemberList() {
method
:
'GET'
,
url
:
`/api/BillDict/member`
,
})
}
\ No newline at end of file
}
/**
* 获取筛选项配置 - 行业列表
* GET /api/billImpactAnalysis/industry/hylyList
*/
export
async
function
getIndustryKeyList
()
{
return
request
(
'/api/billImpactAnalysis/industry/hylyList'
,
{
method
:
'GET'
,
})
}
/**
* 获取进度阶段配置
* GET /api/commonDict/bill/stage
*/
export
async
function
getBillStageConfig
()
{
return
request
(
'/api/commonDict/bill/stage'
,
{
method
:
'GET'
,
})
}
/**
* 获取法案列表
* GET /api/personHomepage/historyBill/{personId}
* @param {string} personId - 人物ID
* @param {Object} params - 查询参数(不包含分页参数)
*/
export
async
function
getHistoryBillList
(
personId
,
params
=
{})
{
const
queryString
=
Object
.
entries
(
params
)
.
filter
(([,
value
])
=>
value
!==
undefined
&&
value
!==
null
&&
value
!==
''
)
.
map
(([
key
,
value
])
=>
{
if
(
Array
.
isArray
(
value
))
{
return
value
.
map
(
v
=>
`
${
encodeURIComponent
(
key
)}
=
${
encodeURIComponent
(
v
)}
`
).
join
(
'&'
)
}
return
`
${
encodeURIComponent
(
key
)}
=
${
encodeURIComponent
(
value
)}
`
})
.
join
(
'&'
)
const
url
=
queryString
?
`/api/personHomepage/historyBill/
${
personId
}
?
${
queryString
}
`
:
`/api/personHomepage/historyBill/
${
personId
}
`
return
request
(
url
,
{
method
:
'GET'
,
})
}
/**
* 获取法案列表(含阶段字典)
* @param {string} personId - 人物ID
* @param {Object} params - 查询参数
*/
export
async
function
getHistoryBillListWithStage
(
personId
,
params
=
{})
{
const
stageRes
=
await
getBillStageConfig
()
const
billRes
=
await
getHistoryBillList
(
personId
,
params
)
return
{
stageConfig
:
stageRes
,
billList
:
billRes
,
}
}
export
function
getSortOptions
()
{
return
Promise
.
resolve
({
code
:
200
,
data
:
[
{
value
:
'latestMotionTimeDesc'
,
label
:
'最新修议时间倒序'
},
{
value
:
'latestMotionTimeAsc'
,
label
:
'最新修议时间正序'
},
],
})
}
export
async
function
getPotentialNewsList
(
personId
,
params
=
{})
{
const
queryString
=
Object
.
entries
(
params
)
.
filter
(([,
value
])
=>
value
!==
undefined
&&
value
!==
null
&&
value
!==
''
)
.
map
(([
key
,
value
])
=>
`
${
encodeURIComponent
(
key
)}
=
${
encodeURIComponent
(
value
)}
`
)
.
join
(
'&'
)
const
url
=
queryString
?
`/api/personHomepage/historyBill/clause/
${
personId
}
?
${
queryString
}
`
:
`/api/personHomepage/historyBill/clause/
${
personId
}
`
return
request
(
url
,
{
method
:
'GET'
,
})
}
export
async
function
getPotentialNewsKeywords
(
personId
)
{
return
request
(
`/api/personHomepage/historyBill/clauseKeyword/
${
personId
}
`
,
{
method
:
'GET'
,
})
}
\ No newline at end of file
src/api/characterPage/characterPage.js
浏览文件 @
592d75b3
...
...
@@ -112,7 +112,7 @@ export function getCharacterView(params) {
export
function
getCharacterFundSource
(
params
)
{
return
request
({
method
:
'GET'
,
url
:
`/api/personHomepage/personFunds/
${
params
.
personId
}
/
${
params
.
year
}
`
,
url
:
`/api/personHomepage/personFunds/`
,
params
,
})
}
...
...
src/views/characterPage/components/memberOfCongress/assets/close-icon.svg
0 → 100644
浏览文件 @
592d75b3
<svg
viewBox=
"0 0 32 32"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"32.000000"
height=
"32.000000"
fill=
"none"
customFrame=
"#000000"
>
<g
id=
"组合 275"
>
<rect
id=
"矩形 264"
width=
"32.000000"
height=
"32.000000"
x=
"0.000000"
y=
"0.000000"
opacity=
"0"
fill=
"rgb(255,0,0)"
fill-opacity=
"0.319999993"
/>
<path
id=
"合并"
d=
"M9.49992 8.50003L9.4999 2.36022e-06L8.49991 0L8.49993 8.50003L0 8.50003L0 9.50003L8.49993 9.50003L8.49995 17.9999L9.49995 17.9999L9.49993 9.50003L18 9.50003L18 8.50003L9.49992 8.50003Z"
fill=
"rgb(59,65,75)"
fill-rule=
"evenodd"
transform=
"matrix(0.707107,0.707107,-0.707107,0.707107,15.9996,3.27197)"
/>
</g>
</svg>
src/views/characterPage/components/memberOfCongress/components/characterRelationships/assets/icon-circle.svg
0 → 100644
浏览文件 @
592d75b3
<svg
viewBox=
"0 0 16 16"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"16.000000"
height=
"16.000000"
fill=
"none"
>
<rect
id=
"icon-圆形布局 1"
width=
"16.000000"
height=
"16.000000"
x=
"0.000000"
y=
"0.000000"
/>
<path
id=
"矢量 423"
d=
"M13.81 6.6C13.67 5.97 13.39 5.41 13.04 4.85C13.18 4.64 13.25 4.36 13.25 4.15C13.25 3.38 12.62 2.75 11.85 2.75C11.57 2.75 11.36 2.82 11.15 2.96C10.59 2.61 10.03 2.4 9.4 2.19C9.33 1.49 8.7 1 8 1C7.3 1 6.74 1.56 6.6 2.19C5.97 2.4 5.41 2.61 4.85 2.96C4.64 2.82 4.36 2.75 4.15 2.75C3.38 2.75 2.75 3.38 2.75 4.15C2.75 4.43 2.82 4.64 2.96 4.85C2.61 5.41 2.4 5.97 2.19 6.6C1.56 6.74 1 7.3 1 8C1 8.7 1.56 9.26 2.19 9.4C2.33 10.03 2.61 10.59 2.96 11.15C2.82 11.36 2.75 11.64 2.75 11.85C2.75 12.62 3.38 13.25 4.15 13.25C4.43 13.25 4.64 13.18 4.85 13.04C5.41 13.39 5.97 13.6 6.6 13.81C6.67 14.51 7.3 15 8 15C8.7 15 9.26 14.44 9.4 13.81C10.03 13.67 10.59 13.39 11.15 13.04C11.36 13.18 11.64 13.25 11.85 13.25C12.62 13.25 13.25 12.62 13.25 11.85C13.25 11.57 13.18 11.36 13.04 11.15C13.39 10.59 13.6 10.03 13.81 9.4C14.51 9.33 15 8.7 15 8C15 7.3 14.44 6.74 13.81 6.6L13.81 6.6ZM12.55 10.66C12.34 10.52 12.13 10.45 11.85 10.45C11.08 10.45 10.45 11.08 10.45 11.85C10.45 12.13 10.52 12.34 10.66 12.55C10.24 12.76 9.82 12.97 9.33 13.11C9.12 12.62 8.63 12.2 8 12.2C7.44 12.2 6.88 12.55 6.67 13.11C6.18 12.97 5.76 12.83 5.34 12.55C5.48 12.34 5.55 12.13 5.55 11.85C5.55 11.08 4.92 10.45 4.15 10.45C3.87 10.45 3.66 10.52 3.45 10.66C3.24 10.24 3.03 9.82 2.89 9.33C3.45 9.12 3.8 8.56 3.8 8C3.8 7.44 3.45 6.88 2.96 6.74C3.1 6.25 3.24 5.83 3.52 5.41C3.73 5.55 3.94 5.62 4.22 5.62C4.99 5.62 5.62 4.99 5.62 4.22C5.62 3.94 5.55 3.73 5.41 3.52C5.83 3.31 6.25 3.1 6.74 2.96C6.88 3.45 7.44 3.8 8 3.8C8.56 3.8 9.12 3.45 9.33 2.89C9.82 3.03 10.24 3.17 10.66 3.45C10.52 3.66 10.45 3.87 10.45 4.15C10.45 4.92 11.08 5.55 11.85 5.55C12.13 5.55 12.34 5.48 12.55 5.34C12.76 5.76 12.97 6.18 13.11 6.67C12.55 6.88 12.2 7.44 12.2 8C12.2 8.56 12.55 9.12 13.04 9.26C12.97 9.75 12.76 10.24 12.55 10.66L12.55 10.66Z"
fill=
"rgb(59,65,75)"
fill-rule=
"nonzero"
/>
</svg>
src/views/characterPage/components/memberOfCongress/components/characterRelationships/assets/icon-download.svg
0 → 100644
浏览文件 @
592d75b3
<svg
viewBox=
"0 0 28 28"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"28.000000"
height=
"28.000000"
fill=
"none"
>
<defs>
<clipPath
id=
"clipPath_1"
>
<rect
width=
"18.000000"
height=
"15.000000"
x=
"5.000000"
y=
"5.000000"
fill=
"rgb(255,255,255)"
/>
</clipPath>
</defs>
<rect
id=
"导出数据"
width=
"28.000000"
height=
"28.000000"
x=
"0.000000"
y=
"0.000000"
/>
<g
id=
"容器 742"
customFrame=
"url(#clipPath_1)"
>
<rect
id=
"容器 742"
width=
"18.000000"
height=
"15.000000"
x=
"5.000000"
y=
"5.000000"
/>
<rect
id=
"矩形 347"
width=
"2.000000"
height=
"6.000000"
x=
"13.000000"
y=
"5.000000"
fill=
"rgb(132,136,142)"
/>
<path
id=
"矢量 600"
d=
"M18 11L10 11L14 16L18 11Z"
fill=
"rgb(132,136,142)"
fill-rule=
"evenodd"
/>
<path
id=
"矢量 601"
d=
"M22 19.9996L22.9999 15.0012L19.9999 12.0011L18.9999 12L21.0003 15.001L17.9999 15.0015L16.9998 17.9987L14 17.9996L11.0001 17.9997L9.99998 15.002L7.00017 15.0028L8.99996 12.0008L8 12.0004L5 15.0023L6.00016 20L22 19.9996Z"
fill=
"rgb(132,136,142)"
fill-rule=
"evenodd"
/>
</g>
</svg>
src/views/characterPage/components/memberOfCongress/components/characterRelationships/assets/icon-expand.svg
0 → 100644
浏览文件 @
592d75b3
<svg
viewBox=
"0 0 28 28"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"28.000000"
height=
"28.000000"
fill=
"none"
>
<defs>
<clipPath
id=
"clipPath_0"
>
<rect
width=
"20.000000"
height=
"20.000000"
x=
"4.000000"
y=
"4.000000"
fill=
"rgb(255,255,255)"
/>
</clipPath>
</defs>
<rect
id=
"数据源"
width=
"28.000000"
height=
"28.000000"
x=
"0.000000"
y=
"0.000000"
/>
<g
id=
"数据库 1"
clip-path=
"url(#clipPath_0)"
customFrame=
"url(#clipPath_0)"
>
<rect
id=
"数据库 1"
width=
"20.000000"
height=
"20.000000"
x=
"4.000000"
y=
"4.000000"
/>
<path
id=
"合并"
d=
"M10.6426 6.48828C11.6719 6.28906 12.791 6.1875 14 6.1875C15.209 6.1875 16.3281 6.28906 17.3574 6.48828C18.3867 6.6875 19.2012 6.96094 19.7988 7.30469C20.3965 7.64844 20.6973 8.01953 20.6973 8.42188L20.6973 9.53906C20.6973 9.94141 20.3984 10.3125 19.7988 10.6563C19.1992 11 18.3867 11.2715 17.3574 11.4727C16.3281 11.6699 15.209 11.7695 14 11.7695C12.791 11.7695 11.6719 11.668 10.6426 11.4688C9.61328 11.2695 8.79883 10.9961 8.20117 10.6523C7.60156 10.3086 7.30273 9.9375 7.30273 9.53516L7.30273 8.41797C7.30273 8.01563 7.60156 7.64453 8.20117 7.30078C8.80078 6.96094 9.61328 6.68945 10.6426 6.48828ZM10.1387 12.5078C11.3359 12.7578 12.623 12.8828 14 12.8828C15.377 12.8828 16.6641 12.7578 17.8613 12.5078C19.0586 12.2578 20.0039 11.8887 20.6953 11.4004L20.6953 12.8828C20.6953 13.2852 20.3965 13.6563 19.7969 14C19.1973 14.3438 18.3848 14.6152 17.3555 14.8164C16.3281 15.0156 15.209 15.1152 14 15.1152C12.791 15.1152 11.6719 15.0137 10.6426 14.8145C9.61328 14.6152 8.79883 14.3418 8.20117 13.998C7.60156 13.6543 7.30273 13.2832 7.30273 12.8809L7.30273 11.3984C7.99609 11.8906 8.94141 12.2598 10.1387 12.5078ZM10.1387 15.8574C11.3359 16.1074 12.623 16.2324 14 16.2324C14.6624 16.2324 15.3041 16.2035 15.9249 16.1456C14.2088 16.4715 12.8443 17.3161 12.2805 18.3935C11.7114 18.3432 11.1654 18.2672 10.6426 18.166C9.61328 17.9668 8.80078 17.6934 8.20117 17.3496C7.60156 17.0059 7.30273 16.6348 7.30273 16.2324L7.30273 14.75C7.9961 15.2383 8.94141 15.6074 10.1387 15.8574ZM17.5 16C17.3281 16 17.1581 16.005 16.9902 16.0148C17.2857 15.9695 17.5761 15.917 17.8613 15.8574C19.0586 15.6074 20.0039 15.2383 20.6953 14.75L20.6953 16.2324C20.6953 16.3614 20.6646 16.4872 20.6031 16.6099C19.7199 16.2251 18.6512 16 17.5 16ZM13 19.5C13 18.1193 15.0147 17 17.5 17C19.9853 17 22 18.1193 22 19.5C22 20.8807 19.9853 22 17.5 22C15.0147 22 13 20.8807 13 19.5ZM17.5 18C18.3284 18 19 18.6716 19 19.5C19 20.3284 18.3284 21 17.5 21C16.6716 21 16 20.3284 16 19.5C16 18.6716 16.6716 18 17.5 18ZM12 19.5L12 19.4861C11.3572 19.4236 10.7367 19.33 10.1387 19.2051C8.94141 18.9551 7.99609 18.5859 7.30273 18.0957L7.30273 19.5781C7.30273 19.9805 7.60156 20.3516 8.20117 20.6953C8.79883 21.0391 9.61328 21.3125 10.6426 21.5117C11.4872 21.6752 12.3923 21.7729 13.3579 21.8027C12.5123 21.1873 12 20.3817 12 19.5Z"
fill=
"rgb(132,136,142)"
fill-rule=
"evenodd"
/>
</g>
</svg>
src/views/characterPage/components/memberOfCongress/components/characterRelationships/assets/icon-force.svg
0 → 100644
浏览文件 @
592d75b3
<svg
viewBox=
"0 0 16 16"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"16.000000"
height=
"16.000000"
fill=
"none"
>
<rect
id=
"拓扑 1"
width=
"16.000000"
height=
"16.000000"
x=
"0.000000"
y=
"0.000000"
/>
<path
id=
"矢量 422"
d=
"M12.7727 11.142C12.4273 11.142 12.107 11.2495 11.8433 11.4327L9.42154 9.6837C9.65586 9.26852 9.78974 8.7891 9.78974 8.27836C9.78974 8.18728 9.7853 8.09723 9.77697 8.00833L12.257 6.54325C12.6396 6.77576 13.1451 6.72689 13.4758 6.39622C13.8641 6.00791 13.8641 5.37835 13.4758 4.99004C13.0875 4.60173 12.4579 4.60173 12.0696 4.99004C11.7835 5.27612 11.7085 5.69309 11.844 6.04812L9.63655 7.35217C9.40869 6.68522 8.9416 6.12922 8.33762 5.78637L8.77881 3.58047C8.81046 3.5835 8.84253 3.58519 8.87497 3.58519C9.42413 3.58519 9.8693 3.14002 9.8693 2.59087C9.8693 2.04173 9.42413 1.59656 8.87497 1.59656C8.32582 1.59656 7.88065 2.04173 7.88065 2.59087C7.88065 2.87182 7.99736 3.12535 8.18469 3.30617L7.73951 5.53213C7.48164 5.45587 7.2087 5.41473 6.9261 5.41473C6.388 5.41473 5.88464 5.56327 5.45458 5.8214L4.08134 4.61294C4.32918 4.12086 4.24801 3.50548 3.83714 3.09461C3.32457 2.58205 2.49355 2.58205 1.98098 3.09461C1.46841 3.60717 1.46842 4.4382 1.98098 4.95078C2.43973 5.40952 3.15341 5.45735 3.66574 5.09492L4.93964 6.21596C4.39895 6.73684 4.06247 7.46826 4.06247 8.27836C4.06247 9.15881 4.45994 9.94631 5.0851 10.4716L3.95324 11.6792C3.74531 11.5408 3.49572 11.4602 3.22724 11.4602C2.50236 11.4602 1.91474 12.0478 1.91474 12.7727C1.91474 13.4975 2.50236 14.0852 3.22724 14.0852C3.95212 14.0852 4.53974 13.4975 4.53974 12.7727C4.53974 12.5477 4.48308 12.3359 4.38332 12.1508L5.62251 10.8288C6.01351 11.029 6.45662 11.142 6.9261 11.142C7.76819 11.142 8.52534 10.7785 9.04931 10.1999L11.3976 11.8958C11.2358 12.149 11.142 12.4499 11.142 12.7727C11.142 13.6733 11.8721 14.4034 12.7727 14.4034C13.6733 14.4034 14.4034 13.6733 14.4034 12.7727C14.4034 11.8721 13.6733 11.142 12.7727 11.142L12.7727 11.142Z"
fill=
"rgb(59,65,75)"
fill-rule=
"nonzero"
/>
</svg>
src/views/characterPage/components/memberOfCongress/components/characterRelationships/assets/icon-star.svg
0 → 100644
浏览文件 @
592d75b3
<svg
viewBox=
"0 0 28 28"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"28.000000"
height=
"28.000000"
fill=
"none"
>
<rect
id=
"收藏"
width=
"28.000000"
height=
"28.000000"
x=
"0.000000"
y=
"0.000000"
/>
<path
id=
"星形 2"
d=
"M15.9534 11.0113C15.9936 11.1349 16.1088 11.2186 16.2388 11.2186L21.6363 11.2188C21.9269 11.2188 22.0478 11.5907 21.8127 11.7615L17.446 14.9343C17.3409 15.0107 17.2969 15.1461 17.3371 15.2697L19.0048 20.4031C19.0946 20.6795 18.7783 20.9094 18.5432 20.7385L14.1763 17.5661C14.0712 17.4897 13.9288 17.4897 13.8237 17.5661L9.45683 20.7385C9.22171 20.9094 8.90539 20.6795 8.99518 20.4031L10.6629 15.2697C10.7031 15.1461 10.6591 15.0107 10.554 14.9343L6.18734 11.7615C5.95224 11.5907 6.07307 11.2188 6.36368 11.2188L11.7612 11.2186C11.8912 11.2186 12.0064 11.1349 12.0466 11.0113L13.7147 5.87799C13.8045 5.60161 14.1955 5.60161 14.2853 5.87799L15.9534 11.0113Z"
fill=
"rgb(132,136,142)"
fill-rule=
"evenodd"
/>
</svg>
src/views/characterPage/components/memberOfCongress/components/characterRelationships/assets/icon-tree.svg
0 → 100644
浏览文件 @
592d75b3
<svg
viewBox=
"0 0 16 16"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"16.000000"
height=
"16.000000"
fill=
"none"
>
<rect
id=
"绿洲_拓扑图 1"
width=
"16.000000"
height=
"16.000000"
x=
"0.000000"
y=
"0.000000"
/>
<path
id=
"矢量 422"
d=
"M15.0147 9.88449L14.1867 9.88449L14.1867 8.65516C14.1867 7.53912 13.312 6.63116 12.2368 6.63116L8.37334 6.63116L8.37334 5.08449L9.2317 5.08449C9.42605 5.08449 9.58222 4.94394 9.58222 4.74957L9.58222 2.34115C9.58222 2.11642 9.40148 1.92004 9.17669 1.92004L6.76829 1.92004C6.57396 1.92004 6.41778 2.09179 6.41778 2.28616L6.41778 4.69459C6.41778 4.91931 6.59853 5.08449 6.82329 5.08449L7.62667 5.08449L7.62667 6.63116L3.59268 6.63116C2.62816 6.63116 1.81334 7.45731 1.81334 8.43519L1.81334 9.88449L0.930314 9.88449C0.735986 9.88449 0.58667 10.0572 0.58667 10.2515L0.58667 12.66C0.58667 12.8847 0.760554 13.0489 0.985319 13.0489L3.39371 13.0489C3.58806 13.0489 3.75111 12.9093 3.75111 12.7149L3.75111 10.3066C3.75111 10.0818 3.56351 9.88451 3.33874 9.88451L2.52445 9.88451L2.52445 8.43521C2.52445 7.84276 3.01364 7.34227 3.59268 7.34227L7.62667 7.34227L7.62667 9.88449L6.76829 9.88449C6.57396 9.88449 6.41778 10.0572 6.41778 10.2515L6.41778 12.66C6.41778 12.8847 6.59853 13.0489 6.82329 13.0489L9.2317 13.0489C9.42605 13.0489 9.58222 12.9093 9.58222 12.7149L9.58222 10.3066C9.58222 10.0818 9.40148 9.88451 9.1767 9.88451L8.37334 9.88451L8.37334 7.34227L12.2368 7.34227C12.9199 7.34227 13.4756 7.93123 13.4756 8.65516L13.4756 9.88449L12.6063 9.88449C12.4119 9.88449 12.2489 10.0572 12.2489 10.2515L12.2489 12.66C12.2489 12.8847 12.4365 13.0489 12.6613 13.0489L15.0697 13.0489C15.264 13.0489 15.4133 12.9093 15.4133 12.7149L15.4133 10.3066C15.4133 10.0818 15.2395 9.88449 15.0147 9.88449Z"
fill=
"rgb(59,65,75)"
fill-rule=
"nonzero"
/>
</svg>
src/views/characterPage/components/memberOfCongress/components/characterRelationships/index.vue
浏览文件 @
592d75b3
差异被折叠。
点击展开。
src/views/characterPage/components/memberOfCongress/components/historicalProposal/api/bills.js
0 → 100644
浏览文件 @
592d75b3
/**
* Mock API - 法案数据模拟接口
* 所有数据集中管理,方便后续替换为真实 API
*/
// ===== 筛选项配置数据 =====
const
DOMAIN_OPTIONS
=
[
{
id
:
'all'
,
label
:
'全部领域'
},
{
id
:
'ai'
,
label
:
'人工智能'
},
{
id
:
'ic'
,
label
:
'集成电路'
},
{
id
:
'quantum'
,
label
:
'量子科技'
},
{
id
:
'network'
,
label
:
'新一代通信网络'
},
{
id
:
'biotech'
,
label
:
'生物技术'
},
{
id
:
'ocean'
,
label
:
'海洋'
},
{
id
:
'deep-sea'
,
label
:
'深海'
},
{
id
:
'polar'
,
label
:
'极地'
},
{
id
:
'nuclear'
,
label
:
'核'
},
{
id
:
'other'
,
label
:
'其他'
},
]
const
TIME_OPTIONS
=
[
{
id
:
'all'
,
label
:
'全部时间'
},
{
id
:
'2025'
,
label
:
'2025年'
},
{
id
:
'2024'
,
label
:
'2024年'
},
{
id
:
'2023'
,
label
:
'2023年'
},
{
id
:
'2022'
,
label
:
'2022年'
},
{
id
:
'2021'
,
label
:
'2021年'
},
{
id
:
'earlier'
,
label
:
'更早'
},
]
// ===== 进度阶段配置 =====
const
PROGRESS_STAGES
=
[
'提出'
,
'众议院通过'
,
'参议院通过'
,
'分歧协调'
,
'提交总统'
,
'法案通过'
]
// ===== 法案模拟数据 =====
const
MOCK_BILLS
=
[
{
id
:
1
,
billNumber
:
'H.R.1'
,
billSerial
:
'第1234'
,
title
:
'国家人工智能安全法案'
,
titleEn
:
'One Big Beautiful Bill Act'
,
importance
:
'特别重大'
,
proposer
:
{
name
:
'乔迪'
,
avatar
:
null
},
committee
:
'众议院科技委员会'
,
relatedDomains
:
[
'人工智能'
,
'集成电路'
],
latestMotion
:
'2025.07.04'
,
latestMotionResult
:
'成'
,
currentStage
:
5
,
congressSession
:
'119th'
,
},
{
id
:
2
,
billNumber
:
'H.R.2648'
,
billSerial
:
'第2648'
,
title
:
'半导体供应链安全法'
,
titleEn
:
'CHIPS and Science Act Extension'
,
importance
:
'重大'
,
proposer
:
{
name
:
'佩洛西'
,
avatar
:
null
},
committee
:
'参议院商务委员会'
,
relatedDomains
:
[
'集成电路'
],
latestMotion
:
'2025.06.18'
,
latestMotionResult
:
'通过'
,
currentStage
:
3
,
congressSession
:
'119th'
,
},
{
id
:
3
,
billNumber
:
'S.1052'
,
billSerial
:
'第1052'
,
title
:
'量子计算出口管制法'
,
titleEn
:
'Quantum Computing Export Control Act'
,
importance
:
'关注'
,
proposer
:
{
name
:
'沃纳'
,
avatar
:
null
},
committee
:
'参议院情报委员会'
,
relatedDomains
:
[
'量子科技'
,
'人工智能'
],
latestMotion
:
'2025.05.22'
,
latestMotionResult
:
'通过'
,
currentStage
:
1
,
congressSession
:
'119th'
,
},
{
id
:
4
,
billNumber
:
'H.R.4217'
,
billSerial
:
'第4217'
,
title
:
'清洁能源核技术推进法案'
,
titleEn
:
'Clean Nuclear Energy Advancement Act'
,
importance
:
'一般'
,
proposer
:
{
name
:
'格兰姆'
,
avatar
:
null
},
committee
:
'众议院能源委员会'
,
relatedDomains
:
[
'核'
,
'生物技术'
],
latestMotion
:
'2025.04.11'
,
latestMotionResult
:
'提出'
,
currentStage
:
0
,
congressSession
:
'119th'
,
},
{
id
:
5
,
billNumber
:
'S.3301'
,
billSerial
:
'第3301'
,
title
:
'深海战略资源保护法'
,
titleEn
:
'Deep Sea Strategic Resources Protection Act'
,
importance
:
'特别重大'
,
proposer
:
{
name
:
'卢比奥'
,
avatar
:
null
},
committee
:
'参议院外交委员会'
,
relatedDomains
:
[
'深海'
,
'海洋'
],
latestMotion
:
'2025.07.01'
,
latestMotionResult
:
'通过'
,
currentStage
:
4
,
congressSession
:
'119th'
,
},
]
// ===== 潜在新闻模拟数据 =====
const
MOCK_POTENTIAL_NEWS
=
[
{
id
:
1
,
title
:
'我国新一代人工智能大模型正式开放公众体验。'
,
tags
:
[{
label
:
'人工智能'
,
color
:
'blue'
},
{
label
:
'科技突破'
,
color
:
'green'
}]
},
{
id
:
2
,
title
:
'2026 年全国春运客流总量再创新高'
,
tags
:
[{
label
:
'春运'
,
color
:
'blue'
}]
},
{
id
:
3
,
title
:
'我国新一代人工智能大模型正式开放公众体验。'
,
tags
:
[{
label
:
'人工智能'
,
color
:
'blue'
},
{
label
:
'科技突破'
,
color
:
'green'
}]
},
{
id
:
4
,
title
:
'2026 年全国春运客流总量再创新高'
,
tags
:
[{
label
:
'人工智能'
,
color
:
'blue'
},
{
label
:
'科技突破'
,
color
:
'green'
}]
},
{
id
:
5
,
title
:
'我国新一代人工智能大模型正式开放公众体验。'
,
tags
:
[{
label
:
'人工智能'
,
color
:
'blue'
},
{
label
:
'科技突破'
,
color
:
'green'
}]
},
{
id
:
6
,
title
:
'2026 年全国春运客流总量再创新高'
,
tags
:
[{
label
:
'人工智能'
,
color
:
'blue'
},
{
label
:
'科技突破'
,
color
:
'green'
}]
},
{
id
:
7
,
title
:
'我国新一代人工智能大模型正式开放公众体验。'
,
tags
:
[{
label
:
'人工智能'
,
color
:
'blue'
},
{
label
:
'科技突破'
,
color
:
'green'
}]
},
{
id
:
8
,
title
:
'2026 年全国春运客流总量再创新高'
,
tags
:
[{
label
:
'人工智能'
,
color
:
'blue'
},
{
label
:
'科技突破'
,
color
:
'green'
}]
},
{
id
:
9
,
title
:
'我国新一代人工智能大模型正式开放公众体验。'
,
tags
:
[{
label
:
'人工智能'
,
color
:
'blue'
},
{
label
:
'科技突破'
,
color
:
'green'
}]
},
{
id
:
10
,
title
:
'2026 年全国春运客流总量再创新高'
,
tags
:
[{
label
:
'人工智能'
,
color
:
'blue'
},
{
label
:
'科技突破'
,
color
:
'green'
}]
},
]
const
MOCK_KEYWORDS
=
[
{
label
:
'财政责任'
,
color
:
'red'
,
value
:
48
},
{
label
:
'地球化改造'
,
color
:
'red'
,
value
:
36
},
{
label
:
'双边合作'
,
color
:
'cyan'
,
value
:
32
},
{
label
:
'Neuralink+Grok'
,
color
:
'blue'
,
value
:
44
},
{
label
:
'能源政策'
,
color
:
'blue'
,
value
:
40
},
{
label
:
'保守派财政立场'
,
color
:
'red'
,
value
:
28
},
{
label
:
'农场法案倡导者'
,
color
:
'gold'
,
value
:
30
},
{
label
:
'环境议题'
,
color
:
'cyan'
,
value
:
34
},
{
label
:
'外交政策立场'
,
color
:
'gold'
,
value
:
26
},
{
label
:
'农业委员会成员'
,
color
:
'red'
,
value
:
22
},
{
label
:
'教育政策'
,
color
:
'cyan'
,
value
:
24
},
{
label
:
'工作可选项'
,
color
:
'gold'
,
value
:
20
},
{
label
:
'保守派价值观'
,
color
:
'blue'
,
value
:
38
},
{
label
:
'可持续能源'
,
color
:
'volcano'
,
value
:
42
},
{
label
:
'农业经济引擎'
,
color
:
'red'
,
value
:
18
},
{
label
:
'第一性原理'
,
color
:
'gold'
,
value
:
26
},
{
label
:
'多智能体协作'
,
color
:
'cyan'
,
value
:
28
},
{
label
:
'可持续富足'
,
color
:
'gold'
,
value
:
22
},
{
label
:
'为后代负责'
,
color
:
'gold'
,
value
:
32
},
]
// ===== 模拟网络延迟 =====
function
delay
(
ms
=
300
)
{
return
new
Promise
((
resolve
)
=>
setTimeout
(
resolve
,
ms
))
}
// ===== API 接口函数 =====
/**
* 获取筛选项配置
*/
export
async
function
fetchFilterOptions
()
{
await
delay
(
100
)
return
{
code
:
200
,
data
:
{
domains
:
DOMAIN_OPTIONS
,
timeRanges
:
TIME_OPTIONS
,
},
}
}
/**
* 获取进度阶段配置
*/
export
async
function
fetchProgressStages
()
{
await
delay
(
50
)
return
{
code
:
200
,
data
:
PROGRESS_STAGES
,
}
}
/**
* 获取法案列表
* @param {Object} params - 查询参数
* @param {string} params.keyword - 搜索关键词
* @param {string[]} params.domains - 选中的领域
* @param {string[]} params.timeRanges - 选中的时间范围
* @param {boolean} params.chinaRelatedOnly - 只看涉华法案
* @param {string} params.sortBy - 排序方式
*/
export
async
function
fetchBills
(
params
=
{})
{
await
delay
(
400
)
let
result
=
[...
MOCK_BILLS
]
// 模拟关键词过滤
if
(
params
.
keyword
)
{
const
kw
=
params
.
keyword
.
toLowerCase
()
result
=
result
.
filter
(
(
b
)
=>
b
.
title
.
toLowerCase
().
includes
(
kw
)
||
b
.
titleEn
.
toLowerCase
().
includes
(
kw
)
||
b
.
billNumber
.
toLowerCase
().
includes
(
kw
)
)
}
// 模拟领域过滤
if
(
params
.
domains
&&
params
.
domains
.
length
>
0
&&
!
params
.
domains
.
includes
(
'all'
))
{
result
=
result
.
filter
((
b
)
=>
b
.
relatedDomains
.
some
((
d
)
=>
params
.
domains
.
includes
(
d
))
)
}
// 模拟总量 1149
const
simulatedTotal
=
1149
const
page
=
params
.
page
||
1
const
pageSize
=
params
.
pageSize
||
10
// 循环填充 mock 数据以模拟分页
const
pagedList
=
[]
for
(
let
i
=
0
;
i
<
pageSize
;
i
++
)
{
const
src
=
result
[
i
%
result
.
length
]
pagedList
.
push
({
...
src
,
id
:
(
page
-
1
)
*
pageSize
+
i
+
1
,
})
}
return
{
code
:
200
,
data
:
{
list
:
pagedList
,
total
:
simulatedTotal
,
},
}
}
/**
* 获取潜在新闻列表
*/
export
async
function
fetchPotentialNews
(
params
=
{})
{
await
delay
(
300
)
const
page
=
params
.
page
||
1
const
pageSize
=
params
.
pageSize
||
10
// simulate 96 total items
const
totalItems
=
96
const
list
=
MOCK_POTENTIAL_NEWS
.
map
((
item
,
i
)
=>
({
...
item
,
id
:
(
page
-
1
)
*
pageSize
+
i
+
1
,
}))
return
{
code
:
200
,
data
:
{
list
,
total
:
totalItems
,
},
}
}
/**
* 获取潜在新闻分析关键词
*/
export
async
function
fetchPotentialNewsKeywords
()
{
await
delay
(
200
)
return
{
code
:
200
,
data
:
MOCK_KEYWORDS
,
}
}
/**
* 获取排序选项
*/
export
async
function
fetchSortOptions
()
{
await
delay
(
50
)
return
{
code
:
200
,
data
:
[
{
value
:
'publishTimeDesc'
,
label
:
'发布时间倒序'
},
{
value
:
'publishTimeAsc'
,
label
:
'发布时间正序'
},
],
}
}
src/views/characterPage/components/memberOfCongress/components/historicalProposal/assets/img.png
deleted
100644 → 0
浏览文件 @
2d832f9c
5.5 KB
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/BillCard.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<div
class=
"bill-card"
>
<div
class=
"bill-card-inner"
>
<div
class=
"bill-card-preview"
>
<DocumentPreview
:bill-number=
"bill.billId"
:bill-serial=
"bill.name"
/>
</div>
<div
class=
"bill-card-detail"
>
<div
class=
"bill-card-upper"
>
<div
class=
"bill-card-title-row"
>
<h3
class=
"bill-card-title"
>
{{
bill
.
name
}}
</h3>
</div>
<p
class=
"bill-card-desc"
>
{{
bill
.
ename
}}
</p>
</div>
<div
class=
"bill-card-divider"
/>
<div
class=
"bill-card-meta"
>
<div
class=
"meta-row"
>
<span
class=
"meta-label"
>
提案人:
</span>
<span
class=
"meta-value sponsor-name"
:title=
"allSponsorNames"
>
{{
firstSponsorName
}}
</span>
</div>
<div
class=
"meta-row"
>
<span
class=
"meta-label"
>
相关领域:
</span>
<div
class=
"meta-tags"
>
<TagBadge
v-for=
"item in bill.industryList"
:key=
"item.industryName"
:label=
"item.industryName"
tag-class=
"tag3"
/>
</div>
</div>
<div
class=
"meta-row"
>
<span
class=
"meta-label"
>
最新动议:
</span>
<span
class=
"meta-value"
>
{{
latestActionText
}}
</span>
</div>
<div
class=
"meta-row meta-row-progress"
>
<ProgressBar
:stages=
"stageNames"
:current=
"currentStageIndex"
/>
</div>
</div>
</div>
</div>
</div>
</
template
>
<
script
setup
>
import
{
computed
}
from
'vue'
import
DocumentPreview
from
'./DocumentPreview.vue'
import
TagBadge
from
'./TagBadge.vue'
import
ProgressBar
from
'./ProgressBar.vue'
const
props
=
defineProps
({
bill
:
{
type
:
Object
,
required
:
true
},
progressStages
:
{
type
:
Array
,
default
:
()
=>
[]
},
})
// 提案人:只展示第一个,悬浮 title 显示全部
const
firstSponsorName
=
computed
(()
=>
{
if
(
!
props
.
bill
.
sponsorList
||
props
.
bill
.
sponsorList
.
length
===
0
)
return
''
return
props
.
bill
.
sponsorList
[
0
]
||
''
})
const
allSponsorNames
=
computed
(()
=>
{
if
(
!
props
.
bill
.
sponsorList
||
props
.
bill
.
sponsorList
.
length
===
0
)
return
''
return
props
.
bill
.
sponsorList
.
filter
(
Boolean
).
join
(
'
\
n'
)
})
// 最新动议:null 替换为至今
const
latestActionText
=
computed
(()
=>
{
if
(
!
props
.
bill
.
latestAction
)
return
''
// 将 "null" 字符串替换为 "至今"
return
props
.
bill
.
latestAction
.
replace
(
/
\b
null
\b
/gi
,
'至今'
).
trim
()
})
// 法案进展:stageList 是对象数组 [{id, name}],提取 name 列表
const
stageNames
=
computed
(()
=>
{
if
(
!
props
.
progressStages
||
props
.
progressStages
.
length
===
0
)
return
[]
// progressStages 是全量阶段字典 [{id, name}],提取 name
return
props
.
progressStages
.
map
(
s
=>
s
.
name
||
s
)
})
// 当前阶段:找到 stageList 最后一个阶段在全量字典中的位置
const
currentStageIndex
=
computed
(()
=>
{
if
(
!
props
.
bill
.
stageList
||
props
.
bill
.
stageList
.
length
===
0
)
return
0
if
(
!
props
.
progressStages
||
props
.
progressStages
.
length
===
0
)
return
0
// stageList 是已走过的阶段对象数组,取最后一个
const
lastStage
=
props
.
bill
.
stageList
[
props
.
bill
.
stageList
.
length
-
1
]
const
lastStageName
=
lastStage
?.
name
||
lastStage
const
idx
=
props
.
progressStages
.
findIndex
(
s
=>
(
s
.
name
||
s
)
===
lastStageName
)
return
idx
>
-
1
?
idx
:
0
})
</
script
>
<
style
scoped
>
.bill-card
{
width
:
1224px
;
max-width
:
100%
;
height
:
320px
;
background
:
#fff
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
border-radius
:
10px
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0.1
);
box-sizing
:
border-box
;
padding
:
12px
16px
;
cursor
:
pointer
;
overflow
:
hidden
;
transition
:
box-shadow
0.2s
;
}
.bill-card
:hover
{
box-shadow
:
0px
0px
24px
0px
rgba
(
25
,
69
,
130
,
0.16
);
}
.bill-card-inner
{
display
:
flex
;
flex-direction
:
row
;
gap
:
13px
;
align-items
:
center
;
height
:
100%
;
}
.bill-card-preview
{
flex-shrink
:
0
;
width
:
240px
;
height
:
296px
;
}
.bill-card-detail
{
flex
:
1
;
min-width
:
0
;
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
align-items
:
flex-start
;
height
:
100%
;
}
.bill-card-upper
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
width
:
100%
;
}
.bill-card-title-row
{
display
:
flex
;
align-items
:
flex-start
;
gap
:
20px
;
width
:
100%
;
}
.bill-card-title
{
font-size
:
20px
;
font-weight
:
700
;
color
:
rgba
(
59
,
65
,
75
,
1
);
margin
:
0
;
line-height
:
26px
;
flex
:
1
;
min-width
:
0
;
}
.bill-card-desc
{
font-size
:
16px
;
font-weight
:
400
;
color
:
rgba
(
59
,
65
,
75
,
1
);
margin
:
0
;
line-height
:
24px
;
display
:
-webkit-box
;
-webkit-line-clamp
:
2
;
-webkit-box-orient
:
vertical
;
overflow
:
hidden
;
}
.bill-card-divider
{
width
:
100%
;
height
:
1px
;
background
:
rgba
(
234
,
236
,
238
,
1
);
flex-shrink
:
0
;
}
.bill-card-meta
{
width
:
100%
;
flex
:
1
;
position
:
relative
;
min-height
:
0
;
}
.meta-row
{
display
:
flex
;
align-items
:
center
;
position
:
absolute
;
left
:
0
;
width
:
100%
;
}
.meta-row
:nth-child
(
1
)
{
top
:
0
;
}
.meta-row
:nth-child
(
2
)
{
top
:
36px
;
}
.meta-row
:nth-child
(
3
)
{
top
:
72px
;
}
.meta-row
:nth-child
(
4
)
{
top
:
108px
;
}
.meta-row
:nth-child
(
5
)
{
top
:
144px
;
}
.meta-label
{
font-size
:
16px
;
font-weight
:
700
;
color
:
rgba
(
59
,
65
,
75
,
1
);
letter-spacing
:
1px
;
line-height
:
24px
;
white-space
:
nowrap
;
flex-shrink
:
0
;
width
:
100px
;
}
.meta-value
{
font-size
:
16px
;
font-weight
:
400
;
color
:
rgba
(
95
,
101
,
108
,
1
);
line-height
:
24px
;
}
.sponsor-name
{
cursor
:
default
;
white-space
:
nowrap
;
}
.meta-tags
{
display
:
flex
;
gap
:
8px
;
flex-wrap
:
wrap
;
align-items
:
center
;
}
.meta-row-progress
{
left
:
0
;
right
:
0
;
width
:
100%
;
}
.meta-row-progress
:deep
(
.progress-bar
)
{
width
:
100%
;
}
.meta-progress
{
flex
:
1
;
min-width
:
0
;
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/BillList.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<div
class=
"bill-list"
>
<div
v-if=
"loading"
class=
"bill-list-loading"
>
<div
v-for=
"i in 3"
:key=
"i"
class=
"bill-list-skeleton"
>
<div
class=
"skeleton-left"
/>
<div
class=
"skeleton-right"
>
<div
class=
"skeleton-line w30"
/>
<div
class=
"skeleton-line w50"
/>
<div
class=
"skeleton-divider"
/>
<div
class=
"skeleton-line w60"
/>
<div
class=
"skeleton-line w40"
/>
</div>
</div>
</div>
<div
v-else-if=
"bills.length === 0"
class=
"bill-list-empty"
>
<svg
width=
"48"
height=
"48"
viewBox=
"0 0 24 24"
fill=
"none"
stroke=
"#bfbfbf"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
>
<path
d=
"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
<p>
暂无相关法案
</p>
</div>
<template
v-else
>
<BillCard
v-for=
"bill in bills"
:key=
"bill.id"
:bill=
"bill"
:progress-stages=
"progressStages"
/>
<div
v-if=
"total > 0"
class=
"bill-pagination"
>
<span
class=
"bill-pagination-total"
>
{{
'
\
u5171'
+
total
+
'
\
u9879
\
u6cd5
\
u6848'
}}
</span>
<el-pagination
:current-page=
"currentPage"
:page-size=
"pageSize"
:total=
"total"
:pager-count=
"5"
layout=
"prev, pager, next"
background
@
current-change=
"$emit('page-change', $event)"
/>
</div>
</
template
>
</div>
</template>
<
script
setup
>
import
BillCard
from
'./BillCard.vue'
defineProps
({
bills
:
{
type
:
Array
,
required
:
true
},
loading
:
{
type
:
Boolean
,
default
:
false
},
total
:
{
type
:
Number
,
default
:
0
},
currentPage
:
{
type
:
Number
,
default
:
1
},
pageSize
:
{
type
:
Number
,
default
:
10
},
progressStages
:
{
type
:
Array
,
default
:
()
=>
[
'提出'
,
'众议院通过'
,
'参议院通过'
,
'分歧协调'
,
'提交总统'
,
'法案通过'
]
},
})
defineEmits
([
'page-change'
])
</
script
>
<
style
scoped
>
.bill-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
16px
;
}
.bill-list-loading
{
display
:
flex
;
flex-direction
:
column
;
gap
:
16px
;
}
.bill-list-skeleton
{
display
:
flex
;
gap
:
24px
;
background
:
#fff
;
border
:
1px
solid
#ebedf0
;
border-radius
:
8px
;
padding
:
24px
;
}
.skeleton-left
{
width
:
120px
;
height
:
190px
;
background
:
#f0f0f0
;
border-radius
:
4px
;
animation
:
pulse
1.5s
ease-in-out
infinite
;
}
.skeleton-right
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.skeleton-line
{
height
:
16px
;
background
:
#f0f0f0
;
border-radius
:
4px
;
animation
:
pulse
1.5s
ease-in-out
infinite
;
}
.skeleton-line.w30
{
width
:
30%
;
}
.skeleton-line.w40
{
width
:
40%
;
}
.skeleton-line.w50
{
width
:
50%
;
}
.skeleton-line.w60
{
width
:
60%
;
}
.skeleton-divider
{
height
:
1px
;
background
:
#ebedf0
;
}
.bill-list-empty
{
background
:
#fff
;
border
:
1px
solid
#ebedf0
;
border-radius
:
8px
;
padding
:
48px
;
text-align
:
center
;
color
:
#bfbfbf
;
font-size
:
13px
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
gap
:
12px
;
}
@keyframes
pulse
{
0
%,
100
%
{
opacity
:
1
;
}
50
%
{
opacity
:
0.5
;
}
}
/* Pagination */
.bill-pagination
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
16px
0
0
;
}
.bill-pagination-total
{
font-size
:
14px
;
font-family
:
'Microsoft YaHei'
,
sans-serif
;
font-weight
:
400
;
color
:
rgba
(
95
,
101
,
108
,
1
);
white-space
:
nowrap
;
}
/* Override el-pagination to match design */
.bill-pagination
:deep
(
.el-pagination
)
{
--el-pagination-bg-color
:
#fff
;
--el-pagination-hover-color
:
rgba
(
5
,
95
,
194
,
1
);
padding
:
0
;
}
.bill-pagination
:deep
(
.el-pagination.is-background
.btn-prev
),
.bill-pagination
:deep
(
.el-pagination.is-background
.btn-next
),
.bill-pagination
:deep
(
.el-pagination.is-background
.el-pager
li
)
{
min-width
:
32px
;
height
:
32px
;
line-height
:
32px
;
border-radius
:
4px
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
background
:
#fff
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-size
:
14px
;
font-family
:
'Microsoft YaHei'
,
sans-serif
;
margin
:
0
3px
;
}
.bill-pagination
:deep
(
.el-pagination.is-background
.el-pager
li
:not
(
.is-disabled
)
.is-active
)
{
background
:
#fff
;
color
:
rgba
(
5
,
95
,
194
,
1
);
border-color
:
rgba
(
5
,
95
,
194
,
1
);
}
.bill-pagination
:deep
(
.el-pagination.is-background
.el-pager
li
:not
(
.is-disabled
)
:hover
)
{
color
:
rgba
(
5
,
95
,
194
,
1
);
}
.bill-pagination
:deep
(
.el-pagination.is-background
.btn-prev
:hover
),
.bill-pagination
:deep
(
.el-pagination.is-background
.btn-next
:hover
)
{
color
:
rgba
(
5
,
95
,
194
,
1
);
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/BillTracker.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<div
class=
"bill-tracker-root"
ref=
"trackerRoot"
>
<div
class=
"bt-topbar"
>
<TopBar
:keyword=
"filters.searchText"
:china-related-only=
"filters.isCN"
:sort-by=
"filters.sortBy"
:sort-options=
"sortOptions"
@
update:keyword=
"updateKeyword"
@
toggle-china-related=
"updateChinaRelated(!filters.isCN)"
@
update:sort=
"updateSort"
/>
</div>
<div
class=
"bt-main"
>
<div
class=
"bt-sidebar"
>
<SidebarFilter
:domains=
"filterOptions.domains"
:time-ranges=
"filterOptions.timeRanges"
:selected-domains=
"filters.selectedDomains"
:selected-time-ranges=
"filters.selectedTimeRanges"
@
toggle-domain=
"toggleDomain"
@
toggle-time=
"toggleTimeRange"
/>
</div>
<div
class=
"bt-list-area"
>
<BillList
:bills=
"bills"
:loading=
"loading"
:total=
"total"
:current-page=
"currentPage"
:page-size=
"pageSize"
:progress-stages=
"stageConfig"
@
page-change=
"onPageChange"
/>
</div>
</div>
</div>
</
template
>
<
script
setup
>
import
{
ref
,
onMounted
}
from
'vue'
import
TopBar
from
'./TopBar.vue'
import
SidebarFilter
from
'./SidebarFilter.vue'
import
BillList
from
'./BillList.vue'
import
{
useBills
}
from
'../composables/useBills.js'
const
trackerRoot
=
ref
(
null
)
const
{
bills
,
stageConfig
,
total
,
loading
,
currentPage
,
pageSize
,
filters
,
filterOptions
,
sortOptions
,
init
,
updateKeyword
,
toggleDomain
,
toggleTimeRange
,
updateSort
,
updateChinaRelated
,
handlePageChange
,
}
=
useBills
()
const
onPageChange
=
async
(
page
)
=>
{
await
handlePageChange
(
page
)
// 数据加载完成后滚动到顶部
if
(
trackerRoot
.
value
)
{
trackerRoot
.
value
.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'start'
})
}
}
onMounted
(
async
()
=>
{
try
{
await
init
()
}
catch
(
err
)
{
console
.
error
(
'初始化失败:'
,
err
)
}
})
</
script
>
<
style
scoped
>
.bill-tracker-root
{
width
:
100%
;
font-family
:
'Source Han Sans CN'
,
'Microsoft YaHei'
,
'PingFang SC'
,
'Helvetica Neue'
,
Arial
,
sans-serif
;
-webkit-font-smoothing
:
antialiased
;
color
:
rgba
(
59
,
65
,
75
,
1
);
}
.bt-topbar
{
padding
:
12px
0
;
}
.bt-main
{
display
:
flex
;
gap
:
16px
;
}
.bt-sidebar
{
flex-shrink
:
0
;
}
.bt-list-area
{
flex
:
1
;
min-width
:
0
;
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/DocumentPreview.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<div
class=
"doc-preview"
>
<div
class=
"doc-image"
>
<div
class=
"doc-frame"
>
<div
class=
"doc-seal"
>
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
>
<circle
cx=
"8"
cy=
"8"
r=
"7"
stroke=
"#999"
stroke-width=
"0.8"
fill=
"none"
/>
<circle
cx=
"8"
cy=
"8"
r=
"4"
stroke=
"#999"
stroke-width=
"0.5"
fill=
"none"
/>
<line
x1=
"8"
y1=
"1"
x2=
"8"
y2=
"3"
stroke=
"#999"
stroke-width=
"0.5"
/>
<line
x1=
"8"
y1=
"13"
x2=
"8"
y2=
"15"
stroke=
"#999"
stroke-width=
"0.5"
/>
<line
x1=
"1"
y1=
"8"
x2=
"3"
y2=
"8"
stroke=
"#999"
stroke-width=
"0.5"
/>
<line
x1=
"13"
y1=
"8"
x2=
"15"
y2=
"8"
stroke=
"#999"
stroke-width=
"0.5"
/>
</svg>
</div>
<div
class=
"doc-text"
>
<div
class=
"doc-line-italic"
>
One Hundred Nineteenth Congress
</div>
<div
class=
"doc-line-small"
>
of the
</div>
<div
class=
"doc-line-italic"
>
United States of America
</div>
<div
class=
"doc-body-lines"
>
<div
class=
"doc-body-line"
style=
"width: 100%"
/>
<div
class=
"doc-body-line"
style=
"width: 83%"
/>
<div
class=
"doc-body-line"
style=
"width: 92%"
/>
<div
class=
"doc-body-line"
style=
"width: 75%"
/>
<div
class=
"doc-body-line"
style=
"width: 100%"
/>
<div
class=
"doc-body-line"
style=
"width: 67%"
/>
</div>
</div>
</div>
</div>
<div
class=
"doc-info"
>
<div
class=
"doc-number"
>
{{
billNumber
}}
</div>
<div
class=
"doc-serial"
>
{{
billSerial
}}
</div>
</div>
</div>
</
template
>
<
script
setup
>
defineProps
({
billNumber
:
{
type
:
String
,
required
:
true
},
billSerial
:
{
type
:
String
,
required
:
true
},
})
</
script
>
<
style
scoped
>
.doc-preview
{
width
:
240px
;
height
:
296px
;
position
:
relative
;
}
.doc-image
{
width
:
222px
;
height
:
296px
;
position
:
absolute
;
left
:
13px
;
top
:
0
;
}
.doc-frame
{
width
:
100%
;
height
:
100%
;
background
:
#fff
;
border
:
1px
solid
#ebedf0
;
border-radius
:
4px
;
box-shadow
:
0
1px
3px
rgba
(
0
,
0
,
0
,
0.06
);
position
:
relative
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
padding
:
16px
12px
12px
;
overflow
:
hidden
;
box-sizing
:
border-box
;
}
.doc-seal
{
position
:
absolute
;
top
:
8px
;
left
:
8px
;
}
.doc-text
{
margin-top
:
20px
;
width
:
100%
;
display
:
flex
;
flex-direction
:
column
;
gap
:
3px
;
padding
:
0
4px
;
}
.doc-line-italic
{
font-size
:
7px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
text-align
:
center
;
font-style
:
italic
;
line-height
:
1.4
;
}
.doc-line-small
{
font-size
:
6px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
text-align
:
center
;
line-height
:
1.4
;
}
.doc-body-lines
{
margin-top
:
10px
;
border-top
:
1px
solid
#ebedf0
;
padding-top
:
8px
;
display
:
flex
;
flex-direction
:
column
;
gap
:
5px
;
}
.doc-body-line
{
height
:
2.5px
;
background
:
#e0e0e0
;
border-radius
:
1px
;
margin
:
0
auto
;
}
.doc-info
{
position
:
absolute
;
left
:
0
;
bottom
:
0
;
width
:
240px
;
height
:
78px
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
flex-end
;
padding-bottom
:
4px
;
background
:
linear-gradient
(
to
bottom
,
rgba
(
255
,
255
,
255
,
0
)
0%
,
rgba
(
255
,
255
,
255
,
0.95
)
40%
);
border-radius
:
0
0
4px
4px
;
}
.doc-number
{
font-size
:
20px
;
font-weight
:
700
;
color
:
rgba
(
59
,
65
,
75
,
1
);
line-height
:
36px
;
text-align
:
center
;
}
.doc-serial
{
font-size
:
16px
;
font-weight
:
400
;
color
:
rgba
(
95
,
101
,
108
,
1
);
line-height
:
24px
;
text-align
:
center
;
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/ImportanceBadge.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<span
class=
"importance-badge"
:class=
"'importance-' + level"
>
<span
class=
"importance-dot"
></span>
{{
label
}}
</span>
</
template
>
<
script
setup
>
import
{
computed
}
from
'vue'
const
props
=
defineProps
({
label
:
{
type
:
String
,
required
:
true
},
})
const
level
=
computed
(()
=>
{
if
(
props
.
label
.
includes
(
'特别重大'
)
||
props
.
label
.
includes
(
'高'
))
return
'high'
if
(
props
.
label
.
includes
(
'重大'
)
||
props
.
label
.
includes
(
'中'
)
||
props
.
label
.
includes
(
'橙'
))
return
'medium'
if
(
props
.
label
.
includes
(
'关注'
)
||
props
.
label
.
includes
(
'黄'
))
return
'warning'
return
'low'
})
</
script
>
<
style
scoped
>
.importance-badge
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
6px
;
padding
:
2px
8px
;
border-radius
:
20px
;
font-size
:
16px
;
font-weight
:
400
;
white-space
:
nowrap
;
flex-shrink
:
0
;
line-height
:
24px
;
}
.importance-dot
{
display
:
inline-block
;
width
:
4px
;
height
:
4px
;
border-radius
:
50%
;
flex-shrink
:
0
;
}
.importance-high
{
background
:
rgba
(
206
,
79
,
81
,
0.1
);
color
:
rgba
(
206
,
79
,
81
,
1
);
}
.importance-high
.importance-dot
{
background
:
rgba
(
206
,
79
,
81
,
1
);
}
.importance-medium
{
background
:
rgba
(
255
,
149
,
77
,
0.1
);
color
:
rgba
(
255
,
149
,
77
,
1
);
}
.importance-medium
.importance-dot
{
background
:
rgba
(
255
,
149
,
77
,
1
);
}
.importance-warning
{
background
:
rgba
(
232
,
189
,
11
,
0.1
);
color
:
rgba
(
232
,
189
,
11
,
1
);
}
.importance-warning
.importance-dot
{
background
:
rgba
(
232
,
189
,
11
,
1
);
}
.importance-low
{
background
:
rgba
(
33
,
129
,
57
,
0.1
);
color
:
rgba
(
33
,
129
,
57
,
1
);
}
.importance-low
.importance-dot
{
background
:
rgba
(
33
,
129
,
57
,
1
);
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/PotentialNews.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<div
class=
"potential-news-root"
>
<div
class=
"pn-analysis"
>
<PotentialNewsAnalysis
:keywords=
"keywords"
/>
</div>
<div
class=
"pn-list"
>
<PotentialNewsList
:news-list=
"newsList"
:total=
"total"
:current-page=
"currentPage"
:page-size=
"10"
@
page-change=
"handlePageChange"
/>
</div>
</div>
</
template
>
<
script
setup
>
import
{
ref
,
onMounted
,
computed
}
from
'vue'
import
{
useRoute
}
from
'vue-router'
import
PotentialNewsList
from
'./PotentialNewsList.vue'
import
PotentialNewsAnalysis
from
'./PotentialNewsAnalysis.vue'
import
{
getPotentialNewsList
,
getPotentialNewsKeywords
}
from
'@/api/bill/billHome.js'
const
route
=
useRoute
()
const
personId
=
computed
(()
=>
route
.
params
.
personId
||
route
.
query
.
personId
||
''
)
const
newsList
=
ref
([])
const
total
=
ref
(
0
)
const
currentPage
=
ref
(
1
)
const
keywords
=
ref
([])
const
COLORS
=
[
'red'
,
'cyan'
,
'gold'
,
'blue'
,
'volcano'
]
async
function
loadNews
(
page
=
1
)
{
if
(
!
personId
.
value
)
return
currentPage
.
value
=
page
const
res
=
await
getPotentialNewsList
(
personId
.
value
,
{
currentPage
:
page
-
1
,
pageSize
:
10
,
})
if
(
res
.
code
===
200
&&
res
.
data
)
{
newsList
.
value
=
res
.
data
.
content
||
[]
total
.
value
=
res
.
data
.
totalElements
||
0
}
else
{
newsList
.
value
=
[]
total
.
value
=
0
}
}
async
function
loadKeywords
()
{
if
(
!
personId
.
value
)
return
const
res
=
await
getPotentialNewsKeywords
(
personId
.
value
)
if
(
res
.
code
===
200
&&
res
.
data
)
{
keywords
.
value
=
res
.
data
.
map
((
item
,
index
)
=>
({
label
:
item
.
clause
,
value
:
item
.
count
,
color
:
COLORS
[
index
%
COLORS
.
length
],
}))
}
}
async
function
handlePageChange
(
page
)
{
await
loadNews
(
page
)
}
onMounted
(
async
()
=>
{
if
(
!
personId
.
value
)
return
await
Promise
.
all
([
loadNews
(),
loadKeywords
()])
})
</
script
>
<
style
scoped
>
.potential-news-root
{
display
:
flex
;
gap
:
16px
;
width
:
100%
;
font-family
:
'Source Han Sans CN'
,
'Microsoft YaHei'
,
'PingFang SC'
,
'Helvetica Neue'
,
Arial
,
sans-serif
;
-webkit-font-smoothing
:
antialiased
;
color
:
rgba
(
59
,
65
,
75
,
1
);
}
.pn-analysis
{
width
:
430px
;
flex-shrink
:
0
;
}
.pn-list
{
flex
:
1
;
min-width
:
0
;
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/PotentialNewsAnalysis.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<div
class=
"pna-container"
>
<!-- Header -->
<div
class=
"pna-header"
>
<div
class=
"pna-header-left"
>
<svg
class=
"pna-header-icon"
width=
"8"
height=
"20"
viewBox=
"0 0 8 20"
fill=
"none"
>
<rect
width=
"8"
height=
"20"
rx=
"2"
fill=
"rgba(5,95,194,1)"
/>
</svg>
<span
class=
"pna-header-title"
>
潜在提案主题分析
</span>
</div>
<div
class=
"pna-header-actions"
>
<!-- 下载图标 (9_1305): 竖线 + 向下箭头 + 底座 -->
<svg
class=
"pna-action-icon"
width=
"28"
height=
"28"
viewBox=
"0 0 28 28"
fill=
"none"
>
<rect
x=
"13"
y=
"7"
width=
"2"
height=
"6"
rx=
"0.5"
fill=
"rgba(132,136,142,1)"
/>
<path
d=
"M10 13l4 4 4-4"
stroke=
"rgba(132,136,142,1)"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
fill=
"none"
/>
<path
d=
"M7 19h14"
stroke=
"rgba(132,136,142,1)"
stroke-width=
"1.5"
stroke-linecap=
"round"
/>
</svg>
<!-- 收藏/星标图标 (9_1303): 五角星 -->
<svg
class=
"pna-action-icon"
width=
"28"
height=
"28"
viewBox=
"0 0 28 28"
fill=
"none"
>
<path
d=
"M14 6l2.47 5.01L22 11.76l-4 3.9.94 5.5L14 18.51l-4.94 2.65.94-5.5-4-3.9 5.53-.75L14 6z"
stroke=
"rgba(132,136,142,1)"
stroke-width=
"1.5"
stroke-linejoin=
"round"
fill=
"none"
/>
</svg>
</div>
</div>
<!-- ECharts word cloud -->
<div
ref=
"chartRef"
class=
"pna-cloud"
></div>
</div>
</
template
>
<
script
setup
>
import
{
ref
,
onMounted
,
onBeforeUnmount
,
watch
,
nextTick
}
from
'vue'
import
*
as
echarts
from
'echarts'
import
'echarts-wordcloud'
const
props
=
defineProps
({
keywords
:
{
type
:
Array
,
default
:
()
=>
[]
},
})
const
chartRef
=
ref
(
null
)
let
chartInstance
=
null
// 设计稿色板
const
COLOR_MAP
=
{
red
:
'rgba(206, 79, 81, 1)'
,
cyan
:
'rgba(19, 168, 168, 1)'
,
gold
:
'rgba(250, 173, 20, 1)'
,
blue
:
'rgba(22, 119, 255, 1)'
,
volcano
:
'rgba(255, 122, 69, 1)'
,
}
function
buildOption
(
data
)
{
const
seriesData
=
data
.
map
((
item
)
=>
({
name
:
item
.
label
,
value
:
item
.
value
||
item
.
size
||
18
,
textStyle
:
{
color
:
COLOR_MAP
[
item
.
color
]
||
COLOR_MAP
.
blue
,
},
}))
return
{
series
:
[
{
type
:
'wordCloud'
,
shape
:
'circle'
,
left
:
'center'
,
top
:
'center'
,
width
:
'90%'
,
height
:
'90%'
,
sizeRange
:
[
12
,
28
],
rotationRange
:
[
-
45
,
45
],
rotationStep
:
15
,
gridSize
:
8
,
drawOutOfBound
:
false
,
layoutAnimation
:
true
,
textStyle
:
{
fontFamily
:
'Microsoft YaHei, Source Han Sans CN, Noto Sans SC, sans-serif'
,
fontWeight
:
400
,
},
emphasis
:
{
textStyle
:
{
fontWeight
:
700
,
},
},
data
:
seriesData
,
},
],
}
}
function
initChart
()
{
if
(
!
chartRef
.
value
)
return
chartInstance
=
echarts
.
init
(
chartRef
.
value
)
if
(
props
.
keywords
.
length
>
0
)
{
chartInstance
.
setOption
(
buildOption
(
props
.
keywords
))
}
}
function
handleResize
()
{
chartInstance
?.
resize
()
}
watch
(
()
=>
props
.
keywords
,
(
val
)
=>
{
if
(
chartInstance
&&
val
.
length
>
0
)
{
chartInstance
.
setOption
(
buildOption
(
val
))
}
},
{
deep
:
true
}
)
onMounted
(()
=>
{
nextTick
(()
=>
{
initChart
()
window
.
addEventListener
(
'resize'
,
handleResize
)
})
})
onBeforeUnmount
(()
=>
{
window
.
removeEventListener
(
'resize'
,
handleResize
)
chartInstance
?.
dispose
()
chartInstance
=
null
})
</
script
>
<
style
scoped
>
.pna-container
{
width
:
100%
;
height
:
100%
;
display
:
flex
;
flex-direction
:
column
;
background
:
#fff
;
border-radius
:
10px
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0.1
);
overflow
:
hidden
;
}
.pna-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
14px
24px
;
flex-shrink
:
0
;
}
.pna-header-left
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
}
.pna-header-title
{
font-size
:
20px
;
font-weight
:
700
;
line-height
:
26px
;
color
:
rgba
(
5
,
95
,
194
,
1
);
font-family
:
'Source Han Sans CN'
,
'Noto Sans SC'
,
sans-serif
;
}
.pna-header-actions
{
display
:
flex
;
align-items
:
center
;
gap
:
4px
;
}
.pna-action-icon
{
cursor
:
pointer
;
opacity
:
0.7
;
transition
:
opacity
0.2s
;
}
.pna-action-icon
:hover
{
opacity
:
1
;
}
.pna-cloud
{
flex
:
1
;
min-height
:
400px
;
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/PotentialNewsList.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<div
class=
"pn-list-container"
>
<div
v-if=
"loading"
class=
"pn-loading-mask"
>
<div
class=
"pn-spinner"
></div>
</div>
<div
class=
"pn-header"
>
<div
class=
"pn-header-left"
>
<svg
class=
"pn-header-icon"
width=
"8"
height=
"20"
viewBox=
"0 0 8 20"
fill=
"none"
>
<rect
width=
"8"
height=
"20"
rx=
"2"
fill=
"rgba(5,95,194,1)"
/>
</svg>
<span
class=
"pn-header-title"
>
潜在提案举措分析
</span>
</div>
<div
class=
"pn-header-actions"
>
<svg
class=
"pn-action-icon"
width=
"28"
height=
"28"
viewBox=
"0 0 28 28"
fill=
"none"
>
<rect
x=
"13"
y=
"7"
width=
"2"
height=
"6"
rx=
"0.5"
fill=
"rgba(132,136,142,1)"
/>
<path
d=
"M10 13l4 4 4-4"
stroke=
"rgba(132,136,142,1)"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
fill=
"none"
/>
<path
d=
"M7 19h14"
stroke=
"rgba(132,136,142,1)"
stroke-width=
"1.5"
stroke-linecap=
"round"
/>
</svg>
<svg
class=
"pn-action-icon"
width=
"28"
height=
"28"
viewBox=
"0 0 28 28"
fill=
"none"
>
<path
d=
"M14 6l2.47 5.01L22 11.76l-4 3.9.94 5.5L14 18.51l-4.94 2.65.94-5.5-4-3.9 5.53-.75L14 6z"
stroke=
"rgba(132,136,142,1)"
stroke-width=
"1.5"
stroke-linejoin=
"round"
fill=
"none"
/>
</svg>
</div>
</div>
<div
class=
"pn-search-wrapper"
>
<div
class=
"pn-search"
>
<span
class=
"pn-search-placeholder"
>
{{
keyword
||
'搜索新闻'
}}
</span>
<svg
class=
"pn-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"
fill=
"none"
/>
<path
d=
"M11 11L14 14"
stroke=
"rgba(132,136,142,1)"
stroke-width=
"1.2"
stroke-linecap=
"round"
/>
</svg>
</div>
</div>
<div
class=
"pn-rows"
:class=
"
{ 'pn-rows-loading': loading }">
<div
v-if=
"loading"
class=
"pn-rows-spinner"
>
<div
class=
"pn-spinner-icon"
></div>
<span
class=
"pn-spinner-text"
>
加载中...
</span>
</div>
<div
v-for=
"(item, index) in newsList"
v-show=
"!loading"
:key=
"item.tkid"
:class=
"['pn-row',
{ 'pn-row-alt': index % 2 === 0 }]"
>
<div
class=
"pn-row-num"
>
<span
class=
"pn-num-circle"
></span>
<span
class=
"pn-num-text"
>
{{
index
+
1
+
(
currentPage
-
1
)
*
pageSize
}}
</span>
</div>
<p
class=
"pn-row-title"
>
{{
item
.
fynr
}}
</p>
</div>
</div>
<div
class=
"pn-footer"
>
<span
class=
"pn-footer-total"
>
共
{{
total
}}
条关键新闻
</span>
<div
class=
"pn-pagination"
>
<button
class=
"pn-page-btn pn-page-prev"
:disabled=
"currentPage
<
=
1
"
@
click=
"$emit('page-change', currentPage - 1)"
>
<svg
width=
"10"
height=
"10"
viewBox=
"0 0 10 10"
fill=
"none"
>
<path
d=
"M6.5 1.5L3.5 5L6.5 8.5"
stroke=
"currentColor"
stroke-width=
"1.2"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</svg>
</button>
<button
v-for=
"page in displayedPages"
:key=
"page"
:class=
"['pn-page-num',
{ active: page === currentPage }]"
@click="$emit('page-change', page)"
>
{{
page
}}
</button>
<button
class=
"pn-page-btn pn-page-next"
:disabled=
"currentPage >= totalPages"
@
click=
"$emit('page-change', currentPage + 1)"
>
<svg
width=
"10"
height=
"10"
viewBox=
"0 0 10 10"
fill=
"none"
>
<path
d=
"M3.5 1.5L6.5 5L3.5 8.5"
stroke=
"currentColor"
stroke-width=
"1.2"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</svg>
</button>
</div>
</div>
</div>
</
template
>
<
script
setup
>
import
{
computed
}
from
'vue'
import
TagBadge
from
'./TagBadge.vue'
const
tagColorMap
=
{
blue
:
'tag6'
,
green
:
''
,
}
const
props
=
defineProps
({
newsList
:
{
type
:
Array
,
default
:
()
=>
[]
},
total
:
{
type
:
Number
,
default
:
0
},
currentPage
:
{
type
:
Number
,
default
:
1
},
pageSize
:
{
type
:
Number
,
default
:
10
},
keyword
:
{
type
:
String
,
default
:
''
},
loading
:
{
type
:
Boolean
,
default
:
false
},
})
defineEmits
([
'page-change'
])
const
totalPages
=
computed
(()
=>
Math
.
max
(
1
,
Math
.
ceil
(
props
.
total
/
props
.
pageSize
)))
const
displayedPages
=
computed
(()
=>
{
const
pages
=
[]
const
tp
=
totalPages
.
value
const
cp
=
props
.
currentPage
const
start
=
Math
.
max
(
1
,
cp
-
2
)
const
end
=
Math
.
min
(
tp
,
start
+
4
)
for
(
let
i
=
start
;
i
<=
end
;
i
++
)
{
pages
.
push
(
i
)
}
return
pages
})
</
script
>
<
style
scoped
>
.pn-list-container
{
width
:
100%
;
height
:
100%
;
display
:
flex
;
flex-direction
:
column
;
background
:
#fff
;
border-radius
:
10px
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0.1
);
overflow
:
hidden
;
}
.pn-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
14px
24px
;
flex-shrink
:
0
;
}
.pn-header-left
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
}
.pn-header-title
{
font-size
:
20px
;
font-weight
:
700
;
line-height
:
26px
;
color
:
rgba
(
5
,
95
,
194
,
1
);
font-family
:
'Source Han Sans CN'
,
'Noto Sans SC'
,
sans-serif
;
}
.pn-header-actions
{
display
:
flex
;
align-items
:
center
;
gap
:
4px
;
}
.pn-action-icon
{
cursor
:
pointer
;
opacity
:
0.7
;
transition
:
opacity
0.2s
;
}
.pn-action-icon
:hover
{
opacity
:
1
;
}
.pn-search-wrapper
{
padding
:
0
24px
0
24px
;
flex-shrink
:
0
;
}
.pn-search
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
height
:
32px
;
padding
:
0
12px
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
4px
;
background
:
#fff
;
}
.pn-search-placeholder
{
font-size
:
16px
;
line-height
:
24px
;
color
:
rgba
(
132
,
136
,
142
,
1
);
font-family
:
'Source Han Sans CN'
,
'Noto Sans SC'
,
sans-serif
;
}
.pn-search-icon
{
flex-shrink
:
0
;
}
.pn-rows
{
flex
:
1
;
overflow-y
:
auto
;
margin-top
:
12px
;
position
:
relative
;
}
.pn-rows-loading
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.pn-rows-spinner
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
gap
:
12px
;
}
.pn-spinner-icon
{
width
:
40px
;
height
:
40px
;
border
:
4px
solid
rgba
(
22
,
119
,
255
,
0.2
);
border-top-color
:
rgba
(
22
,
119
,
255
,
1
);
border-radius
:
50%
;
animation
:
spin
0.8s
linear
infinite
;
}
.pn-spinner-text
{
font-size
:
14px
;
color
:
rgba
(
132
,
136
,
142
,
1
);
}
.pn-row
{
position
:
relative
;
height
:
60px
;
display
:
flex
;
align-items
:
center
;
padding
:
0
24px
;
border-top
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
background
:
#fff
;
}
.pn-row-alt
{
background
:
rgba
(
247
,
248
,
249
,
1
);
}
.pn-row-num
{
position
:
relative
;
width
:
24px
;
height
:
24px
;
flex-shrink
:
0
;
margin-right
:
12px
;
}
.pn-num-circle
{
position
:
absolute
;
inset
:
0
;
border-radius
:
50%
;
background
:
radial-gradient
(
circle
at
40%
35%
,
rgba
(
5
,
95
,
194
,
0.06
)
0%
,
rgba
(
5
,
95
,
194
,
0.12
)
100%
);
border
:
1px
solid
rgba
(
5
,
95
,
194
,
0.15
);
}
.pn-num-text
{
position
:
absolute
;
inset
:
0
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
12px
;
font-weight
:
400
;
line-height
:
12px
;
color
:
rgba
(
5
,
95
,
194
,
1
);
font-family
:
'Microsoft YaHei'
,
sans-serif
;
}
.pn-row-title
{
flex
:
1
;
min-width
:
0
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
30px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
'Source Han Sans CN'
,
'Noto Sans SC'
,
sans-serif
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
.pn-row-tags
{
display
:
flex
;
gap
:
8px
;
flex-shrink
:
0
;
margin-left
:
24px
;
}
.pn-footer
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
12px
24px
;
border-top
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
flex-shrink
:
0
;
}
.pn-footer-total
{
font-size
:
16px
;
font-weight
:
400
;
line-height
:
24px
;
color
:
rgba
(
132
,
136
,
142
,
1
);
font-family
:
'Source Han Sans CN'
,
'Noto Sans SC'
,
sans-serif
;
}
.pn-pagination
{
display
:
flex
;
align-items
:
center
;
gap
:
4px
;
}
.pn-page-btn
{
width
:
28px
;
height
:
28px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border
:
1px
solid
rgba
(
0
,
0
,
0
,
0.15
);
border-radius
:
4px
;
background
:
#fff
;
cursor
:
pointer
;
color
:
rgba
(
0
,
0
,
0
,
0.65
);
transition
:
border-color
0.2s
,
color
0.2s
;
}
.pn-page-btn
:disabled
{
opacity
:
0.4
;
cursor
:
not-allowed
;
}
.pn-page-num
{
min-width
:
28px
;
height
:
28px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border
:
1px
solid
rgba
(
0
,
0
,
0
,
0.15
);
border-radius
:
4px
;
background
:
#fff
;
cursor
:
pointer
;
font-size
:
14px
;
font-weight
:
400
;
line-height
:
22px
;
color
:
rgba
(
0
,
0
,
0
,
0.88
);
font-family
:
'Microsoft YaHei'
,
sans-serif
;
padding
:
0
4px
;
transition
:
border-color
0.2s
,
color
0.2s
;
}
.pn-page-num.active
{
border-color
:
rgba
(
22
,
119
,
255
,
1
);
color
:
rgba
(
22
,
119
,
255
,
1
);
}
.pn-page-num
:hover:not
(
.active
)
{
border-color
:
rgba
(
22
,
119
,
255
,
0.5
);
color
:
rgba
(
22
,
119
,
255
,
0.8
);
}
.pn-loading-mask
{
position
:
absolute
;
inset
:
0
;
background
:
rgba
(
255
,
255
,
255
,
0.6
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
z-index
:
100
;
backdrop-filter
:
blur
(
2px
);
}
.pn-spinner
{
width
:
40px
;
height
:
40px
;
border
:
4px
solid
rgba
(
22
,
119
,
255
,
0.2
);
border-top-color
:
rgba
(
22
,
119
,
255
,
1
);
border-radius
:
50%
;
animation
:
spin
0.8s
linear
infinite
;
}
@keyframes
spin
{
to
{
transform
:
rotate
(
360deg
);
}
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/ProgressBar.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<div
class=
"progress-bar"
>
<span
class=
"progress-title"
>
法案进展:
</span>
<div
class=
"progress-steps"
>
<div
v-for=
"(stage, idx) in [...visibleStages].reverse()"
:key=
"idx"
class=
"tag"
:style=
"
{ zIndex: visibleStages.length - idx }"
>
{{
stage
}}
</div>
</div>
</div>
</
template
>
<
script
setup
>
import
{
computed
}
from
'vue'
const
props
=
defineProps
({
stages
:
{
type
:
Array
,
required
:
true
},
current
:
{
type
:
Number
,
default
:
0
},
})
/**
* 只渲染到当前步骤为止的阶段
* @returns {Array} 可见的阶段列表
*/
const
visibleStages
=
computed
(()
=>
{
return
props
.
stages
.
slice
(
0
,
props
.
current
+
1
)
})
</
script
>
<
style
scoped
>
.progress-bar
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
width
:
100%
;
height
:
26px
;
}
.progress-title
{
font-size
:
16px
;
font-weight
:
700
;
color
:
rgba
(
59
,
65
,
75
,
1
);
letter-spacing
:
1px
;
line-height
:
24px
;
white-space
:
nowrap
;
flex-shrink
:
0
;
}
.progress-steps
{
display
:
flex
;
align-items
:
center
;
}
.tag
{
height
:
24px
;
line-height
:
22px
;
padding
:
0
10px
0
30px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
color
:
rgb
(
95
,
101
,
108
);
border-top
:
1px
solid
rgb
(
234
,
236
,
238
);
border-bottom
:
1px
solid
rgb
(
234
,
236
,
238
);
position
:
relative
;
margin-left
:
-10px
;
font-family
:
'Microsoft YaHei'
,
sans-serif
;
font-size
:
14px
;
font-weight
:
400
;
white-space
:
nowrap
;
box-sizing
:
border-box
;
}
.tag
::after
{
content
:
''
;
position
:
absolute
;
top
:
50%
;
right
:
-8.485px
;
width
:
16.97px
;
height
:
16.97px
;
background
:
inherit
;
border-top
:
1px
solid
rgb
(
234
,
236
,
238
);
border-right
:
1px
solid
rgb
(
234
,
236
,
238
);
transform
:
translateY
(
-50%
)
rotate
(
45deg
);
z-index
:
1
;
box-shadow
:
2px
-2px
2px
rgba
(
0
,
0
,
0
,
0.05
);
box-sizing
:
border-box
;
}
.tag
:first-child
{
margin-left
:
0
;
padding-left
:
10px
;
border-left
:
1px
solid
rgb
(
234
,
236
,
238
);
border-radius
:
4px
0
0
4px
;
}
.tag
:last-child
{
background
:
rgb
(
59
,
65
,
75
);
color
:
rgba
(
255
,
255
,
255
,
1
);
border-color
:
rgb
(
59
,
65
,
75
);
padding-right
:
10px
;
border-radius
:
0
;
border-right
:
none
;
}
.tag
:last-child::after
{
display
:
block
;
border-color
:
rgb
(
59
,
65
,
75
);
box-shadow
:
none
;
}
.tag
:first-child:last-child
{
margin-left
:
0
;
padding
:
0
10px
;
border-radius
:
4px
0
0
4px
;
background
:
rgb
(
59
,
65
,
75
);
color
:
rgba
(
255
,
255
,
255
,
1
);
border
:
1px
solid
rgb
(
59
,
65
,
75
);
border-right
:
none
;
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/SidebarFilter.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<aside
class=
"sidebar-filter"
>
<div
class=
"sf-section"
>
<div
class=
"sf-title-row"
>
<span
class=
"sf-title-bar"
></span>
<span
class=
"sf-title-text"
>
科技领域
</span>
</div>
<div
class=
"sf-options"
>
<label
v-for=
"opt in domains"
:key=
"opt.id"
class=
"sf-label"
>
<span
class=
"sf-checkbox-icon"
:class=
"
{ 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=
"#055FC2"
/>
<path
d=
"M3.5 7L6 9.5L10.5 5"
stroke=
"#fff"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</svg>
<span
v-else
class=
"sf-checkbox-empty"
></span>
</span>
<span
class=
"sf-text"
@
click=
"$emit('toggle-domain', opt.id)"
>
{{
opt
.
label
}}
</span>
</label>
</div>
</div>
<div
class=
"sf-section"
>
<div
class=
"sf-title-row"
>
<span
class=
"sf-title-bar"
></span>
<span
class=
"sf-title-text"
>
发布时间
</span>
</div>
<div
class=
"sf-options"
>
<label
v-for=
"opt in timeRanges"
:key=
"opt.id"
class=
"sf-label"
>
<span
class=
"sf-checkbox-icon"
:class=
"
{ checked: selectedTimeRanges.includes(opt.id) }"
@click.prevent="$emit('toggle-time', opt.id)"
>
<svg
v-if=
"selectedTimeRanges.includes(opt.id)"
width=
"14"
height=
"14"
viewBox=
"0 0 14 14"
fill=
"none"
>
<rect
width=
"14"
height=
"14"
rx=
"4"
fill=
"#055FC2"
/>
<path
d=
"M3.5 7L6 9.5L10.5 5"
stroke=
"#fff"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</svg>
<span
v-else
class=
"sf-checkbox-empty"
></span>
</span>
<span
class=
"sf-text"
@
click=
"$emit('toggle-time', opt.id)"
>
{{
opt
.
label
}}
</span>
</label>
</div>
</div>
</aside>
</
template
>
<
script
setup
>
defineProps
({
domains
:
{
type
:
Array
,
default
:
()
=>
[]
},
timeRanges
:
{
type
:
Array
,
default
:
()
=>
[]
},
selectedDomains
:
{
type
:
Array
,
default
:
()
=>
[
'all'
]
},
selectedTimeRanges
:
{
type
:
Array
,
default
:
()
=>
[
'all'
]
},
})
defineEmits
([
'toggle-domain'
,
'toggle-time'
])
</
script
>
<
style
scoped
>
.sidebar-filter
{
width
:
360px
;
background
:
#fff
;
border-radius
:
10px
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0.1
);
box-sizing
:
border-box
;
padding
:
16px
0
24px
0
;
display
:
flex
;
flex-direction
:
column
;
gap
:
16px
;
align-items
:
flex-start
;
overflow
:
hidden
;
}
.sf-section
{
width
:
100%
;
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
align-items
:
flex-start
;
}
.sf-title-row
{
display
:
flex
;
align-items
:
center
;
gap
:
17px
;
width
:
100%
;
padding
:
0
0
0
0
;
box-sizing
:
border-box
;
}
.sf-title-bar
{
display
:
inline-block
;
width
:
8px
;
height
:
16px
;
background
:
rgba
(
5
,
95
,
194
,
1
);
border-radius
:
0
2px
2px
0
;
flex-shrink
:
0
;
}
.sf-title-text
{
font-size
:
16px
;
font-weight
:
700
;
font-family
:
'Source Han Sans CN'
,
'Noto Sans SC'
,
sans-serif
;
color
:
rgba
(
5
,
95
,
194
,
1
);
letter-spacing
:
1px
;
line-height
:
24px
;
}
.sf-options
{
display
:
flex
;
flex-direction
:
row
;
flex-wrap
:
wrap
;
gap
:
8px
4px
;
align-content
:
flex-start
;
align-items
:
flex-start
;
padding
:
0
0
0
24px
;
width
:
100%
;
box-sizing
:
border-box
;
}
.sf-label
{
width
:
160px
;
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
cursor
:
pointer
;
flex-shrink
:
0
;
}
.sf-checkbox-icon
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
width
:
14px
;
height
:
14px
;
flex-shrink
:
0
;
cursor
:
pointer
;
}
.sf-checkbox-empty
{
display
:
block
;
width
:
14px
;
height
:
14px
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
4px
;
box-sizing
:
border-box
;
background
:
#fff
;
}
.sf-text
{
font-size
:
16px
;
font-weight
:
400
;
font-family
:
'Source Han Sans CN'
,
'Noto Sans SC'
,
sans-serif
;
color
:
rgba
(
95
,
101
,
108
,
1
);
line-height
:
24px
;
cursor
:
pointer
;
white-space
:
nowrap
;
}
.sf-label
:hover
.sf-text
{
color
:
rgba
(
59
,
65
,
75
,
1
);
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/TagBadge.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<AreaTag
:tag-name=
"label"
:class=
"tagClass"
/>
</
template
>
<
script
setup
>
import
{
h
,
defineComponent
}
from
'vue'
const
AreaTag
=
defineComponent
({
name
:
'AreaTag'
,
props
:
{
tagName
:
{
type
:
String
,
default
:
'标签名称'
},
},
setup
(
props
)
{
return
()
=>
h
(
'div'
,
{
class
:
'tag-wrapper'
},
props
.
tagName
)
},
})
defineProps
({
label
:
{
type
:
String
,
required
:
true
},
tagClass
:
{
type
:
String
,
default
:
'tag3'
},
})
</
script
>
<
style
scoped
>
.tag-wrapper
{
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
height
:
24px
;
padding
:
0
8px
;
line-height
:
24px
;
text-align
:
center
;
font-family
:
Microsoft
YaHei
,
sans-serif
;
font-size
:
14px
;
font-weight
:
400
;
letter-spacing
:
0px
;
box-sizing
:
border-box
;
border-radius
:
4px
;
border
:
1px
solid
rgba
(
183
,
235
,
143
,
1
);
background
:
rgba
(
246
,
255
,
237
,
1
);
color
:
rgba
(
82
,
196
,
26
,
1
);
}
.tag3
.tag-wrapper
,
:deep
(
.tag3
)
{
border
:
1px
solid
rgba
(
174
,
214
,
255
,
1
);
background
:
rgba
(
246
,
250
,
255
,
1
);
color
:
rgba
(
5
,
95
,
194
,
1
);
}
.tag1
.tag-wrapper
,
:deep
(
.tag1
)
{
border
:
1px
solid
rgba
(
255
,
163
,
158
,
1
);
background
:
rgba
(
255
,
241
,
240
,
1
);
color
:
rgba
(
245
,
34
,
45
,
1
);
}
.tag6
.tag-wrapper
,
:deep
(
.tag6
)
{
border
:
1px
solid
rgba
(
145
,
202
,
255
,
1
);
background
:
rgba
(
230
,
244
,
255
,
1
);
color
:
rgba
(
22
,
119
,
255
,
1
);
}
.tag8
.tag-wrapper
,
:deep
(
.tag8
)
{
border
:
1px
solid
rgba
(
255
,
229
,
143
,
1
);
background
:
rgba
(
255
,
251
,
230
,
1
);
color
:
rgba
(
250
,
173
,
20
,
1
);
}
.tag9
.tag-wrapper
,
:deep
(
.tag9
)
{
border
:
1px
solid
rgba
(
255
,
213
,
145
,
1
);
background
:
rgba
(
255
,
247
,
230
,
1
);
color
:
rgba
(
250
,
140
,
22
,
1
);
}
.tag4
.tag-wrapper
,
:deep
(
.tag4
)
{
border
:
1px
solid
rgba
(
211
,
173
,
247
,
1
);
background
:
rgba
(
249
,
240
,
255
,
1
);
color
:
rgba
(
114
,
46
,
209
,
1
);
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/TopBar.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<header
class=
"topbar"
>
<div
class=
"topbar-search"
>
<input
type=
"text"
:value=
"keyword"
@
input=
"$emit('update:keyword', $event.target.value)"
placeholder=
"搜索法案"
class=
"topbar-search-input"
/>
<svg
class=
"topbar-search-icon"
width=
"16"
height=
"16"
viewBox=
"0 0 24 24"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"2"
stroke-linecap=
"round"
stroke-linejoin=
"round"
>
<circle
cx=
"11"
cy=
"11"
r=
"8"
/>
<line
x1=
"21"
y1=
"21"
x2=
"16.65"
y2=
"16.65"
/>
</svg>
</div>
<div
class=
"topbar-spacer"
></div>
<div
class=
"topbar-sort"
ref=
"sortDropdownRef"
>
<button
class=
"topbar-sort-btn"
@
click=
"showSortDropdown = !showSortDropdown"
>
<svg
class=
"topbar-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=
"topbar-sort-label"
>
{{
currentSortLabel
}}
</span>
<svg
class=
"topbar-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=
"topbar-sort-dropdown"
>
<button
v-for=
"opt in sortOptions"
:key=
"opt.value"
@
click=
"selectSort(opt.value)"
class=
"topbar-sort-option"
:class=
"
{ active: sortBy === opt.value }"
>
{{
opt
.
label
}}
</button>
</div>
</div>
<label
class=
"topbar-checkbox-label"
>
<input
type=
"checkbox"
:checked=
"chinaRelatedOnly"
@
change=
"$emit('toggle-china-related')"
class=
"topbar-checkbox"
/>
<span>
只看涉华法案
</span>
</label>
</header>
</
template
>
<
script
setup
>
import
{
ref
,
computed
,
onMounted
,
onUnmounted
}
from
'vue'
const
props
=
defineProps
({
keyword
:
{
type
:
String
,
default
:
''
},
chinaRelatedOnly
:
{
type
:
Boolean
,
default
:
true
},
sortBy
:
{
type
:
String
,
default
:
'publishTimeDesc'
},
sortOptions
:
{
type
:
Array
,
default
:
()
=>
[]
},
})
const
emit
=
defineEmits
([
'update:keyword'
,
'toggle-china-related'
,
'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
>
.topbar
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
flex-wrap
:
wrap
;
}
.topbar-search
{
position
:
relative
;
flex
:
1
;
min-width
:
180px
;
max-width
:
400px
;
}
.topbar-search-input
{
width
:
100%
;
height
:
32px
;
padding
:
0
32px
0
12px
;
font-size
:
14px
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
4px
;
outline
:
none
;
color
:
rgba
(
59
,
65
,
75
,
1
);
background
:
#fff
;
box-sizing
:
border-box
;
line-height
:
22px
;
}
.topbar-search-input
::placeholder
{
color
:
rgba
(
132
,
136
,
142
,
1
);
}
.topbar-search-input
:focus
{
border-color
:
rgba
(
5
,
95
,
194
,
1
);
box-shadow
:
0
0
0
2px
rgba
(
5
,
95
,
194
,
0.1
);
}
.topbar-search-icon
{
position
:
absolute
;
right
:
10px
;
top
:
50%
;
transform
:
translateY
(
-50%
);
color
:
rgba
(
132
,
136
,
142
,
1
);
pointer-events
:
none
;
}
.topbar-spacer
{
flex
:
1
;
}
.topbar-sort
{
position
:
relative
;
flex-shrink
:
0
;
}
.topbar-sort-btn
{
width
:
170px
;
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
;
}
.topbar-sort-btn
:hover
{
border-color
:
rgba
(
5
,
95
,
194
,
0.4
);
}
.topbar-sort-icon
{
flex-shrink
:
0
;
margin-right
:
6px
;
}
.topbar-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
;
}
.topbar-sort-arrow
{
flex-shrink
:
0
;
transition
:
transform
0.2s
;
}
.topbar-sort-arrow.open
{
transform
:
rotate
(
180deg
);
}
.topbar-sort-dropdown
{
position
:
absolute
;
right
:
0
;
top
:
100%
;
margin-top
:
4px
;
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
;
min-width
:
140px
;
}
.topbar-sort-option
{
display
:
block
;
width
:
100%
;
text-align
:
left
;
padding
:
8px
12px
;
font-size
:
14px
;
background
:
none
;
border
:
none
;
cursor
:
pointer
;
color
:
rgba
(
95
,
101
,
108
,
1
);
}
.topbar-sort-option
:hover
{
background
:
rgba
(
247
,
248
,
249
,
1
);
}
.topbar-sort-option.active
{
color
:
rgba
(
5
,
95
,
194
,
1
);
font-weight
:
500
;
}
.topbar-checkbox-label
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
8px
;
cursor
:
pointer
;
font-size
:
16px
;
font-weight
:
400
;
color
:
rgba
(
132
,
136
,
142
,
1
);
white-space
:
nowrap
;
line-height
:
24px
;
}
.topbar-checkbox
{
width
:
16px
;
height
:
16px
;
accent-color
:
rgba
(
5
,
95
,
194
,
1
);
cursor
:
pointer
;
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/components/UserAvatar.vue
0 → 100644
浏览文件 @
592d75b3
<
template
>
<div
class=
"user-avatar"
>
<div
class=
"user-avatar-circle"
:style=
"
{ backgroundColor: avatarColor }">
{{
initial
}}
</div>
<span
class=
"user-avatar-name"
>
{{
name
}}
</span>
</div>
</
template
>
<
script
setup
>
import
{
computed
}
from
'vue'
const
props
=
defineProps
({
name
:
{
type
:
String
,
required
:
true
},
avatar
:
{
type
:
String
,
default
:
null
},
})
const
COLORS
=
[
'#CE4F51'
,
'#055FC2'
,
'#E8BD0B'
,
'#218139'
,
'#FF954D'
,
'#3B414B'
]
const
avatarColor
=
computed
(()
=>
{
let
hash
=
0
for
(
let
i
=
0
;
i
<
props
.
name
.
length
;
i
++
)
{
hash
=
props
.
name
.
charCodeAt
(
i
)
+
((
hash
<<
5
)
-
hash
)
}
return
COLORS
[
Math
.
abs
(
hash
)
%
COLORS
.
length
]
})
const
initial
=
computed
(()
=>
props
.
name
.
charAt
(
0
))
</
script
>
<
style
scoped
>
.user-avatar
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
8px
;
}
.user-avatar-circle
{
width
:
20px
;
height
:
20px
;
border-radius
:
50%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
color
:
#fff
;
font-size
:
10px
;
font-weight
:
500
;
flex-shrink
:
0
;
}
.user-avatar-name
{
font-size
:
16px
;
font-weight
:
400
;
color
:
rgba
(
59
,
65
,
75
,
1
);
line-height
:
24px
;
}
</
style
>
src/views/characterPage/components/memberOfCongress/components/historicalProposal/composables/useBills.js
0 → 100644
浏览文件 @
592d75b3
import
{
ref
,
reactive
,
watch
,
computed
}
from
'vue'
import
{
useRoute
}
from
'vue-router'
import
{
getHistoryBillList
,
getBillStageConfig
,
getIndustryKeyList
,
getSortOptions
}
from
'@/api/bill/billHome.js'
/**
* 法案数据管理 composable
*/
export
function
useBills
()
{
const
route
=
useRoute
()
const
personId
=
computed
(()
=>
route
.
params
.
personId
||
route
.
query
.
personId
||
''
)
const
bills
=
ref
([])
const
stageConfig
=
ref
([])
const
total
=
ref
(
0
)
const
loading
=
ref
(
false
)
const
filterOptions
=
reactive
({
domains
:
[],
timeRanges
:
[],
})
const
sortOptions
=
ref
([])
const
currentPage
=
ref
(
1
)
const
pageSize
=
ref
(
10
)
const
filters
=
reactive
({
searchText
:
''
,
selectedDomains
:
[
'all'
],
selectedTimeRanges
:
[
'all'
],
sortBy
:
'latestMotionTimeDesc'
,
isCN
:
true
,
})
// 获取筛选项配置
async
function
loadFilterOptions
()
{
try
{
const
res
=
await
getIndustryKeyList
()
if
(
res
.
code
===
200
&&
res
.
data
)
{
// 行业列表
filterOptions
.
domains
=
[
{
id
:
'all'
,
label
:
'全部'
},
...
res
.
data
.
map
(
item
=>
({
id
:
item
.
id
,
label
:
item
.
name
,
}))
]
// 发布时间:从当前年份向前推5年动态生成
const
currentYear
=
new
Date
().
getFullYear
()
filterOptions
.
timeRanges
=
[
{
id
:
'all'
,
label
:
'全部'
},
...
Array
.
from
({
length
:
5
},
(
_
,
i
)
=>
{
const
year
=
currentYear
-
i
return
{
id
:
String
(
year
),
label
:
String
(
year
)
}
}),
]
}
}
catch
(
error
)
{
console
.
error
(
'加载筛选项失败:'
,
error
)
}
}
// 获取排序选项
async
function
loadSortOptions
()
{
try
{
const
res
=
await
getSortOptions
()
if
(
res
.
code
===
200
&&
res
.
data
)
{
sortOptions
.
value
=
res
.
data
}
}
catch
(
error
)
{
console
.
error
(
'加载排序选项失败:'
,
error
)
}
}
// 构建请求参数(不包含分页)
function
buildParams
()
{
const
params
=
{}
if
(
filters
.
searchText
)
{
params
.
searchText
=
filters
.
searchText
}
if
(
filters
.
selectedDomains
.
length
>
0
&&
!
filters
.
selectedDomains
.
includes
(
'all'
))
{
params
.
industryId
=
filters
.
selectedDomains
}
if
(
filters
.
selectedTimeRanges
.
length
>
0
&&
!
filters
.
selectedTimeRanges
.
includes
(
'all'
))
{
params
.
years
=
filters
.
selectedTimeRanges
}
params
.
isCN
=
filters
.
isCN
params
.
sortFun
=
filters
.
sortBy
===
'latestMotionTimeAsc'
return
params
}
// 获取阶段字典(仅初始化时调用一次)
async
function
loadStageConfig
()
{
try
{
const
res
=
await
getBillStageConfig
()
if
(
res
.
code
===
200
&&
res
.
data
)
{
stageConfig
.
value
=
res
.
data
}
}
catch
(
error
)
{
console
.
error
(
'加载阶段字典失败:'
,
error
)
}
}
// 根据当前筛选条件和分页参数加载法案列表
async
function
loadBills
()
{
if
(
!
personId
.
value
)
return
loading
.
value
=
true
try
{
const
params
=
buildParams
()
params
.
currentPage
=
currentPage
.
value
-
1
params
.
pageSize
=
pageSize
.
value
const
res
=
await
getHistoryBillList
(
personId
.
value
,
params
)
if
(
res
.
code
===
200
&&
res
.
data
)
{
bills
.
value
=
res
.
data
.
content
||
[]
total
.
value
=
res
.
data
.
totalElements
||
0
}
else
{
// 接口返回错误时清空数据
bills
.
value
=
[]
total
.
value
=
0
}
}
catch
(
error
)
{
console
.
error
(
'加载法案列表失败:'
,
error
)
bills
.
value
=
[]
total
.
value
=
0
}
finally
{
loading
.
value
=
false
}
}
// 更新搜索关键词
function
updateKeyword
(
keyword
)
{
filters
.
searchText
=
keyword
}
// 切换领域筛选项
function
toggleDomain
(
domainId
)
{
if
(
domainId
===
'all'
)
{
filters
.
selectedDomains
=
[
'all'
]
}
else
{
// 去掉 all,切换具体项
const
withoutAll
=
filters
.
selectedDomains
.
filter
(
d
=>
d
!==
'all'
)
const
idx
=
withoutAll
.
indexOf
(
domainId
)
if
(
idx
>
-
1
)
{
withoutAll
.
splice
(
idx
,
1
)
}
else
{
withoutAll
.
push
(
domainId
)
}
// 没有选中任何具体项时回到全部
filters
.
selectedDomains
=
withoutAll
.
length
>
0
?
withoutAll
:
[
'all'
]
}
}
// 切换时间范围筛选项
function
toggleTimeRange
(
timeId
)
{
if
(
timeId
===
'all'
)
{
filters
.
selectedTimeRanges
=
[
'all'
]
}
else
{
const
withoutAll
=
filters
.
selectedTimeRanges
.
filter
(
d
=>
d
!==
'all'
)
const
idx
=
withoutAll
.
indexOf
(
timeId
)
if
(
idx
>
-
1
)
{
withoutAll
.
splice
(
idx
,
1
)
}
else
{
withoutAll
.
push
(
timeId
)
}
filters
.
selectedTimeRanges
=
withoutAll
.
length
>
0
?
withoutAll
:
[
'all'
]
}
}
// 更新排序方式
function
updateSort
(
sortBy
)
{
filters
.
sortBy
=
sortBy
}
function
updateChinaRelated
(
isCN
)
{
filters
.
isCN
=
isCN
}
// 翻页处理
async
function
handlePageChange
(
page
)
{
currentPage
.
value
=
page
await
loadBills
()
}
// 初始化(阶段字典只在这里获取一次)
async
function
init
()
{
await
Promise
.
all
([
loadFilterOptions
(),
loadSortOptions
(),
loadStageConfig
()])
await
loadBills
()
}
// 监听筛选变化自动重新获取
watch
(
()
=>
({
searchText
:
filters
.
searchText
,
domains
:
[...
filters
.
selectedDomains
],
timeRanges
:
[...
filters
.
selectedTimeRanges
],
sortBy
:
filters
.
sortBy
,
isCN
:
filters
.
isCN
,
}),
()
=>
{
currentPage
.
value
=
1
loadBills
()
},
{
deep
:
true
}
)
return
{
bills
,
stageConfig
,
total
,
loading
,
currentPage
,
pageSize
,
filters
,
filterOptions
,
sortOptions
,
init
,
loadBills
,
updateKeyword
,
toggleDomain
,
toggleTimeRange
,
updateSort
,
updateChinaRelated
,
handlePageChange
,
}
}
src/views/characterPage/components/memberOfCongress/components/historicalProposal/index.vue
deleted
100644 → 0
浏览文件 @
2d832f9c
差异被折叠。
点击展开。
src/views/characterPage/components/memberOfCongress/index.vue
浏览文件 @
592d75b3
差异被折叠。
点击展开。
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论