Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
1
合并请求
1
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
1456a5ff
提交
1456a5ff
authored
3月 27, 2026
作者:
刘宇琪
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
刘宇琪 法案预测
上级
247def06
隐藏空白字符变更
内嵌
并排
正在显示
5 个修改的文件
包含
101 行增加
和
107 行删除
+101
-107
billHome.js
src/api/bill/billHome.js
+36
-61
FilterSection.vue
...l/influence/ProgressForecast/components/FilterSection.vue
+8
-10
PredictionPhaseCard.vue
...uence/ProgressForecast/components/PredictionPhaseCard.vue
+32
-14
Step2FilterBills.vue
...nfluence/ProgressForecast/components/Step2FilterBills.vue
+5
-4
index.vue
src/views/bill/influence/ProgressForecast/index.vue
+20
-18
没有找到文件。
src/api/bill/billHome.js
浏览文件 @
1456a5ff
...
...
@@ -335,14 +335,18 @@ export function getProgressPrediction(billId) {
* @returns {Promise<Object>} 相似法案列表
*/
export
function
getSimiBills
(
params
=
{})
{
// domains 如果是数组则用逗号拼接
const
domains
=
Array
.
isArray
(
params
.
domains
)
?
params
.
domains
.
join
(
','
)
:
params
.
domains
return
request
(
'/api/BillProgressPrediction/simiBills'
,
{
method
:
'GET'
,
params
:
{
billIds
:
params
.
billIds
,
domains
:
params
.
domains
,
patternType
:
params
.
patternType
,
proposalType
:
params
.
proposalType
,
...
params
domains
:
domains
,
patternType
:
params
.
patternType
,
proposalType
:
params
.
proposalType
}
})
}
...
...
@@ -353,80 +357,51 @@ export function getSimiBills(params = {}) {
* @returns {Object} 转换后的统计数据和法案列表
*/
export
function
transformSimiBillsData
(
apiData
)
{
if
(
!
apiData
||
!
apiData
.
data
||
!
Array
.
isArray
(
apiData
.
data
)
)
{
if
(
!
apiData
||
!
apiData
.
data
)
{
return
{
stats
:
null
,
bills
:
[]
}
}
const
bills
=
apiData
.
data
const
data
=
apiData
.
data
const
simiBills
=
data
.
simi_bills
||
[]
// 计算统计数据
let
becameLaw
=
0
let
notPassedOrShelved
=
0
let
totalDays
=
0
let
completedBills
=
0
bills
.
forEach
(
bill
=>
{
const
actions
=
bill
.
bill_actions
||
[]
const
hasBecameLaw
=
actions
.
some
(
a
=>
a
.
action_type
===
'BecameLaw'
||
a
.
action_type
===
'President'
&&
a
.
action_desc
?.
includes
(
'签署'
)
)
if
(
hasBecameLaw
)
{
becameLaw
++
// 计算耗时
if
(
actions
.
length
>=
2
)
{
const
firstDate
=
new
Date
(
actions
[
0
].
action_date
)
const
lastDate
=
new
Date
(
actions
[
actions
.
length
-
1
].
action_date
)
const
days
=
Math
.
ceil
((
lastDate
-
firstDate
)
/
(
1000
*
60
*
60
*
24
))
if
(
days
>
0
)
{
totalDays
+=
days
completedBills
++
}
}
}
else
{
notPassedOrShelved
++
}
})
const
medianDays
=
completedBills
>
0
?
Math
.
round
(
totalDays
/
completedBills
)
:
223
const
passRate
=
bills
.
length
>
0
?
((
becameLaw
/
bills
.
length
)
*
100
).
toFixed
(
1
)
:
'0'
// 直接使用 API 返回的统计数据
const
stats
=
{
totalBills
:
b
ills
.
length
,
becameLaw
,
notPassedOrShelved
,
medianDays
,
passRate
totalBills
:
data
.
count
||
simiB
ills
.
length
,
becameLaw
:
data
.
become_law
||
0
,
notPassedOrShelved
:
(
data
.
count
||
simiBills
.
length
)
-
(
data
.
become_law
||
0
)
,
medianDays
:
data
.
become_law_avg_days
||
0
,
passRate
:
data
.
become_law_prop
?
(
data
.
become_law_prop
*
100
).
toFixed
(
1
)
:
'0'
}
// 转换法案列表格式
const
transformedBills
=
b
ills
.
map
(
bill
=>
({
const
transformedBills
=
simiB
ills
.
map
(
bill
=>
({
id
:
bill
.
bill_id
,
title
:
bill
.
bill_name
||
bill
.
bill_id
,
proposalDate
:
extractProposalDate
(
bill
.
poli_pattern_desc
),
areas
:
bill
.
domain_name
?
(
Array
.
isArray
(
bill
.
domain_name
)
?
bill
.
domain_name
:
[
bill
.
domain_name
])
:
[],
proposer
:
extractProposer
(
bill
.
bill_sponsors
),
coProposers
:
bill
.
bill_proposal_desc
||
''
,
proposalDate
:
bill
.
proposed_date
?
formatDate
(
bill
.
proposed_date
)
:
''
,
areas
:
bill
.
bill_domain
?
(
Array
.
isArray
(
bill
.
bill_domain
)
?
bill
.
bill_domain
:
[
bill
.
bill_domain
])
:
[],
proposer
:
bill
.
key_sponsor_name
||
extractProposer
(
bill
.
bill_sponsors
),
proposerParty
:
bill
.
key_sponsor_party
||
''
,
coProposers
:
bill
.
co_sponsor_desc
||
bill
.
bill_proposal_desc
||
''
,
proposalType
:
bill
.
bill_proposal_type
||
''
,
governmentType
:
formatGovernmentType
(
bill
.
poli_pattern_type
,
bill
.
poli_pattern_desc
),
passDays
:
calculateTotalDays
(
bill
.
bill_actions
),
patternType
:
bill
.
poli_pattern_type
||
''
,
billStatus
:
bill
.
bill_status
||
''
,
passDays
:
bill
.
bill_action_days
||
calculateTotalDays
(
bill
.
bill_actions
),
yearsDifference
:
bill
.
years_difference
||
0
,
billActions
:
bill
.
bill_actions
||
[],
billSponsors
:
bill
.
bill_sponsors
||
[],
selected
:
true
// 默认全选
}))
return
{
stats
,
bills
:
transformedBills
}
}
/**
* 从政治格局描述中提取提案时间
* @param {string} desc - 政治格局描述
* @returns {string} 提案时间
*/
function
extractProposalDate
(
desc
)
{
if
(
!
desc
)
return
''
const
match
=
desc
.
match
(
/
(\d{4})
-
(\d{2})
-
(\d{2})
/
)
function
formatDate
(
dateStr
)
{
if
(
!
dateStr
)
return
''
const
match
=
dateStr
.
match
(
/
(\d{4})
-
(\d{2})
-
(\d{2})
/
)
if
(
match
)
{
return
`
${
match
[
1
]}
年
${
parseInt
(
match
[
2
])}
月
${
parseInt
(
match
[
3
])}
日`
}
return
''
return
dateStr
}
/**
* 从提案人列表中提取主提案人
...
...
@@ -504,9 +479,9 @@ export function transformProposalInfo(apiData) {
return
{
// 提案标题
title
:
data
.
bill_
title
||
'H.R.1-大而美法案'
,
title
:
data
.
bill_
name_zh
,
// 提案时间 - 从政治格局描述中提取
date
:
extractDateFromDesc
(
data
.
poli_pattern_desc
)
||
'2025年5月20日'
,
date
:
extractDateFromDesc
(
data
.
poli_pattern_desc
)
,
// 涉及领域 TAG - 使用 domain_name
areas
:
Array
.
isArray
(
data
.
domain_name
)
?
data
.
domain_name
:
(
data
.
domain_name
?
[
data
.
domain_name
]
:
[]),
// 政策领域完整选项列表 - 使用 bill_domain(用于筛选下拉框)
...
...
src/views/bill/influence/ProgressForecast/components/FilterSection.vue
浏览文件 @
1456a5ff
...
...
@@ -62,7 +62,6 @@
<div
class=
"field-content"
>
<el-select
v-model=
"localValues.oppositionProposer"
multiple
placeholder=
"请选择"
style=
"width: 420px"
@
change=
"handleChange"
...
...
@@ -83,7 +82,6 @@
<div
class=
"field-content"
>
<el-select
v-model=
"localValues.proposalTime"
multiple
placeholder=
"请选择"
style=
"width: 420px"
@
change=
"handleChange"
...
...
@@ -115,18 +113,18 @@ const props = defineProps<{
const
localValues
=
ref
({
policyArea
:
[]
as
string
[],
governmentType
:
[]
as
string
[],
oppositionProposer
:
[]
as
string
[]
,
proposalTime
:
[]
as
string
[]
,
oppositionProposer
:
''
as
string
,
proposalTime
:
''
as
string
,
})
// 根据 proposalInfo 计算初始筛选值(即"设置为当前提案"的目标状态)
function
buildInitialValues
(
info
?:
ProposalInfo
|
null
):
Record
<
string
,
string
[]
>
{
if
(
!
info
)
return
{
policyArea
:
[],
governmentType
:
[],
oppositionProposer
:
[],
proposalTime
:
[]
}
function
buildInitialValues
(
info
?:
ProposalInfo
|
null
):
Record
<
string
,
string
[]
|
string
>
{
if
(
!
info
)
return
{
policyArea
:
[],
governmentType
:
[],
oppositionProposer
:
''
,
proposalTime
:
''
}
return
{
policyArea
:
info
.
defaultDomains
?.
length
?
[...
info
.
defaultDomains
]
:
[...(
info
.
areas
||
[])],
governmentType
:
info
.
patternType
?
[
info
.
patternType
]
:
[],
oppositionProposer
:
[]
,
proposalTime
:
[]
,
oppositionProposer
:
''
,
proposalTime
:
''
,
}
}
...
...
@@ -144,8 +142,8 @@ function reset() {
localValues
.
value
=
{
policyArea
:
[],
governmentType
:
[],
oppositionProposer
:
[]
,
proposalTime
:
[]
,
oppositionProposer
:
''
,
proposalTime
:
''
,
}
}
...
...
src/views/bill/influence/ProgressForecast/components/PredictionPhaseCard.vue
浏览文件 @
1456a5ff
...
...
@@ -2,8 +2,8 @@
<div
v-if=
"phase"
class=
"phase-card"
:class=
"borderColorClass"
>
<div
class=
"phase-header flex-display-start"
>
<div>
<
h3
class=
"phase-title main-color text-title-2-bold"
>
{{
phase
.
title
}}
</h3
>
<
p
class=
"text-tip-2 text-primary-50-clor"
>
{{
phase
.
description
}}
</p
>
<
div
class=
"phase-title main-color text-title-2-bold"
>
{{
phase
.
title
}}
</div
>
<
div
class=
"text-tip-2 text-primary-50-clor"
>
{{
phase
.
description
}}
</div
>
</div>
<div
class=
"phase-status"
>
<span
class=
"risk-badge"
:class=
"riskColorClass"
>
{{
riskLabel
}}
</span>
...
...
@@ -18,32 +18,41 @@
<p
v-if=
"phase.riskLevel !== 'passed'"
class=
"text-tip-2 text-primary-50-clor"
>
{{
phase
.
estimatedDays
}}
</p>
</div>
</div>
<div
style=
"display: flex;"
>
<div
class=
"box-title-row"
><img
src=
"../assets/input.svg"
/>
<span
class=
"text-compact-bold text-primary-80-clor"
style=
"margin-left: 8px;"
>
预测模型数据输入
</span></div>
<div
class=
"box-hint flex-display-center text-tip-2 text-primary-50-clor"
style=
"margin-left: auto;"
>
<img
src=
"../assets/importent.svg"
/>
<span>
此阶段预测基于以下多维特征
</span>
</div>
</div>
<div
class=
"model-inputs-box"
>
<div
class=
"box-header flex-display-start"
>
<div
class=
"box-title-row flex-display-center"
>
<img
src=
"../assets/input.svg"
/>
<span
class=
"text-compact-bold"
>
预测模型数据输入
</span>
</div>
<div
class=
"box-hint flex-display-center text-tip-2 text-primary-50-clor"
>
<img
src=
"../assets/importent.svg"
/>
<span>
此阶段预测基于以下多维特征
</span>
</div>
</div>
<div
class=
"model-inputs"
>
<
p
<
div
v-for=
"(input, index) in phase.modelInputs"
:key=
"index"
class=
"text-tip-2 text-primary-65-clor"
>
{{
input
}}
</
p
>
</
div
>
</div>
</div>
<div
v-if=
"phase.predictionBasis"
class=
"facts-section"
>
<div
class=
"box-header flex-display-start"
>
<div
class=
"box-title-row flex-display-center"
>
<img
src=
"../assets/icon1.svg"
/>
<span
class=
"text-compact-bold"
>
通过性预测依据
</span>
<span
class=
"text-compact-bold
text-primary-80-clor
"
>
通过性预测依据
</span>
</div>
<div
class=
"box-hint flex-display-center text-tip-2 text-primary-50-clor"
>
<img
src=
"../assets/importent.svg"
/>
...
...
@@ -51,9 +60,11 @@
</div>
</div>
<div
class=
"prediction-basis-content"
>
<
p
class=
"text-tip-2 text-primary-65-clor"
>
{{
phase
.
predictionBasis
}}
</p
>
<
div
class=
"text-tip-2 text-primary-65-clor"
>
{{
phase
.
predictionBasis
}}
</div
>
</div>
</div>
<!-- 底部虚线分隔 -->
<div
class=
"phase-divider"
></div>
</div>
</
template
>
...
...
@@ -144,7 +155,8 @@ const riskLabel = computed(() => {
<
style
scoped
>
.phase-card
{
padding-left
:
24px
;
padding-bottom
:
32px
;
/* padding-bottom: 16px; */
/* padding-top: 16px; */
}
.border-primary
{
...
...
@@ -166,7 +178,7 @@ const riskLabel = computed(() => {
.phase-header
{
justify-content
:
space-between
;
align-items
:
flex-start
;
margin-bottom
:
16px
;
/* margin-bottom: 16px; */
}
.phase-header
>
div
:first-child
{
...
...
@@ -288,4 +300,10 @@ const riskLabel = computed(() => {
height
:
16px
;
color
:
var
(
--text-primary-65-color
);
}
/* 底部虚线分隔 */
.phase-divider
{
margin-top
:
16px
;
border-bottom
:
1px
solid
var
(
--bg-black-10
,
#e5e5e5
);
}
</
style
>
src/views/bill/influence/ProgressForecast/components/Step2FilterBills.vue
浏览文件 @
1456a5ff
...
...
@@ -125,22 +125,23 @@ async function loadData() {
// 使用真实 API,传入 billIds、domains、patternType、proposalType
const
params
=
{
billIds
:
filterParams
?.
value
.
billIds
,
domains
:
JSON
.
stringify
(
filterParams
?.
value
.
domains
)
||
[],
domains
:
filterParams
?.
value
.
domains
||
[],
patternType
:
filterParams
?.
value
.
patternType
||
'统一政府'
,
proposalType
:
filterParams
?.
value
.
proposalType
||
'两党共同提案'
}
const
response
=
await
getSimiBills
(
params
)
if
(
response
&&
response
.
data
)
{
// 保存原始数据
rawBillsData
.
value
=
response
.
data
// 保存原始数据
(新 API 返回结构中 simi_bills 是法案数组)
rawBillsData
.
value
=
response
.
data
.
simi_bills
||
[]
const
{
stats
:
apiStats
,
bills
:
apiBills
}
=
transformSimiBillsData
(
response
)
stats
.
value
=
apiStats
bills
.
value
=
apiBills
}
else
{
stats
.
value
=
null
bills
.
value
=
[]
rawBillsData
.
value
=
[]
}
}
catch
(
error
)
{
console
.
error
(
'获取相似法案失败:'
,
error
)
...
...
src/views/bill/influence/ProgressForecast/index.vue
浏览文件 @
1456a5ff
...
...
@@ -370,24 +370,26 @@ function transformPredictionResult(data: any) {
const
factors
=
data
?.
factor_analysis
||
[]
// 转换阶段分析
const
phases
=
stages
.
map
((
stage
:
any
,
index
:
number
)
=>
({
id
:
index
+
1
,
title
:
`阶段
${
index
+
1
}
:
${
stage
.
stage
}
`
,
description
:
stage
.
stage
,
riskLevel
:
probabilityToRisk
(
stage
.
predicted_pass_probability
),
progressLevel
:
probabilityToProgressLevel
(
stage
.
predicted_pass_probability
),
estimatedDays
:
`预计耗时
${
stage
.
predicted_passing_time
}
天`
,
modelInputs
:
[
stage
.
analysis
],
supportingFacts
:
{
title
:
'通过性预测依据'
,
basedOn
:
'此阶段预测基于以下观点'
,
stats
:
[
{
value
:
`
${
stage
.
predicted_pass_probability
}
`
,
label
:
'通过概率'
},
{
value
:
`
${
stage
.
predicted_passing_time
}
天`
,
label
:
'预计耗时'
}
]
},
predictionBasis
:
stage
.
prediction_basis
}))
const
chineseNum
=
[
'一'
,
'二'
,
'三'
,
'四'
,
'五'
,
'六'
,
'七'
,
'八'
,
'九'
,
'十'
]
const
phases
=
stages
.
map
((
stage
:
any
,
index
:
number
)
=>
({
id
:
index
+
1
,
title
:
`阶段
${
chineseNum
[
index
]
||
index
+
1
}
:
${
stage
.
stage
}
`
,
description
:
stage
.
stage
,
riskLevel
:
probabilityToRisk
(
stage
.
predicted_pass_probability
),
progressLevel
:
probabilityToProgressLevel
(
stage
.
predicted_pass_probability
),
estimatedDays
:
`预计耗时
${
stage
.
predicted_passing_time
}
天`
,
modelInputs
:
[
stage
.
analysis
],
supportingFacts
:
{
title
:
'通过性预测依据'
,
basedOn
:
'此阶段预测基于以下观点'
,
stats
:
[
{
value
:
`
${
stage
.
predicted_pass_probability
}
`
,
label
:
'通过概率'
},
{
value
:
`
${
stage
.
predicted_passing_time
}
天`
,
label
:
'预计耗时'
}
]
},
predictionBasis
:
stage
.
prediction_basis
}))
return
{
title
:
'立法进展阶段预测分析'
,
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论