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

合并分支 'zym-dev' 到 'pre'

Zym dev 查看合并请求 !281
流水线 #220 已通过 于阶段
in 2 分 36 秒
<svg viewBox="0 0 12 13" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.000000" height="13.000000" fill="none" customFrame="#000000">
<path id="矢量 1651" d="M8.05031 2.15239e-07C8.33835 2.69049e-07 8.61901 0.0965686 8.85205 0.275865C9.08508 0.455161 9.25853 0.70798 9.34755 0.998087C9.43656 1.28819 9.43656 1.6007 9.34755 1.8908C9.25853 2.18091 9.08508 2.43373 8.85205 2.61303C8.61901 2.79232 8.33835 2.88889 8.05031 2.88889L3.9583 2.88889C3.69558 2.88873 3.43848 2.80821 3.21793 2.65703C2.99737 2.50585 2.82274 2.29043 2.71502 2.03667C2.35984 2.08328 2.0252 2.23849 1.752 2.48332C1.4788 2.72814 1.27889 3.05197 1.17673 3.41519C1.07456 3.77842 1.07456 4.1653 1.17673 4.52853C1.27889 4.89176 1.4788 5.21558 1.752 5.46041C2.0252 5.70523 2.35984 5.86044 2.71502 5.90706C2.82284 5.65343 2.99751 5.43816 3.21806 5.28711C3.43861 5.13606 3.69565 5.05566 3.9583 5.05556L8.05031 5.05556C8.31755 5.05563 8.57888 5.13883 8.80185 5.29483C9.02483 5.45084 9.19961 5.67276 9.3045 5.93306C9.88944 5.98317 10.4467 6.21802 10.9042 6.60722C11.3618 6.99642 11.6985 7.52206 11.8708 8.11613C12.0431 8.7102 12.0431 9.34536 11.8708 9.93943C11.6985 10.5335 11.3618 11.0591 10.9042 11.4483C10.4467 11.8375 9.88944 12.0724 9.3045 12.1225C9.19961 12.3828 9.02483 12.6047 8.80185 12.7607C8.57888 12.9167 8.31755 12.9999 8.05031 13L3.9583 13C3.71881 13.0001 3.48352 12.9334 3.27608 12.8067C3.06865 12.6799 2.89638 12.4976 2.77661 12.2779C2.65684 12.0583 2.59378 11.8092 2.59378 11.5556C2.59378 11.3019 2.65684 11.0528 2.77661 10.8332C2.89638 10.6136 3.06865 10.4312 3.27608 10.3044C3.48352 10.1777 3.71881 10.111 3.9583 10.1111L8.05031 10.1111C8.60409 10.1111 9.07944 10.4607 9.29291 10.9626C9.64884 10.9171 9.98445 10.7624 10.2585 10.5177C10.5326 10.273 10.7333 9.94881 10.8358 9.58501C10.9384 9.22121 10.9384 8.83362 10.8358 8.46982C10.7333 8.10603 10.5326 7.78186 10.2585 7.53714C9.98445 7.29241 9.64884 7.13778 9.29291 7.09222C9.18531 7.34593 9.0108 7.56132 8.79036 7.71251C8.56992 7.86369 8.31294 7.94423 8.05031 7.94444L3.9583 7.94444C3.69087 7.94445 3.42933 7.86121 3.20622 7.70506C2.9831 7.54892 2.80826 7.32676 2.70342 7.06622C2.11732 7.01784 1.55849 6.78394 1.09952 6.39491C0.640554 6.00587 0.302655 5.47968 0.129709 4.88468C-0.0432364 4.28967 -0.0432363 3.65334 0.129709 3.05833C0.302654 2.46332 0.640554 1.93713 1.09952 1.54809C1.55849 1.15906 2.11732 0.925163 2.70342 0.876778C2.80847 0.616508 2.9834 0.394654 3.2065 0.238777C3.4296 0.0829001 3.69102 -0.000130273 3.9583 2.15239e-07L8.05031 2.15239e-07Z" fill="rgb(95,101,108)" fill-rule="nonzero" />
</svg>
<svg viewBox="0 0 12 13.3145" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.000000" height="13.314453" fill="none" customFrame="#000000">
<path id="矢量 52" d="M2.47123 9.11862L8.566 3.02386C8.82631 2.76355 8.82631 2.3415 8.566 2.08119C8.30569 1.82088 7.88364 1.82088 7.62333 2.08119L1.52857 8.17595C1.40356 8.30096 1.33333 8.4705 1.33333 8.64729C1.33333 9.01542 1.63176 9.31385 1.9999 9.31385C2.17668 9.31385 2.34623 9.24363 2.47123 9.11862ZM2.82867 10.6472L1 10.6472C0.447715 10.6472 0 10.1995 0 9.64719L0 8.23273C0 7.96752 0.105357 7.71316 0.292894 7.52563L7.62333 0.195193C7.74833 0.0702133 7.91787 0 8.09467 0C8.27147 0 8.441 0.0702133 8.566 0.195193L10.452 2.08119C10.577 2.20621 10.6472 2.37575 10.6472 2.55253C10.6472 2.7293 10.577 2.89884 10.452 3.02386L2.82867 10.6472L2.82867 10.6472ZM0.666666 11.9805L11.3333 11.9805C11.7015 11.9805 12 12.279 12 12.6472C12 13.0154 11.7015 13.3139 11.3333 13.3139L0.666667 13.3139C0.298477 13.3139 0 13.0154 0 12.6472C0 12.279 0.298477 11.9805 0.666666 11.9805Z" fill="rgb(255,255,255)" fill-rule="evenodd" />
</svg>
...@@ -38,13 +38,9 @@ ...@@ -38,13 +38,9 @@
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div <div class="left-box-bottom-item"
class="left-box-bottom-item" :class="{ leftBoxBottomItemActive: activeTitle === item.name }" v-for="item in tabs"
:class="{ leftBoxBottomItemActive: activeTitle === item.name }" :key="item.path" @click="emit('tab-click', item)">
v-for="item in tabs"
:key="item.path"
@click="emit('tab-click', item)"
>
<div class="icon"> <div class="icon">
<img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" /> <img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" />
<img v-else :src="item.icon" alt="" /> <img v-else :src="item.icon" alt="" />
...@@ -88,7 +84,13 @@ ...@@ -88,7 +84,13 @@
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div class="btn3" @click="emit('open-analysis')"> <div class="btn2" @click="emit('open-analysis', 'forsee')">
<div class="icon">
<img :src="btnIconForsee" alt="" />
</div>
<div class="text">{{ "进展预测" }}</div>
</div>
<div class="btn3" @click="emit('open-analysis', 'analysis')">
<div class="icon"> <div class="icon">
<img :src="btnIconAnalysis" alt="" /> <img :src="btnIconAnalysis" alt="" />
</div> </div>
...@@ -103,7 +105,8 @@ ...@@ -103,7 +105,8 @@
<script setup> <script setup>
import { computed } from "vue"; import { computed } from "vue";
import btnIconAnalysis from "@/views/thinkTank/ReportDetail/images/btn-icon3.png"; import btnIconAnalysis from "@/views/bill/billLayout/assets/icons/writting-icon.svg";
import btnIconForsee from "@/views/bill/billLayout/assets/icons/forsee-icon.svg";
const props = defineProps({ const props = defineProps({
billInfo: { billInfo: {
...@@ -347,6 +350,29 @@ const emit = defineEmits(["tab-click", "open-analysis"]); ...@@ -347,6 +350,29 @@ const emit = defineEmits(["tab-click", "open-analysis"]);
justify-content: flex-end; justify-content: flex-end;
gap: 8px; gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.btn2 {
cursor: pointer;
width: 120px;
height: 36px;
border-radius: 6px;
background: var(--bg-white-100);
border: 1px solid var(--bg-black-10);
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.btn3 { .btn3 {
cursor: pointer; cursor: pointer;
width: 120px; width: 120px;
...@@ -358,16 +384,6 @@ const emit = defineEmits(["tab-click", "open-analysis"]); ...@@ -358,16 +384,6 @@ const emit = defineEmits(["tab-click", "open-analysis"]);
align-items: center; align-items: center;
gap: 8px; gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text { .text {
height: 24px; height: 24px;
color: rgba(255, 255, 255, 1); color: rgba(255, 255, 255, 1);
...@@ -387,4 +403,3 @@ const emit = defineEmits(["tab-click", "open-analysis"]); ...@@ -387,4 +403,3 @@ const emit = defineEmits(["tab-click", "open-analysis"]);
} }
} }
</style> </style>
...@@ -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 />
...@@ -115,12 +108,24 @@ const handleClickMainHeaderBtn = item => { ...@@ -115,12 +108,24 @@ const handleClickMainHeaderBtn = item => {
}); });
}; };
const handleAnalysisClick = () => { const handleAnalysisClick = analysisType => {
const billId = route.query.billId;
if (!billId) return;
// 进展预测 -> 法案简介页(法案进展)
if (analysisType === "forsee") {
router.push({
path: `/billLayout/ProgressForecast/${billId}`,
});
return;
}
// 分析报告 -> 写作助手
router.push({ router.push({
path: "/writtingAsstaint", path: "/writtingAsstaint",
query: { query: {
topic: "法案", topic: "法案",
fileId: route.query.billId fileId: String(billId)
} }
}); });
}; };
...@@ -149,11 +154,13 @@ watch( ...@@ -149,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,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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论