Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
ca4247e0
提交
ca4247e0
authored
3月 31, 2026
作者:
张伊明
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
fix 修复深度挖掘样式问题
上级
e25eb5aa
流水线
#216
已通过 于阶段
in 1 分 26 秒
变更
2
流水线
1
隐藏空白字符变更
内嵌
并排
正在显示
2 个修改的文件
包含
190 行增加
和
87 行删除
+190
-87
index.vue
src/views/bill/billLayout/index.vue
+7
-13
index.vue
src/views/bill/deepDig/processOverview/index.vue
+183
-74
没有找到文件。
src/views/bill/billLayout/index.vue
浏览文件 @
ca4247e0
...
@@ -2,16 +2,9 @@
...
@@ -2,16 +2,9 @@
<div
class=
"layout-container"
>
<div
class=
"layout-container"
>
<!-- 导航菜单 -->
<!-- 导航菜单 -->
<div
class=
"layout-main"
>
<div
class=
"layout-main"
>
<BillHeader
<BillHeader
:billInfo=
"billInfoGlobal"
:defaultLogo=
"USALogo"
:tabs=
"mainHeaderBtnList"
:billInfo=
"billInfoGlobal"
:activeTitle=
"activeTitle"
:showTabs=
"showHeaderTabs"
:showActions=
"showHeaderActions"
:defaultLogo=
"USALogo"
@
tab-click=
"handleClickMainHeaderBtn"
@
open-analysis=
"handleAnalysisClick"
/>
:tabs=
"mainHeaderBtnList"
:activeTitle=
"activeTitle"
:showTabs=
"showHeaderTabs"
:showActions=
"showHeaderActions"
@
tab-click=
"handleClickMainHeaderBtn"
@
open-analysis=
"handleAnalysisClick"
/>
<div
class=
"layout-main-center"
>
<div
class=
"layout-main-center"
>
<router-view
/>
<router-view
/>
...
@@ -122,8 +115,7 @@ const handleAnalysisClick = analysisType => {
...
@@ -122,8 +115,7 @@ const handleAnalysisClick = analysisType => {
// 进展预测 -> 法案简介页(法案进展)
// 进展预测 -> 法案简介页(法案进展)
if
(
analysisType
===
"forsee"
)
{
if
(
analysisType
===
"forsee"
)
{
router
.
push
({
router
.
push
({
path
:
"/billLayout/bill/introduction"
,
path
:
`/billLayout/ProgressForecast/
${
billId
}
`
,
query
:
{
billId
:
String
(
billId
)
}
});
});
return
;
return
;
}
}
...
@@ -162,11 +154,13 @@ watch(
...
@@ -162,11 +154,13 @@ watch(
// height: 1016px;
// height: 1016px;
background
:
rgba
(
249
,
250
,
252
,
1
);
background
:
rgba
(
249
,
250
,
252
,
1
);
position
:
relative
;
position
:
relative
;
// margin: 0 auto;
// margin: 0 auto;
.layout-main
{
.layout-main
{
width
:
100%
;
width
:
100%
;
height
:
calc
(
100vh
-
72px
)
;
height
:
100vh
;
overflow-y
:
auto
;
overflow-y
:
auto
;
.layout-main-center
{
.layout-main-center
{
// height: calc(100% - 137px);
// height: calc(100% - 137px);
width
:
1600px
;
width
:
1600px
;
...
...
src/views/bill/deepDig/processOverview/index.vue
浏览文件 @
ca4247e0
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
<div
class=
"left"
:style=
"
{ width: (maxLineWidth + 250) + 'px' }">
<div
class=
"left"
:style=
"
{ width: (maxLineWidth + 250) + 'px' }">
<div
class=
"top"
>
<div
class=
"top"
>
<div
class=
"top-line"
:style=
"
{ width: lineWidth }">
<div
class=
"top-line"
:style=
"
{ width: lineWidth }">
<div
class=
"top-line1"
></div>
<div
class=
"top-line1"
ref=
"topLineEndRef"
></div>
</div>
</div>
<div
class=
"start"
>
<div
class=
"start"
>
<div
class=
"icon"
>
<div
class=
"icon"
>
...
@@ -14,42 +14,52 @@
...
@@ -14,42 +14,52 @@
<div
class=
"name"
>
{{
"参议院"
}}
</div>
<div
class=
"name"
>
{{
"参议院"
}}
</div>
</div>
</div>
<div
class=
"content-box"
:style=
"senateBoxStyle"
>
<div
class=
"content-box"
:style=
"senateBoxStyle"
>
<div
class=
"item-box"
v-for=
"(item, index) in senateList"
:key=
"item.id"
<div
style=
"width: 280px; flex-shrink: 0;"
>
class=
"item-box"
<div
class=
"item-box-dot"
>
v-for=
"slot in senateSlots"
<img
src=
"./assets/images/top-line-dot.png"
alt=
""
/>
:key=
"slot.key"
</div>
style=
"width: 280px; flex-shrink: 0;"
<div
class=
"item-content"
>
>
<div
class=
"item-header"
>
<template
v-if=
"slot.item"
>
<div
class=
"item-title"
:title=
"item.actionTitle"
>
<div
class=
"item-box-dot"
>
{{
item
.
actionTitle
}}
<span
v-if=
"item.versionId"
>
(
{{
item
.
versionId
}}
)
</span>
<img
src=
"./assets/images/top-line-dot.png"
alt=
""
/>
</div>
<div
class=
"item-content"
>
<div
class=
"item-header"
>
<div
class=
"item-title"
:title=
"slot.item.actionTitle"
>
{{
slot
.
item
.
actionTitle
}}
<span
v-if=
"slot.item.versionId"
>
(
{{
slot
.
item
.
versionId
}}
)
</span>
</div>
<div
class=
"item-header-icon"
@
click=
"handleClickDetail(true, slot.item, $event)"
>
<img
src=
"./assets/images/item-header-icon.png"
alt=
""
/>
</div>
</div>
</div>
<div
class=
"item-
header-icon"
@
click=
"handleClickDetail(true, item, $event)
"
>
<div
class=
"item-
info"
v-if=
"slot.item.agreeVote !== null || slot.item.disagreeVote !== null
"
>
<img
src=
"./assets/images/item-header-icon.png"
alt=
""
/>
{{
formatVoteText
(
slot
.
item
)
}}
</div>
</div>
</div>
<div
class=
"item-main"
v-if=
"slot.item.fynrList && slot.item.fynrList.length"
>
<div
class=
"item-info"
v-if=
"item.agreeVote !== null || item.disagreeVote !== null"
>
<div
{{
(
item
.
agreeVote
||
0
)
+
"赞成:"
+
(
item
.
disagreeVote
||
0
)
+
"反对"
}}
class=
"item-main-item"
</div>
v-for=
"(sub, subIndex) in slot.item.fynrList"
<div
class=
"item-main"
v-if=
"item.fynrList && item.fynrList.length"
>
:key=
"`$
{slot.item.id}-${subIndex}-${sub}`"
<div
class=
"item-main-item"
v-for=
"(sub, subIndex) in item.fynrList"
:key=
"subIndex"
>
>
<div
class=
"icon"
></div>
<div
class=
"icon"
></div>
<CommonPrompt
:content=
"sub"
>
<CommonPrompt
:content=
"sub"
>
<div
class=
"text"
>
{{
sub
}}
</div>
<div
class=
"text"
>
{{
sub
}}
</div>
</CommonPrompt>
</CommonPrompt>
</div>
</div>
</div>
</div>
</div>
</div
>
<div
class=
"item-time"
>
<div
class=
"item-time"
>
{{
slot
.
item
.
actionDate
}}
{{
item
.
actionDate
}}
</div>
</
div
>
</
template
>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class=
"bottom"
>
<div
class=
"bottom"
>
<div
class=
"bottom-line"
:style=
"{ width: lineWidth }"
>
<div
class=
"bottom-line"
:style=
"{ width: lineWidth }"
>
<div
class=
"bottom-line1"
></div>
<div
class=
"bottom-line1"
ref=
"bottomLineEndRef"
></div>
</div>
</div>
<div
class=
"start"
>
<div
class=
"start"
>
<div
class=
"name"
>
{{ "众议院" }}
</div>
<div
class=
"name"
>
{{ "众议院" }}
</div>
...
@@ -59,39 +69,49 @@
...
@@ -59,39 +69,49 @@
</div>
</div>
</div>
</div>
<div
class=
"content-box"
:style=
"houseBoxStyle"
>
<div
class=
"content-box"
:style=
"houseBoxStyle"
>
<div
class=
"item-box"
v-for=
"(item, index) in houseList"
:key=
"item.id"
<div
style=
"width: 280px; flex-shrink: 0;"
>
class=
"item-box"
<div
class=
"item-time"
>
v-for=
"slot in houseSlots"
{{
item
.
actionDate
}}
:key=
"slot.key"
</div>
style=
"width: 280px; flex-shrink: 0;"
<div
class=
"item-box-dot"
>
>
<img
src=
"./assets/images/bottom-line-dot.png"
alt=
""
/>
<
template
v-if=
"slot.item"
>
</div>
<div
class=
"item-time"
>
<div
class=
"item-content"
>
{{
slot
.
item
.
actionDate
}}
<div
class=
"item-header"
>
<div
class=
"item-title"
:title=
"item.actionTitle"
>
{{
item
.
actionTitle
}}
<span
v-if=
"item.versionId"
>
(
{{
item
.
versionId
}}
)
</span>
</div>
<div
class=
"item-header-icon"
@
click=
"handleClickDetail(true, item, $event)"
>
<img
src=
"./assets/images/item-header-icon.png"
alt=
""
/>
</div>
</div>
</div>
<div
class=
"item-
info"
v-if=
"item.agreeVote !== null || item.disagreeVote !== null
"
>
<div
class=
"item-
box-dot
"
>
{{
(
item
.
agreeVote
||
0
)
+
"赞成:"
+
(
item
.
disagreeVote
||
0
)
+
"反对"
}}
<img
src=
"./assets/images/bottom-line-dot.png"
alt=
""
/>
</div>
</div>
<div
class=
"item-main"
v-if=
"item.fynrList && item.fynrList.length"
>
<div
class=
"item-content"
>
<div
class=
"item-main-item"
v-for=
"(sub, subIndex) in item.fynrList"
:key=
"subIndex"
>
<div
class=
"item-header"
>
<div
class=
"icon"
></div>
<div
class=
"item-title"
:title=
"slot.item.actionTitle"
>
<CommonPrompt
:content=
"sub"
>
{{
slot
.
item
.
actionTitle
}}
<span
v-if=
"slot.item.versionId"
>
(
{{
slot
.
item
.
versionId
}}
)
</span>
<div
class=
"text"
>
{{
sub
}}
</div>
</div>
</CommonPrompt>
<div
class=
"item-header-icon"
@
click=
"handleClickDetail(true, slot.item, $event)"
>
<img
src=
"./assets/images/item-header-icon.png"
alt=
""
/>
</div>
</div>
<div
class=
"item-info"
v-if=
"slot.item.agreeVote !== null || slot.item.disagreeVote !== null"
>
{{
formatVoteText
(
slot
.
item
)
}}
</div>
<div
class=
"item-main"
v-if=
"slot.item.fynrList && slot.item.fynrList.length"
>
<div
class=
"item-main-item"
v-for=
"(sub, subIndex) in slot.item.fynrList"
:key=
"`$
{slot.item.id}-${subIndex}-${sub}`"
>
<div
class=
"icon"
></div>
<CommonPrompt
:content=
"sub"
>
<div
class=
"text"
>
{{
sub
}}
</div>
</CommonPrompt>
</div>
</div>
</div>
</div>
</div>
</
div
>
</
template
>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class=
"right"
:style=
"
{ left: rightPos }">
<div
class=
"right"
:style=
"{ left: rightPos
, top: rightTop
}"
>
<div
class=
"junction-dot"
>
<div
class=
"junction-dot"
>
<div
class=
"inner-dot"
></div>
<div
class=
"inner-dot"
></div>
</div>
</div>
...
@@ -111,7 +131,7 @@
...
@@ -111,7 +131,7 @@
</template>
</template>
<
script
setup
>
<
script
setup
>
import
{
ref
,
onMounted
,
computed
}
from
"vue"
;
import
{
ref
,
onMounted
,
computed
,
nextTick
}
from
"vue"
;
import
{
getBillDyqkSummary
}
from
"@/api/bill"
;
import
{
getBillDyqkSummary
}
from
"@/api/bill"
;
import
CommonPrompt
from
"../../commonPrompt/index.vue"
;
import
CommonPrompt
from
"../../commonPrompt/index.vue"
;
import
ProcessOverviewDetailDialog
from
"../../ProcessOverviewDetailDialog.vue"
;
import
ProcessOverviewDetailDialog
from
"../../ProcessOverviewDetailDialog.vue"
;
...
@@ -138,30 +158,81 @@ const getBillDyqkSummaryList = async () => {
...
@@ -138,30 +158,81 @@ const getBillDyqkSummaryList = async () => {
}
}
};
};
// 总统签署节点
const
ORG_SENATE
=
"参议院"
;
const
ORG_HOUSE
=
"众议院"
;
const
PRESIDENT_KEYWORD
=
"呈递给总统"
;
const
TIMELINE_ITEM_WIDTH_PX
=
280
;
const
getTime
=
(
actionDate
)
=>
{
const
t
=
new
Date
(
actionDate
).
getTime
();
return
Number
.
isFinite
(
t
)
?
t
:
0
;
};
const
formatVoteText
=
(
item
)
=>
{
if
(
!
item
)
return
""
;
const
agree
=
item
.
agreeVote
??
0
;
const
disagree
=
item
.
disagreeVote
??
0
;
return
`
${
agree
}
赞成:
${
disagree
}
反对`
;
};
// 总统交汇节点(用于确定两条时间线的汇合位置)
const
presidentAction
=
computed
(()
=>
{
const
presidentAction
=
computed
(()
=>
{
return
actionList
.
value
.
find
(
item
=>
item
.
actionTitle
&&
item
.
actionTitle
.
includes
(
"呈递给总统"
))
||
null
;
return
(
actionList
.
value
.
find
(
(
item
)
=>
item
.
actionTitle
&&
item
.
actionTitle
.
includes
(
PRESIDENT_KEYWORD
)
)
||
null
);
});
// 全局时间排序后的“时间步”
const
sortedTimeline
=
computed
(()
=>
{
return
[...
actionList
.
value
].
sort
((
a
,
b
)
=>
{
const
tA
=
getTime
(
a
.
actionDate
);
const
tB
=
getTime
(
b
.
actionDate
);
if
(
tA
!==
tB
)
return
tA
-
tB
;
// 时间相同的情况下用 id 保证稳定排序,避免节点在不同渲染中漂移
const
idA
=
String
(
a
.
id
??
""
);
const
idB
=
String
(
b
.
id
??
""
);
return
idA
.
localeCompare
(
idB
);
});
});
// 交汇点(总统节点)在全局时间线里的位置:slice endIndexExclusive 用来排除总统节点本身
const
mergeIndexExclusive
=
computed
(()
=>
{
if
(
!
sortedTimeline
.
value
.
length
)
return
0
;
if
(
!
presidentAction
.
value
)
return
sortedTimeline
.
value
.
length
;
const
idx
=
sortedTimeline
.
value
.
findIndex
((
item
)
=>
item
.
id
===
presidentAction
.
value
.
id
);
return
idx
>=
0
?
idx
:
sortedTimeline
.
value
.
length
;
});
});
// 参议院列表
// 两条时间线共享同一组时间步(每个时间步只展示属于该阵营的事件;其他阵营用空占位对齐)
const
senateList
=
computed
(()
=>
{
const
timelineSlots
=
computed
(()
=>
{
return
actionList
.
value
return
sortedTimeline
.
value
.
slice
(
0
,
mergeIndexExclusive
.
value
);
.
filter
(
item
=>
item
.
orgName
===
"参议院"
&&
(
!
presidentAction
.
value
||
item
.
id
!==
presidentAction
.
value
.
id
))
.
sort
((
a
,
b
)
=>
new
Date
(
a
.
actionDate
)
-
new
Date
(
b
.
actionDate
));
});
});
// 众议院列表
const
senateSlots
=
computed
(()
=>
{
const
houseList
=
computed
(()
=>
{
return
timelineSlots
.
value
.
map
((
step
)
=>
(
{
return
actionList
.
value
key
:
step
.
id
,
.
filter
(
item
=>
item
.
orgName
===
"众议院"
&&
(
!
presidentAction
.
value
||
item
.
id
!==
presidentAction
.
value
.
id
))
item
:
step
.
orgName
===
ORG_SENATE
?
step
:
null
.
sort
((
a
,
b
)
=>
new
Date
(
a
.
actionDate
)
-
new
Date
(
b
.
actionDate
));
}
));
});
});
// 计算最大线条宽度数值
const
houseSlots
=
computed
(()
=>
{
return
timelineSlots
.
value
.
map
((
step
)
=>
({
key
:
step
.
id
,
item
:
step
.
orgName
===
ORG_HOUSE
?
step
:
null
}));
});
const
timelineCount
=
computed
(()
=>
timelineSlots
.
value
.
length
);
// 计算最大线条宽度数值(两条线共享时间步长度)
const
maxLineWidth
=
computed
(()
=>
{
const
maxLineWidth
=
computed
(()
=>
{
const
senateWidth
=
254
+
senateList
.
value
.
length
*
280
;
const
senateWidth
=
254
+
timelineCount
.
value
*
TIMELINE_ITEM_WIDTH_PX
;
const
houseWidth
=
150
+
houseList
.
value
.
length
*
280
;
const
houseWidth
=
150
+
timelineCount
.
value
*
TIMELINE_ITEM_WIDTH_PX
;
return
Math
.
max
(
1100
,
senateWidth
,
houseWidth
);
return
Math
.
max
(
senateWidth
,
houseWidth
);
});
});
// 绑定给线条的样式
// 绑定给线条的样式
...
@@ -173,7 +244,7 @@ const lineWidth = computed(() => {
...
@@ -173,7 +244,7 @@ const lineWidth = computed(() => {
const
senateBoxStyle
=
computed
(()
=>
{
const
senateBoxStyle
=
computed
(()
=>
{
return
{
return
{
width
:
(
maxLineWidth
.
value
+
110
-
254
)
+
'px'
,
width
:
(
maxLineWidth
.
value
+
110
-
254
)
+
'px'
,
justifyContent
:
'
space-between
'
justifyContent
:
'
flex-start
'
};
};
});
});
...
@@ -181,7 +252,7 @@ const senateBoxStyle = computed(() => {
...
@@ -181,7 +252,7 @@ const senateBoxStyle = computed(() => {
const
houseBoxStyle
=
computed
(()
=>
{
const
houseBoxStyle
=
computed
(()
=>
{
return
{
return
{
width
:
(
maxLineWidth
.
value
+
110
-
150
)
+
'px'
,
width
:
(
maxLineWidth
.
value
+
110
-
150
)
+
'px'
,
justifyContent
:
'
space-between
'
justifyContent
:
'
flex-start
'
};
};
});
});
...
@@ -190,6 +261,10 @@ const rightPos = computed(() => {
...
@@ -190,6 +261,10 @@ const rightPos = computed(() => {
return
(
maxLineWidth
.
value
+
90
)
+
'px'
;
return
(
maxLineWidth
.
value
+
90
)
+
'px'
;
});
});
const
topLineEndRef
=
ref
(
null
);
const
bottomLineEndRef
=
ref
(
null
);
const
rightTop
=
ref
(
'370px'
);
const
isShowDetailDialog
=
ref
(
false
);
const
isShowDetailDialog
=
ref
(
false
);
const
currentDetailItem
=
ref
({});
const
currentDetailItem
=
ref
({});
const
dialogPos
=
ref
({
left
:
'0px'
,
top
:
'0px'
});
const
dialogPos
=
ref
({
left
:
'0px'
,
top
:
'0px'
});
...
@@ -224,9 +299,43 @@ const handleClickDetail = (isShow, item = {}, event = null) => {
...
@@ -224,9 +299,43 @@ const handleClickDetail = (isShow, item = {}, event = null) => {
};
};
// 挂载阶段调用
// 挂载阶段调用
onMounted
(()
=>
{
onMounted
(
async
()
=>
{
getBillDyqkSummaryList
();
await
getBillDyqkSummaryList
();
await
nextTick
();
updateRightTop
();
});
});
const
updateRightTop
=
()
=>
{
// 交汇点需要精确对齐上下两条斜线端点在页面中的 y 坐标
// rightTop 是相对 .process-overview-wrap 的 absolute top
const
wrap
=
document
.
querySelector
(
'.process-overview-wrap'
);
if
(
!
wrap
)
return
;
const
topLineEndEl
=
topLineEndRef
.
value
;
const
bottomLineEndEl
=
bottomLineEndRef
.
value
;
if
(
!
topLineEndEl
||
!
bottomLineEndEl
)
return
;
const
wrapRect
=
wrap
.
getBoundingClientRect
();
const
topRect
=
topLineEndEl
.
getBoundingClientRect
();
const
bottomRect
=
bottomLineEndEl
.
getBoundingClientRect
();
// 根据 CSS 的旋转原点:
// - top-line1: rotate(45deg) 且 transform-origin: 0 0,因此左侧“尖端”约等于 rect.top
// - bottom-line1: rotate(-45deg) 且 transform-origin: 0 100%,因此左侧“尖端”约等于 rect.bottom
// 但我们对齐的是“斜线中心线”,而不是外包矩形边缘。
// 该斜线块在样式里高度为 8px,所以中心线偏移 4px
const
LINE_THICKNESS_PX
=
8
;
const
topLineCenterY
=
topRect
.
top
+
LINE_THICKNESS_PX
/
2
;
const
bottomLineCenterY
=
bottomRect
.
bottom
-
LINE_THICKNESS_PX
/
2
;
const
desiredCenterY
=
(
topLineCenterY
+
bottomLineCenterY
)
/
2
;
// .right 里 junction-dot 高度为 24px,flex 会让 right-line 与其垂直居中
const
junctionCenterOffsetY
=
12
;
// 经验补偿:整体向上平移约 49px,使视觉交汇点精确落在两条斜线交点
const
VISUAL_OFFSET_Y
=
-
49
;
rightTop
.
value
=
(
desiredCenterY
-
wrapRect
.
top
-
junctionCenterOffsetY
+
VISUAL_OFFSET_Y
)
+
'px'
;
};
</
script
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
>
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论