提交 ca4247e0 authored 作者: 张伊明's avatar 张伊明

fix 修复深度挖掘样式问题

上级 e25eb5aa
流水线 #216 已通过 于阶段
in 1 分 26 秒
...@@ -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;
......
...@@ -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,25 +14,34 @@ ...@@ -14,25 +14,34 @@
<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"
v-for="slot in senateSlots"
:key="slot.key"
style="width: 280px; flex-shrink: 0;"
>
<template v-if="slot.item">
<div class="item-box-dot"> <div class="item-box-dot">
<img src="./assets/images/top-line-dot.png" alt="" /> <img src="./assets/images/top-line-dot.png" alt="" />
</div> </div>
<div class="item-content"> <div class="item-content">
<div class="item-header"> <div class="item-header">
<div class="item-title" :title="item.actionTitle"> <div class="item-title" :title="slot.item.actionTitle">
{{ item.actionTitle }} <span v-if="item.versionId">({{ item.versionId }})</span> {{ slot.item.actionTitle }} <span v-if="slot.item.versionId">({{ slot.item.versionId }})</span>
</div> </div>
<div class="item-header-icon" @click="handleClickDetail(true, item, $event)"> <div class="item-header-icon" @click="handleClickDetail(true, slot.item, $event)">
<img src="./assets/images/item-header-icon.png" alt="" /> <img src="./assets/images/item-header-icon.png" alt="" />
</div> </div>
</div> </div>
<div class="item-info" v-if="item.agreeVote !== null || item.disagreeVote !== null"> <div class="item-info" v-if="slot.item.agreeVote !== null || slot.item.disagreeVote !== null">
{{ (item.agreeVote || 0) + "赞成:" + (item.disagreeVote || 0) + "反对" }} {{ formatVoteText(slot.item) }}
</div> </div>
<div class="item-main" v-if="item.fynrList && item.fynrList.length"> <div class="item-main" v-if="slot.item.fynrList && slot.item.fynrList.length">
<div class="item-main-item" v-for="(sub, subIndex) in item.fynrList" :key="subIndex"> <div
class="item-main-item"
v-for="(sub, subIndex) in slot.item.fynrList"
:key="`${slot.item.id}-${subIndex}-${sub}`"
>
<div class="icon"></div> <div class="icon"></div>
<CommonPrompt :content="sub"> <CommonPrompt :content="sub">
<div class="text">{{ sub }}</div> <div class="text">{{ sub }}</div>
...@@ -41,15 +50,16 @@ ...@@ -41,15 +50,16 @@
</div> </div>
</div> </div>
<div class="item-time"> <div class="item-time">
{{ item.actionDate }} {{ slot.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,28 +69,37 @@ ...@@ -59,28 +69,37 @@
</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"
v-for="slot in houseSlots"
:key="slot.key"
style="width: 280px; flex-shrink: 0;"
>
<template v-if="slot.item">
<div class="item-time"> <div class="item-time">
{{ item.actionDate }} {{ slot.item.actionDate }}
</div> </div>
<div class="item-box-dot"> <div class="item-box-dot">
<img src="./assets/images/bottom-line-dot.png" alt="" /> <img src="./assets/images/bottom-line-dot.png" alt="" />
</div> </div>
<div class="item-content"> <div class="item-content">
<div class="item-header"> <div class="item-header">
<div class="item-title" :title="item.actionTitle"> <div class="item-title" :title="slot.item.actionTitle">
{{ item.actionTitle }} <span v-if="item.versionId">({{ item.versionId }})</span> {{ slot.item.actionTitle }} <span v-if="slot.item.versionId">({{ slot.item.versionId }})</span>
</div> </div>
<div class="item-header-icon" @click="handleClickDetail(true, item, $event)"> <div class="item-header-icon" @click="handleClickDetail(true, slot.item, $event)">
<img src="./assets/images/item-header-icon.png" alt="" /> <img src="./assets/images/item-header-icon.png" alt="" />
</div> </div>
</div> </div>
<div class="item-info" v-if="item.agreeVote !== null || item.disagreeVote !== null"> <div class="item-info" v-if="slot.item.agreeVote !== null || slot.item.disagreeVote !== null">
{{ (item.agreeVote || 0) + "赞成:" + (item.disagreeVote || 0) + "反对" }} {{ formatVoteText(slot.item) }}
</div> </div>
<div class="item-main" v-if="item.fynrList && item.fynrList.length"> <div class="item-main" v-if="slot.item.fynrList && slot.item.fynrList.length">
<div class="item-main-item" v-for="(sub, subIndex) in item.fynrList" :key="subIndex"> <div
class="item-main-item"
v-for="(sub, subIndex) in slot.item.fynrList"
:key="`${slot.item.id}-${subIndex}-${sub}`"
>
<div class="icon"></div> <div class="icon"></div>
<CommonPrompt :content="sub"> <CommonPrompt :content="sub">
<div class="text">{{ sub }}</div> <div class="text">{{ sub }}</div>
...@@ -88,10 +107,11 @@ ...@@ -88,10 +107,11 @@
</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 senateList = computed(() => { const sortedTimeline = computed(() => {
return actionList.value return [...actionList.value].sort((a, b) => {
.filter(item => item.orgName === "参议院" && (!presidentAction.value || item.id !== presidentAction.value.id)) const tA = getTime(a.actionDate);
.sort((a, b) => new Date(a.actionDate) - new Date(b.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 houseList = computed(() => { const mergeIndexExclusive = computed(() => {
return actionList.value if (!sortedTimeline.value.length) return 0;
.filter(item => item.orgName === "众议院" && (!presidentAction.value || item.id !== presidentAction.value.id)) if (!presidentAction.value) return sortedTimeline.value.length;
.sort((a, b) => new Date(a.actionDate) - new Date(b.actionDate));
const idx = sortedTimeline.value.findIndex((item) => item.id === presidentAction.value.id);
return idx >= 0 ? idx : sortedTimeline.value.length;
});
// 两条时间线共享同一组时间步(每个时间步只展示属于该阵营的事件;其他阵营用空占位对齐)
const timelineSlots = computed(() => {
return sortedTimeline.value.slice(0, mergeIndexExclusive.value);
}); });
// 计算最大线条宽度数值 const senateSlots = computed(() => {
return timelineSlots.value.map((step) => ({
key: step.id,
item: step.orgName === ORG_SENATE ? step : null
}));
});
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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论