提交 4d30163e authored 作者: yanpeng's avatar yanpeng

Merge branch 'pre' of http://8.140.26.4:10003/caijian/risk-monitor into yp-dev

流水线 #259 已通过 于阶段
in 1 分 31 秒
......@@ -18,13 +18,13 @@ const getWordCloudChart = data => {
// 其他形状你可以使用形状路径
// shape: 'circle', // 示例
// 或者自定义路径
gridSize: 15, // 网格大小,影响词间距。
gridSize: 5, // 网格大小,影响词间距。
sizeRange: [16, 36], // 定义词云中文字大小的范围
rotationRange: [0, 0],
// rotationRange: [-90, 90],
// rotationStep: 10,
drawOutOfBound: false, // 是否超出画布
shrinkToFit: true, // 是否自动缩小以适应容器
shrinkToFit: false, // 是否自动缩小以适应容器
// 字体
textStyle: {
color: function (params) {
......
......@@ -128,7 +128,19 @@ const homeTitleList = ref([
}
]);
const homeActiveTitleIndex = ref(0);
const homeActiveTitleIndex = computed(() => {
let activeIndex = 1
if (route.fullPath.includes('/ZMOverView')) {
activeIndex = 0
} else if (route.fullPath.includes('/dataLibrary')) {
activeIndex = 3
} else if (route.fullPath.includes('/chat') || route.fullPath.includes('/writtingAsstaint')) {
activeIndex = 2
} else {
activeIndex = 1
}
return activeIndex
})
const isShowMenu = ref(false);
const handleShowMenu = (index, isShow) => {
......@@ -148,79 +160,150 @@ const handleHoverMenu = isShow => {
isShowMenu.value = isShow;
};
const menuList = ref([
// {
// title: "中美科技博弈概览",
// icon: Menu1,
// path: "/ZMOverView"
// },
{
title: "科技法案",
icon: Menu2,
path: "/billHome",
active: false
},
{
title: "科技政令",
icon: Menu3,
path: "/decree",
active: false
},
{
title: "美国科技智库",
icon: Menu4,
path: "/thinkTank",
active: false
},
{
title: "出口管制",
icon: Menu5,
path: "/exportControl",
active: false
},
{
title: "科研合作限制",
icon: Menu6,
path: "/cooperationRestrictions",
active: false
},
{
title: "投融资限制",
icon: Menu7,
path: "/finance",
active: false
},
{
title: "市场准入限制",
icon: Menu8,
path: "/marketAccessRestrictions",
active: false
},
{
title: "规则限制",
icon: Menu9,
path: "/ruleRestrictions",
active: false
},
{
title: "美国科技人物观点",
icon: Menu10,
path: "/technologyFigures",
active: false
},
{
title: "美国主要创新主体动向",
icon: Menu11,
path: "/innovationSubject",
active: false
},
{
title: "美国科研资助体系",
icon: Menu12,
path: "/scientificFunding",
active: false
const menuList = computed(() => {
let menu = [
// {
// title: "中美科技博弈概览",
// icon: Menu1,
// path: "/ZMOverView"
// },
{
title: "科技法案",
icon: Menu2,
path: "/billHome",
active: false
},
{
title: "科技政令",
icon: Menu3,
path: "/decree",
active: false
},
{
title: "美国科技智库",
icon: Menu4,
path: "/thinkTank",
active: false
},
{
title: "出口管制",
icon: Menu5,
path: "/exportControl",
active: false
},
{
title: "科研合作限制",
icon: Menu6,
path: "/cooperationRestrictions",
active: false
},
{
title: "投融资限制",
icon: Menu7,
path: "/finance",
active: false
},
{
title: "市场准入限制",
icon: Menu8,
path: "/marketAccessRestrictions",
active: false
},
{
title: "规则限制",
icon: Menu9,
path: "/ruleRestrictions",
active: false
},
{
title: "美国科技人物观点",
icon: Menu10,
path: "/technologyFigures",
active: false
},
{
title: "美国主要创新主体动向",
icon: Menu11,
path: "/innovationSubject",
active: false
},
{
title: "美国科研资助体系",
icon: Menu12,
path: "/scientificFunding",
active: false
}
]
switch (route.fullPath) {
case '/billHome':
menu.forEach(item => {
item.active = false
})
menu[0].active = true
break
case '/decree':
menu.forEach(item => {
item.active = false
})
menu[1].active = true
break
case '/thinkTank':
menu.forEach(item => {
item.active = false
})
menu[2].active = true
break
case '/exportControl':
menu.forEach(item => {
item.active = false
})
menu[3].active = true
break
case '/cooperationRestrictions':
menu.forEach(item => {
item.active = false
})
menu[4].active = true
break
case '/finance':
menu.forEach(item => {
item.active = false
})
menu[5].active = true
break
case '/marketAccessRestrictions':
menu.forEach(item => {
item.active = false
})
menu[6].active = true
break
case '/ruleRestrictions':
menu.forEach(item => {
item.active = false
})
menu[7].active = true
break
case '/technologyFigures':
menu.forEach(item => {
item.active = false
})
menu[8].active = true
break
case '/innovationSubject':
menu.forEach(item => {
item.active = false
})
menu[9].active = true
break
case '/scientificFunding':
menu.forEach(item => {
item.active = false
})
menu[10].active = true
break
}
]);
return menu
})
const isShowTool = ref(false);
......@@ -243,13 +326,7 @@ const toolList = ref([
])
const handleToModule = (item, index) => {
window.sessionStorage.setItem('homeActiveTitleIndex', index)
if (index === 1) {
homeActiveTitleIndex.value = index
menuList.value.forEach(val => {
val.active = false
})
item.active = true
router.push({
path: item.path
})
......@@ -268,10 +345,7 @@ const handleToModule = (item, index) => {
};
const handleClickTitle = (item, index) => {
if (index === 0 || index === 3) {
window.sessionStorage.setItem('homeActiveTitleIndex', index)
homeActiveTitleIndex.value = index
router.push(item.path)
}
};
......@@ -282,17 +356,8 @@ const handleClickToolBox = () => {
onMounted(() => {
handleGetPersonType();
if (route.query.titleIndex) {
homeActiveTitleIndex.value = Number(route.query.titleIndex)
} else {
homeActiveTitleIndex.value = Number(window.sessionStorage.getItem('homeActiveTitleIndex'))
}
});
onUnmounted(() => {
window.sessionStorage.removeItem('homeActiveTitleIndex')
})
</script>
<style lang="scss" scoped>
......
......@@ -73,8 +73,9 @@ const setChart = (option, chartId, allowClick, selectParam) => {
});
window.open(route.href, "_blank");
} else if (params.componentType === 'series' && params.seriesType === 'bar') {
const year = params.name.slice(0, 4)
const quatarNum = Number(params.name[params.name.length - 1])
selectParam.selectedDate = JSON.stringify(getQuarterRange(selectParam.selectedDate, quatarNum))
selectParam.selectedDate = JSON.stringify(getQuarterRange(year, quatarNum))
const route = router.resolve({
path: "/dataLibrary/dataDecree",
query: selectParam
......@@ -86,7 +87,7 @@ const setChart = (option, chartId, allowClick, selectParam) => {
case '科技智库报告':
if (selectParam.key === 1) {
selectParam.domains = params.seriesName
const year = params.name.slice(0,4)
const year = params.name.slice(0, 4)
const quatarNum = Number(params.name[params.name.length - 1])
selectParam.selectedDate = JSON.stringify(getQuarterRange(year, quatarNum))
const route = router.resolve({
......
......@@ -1093,11 +1093,21 @@ const handleBox9Data = async () => {
const selectedIndex = box9LegislativeStatusList.value.findIndex(
item => item.value === box9LegislativeStatus.value
);
const arr = [
{ label: "提出法案", value: "提案" },
{ label: "众议院通过", value: "众议院通过" },
{ label: "参议院通过", value: "参议院通过" },
{ label: "解决分歧", value: "分歧已解决" },
{ label: "完成立法", value: "完成立法" }
]
const status = arr.filter(item => {
return item.value === box9LegislativeStatus.value
})[0].label
const selectParam = {
moduleType: '国会法案',
key: 2,
selectedDate: box9selectetedTime.value ? JSON.stringify([box9selectetedTime.value + '-01-01', box9selectetedTime.value + '-12-31']) : '',
selectedStatus: box9LegislativeStatus.value ? box9LegislativeStatus.value : '全部阶段',
selectedStatus: status ? status : '全部阶段',
isInvolveCn: true
}
box9ChartInstance = setChart(box9Chart, "box9Chart", true, selectParam);
......
......@@ -2,68 +2,93 @@
<div class="process-overview-wrap">
<AnalysisBox title="流程概要" :showAllBtn="false">
<div class="main">
<div class="left" :style="{ width: (maxLineWidth + 250) + 'px' }">
<div class="left" :style="{ width: boardWidth + 'px' }">
<div class="top">
<div class="top-line" :style="{ width: lineWidth }">
<div class="top-line1" ref="topLineEndRef"></div>
<div class="arrow-track">
<div
class="arrow-segment arrow-segment-top"
v-for="i in mainArrowCount"
:key="`top-main-${i}`"
></div>
</div>
<div class="top-line1" ref="topLineEndRef">
<div class="arrow-track">
<div
class="arrow-segment arrow-segment-top"
v-for="i in diagonalArrowCount"
:key="`top-diagonal-${i}`"
></div>
</div>
</div>
</div>
<div class="start">
<div class="icon">
<img src="./assets/images/logo1.png" alt="" />
</div>
<div class="name">{{ "参议院" }}</div>
<div class="name">参议院</div>
</div>
<div class="content-box" :style="senateBoxStyle">
<div
class="item-box"
v-for="slot in senateSlots"
v-for="slot in senateEventsPositioned"
:key="slot.key"
style="width: 280px; flex-shrink: 0;"
:style="{ left: slot.left + 'px', width: TIMELINE_ITEM_WIDTH_PX + 'px' }"
>
<template v-if="slot.item">
<div class="item-box-dot">
<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 class="item-box-dot">
<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-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 class="item-header-icon" @click="handleClickDetail(true, slot.item, $event)">
<img src="./assets/images/item-header-icon.png" alt="" />
</div>
</div>
<div class="item-time">
{{ slot.item.actionDate }}
<div class="item-info" v-if="slot.item.agreeVote !== null || slot.item.disagreeVote !== null">
{{ formatVoteText(slot.item) }}
</div>
</template>
<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 class="item-time">{{ slot.item.actionDate }}</div>
</div>
</div>
</div>
<div class="bottom">
<div class="bottom-line" :style="{ width: lineWidth }">
<div class="bottom-line1" ref="bottomLineEndRef"></div>
<div class="arrow-track">
<div
class="arrow-segment arrow-segment-bottom"
v-for="i in mainArrowCount"
:key="`bottom-main-${i}`"
></div>
</div>
<div class="bottom-line1" ref="bottomLineEndRef">
<div class="arrow-track">
<div
class="arrow-segment arrow-segment-bottom"
v-for="i in diagonalArrowCount"
:key="`bottom-diagonal-${i}`"
></div>
</div>
</div>
</div>
<div class="start">
<div class="name">{{ "众议院" }}</div>
<div class="name">众议院</div>
<div class="icon">
<img src="./assets/images/logo2.png" alt="" />
</div>
......@@ -71,51 +96,93 @@
<div class="content-box" :style="houseBoxStyle">
<div
class="item-box"
v-for="slot in houseSlots"
v-for="slot in houseEventsPositioned"
:key="slot.key"
style="width: 280px; flex-shrink: 0;"
:style="{ left: slot.left + 'px', width: TIMELINE_ITEM_WIDTH_PX + 'px' }"
>
<template v-if="slot.item">
<div class="item-time">
{{ slot.item.actionDate }}
</div>
<div class="item-box-dot">
<img src="./assets/images/bottom-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 class="item-time">{{ slot.item.actionDate }}</div>
<div class="item-box-dot">
<img src="./assets/images/bottom-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-info" v-if="slot.item.agreeVote !== null || slot.item.disagreeVote !== null">
{{ formatVoteText(slot.item) }}
<div class="item-header-icon" @click="handleClickDetail(true, slot.item, $event)">
<img src="./assets/images/item-header-icon.png" alt="" />
</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 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>
</template>
</div>
</div>
</div>
</div>
<div class="right" :style="{ left: rightPos, top: rightTop }">
<div class="junction-dot">
<div class="inner-dot"></div>
</div>
<div class="right-line"></div>
<div class="right-line" :style="{ width: sharedLineWidth + 'px' }">
<div class="arrow-track">
<div
class="arrow-segment arrow-segment-shared"
v-for="i in rightArrowCount"
:key="`right-${i}`"
></div>
</div>
</div>
</div>
<div class="shared-content-box" :style="sharedBoxStyle">
<div
class="item-box"
v-for="item in sharedEvents"
:key="`shared-${item.id}`"
style="width: 280px; flex-shrink: 0;"
>
<div class="item-time">{{ item.actionDate }}</div>
<div class="item-box-dot"></div>
<div class="item-content">
<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 class="item-info" v-if="item.agreeVote !== null || item.disagreeVote !== null">
{{ formatVoteText(item) }}
</div>
<div class="item-main" v-if="item.fynrList && item.fynrList.length">
<div
class="item-main-item"
v-for="(sub, subIndex) in item.fynrList"
:key="`shared-${item.id}-${subIndex}-${sub}`"
>
<div class="icon"></div>
<CommonPrompt :content="sub">
<div class="text">{{ sub }}</div>
</CommonPrompt>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
......@@ -136,38 +203,54 @@ import { getBillDyqkSummary } from "@/api/bill";
import CommonPrompt from "../../commonPrompt/index.vue";
import ProcessOverviewDetailDialog from "../../ProcessOverviewDetailDialog.vue";
// 获取法案流程
const actionList = ref([]);
const isShowDetailDialog = ref(false);
const currentDetailItem = ref({});
const dialogPos = ref({ left: "0px", top: "0px" });
const ORG_SENATE = "参议院";
const ORG_HOUSE = "众议院";
const PRESIDENT_KEYWORD = "呈递给总统";
const TIMELINE_ITEM_WIDTH_PX = 280;
const ARROW_SEGMENT_TOTAL_PX = 16;
const DIAGONAL_LINE_WIDTH_PX = 127;
const getBillDyqkSummaryList = async () => {
try {
const billId = window.sessionStorage.getItem("billId");
if (!billId) return; // 防止 id 为空
const params = {
id: billId
};
const res = await getBillDyqkSummary(params);
if (res && res.code === 200) {
// 确保赋值的是数组,如果 data 为 null 则给空数组
actionList.value = Array.isArray(res.data) ? res.data : [];
} else {
if (!billId) {
actionList.value = [];
return;
}
const res = await getBillDyqkSummary({ id: billId });
actionList.value = res && res.code === 200 && Array.isArray(res.data) ? res.data : [];
} catch (error) {
actionList.value = []; // 出错时也重置为空数组
actionList.value = [];
}
};
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 isDualEvent = (item) => {
const type = String(item?.congressType || "");
return type.includes("双院");
};
const isSenateEvent = (item) => {
const org = String(item?.orgName || "");
const type = String(item?.congressType || "");
return org === ORG_SENATE || type.includes(ORG_SENATE);
};
const isHouseEvent = (item) => {
const org = String(item?.orgName || "");
const type = String(item?.congressType || "");
return org === ORG_HOUSE || type.includes(ORG_HOUSE);
};
const formatVoteText = (item) => {
if (!item) return "";
const agree = item.agreeVote ?? 0;
......@@ -175,130 +258,140 @@ const formatVoteText = (item) => {
return `${agree}赞成:${disagree}反对`;
};
// 总统交汇节点(用于确定两条时间线的汇合位置)
const presidentAction = computed(() => {
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);
return String(a.id ?? "").localeCompare(String(b.id ?? ""));
});
});
// 交汇点(总统节点)在全局时间线里的位置: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);
const idx = sortedTimeline.value.findIndex(
(item) => item.actionTitle && item.actionTitle.includes(PRESIDENT_KEYWORD)
);
return idx >= 0 ? idx : sortedTimeline.value.length;
});
// 两条时间线共享同一组时间步(每个时间步只展示属于该阵营的事件;其他阵营用空占位对齐)
const timelineSlots = computed(() => {
return sortedTimeline.value.slice(0, mergeIndexExclusive.value);
const dualLaneTimeline = computed(() => {
return sortedTimeline.value
.slice(0, mergeIndexExclusive.value)
.filter((item) => !isDualEvent(item));
});
const senateEventsPositioned = computed(() => {
const list = [];
let slotIndex = 0;
dualLaneTimeline.value.forEach((item) => {
if (!isSenateEvent(item)) return;
list.push({
key: item.id,
item,
left: slotIndex * TIMELINE_ITEM_WIDTH_PX
});
slotIndex += 1;
});
return list;
});
const senateSlots = computed(() => {
return timelineSlots.value.map((step) => ({
key: step.id,
item: step.orgName === ORG_SENATE ? step : null
}));
const houseEventsPositioned = computed(() => {
const list = [];
let slotIndex = 0;
dualLaneTimeline.value.forEach((item) => {
if (!isHouseEvent(item)) return;
list.push({
key: item.id,
item,
left: slotIndex * TIMELINE_ITEM_WIDTH_PX
});
slotIndex += 1;
});
return list;
});
const houseSlots = computed(() => {
return timelineSlots.value.map((step) => ({
key: step.id,
item: step.orgName === ORG_HOUSE ? step : null
}));
const sharedEvents = computed(() => {
return sortedTimeline.value.filter((item, idx) => {
return isDualEvent(item) || idx >= mergeIndexExclusive.value;
});
});
const timelineCount = computed(() => timelineSlots.value.length);
const dualLaneCount = computed(() => {
return Math.max(senateEventsPositioned.value.length, houseEventsPositioned.value.length);
});
// 计算最大线条宽度数值(两条线共享时间步长度)
const maxLineWidth = computed(() => {
const senateWidth = 254 + timelineCount.value * TIMELINE_ITEM_WIDTH_PX;
const houseWidth = 150 + timelineCount.value * TIMELINE_ITEM_WIDTH_PX;
const senateWidth = 254 + dualLaneCount.value * TIMELINE_ITEM_WIDTH_PX;
const houseWidth = 150 + dualLaneCount.value * TIMELINE_ITEM_WIDTH_PX;
return Math.max(senateWidth, houseWidth);
});
// 绑定给线条的样式
const lineWidth = computed(() => {
return maxLineWidth.value + 'px';
const lineWidth = computed(() => `${maxLineWidth.value}px`);
const sharedLineWidth = computed(() => {
const w = sharedEvents.value.length * TIMELINE_ITEM_WIDTH_PX;
return Math.max(70, w);
});
// 参议院容器宽度:线条总长 + 线条左偏移(110) - 容器左偏移(254)
const senateBoxStyle = computed(() => {
return {
width: (maxLineWidth.value + 110 - 254) + 'px',
justifyContent: 'flex-start'
};
const boardWidth = computed(() => {
return maxLineWidth.value + 90 + 24 + sharedLineWidth.value + 180;
});
// 众议院容器宽度:线条总长 + 线条左偏移(110) - 容器左偏移(150)
const houseBoxStyle = computed(() => {
return {
width: (maxLineWidth.value + 110 - 150) + 'px',
justifyContent: 'flex-start'
};
const mainArrowCount = computed(() => {
return Math.max(1, Math.ceil(maxLineWidth.value / ARROW_SEGMENT_TOTAL_PX));
});
const rightPos = computed(() => {
// 右侧节点位置 = 线条宽度 + 斜线水平投影长度(约90px)
return (maxLineWidth.value + 90) + 'px';
const diagonalArrowCount = computed(() => {
return Math.max(1, Math.ceil(DIAGONAL_LINE_WIDTH_PX / ARROW_SEGMENT_TOTAL_PX));
});
const rightArrowCount = computed(() => {
return Math.max(1, Math.ceil(sharedLineWidth.value / ARROW_SEGMENT_TOTAL_PX));
});
const senateBoxStyle = computed(() => ({
width: `${maxLineWidth.value + 110 - 254}px`,
justifyContent: "flex-start"
}));
const houseBoxStyle = computed(() => ({
width: `${maxLineWidth.value + 110 - 150}px`,
justifyContent: "flex-start"
}));
const rightPos = computed(() => `${maxLineWidth.value + 90}px`);
const sharedBoxStyle = computed(() => ({
left: `${maxLineWidth.value + 219}px`,
width: `${Math.max(sharedLineWidth.value, sharedEvents.value.length * TIMELINE_ITEM_WIDTH_PX)}px`
}));
const topLineEndRef = ref(null);
const bottomLineEndRef = ref(null);
const rightTop = ref('370px');
const isShowDetailDialog = ref(false);
const currentDetailItem = ref({});
const dialogPos = ref({ left: '0px', top: '0px' });
const rightTop = ref("370px");
const handleClickDetail = (isShow, item = {}, event = null) => {
isShowDetailDialog.value = isShow;
if (isShow) {
currentDetailItem.value = item;
if (event) {
// 计算弹窗位置,出现在点击位置附近(偏移一些避免挡住鼠标)
const x = event.clientX;
const y = event.clientY;
// 获取包裹容器的偏移量,因为弹窗是 absolute 定位在 wrap 里的
const wrap = document.querySelector('.process-overview-wrap');
const rect = wrap.getBoundingClientRect();
let left = x - rect.left + 20;
let top = y - rect.top - 50;
// 边界处理:防止超出右边界
if (left + 480 > rect.width) {
left = x - rect.left - 500;
}
if (!isShow) return;
dialogPos.value = {
left: left + 'px',
top: top + 'px'
};
}
currentDetailItem.value = item;
if (!event) return;
const wrap = document.querySelector(".process-overview-wrap");
if (!wrap) return;
const rect = wrap.getBoundingClientRect();
let left = event.clientX - rect.left + 20;
let top = event.clientY - rect.top - 50;
if (left + 480 > rect.width) {
left = event.clientX - rect.left - 500;
}
dialogPos.value = { left: `${left}px`, top: `${top}px` };
};
// 挂载阶段调用
onMounted(async () => {
await getBillDyqkSummaryList();
await nextTick();
......@@ -306,9 +399,7 @@ onMounted(async () => {
});
const updateRightTop = () => {
// 交汇点需要精确对齐上下两条斜线端点在页面中的 y 坐标
// rightTop 是相对 .process-overview-wrap 的 absolute top
const wrap = document.querySelector('.process-overview-wrap');
const wrap = document.querySelector(".process-overview-wrap");
if (!wrap) return;
const topLineEndEl = topLineEndRef.value;
......@@ -319,22 +410,12 @@ const updateRightTop = () => {
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';
rightTop.value = `${desiredCenterY - wrapRect.top - 12 - 49}px`;
};
</script>
......@@ -345,51 +426,6 @@ const updateRightTop = () => {
margin-top: 16px;
position: relative;
.box-header {
width: 100%;
height: 56px;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
line-height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
display: flex;
justify-content: flex-end;
gap: 4px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
}
}
.main {
margin-left: 24px;
margin-right: 24px;
......@@ -398,7 +434,6 @@ const updateRightTop = () => {
overflow-x: auto;
overflow-y: hidden;
/* 自定义滚动条样式,模拟进度条 */
&::-webkit-scrollbar {
height: 8px;
}
......@@ -423,10 +458,37 @@ const updateRightTop = () => {
display: flex;
flex-direction: column;
justify-content: center;
/* 确保内容区域宽度足够展示所有节点和右侧汇合点 */
min-width: fit-content;
padding-right: 150px;
.arrow-track {
width: 100%;
height: 8px;
display: flex;
align-items: center;
overflow: hidden;
}
.arrow-segment {
width: 14px;
height: 8px;
margin-right: 2px;
flex-shrink: 0;
clip-path: polygon(0 0, 80% 0, 100% 50%, 80% 100%, 0 100%, 14% 50%);
}
.arrow-segment-top {
background: #3c8bf7;
}
.arrow-segment-bottom {
background: #ffac4d;
}
.arrow-segment-shared {
background: #5f656c;
}
.top {
height: 260px;
display: flex;
......@@ -437,8 +499,6 @@ const updateRightTop = () => {
left: 110px;
top: 242px;
height: 8px;
width: 1100px;
background: url("./assets/images/top-line-icon.png");
.top-line1 {
position: absolute;
......@@ -446,7 +506,6 @@ const updateRightTop = () => {
top: 0;
height: 8px;
width: 127px;
background: url("./assets/images/top-line-icon.png");
transform: rotate(45deg);
transform-origin: 0 0;
}
......@@ -475,211 +534,32 @@ const updateRightTop = () => {
width: 100px;
height: 30px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 8;
padding: 1px 8px;
box-sizing: border-box;
border: 1px solid rgba(186, 224, 255, 1);
border-radius: 4px;
background: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 20px;
}
}
.content-box {
display: flex;
position: relative;
margin-left: 134px;
margin-bottom: 19px;
.item-box {
padding: 2px 15px;
display: flex;
flex-direction: column;
justify-content: flex-end;
position: relative;
.item-time {
position: absolute;
bottom: -50px;
left: 30px;
height: 30px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
.item-box-dot {
position: absolute;
z-index: 9999;
left: 10px;
bottom: -7px;
width: 12px;
height: 12px;
img {
width: 100%;
height: 100%;
}
}
.item-content {
border-left: 1px solid rgb(20, 89, 187);
}
.item-header {
display: flex;
align-items: center;
height: 30px;
padding: 0 10px;
margin-bottom: 8px;
.item-title {
line-height: 26px;
color: rgb(59, 65, 75);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
span {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
.item-header-icon {
margin-left: 5px;
width: 16px;
height: 16px;
cursor: pointer;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
}
}
}
.item-info {
padding: 0 10px;
color: rgb(59, 65, 75);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
margin-bottom: 4px;
}
.item-main {
padding: 5px 10px;
.item-main-item {
display: flex;
align-items: baseline;
width: 269px;
margin-bottom: 4px;
.icon {
margin: 9px 12px;
width: 6px;
height: 6px;
border-radius: 3px;
background: #84888e;
}
.text {
width: 240px;
color: rgb(59, 65, 75);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
height: 48px;
}
:deep(.text-ellipsis) {
white-space: normal !important;
}
}
}
.item-footer {
border-left: 1px solid rgb(20, 89, 187);
display: flex;
padding: 8px 15px;
justify-content: space-between;
.item-footer-box {
display: flex;
margin-left: 12px;
.icon {
position: relative;
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
.inner-icon {
position: absolute;
right: -5px;
top: 0;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.8);
display: flex;
box-sizing: border-box;
padding: 1px;
img {
width: 10px;
height: 10px;
}
}
}
.text {
margin-left: 9px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 24px;
}
}
}
position: absolute;
}
}
}
.bottom {
margin-top: 150px;
// height: 300px;
display: flex;
position: relative;
......@@ -688,8 +568,6 @@ const updateRightTop = () => {
left: 110px;
top: 11px;
height: 8px;
width: 1100px;
background: url("./assets/images/bottom-line-icon.png");
.bottom-line1 {
position: absolute;
......@@ -697,7 +575,6 @@ const updateRightTop = () => {
top: 0;
height: 8px;
width: 127px;
background: url("./assets/images/bottom-line-icon.png");
transform: rotate(-45deg);
transform-origin: 0 100%;
}
......@@ -705,7 +582,6 @@ const updateRightTop = () => {
.start {
width: 120px;
// height: 260px;
display: flex;
flex-direction: column;
justify-content: flex-start;
......@@ -726,542 +602,228 @@ const updateRightTop = () => {
width: 100px;
height: 30px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 8;
padding: 1px 8px;
box-sizing: border-box;
border-radius: 4px;
border: 1px solid rgba(255, 229, 143, 1);
background: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 20px;
}
}
.content-box {
display: flex;
position: relative;
margin-left: 30px;
.item-box {
padding: 20px 15px;
display: flex;
flex-direction: column;
justify-content: flex-start;
position: relative;
.item-time {
position: absolute;
top: -30px;
left: 30px;
height: 30px;
color: rgba(255, 172, 77, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
.item-box-dot {
position: absolute;
z-index: 9999;
left: 10px;
top: 7px;
width: 12px;
height: 12px;
img {
width: 100%;
height: 100%;
}
}
.item-content {
border-left: 1px solid rgba(255, 172, 77, 1);
}
.item-header {
display: flex;
align-items: center;
height: 30px;
padding: 5px 10px;
margin-top: 8px;
.item-title {
line-height: 26px;
color: rgb(59, 65, 75);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
span {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
.item-header-icon {
margin-left: 5px;
width: 16px;
height: 16px;
cursor: pointer;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
}
}
}
.item-info {
padding: 0 10px;
color: rgb(59, 65, 75);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
margin-top: 8px;
}
.item-main {
padding: 5px 10px;
.item-main-item {
display: flex;
align-items: baseline;
width: 269px;
margin-top: 4px;
.icon {
margin: 9px 12px;
width: 6px;
height: 6px;
border-radius: 3px;
background: #84888e;
}
.text {
width: 240px;
color: rgb(59, 65, 75);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
height: 48px;
}
:deep(.text-ellipsis) {
white-space: normal !important;
}
}
}
.item-footer {
border-left: 1px solid rgba(255, 172, 77, 1);
display: flex;
padding: 8px 15px;
justify-content: space-between;
.item-footer-box {
display: flex;
margin-left: 12px;
.icon {
position: relative;
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
.inner-icon {
position: absolute;
right: -5px;
top: 0;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.8);
display: flex;
box-sizing: border-box;
padding: 1px;
img {
width: 10px;
height: 10px;
}
}
}
.text {
margin-left: 9px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 24px;
}
}
}
position: absolute;
}
}
}
.right {
position: absolute;
top: 370px;
margin-left: 90px;
.item-box {
padding: 2px 15px;
display: flex;
align-items: center;
flex-direction: column;
position: relative;
.junction-dot {
width: 24px;
height: 24px;
.item-time {
position: absolute;
left: 30px;
height: 30px;
color: rgba(95, 101, 108, 1);
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
.item-box-dot {
position: absolute;
z-index: 5;
left: 10px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #5f656c;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
margin-right: -4px; // 稍微重叠
.inner-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #fff;
}
}
.right-line {
width: 70px;
height: 8px;
background: url("./assets/images/right-line-icon.png");
.item-content {
border-left: 1px solid #919191;
}
}
}
}
.footer {
width: 1710px;
height: 76px;
display: flex;
border: 1px solid rgba(231, 241, 255, 1);
border-radius: 4px;
background: rgba(246, 251, 255, 1);
margin-left: 13px;
.footer-left {
margin-top: 28px;
margin-left: 12px;
width: 19px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.footer-center {
margin-left: 13px;
margin-top: 8px;
width: 1617px;
height: 60px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 24px;
}
.footer-right {
margin-left: 13px;
margin-top: 28px;
width: 24px;
height: 24px;
background: rgba(231, 241, 255, 1);
border-radius: 12px;
background: #e7f1ff;
box-sizing: border-box;
padding: 3px;
img {
width: 100%;
height: 100%;
}
}
}
.dialog-wrapper1 {
position: absolute;
z-index: 9999;
top: -120px;
width: 1600px;
height: 1000px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 10px;
background: rgba(255, 255, 255, 1);
.dialog-header {
height: 48px;
box-sizing: border-box;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
justify-content: space-between;
.item-header {
display: flex;
align-items: center;
height: 30px;
padding: 0 10px;
margin-bottom: 8px;
.item-title {
line-height: 26px;
color: rgb(59, 65, 75);
font-size: 20px;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.header-left {
display: flex;
span {
color: rgba(132, 136, 142, 1);
font-size: 14px;
font-weight: 400;
}
}
.icon {
width: 20px;
height: 20px;
margin-left: 14px;
margin-top: 14px;
.item-header-icon {
margin-left: 5px;
width: 16px;
height: 16px;
cursor: pointer;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
img {
width: 100%;
height: 100%;
}
}
}
.title {
margin-left: 11px;
margin-top: 16px;
height: 14px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 14px;
.item-info {
padding: 0 10px;
color: rgb(59, 65, 75);
font-size: 16px;
line-height: 24px;
margin-bottom: 4px;
}
}
.header-right {
width: 16px;
height: 16px;
margin-top: 16px;
margin-right: 16px;
cursor: pointer;
.item-main {
padding: 5px 10px;
img {
width: 100%;
height: 100%;
}
}
}
.dialog-info {
display: flex;
width: 1552px;
height: 64px;
margin: 12px auto;
.info-box1 {
span {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 14px;
}
}
.item-main-item {
display: flex;
align-items: baseline;
width: 269px;
margin-bottom: 4px;
.icon {
margin: 9px 12px;
width: 6px;
height: 6px;
border-radius: 3px;
background: #84888e;
}
.info-box2 {
margin-left: 24px;
.text {
width: 240px;
color: rgb(59, 65, 75);
font-size: 16px;
line-height: 24px;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
height: 48px;
}
span {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 14px;
:deep(.text-ellipsis) {
white-space: normal !important;
}
}
}
}
.info-box3 {
display: flex;
.top .item-box {
justify-content: flex-end;
.icon1 {
margin-left: 25px;
margin-top: 8px;
width: 14px;
height: 14px;
border-radius: 4px;
border: 1px solid #333;
.item-time {
bottom: -50px;
color: rgba(20, 89, 187, 1);
}
.icon2 {
margin-left: 25px;
margin-top: 8px;
width: 18px;
height: 18px;
border-radius: 4px;
img {
width: 18px;
height: 18px;
}
.item-box-dot {
bottom: -7px;
background: #1677ff;
}
.text {
margin-left: 9px;
margin-top: 9px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 14px;
.item-content {
border-left: 1px solid rgb(20, 89, 187);
}
}
.info-box4 {
margin-left: 500px;
margin-top: 5px;
position: relative;
.bottom .item-box {
padding-top: 20px;
justify-content: flex-start;
.icon {
position: absolute;
right: 5px;
top: 5px;
width: 16px;
height: 16px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
.item-time {
top: -30px;
color: rgba(255, 172, 77, 1);
}
}
}
.dialog-main {
height: 750px;
width: 1505px;
margin: 0 auto;
border-bottom: 1px solid #e9e9e9;
.dialog-main-header {
width: 1505px;
height: 54px;
display: flex;
background: rgba(59, 65, 75, 1);
color: #fff;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 54px;
.dialog-main-header-left {
flex: 1;
padding-left: 16px;
.item-box-dot {
top: 7px;
background: #faad14;
}
.dialog-main-header-right {
flex: 1;
.item-content {
border-left: 1px solid rgba(255, 172, 77, 1);
}
}
.dialog-main-item {
width: 1505px;
.right {
position: absolute;
top: 370px;
margin-left: 90px;
display: flex;
padding: 16px;
height: 139px;
box-sizing: border-box;
.dialog-main-left {
flex: 1;
.dialog-main-item-title {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
display: flex;
.title-left {
width: 96px;
}
align-items: center;
.title-right {
flex: 1;
}
}
.junction-dot {
width: 24px;
height: 24px;
border-radius: 50%;
background: #5f656c;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
margin-right: -4px;
.dialog-main-item-content {
margin-left: 96px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
.inner-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #fff;
}
}
.dialog-main-right {
flex: 1;
.right-line {
height: 8px;
}
}
.dialog-main-item-title {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
display: flex;
.shared-content-box {
position: absolute;
top: 170px;
display: flex;
.title-left {
width: 96px;
}
.item-box {
justify-content: flex-end;
.title-right {
flex: 1;
}
.item-time {
bottom: -50px;
}
.dialog-main-item-content {
margin-left: 96px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
.item-box-dot {
bottom: -7px;
background: #5f656c;
}
}
}
.dialog-main-item:nth-child(2n-1) {
border-radius: 4px;
background: rgba(248, 249, 250, 1);
}
}
.dialog-footer {
height: 100px;
display: flex;
justify-content: center;
align-items: center;
}
}
}
......
......@@ -693,10 +693,6 @@ const statusList = ref([
name: '提出法案',
id: '提出法案'
},
{
name: '通过法案',
id: '通过法案'
},
{
name: '众议院通过',
id: '众议院通过'
......@@ -706,8 +702,12 @@ const statusList = ref([
id: '参议院通过'
},
{
name: '分歧已解决',
id: '分歧已解决'
name: '解决分歧',
id: '解决分歧'
},
{
name: '完成立法',
id: '完成立法'
},
])
......
......@@ -459,6 +459,7 @@ const handleGetThinkTankReportIndustryCloud = async () => {
}));
// 该接口数据用于「报告关键词云」
box5Data.value = data;
console.log("box5Data", box5Data.value);
if (data.length) {
box5WordCloudKey.value += 1;
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论