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

style

调整法案详情页面法案进展样式 调整法案提出人相关样式 refactor 法案详情页面法案进展组件
上级 f09bfd39
...@@ -20,7 +20,7 @@ export function getBillInfo(params) { ...@@ -20,7 +20,7 @@ export function getBillInfo(params) {
export function getBillPerson(params) { export function getBillPerson(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/billInfoBean/person/${params.billId}`, url: `/bill/billInfoBean/person/${params.billId}`,
params, params,
}) })
} }
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
<div class="resource-library-section"> <div class="resource-library-section">
<div class="home-content-footer-header"> <div class="home-content-footer-header">
<div class="btn-box"> <div class="btn-box">
<div class="btn" :class="{ btnActive: activeTabName === cate.name, disabled: index > 2 }" <div class="btn" :class="{ btnActive: activeTabName === cate.name, disabled: cate.active === false}"
v-for="(cate, index) in tabList" :key="index" @click="index <= 2 && handleClickTab(cate)"> v-for="(cate, index) in tabList" :key="index" @click="cate.active === true && handleClickTab(cate)">
{{ cate.name }} {{ cate.name }}
</div> </div>
</div> </div>
</div> </div>
<div class="home-content-footer-main"> <div class="home-content-footer-main" :class="{ 'committee-full-layout': activeTabName === '委员会' }">
<div class="left" v-if="['国会法案', '国会议员', '议员合作关系'].includes(activeTabName)"> <div class="left" v-if="['国会法案', '国会议员', '议员合作关系'].includes(activeTabName)">
<div class="select-box"> <div class="select-box">
<div class="select-box-header"> <div class="select-box-header">
...@@ -77,8 +77,8 @@ ...@@ -77,8 +77,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="right"> <div class="right" :class="{ 'right-full': activeTabName === '委员会' }">
<div class="right-header"> <div class="right-header" v-if="activeTabName !== '委员会'">
<div class="right-header-box"> <div class="right-header-box">
<el-select v-model="footerSelect1" placeholder="选择委员会" style="width: 240px" @change="handleFooterSelect1Change"> <el-select v-model="footerSelect1" placeholder="选择委员会" style="width: 240px" @change="handleFooterSelect1Change">
<el-option v-for="item in postOrgList" :key="item.departmentId" :label="item.departmentName" :value="item.departmentId" /> <el-option v-for="item in postOrgList" :key="item.departmentId" :label="item.departmentName" :value="item.departmentId" />
...@@ -177,6 +177,22 @@ ...@@ -177,6 +177,22 @@
<div class="member-info">关注领域:{{ item.focus || '-' }}</div> <div class="member-info">关注领域:{{ item.focus || '-' }}</div>
</div> </div>
</div> </div>
<div v-else-if="activeTabName === '委员会'" class="committee-list">
<div class="committee-card" v-for="item in committeeList" :key="item.id">
<div class="committee-info">
<img class="committee-avatar" :src="item.avatar || defaultAvatar" alt="committee-avatar" />
<div class="committee-text">
<div class="committee-name">{{ item.name }}</div>
<div class="committee-desc">{{ item.desc }}</div>
</div>
</div>
<div class="committee-bill-grid">
<div class="committee-bill-item" v-for="(bill, idx) in item.bills" :key="`${item.id}-bill-${idx}`">
{{ bill }}
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -201,9 +217,9 @@ const props = defineProps({ ...@@ -201,9 +217,9 @@ const props = defineProps({
const tabList = ref([ const tabList = ref([
{ name: "国会法案", active: true }, { name: "国会法案", active: true },
{ name: "国会议员", active: false }, { name: "国会议员", active: true },
{ name: "议员合作关系", active: false }, { name: "议员合作关系", active: false },
{ name: "涉华委员会", active: false } { name: "委员会", active: true }
]); ]);
const activeTabName = ref("国会法案"); const activeTabName = ref("国会法案");
const handleClickTab = tab => { const handleClickTab = tab => {
...@@ -216,6 +232,10 @@ const handleClickTab = tab => { ...@@ -216,6 +232,10 @@ const handleClickTab = tab => {
if (tab.name === "国会法案") { if (tab.name === "国会法案") {
currentPage.value = 1; currentPage.value = 1;
handleGetBills(); handleGetBills();
return;
}
if (tab.name === "委员会") {
handleGetCommitteeList();
} }
}; };
...@@ -249,6 +269,30 @@ const memberTotal = ref(0); ...@@ -249,6 +269,30 @@ const memberTotal = ref(0);
const memberPageSize = ref(15); const memberPageSize = ref(15);
const memberCurrentPage = ref(1); const memberCurrentPage = ref(1);
const committeeList = ref([
{
id: "committee-1",
avatar: "",
name: "美中经济与安全审查委员会",
desc: "聚焦中美经贸、科技与国家安全议题,跟踪涉华政策与立法动态。",
bills: ["2025年涉华关键技术审查法案", "供应链安全与关键矿产保障法案", "对华投资限制与透明度加强法案", "联邦科研合作安全审查法案"]
},
{
id: "committee-2",
avatar: "",
name: "众议院外交事务委员会",
desc: "负责对外政策与国际关系立法,持续推进对华议题相关审议与听证。",
bills: ["印太战略资源配置法案", "对外援助合规与问责法案", "跨境数据安全与执法协作法案", "关键基础设施网络韧性法案"]
},
{
id: "committee-3",
avatar: "",
name: "参议院军事委员会",
desc: "围绕国防授权、军费及安全合作展开审查,关注涉华国防与技术议题。",
bills: ["国防授权法涉华条款(示例)", "先进半导体供应安全法案", "军民两用技术出口管制法案", "海外关键设施安全评估法案"]
}
]);
const bills = ref([]); const bills = ref([]);
const total = ref(0); const total = ref(0);
const pageSize = ref(4); const pageSize = ref(4);
...@@ -263,6 +307,20 @@ const getRiskTagClass = riskSignal => { ...@@ -263,6 +307,20 @@ const getRiskTagClass = riskSignal => {
return ""; return "";
}; };
// 获取委员会列表(占位)
const handleGetCommitteeList = async () => {
loading.value = true;
try {
// TODO: 接入委员会列表接口后,在此替换为真实请求并赋值 committeeList
// const res = await getCommitteeList(params);
// committeeList.value = res?.data?.content || [];
} catch (error) {
committeeList.value = [];
} finally {
loading.value = false;
}
};
const handleGetHylyList = async () => { const handleGetHylyList = async () => {
try { try {
const res = await getHylyList(); const res = await getHylyList();
...@@ -551,6 +609,17 @@ onMounted(() => { ...@@ -551,6 +609,17 @@ onMounted(() => {
justify-content: space-between; justify-content: space-between;
align-items: flex-start; align-items: flex-start;
&.committee-full-layout {
.right {
margin-left: 0;
width: 1600px;
.right-main {
height: auto;
}
}
}
.left { .left {
width: 300px; width: 300px;
padding-bottom: 33px; padding-bottom: 33px;
...@@ -662,6 +731,14 @@ onMounted(() => { ...@@ -662,6 +731,14 @@ onMounted(() => {
.right-main { .right-main {
height: 1264px; height: 1264px;
.member-grid,
.coop-list,
.committee-list {
display: grid;
column-gap: 16px;
row-gap: 16px;
}
.member-grid, .member-grid,
.coop-list { .coop-list {
display: grid; display: grid;
...@@ -834,6 +911,72 @@ onMounted(() => { ...@@ -834,6 +911,72 @@ onMounted(() => {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.committee-list {
grid-template-columns: 1fr;
.committee-card {
padding: 20px 24px;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: #fff;
}
.committee-info {
display: flex;
gap: 16px;
align-items: flex-start;
padding-bottom: 16px;
border-bottom: 1px solid #eaeced;
}
.committee-avatar {
width: 64px;
height: 64px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.committee-name {
color: #3b414b;
font-family: "Microsoft YaHei";
font-size: 20px;
font-weight: 700;
line-height: 28px;
}
.committee-desc {
margin-top: 8px;
color: #5f656c;
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
.committee-bill-grid {
margin-top: 16px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.committee-bill-item {
height: 48px;
padding: 0 14px;
border-radius: 6px;
background: #f5f8fc;
color: #3b414b;
font-family: "Microsoft YaHei";
font-size: 15px;
font-weight: 400;
line-height: 48px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.right-main-box { .right-main-box {
position: relative; position: relative;
width: 1280px; width: 1280px;
......
import "echarts-wordcloud";
const getWordCloudChart = (data = []) => {
const getWordCloudChart = (data) => {
const option = { const option = {
grid: { grid: {
left: 0, left: 0,
......
<template>
<div class="container">
<div class="svg-timeline">
<svg :viewBox="`0 0 ${svgWidth} ${svgHeight}`" width="100%" height="100%">
<line
v-if="nodes.length > 0"
:x1="nodes[0].x - 100"
:y1="nodes[0].y"
:x2="nodes[0].x"
:y2="nodes[0].y"
stroke="#e8f2ff"
stroke-width="2"
marker-end="url(#arrow)"
/>
<path :d="arcPaths" fill="none" stroke="#e8f2ff" stroke-width="2" marker-end="url(#arrow)" />
<circle
v-for="(node, idx) in nodes"
:key="'circle' + idx"
:cx="node.x"
:cy="node.y"
r="4"
fill="#fff"
stroke="#1677ff"
stroke-width="3"
/>
<line
v-for="(node, idx) in nodes"
:key="'line' + idx"
:x1="node.x"
:y1="node.y + 4"
:x2="node.x"
:y2="node.y + verticalLineLength"
stroke="#1677ff"
:stroke-width="verticalLineWidth"
/>
<foreignObject
v-for="(node, idx) in nodes"
:key="'fo-' + idx"
:x="node.x + 15"
:y="node.y + 5"
:width="nodeGapX - 30"
height="100"
style="overflow: visible;"
>
<div class="node-content" xmlns="http://www.w3.org/1999/xhtml">
<div class="date">{{ node.formattedDate }}</div>
<el-tooltip
effect="dark"
:content="node.actionTitle"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="title">{{ node.actionTitle }}</div>
</el-tooltip>
<div class="votes" v-if="node.voteString">{{ node.voteString }}</div>
</div>
</foreignObject>
<text
v-if="startMonth && nodes.length > 0"
:x="nodes[0].x - 110"
:y="nodes[0].y + 5"
text-anchor="end"
fill="rgb(5, 95, 194)"
font-size="16"
font-weight="700"
>
{{ startMonth }}
</text>
<line
v-if="nodes.length > 0"
:x1="nodes[nodes.length - 1].x"
:y1="nodes[nodes.length - 1].y"
:x2="nodes[nodes.length - 1].row % 2 === 0 ? nodes[nodes.length - 1].x + 100 : nodes[nodes.length - 1].x - 100"
:y2="nodes[nodes.length - 1].y"
stroke="#e8f2ff"
stroke-width="2"
marker-end="url(#arrow)"
/>
<text
v-if="endMonth && nodes.length > 0"
:x="nodes[nodes.length - 1].row % 2 === 0 ? nodes[nodes.length - 1].x + 110 : nodes[nodes.length - 1].x - 110"
:y="nodes[nodes.length - 1].y + 5"
:text-anchor="nodes[nodes.length - 1].row % 2 === 0 ? 'start' : 'end'"
fill="rgb(5, 95, 194)"
font-size="16"
font-weight="700"
>
{{ endMonth }}
</text>
</svg>
</div>
</div>
</template>
<script>
export default {
name: "SBillProgressTimeline",
props: ["dataList"],
data() {
return {
maxPerRow: 5,
nodeGapX: 200,
nodeGapY: 180,
leftMargin: 150,
verticalLineLength: 80,
verticalLineWidth: 1
};
},
computed: {
startMonth() {
if (this.sortedDataList.length === 0) return '';
const date = new Date(this.sortedDataList[0].actionDate);
return `${date.getFullYear()}${date.getMonth() + 1}月`;
},
endMonth() {
if (this.sortedDataList.length === 0) return '';
const date = new Date(this.sortedDataList[this.sortedDataList.length - 1].actionDate);
return `${date.getFullYear()}${date.getMonth() + 1}月`;
},
sortedDataList() {
if (!this.dataList || !Array.isArray(this.dataList)) return [];
return [...this.dataList]
.filter(item => item && item.actionDate)
.sort((a, b) => new Date(a.actionDate) - new Date(b.actionDate));
},
nodes() {
return this.sortedDataList.map((item, idx) => {
const row = Math.floor(idx / this.maxPerRow);
const col = idx % this.maxPerRow;
let x, y;
if (row % 2 === 0) {
x = this.leftMargin + col * this.nodeGapX + 50;
} else {
x = this.leftMargin + (this.maxPerRow - 1 - col) * this.nodeGapX + 50;
}
y = 60 + row * this.nodeGapY;
let formattedDate = "";
if (item.actionDate) {
const dateObj = new Date(item.actionDate);
if (!isNaN(dateObj.getTime())) {
formattedDate = `${dateObj.getMonth() + 1}${dateObj.getDate()}日`;
}
}
let voteString = "";
if (item.agreeVote !== null && item.agreeVote !== undefined && item.disagreeVote !== null && item.disagreeVote !== undefined) {
voteString = `${item.agreeVote}票赞成 : ${item.disagreeVote}票反对`;
}
return { ...item, x, y, row, formattedDate, voteString };
});
},
arcPaths() {
if (this.nodes.length < 2) return "";
let path = `M ${this.nodes[0].x} ${this.nodes[0].y}`;
for (let i = 1; i < this.nodes.length; i++) {
const prev = this.nodes[i - 1];
const curr = this.nodes[i];
const isTurnPoint = i % this.maxPerRow === 0;
if (isTurnPoint) {
const radius = 10 / 2;
if (prev.row % 2 === 0) {
path += ` L ${prev.x + radius} ${prev.y} A ${radius} ${radius} 0 0 1 ${curr.x - radius} ${curr.y} L ${curr.x} ${curr.y}`;
} else {
path += ` L ${prev.x - radius} ${prev.y} A ${radius} ${radius} 0 0 0 ${curr.x + radius} ${curr.y} L ${curr.x} ${curr.y}`;
}
} else {
path += ` L ${curr.x} ${curr.y}`;
}
}
return path;
},
svgWidth() {
return this.leftMargin + this.maxPerRow * this.nodeGapX + 50;
},
svgHeight() {
const listLength = (this.dataList && Array.isArray(this.dataList)) ? this.dataList.length : 0;
return Math.ceil(listLength / this.maxPerRow) * this.nodeGapY + 40;
}
}
};
</script>
<style lang="scss" scoped>
.container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.svg-timeline {
width: 100%;
.node-content {
font-family: Microsoft YaHei, sans-serif;
text-align: left;
padding-left: 4px;
.date {
color: rgb(5, 95, 194);
font-weight: 700;
font-size: 16px;
line-height: 22px;
margin-bottom: 0px;
margin-top: 6px;
}
.title {
color: rgb(59, 65, 75);
font-weight: 700;
font-size: 16px;
line-height: 22px;
margin-bottom: 0px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
display: block;
}
.votes {
color: rgb(95, 101, 108);
font-size: 16px;
font-weight: 400;
line-height: 22px;
}
}
}
</style>
<style>
.common-prompt-popper.el-popper {
padding: 8px 16px !important;
border-radius: 10px !important;
background-color: rgb(59, 65, 75) !important;
font-size: 16px !important;
font-weight: 400 !important;
font-family: "Microsoft YaHei" !important;
line-height: 30px !important;
color: #fff !important;
border: none !important;
max-width: 600px;
}
.common-prompt-popper.el-popper .el-popper__arrow::before {
background-color: rgb(59, 65, 75) !important;
border-color: rgb(59, 65, 75) !important;
}
</style>
<template> <template>
<div class="container"> <div class="bill-progress-list">
<div class="svg-timeline"> <div class="list-wrapper">
<svg :viewBox="`0 0 ${svgWidth} ${svgHeight}`" width="100%" height="100%"> <div class="list-item" v-for="(item, index) in displayList" :key="`${item.actionDate}-${index}`">
<!-- <line <div class="item-main">
:x1="lines[0].x1 - 100" <div class="left">
:y1="lines[0].y1" <div class="dot"></div>
:x2="lines[0].x1" <div class="item-date">{{ item.formattedDate }}</div>
:y2="lines[0].y1" </div>
stroke="#e8f2ff" <el-tooltip
stroke-width="2" effect="dark"
marker-end="url(#arrow)" :content="item.actionTitle"
/> --> popper-class="common-prompt-popper"
<!-- <line placement="top"
v-for="(line, idx) in lines" :show-after="500"
:key="'line' + idx" >
:x1="line.x1" <div class="item-title">{{ item.actionTitle }}</div>
:y1="line.y1" </el-tooltip>
:x2="line.x2" <div class="right">
:y2="line.y2" <div class="risk-tag" :class="item.riskClass">{{ item.riskText }}</div>
stroke="#e8f2ff" <div class="arrow">></div>
stroke-width="2"
marker-end="url(#arrow)"
/> -->
<!-- <path
v-for="(arc, idx) in arcPaths"
:key="'arc' + idx"
:d="arc.d"
fill="none"
stroke="red"
stroke-width="2"
/> -->
<line
v-if="nodes.length > 0"
:x1="nodes[0].x - 100"
:y1="nodes[0].y"
:x2="nodes[0].x"
:y2="nodes[0].y"
stroke="#e8f2ff"
stroke-width="2"
marker-end="url(#arrow)"
/>
<path :d="arcPaths" fill="none" stroke="#e8f2ff" stroke-width="2" marker-end="url(#arrow)" />
<!-- <defs>
<marker
id="arrow"
markerWidth="6"
markerHeight="6"
refX="6"
refY="3"
orient="auto"
markerUnits="strokeWidth"
>
<path d="M0,0 L6,3 L0,6" fill="#333" />
</marker>
</defs> -->
<!-- 节点 -->
<circle
v-for="(node, idx) in nodes"
:key="'circle' + idx"
:cx="node.x"
:cy="node.y"
r="4"
fill="#fff"
stroke="#1677ff"
stroke-width="3"
/>
<line
v-for="(node, idx) in nodes"
:key="'line' + idx"
:x1="node.x"
:y1="node.y + 4"
:x2="node.x"
:y2="node.y + verticalLineLength"
stroke="#1677ff"
:stroke-width="verticalLineWidth"
/>
<foreignObject
v-for="(node, idx) in nodes"
:key="'fo-' + idx"
:x="node.x + 15"
:y="node.y + 5"
:width="nodeGapX - 30"
height="100"
style="overflow: visible;"
>
<div class="node-content" xmlns="http://www.w3.org/1999/xhtml">
<div class="date">{{ node.formattedDate }}</div>
<el-tooltip
effect="dark"
:content="node.actionTitle"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="title">{{ node.actionTitle }}</div>
</el-tooltip>
<div class="votes" v-if="node.voteString">{{ node.voteString }}</div>
</div> </div>
</foreignObject> </div>
<text </div>
v-if="startMonth && nodes.length > 0"
:x="nodes[0].x - 110"
:y="nodes[0].y + 5"
text-anchor="end"
fill="rgb(5, 95, 194)"
font-size="16"
font-weight="700"
>
{{ startMonth }}
</text>
<line <div class="empty" v-if="displayList.length === 0">暂无进展数据</div>
v-if="nodes.length > 0"
:x1="nodes[nodes.length - 1].x"
:y1="nodes[nodes.length - 1].y"
:x2="nodes[nodes.length - 1].row % 2 === 0 ? nodes[nodes.length - 1].x + 100 : nodes[nodes.length - 1].x - 100"
:y2="nodes[nodes.length - 1].y"
stroke="#e8f2ff"
stroke-width="2"
marker-end="url(#arrow)"
/>
<text
v-if="endMonth && nodes.length > 0"
:x="nodes[nodes.length - 1].row % 2 === 0 ? nodes[nodes.length - 1].x + 110 : nodes[nodes.length - 1].x - 110"
:y="nodes[nodes.length - 1].y + 5"
:text-anchor="nodes[nodes.length - 1].row % 2 === 0 ? 'start' : 'end'"
fill="rgb(5, 95, 194)"
font-size="16"
font-weight="700"
>
{{ endMonth }}
</text>
</svg>
</div> </div>
<div class="more" v-if="hasMore">查看更多<img :src="arrowDown"/></div>
</div> </div>
</template> </template>
<script> <script>
import arrowDown from './assets/icons/arrow-down.svg';
const RISK_LEVELS = ["特别重大风险", "重大风险", "较大风险", "一般风险", "低风险"];
const RISK_CLASS_MAP = {
"特别重大风险": "risk-critical",
"重大风险": "risk-high",
"较大风险": "risk-medium",
"一般风险": "risk-normal",
"低风险": "risk-low"
};
export default { export default {
props: ["dataList"], name: "SBillProgressList",
data() { data() {
return { return {
// dataList: [ arrowDown
// { actionDate: "2025-07-08 14:20", actionTitle: "起飞", dyms: '16票赞成 : 21票反对​ ' },
// { actionDate: "2025-07-08 14:22", actionTitle: "转弯" },
// { actionDate: "2025-07-08 14:25", actionTitle: "发现问题",dyms: '16票赞成 : 21票反对​ ' },
// { actionDate: "2025-07-08 14:27", actionTitle: "飞行",dyms: '16票赞成 : 21票反对​ ' },
// { actionDate: "2025-07-08 14:33", actionTitle: "转弯" },
// { actionDate: "2025-07-08 14:35", actionTitle: "飞行" },
// ],
maxPerRow: 5,
nodeGapX: 200,
nodeGapY: 180,
leftMargin: 150,
verticalLineLength: 80,
verticalLineWidth: 1
}; };
}, },
computed: { props: {
startMonth() { dataList: {
if (this.sortedDataList.length === 0) return ''; type: Array,
const date = new Date(this.sortedDataList[0].actionDate); default: () => []
return `${date.getFullYear()}${date.getMonth() + 1}月`;
}, },
endMonth() { mode: {
if (this.sortedDataList.length === 0) return ''; type: String,
const date = new Date(this.sortedDataList[this.sortedDataList.length - 1].actionDate); default: "latest"
return `${date.getFullYear()}${date.getMonth() + 1}月`;
}, },
maxCount: {
type: Number,
default: 5
}
},
computed: {
sortedDataList() { sortedDataList() {
if (!this.dataList || !Array.isArray(this.dataList)) return []; if (!Array.isArray(this.dataList)) return [];
// Clone and sort by date ascending (Old -> New)
return [...this.dataList] return [...this.dataList]
.filter(item => item && item.actionDate) .filter(item => item && item.actionDate)
.sort((a, b) => new Date(a.actionDate) - new Date(b.actionDate)); .sort((a, b) => new Date(b.actionDate) - new Date(a.actionDate));
}, },
nodes() { hasMore() {
// 计算每个节点的坐标(蛇形) return this.sortedDataList.length > this.maxCount;
return this.sortedDataList.map((item, idx) => { },
const row = Math.floor(idx / this.maxPerRow); displayList() {
const col = idx % this.maxPerRow; const list = this.mode === "early" ? [...this.sortedDataList].reverse() : this.sortedDataList;
let x, y; return list.slice(0, this.maxCount).map((item, index) => {
if (row % 2 === 0) {
x = this.leftMargin + col * this.nodeGapX + 50;
} else {
x = this.leftMargin + (this.maxPerRow - 1 - col) * this.nodeGapX + 50;
}
// 节点纵坐标起始值
y = 60 + row * this.nodeGapY;
// Format Date: 2025-07-04 -> 7月4日
let formattedDate = ""; let formattedDate = "";
if (item.actionDate) { const dateObj = new Date(item.actionDate);
const dateObj = new Date(item.actionDate); if (!isNaN(dateObj.getTime())) {
if (!isNaN(dateObj.getTime())) { formattedDate = `${dateObj.getMonth() + 1}${dateObj.getDate()}日`;
formattedDate = `${dateObj.getMonth() + 1}${dateObj.getDate()}日`;
}
} }
// Format Votes const riskText = RISK_LEVELS[Math.min(index, RISK_LEVELS.length - 1)];
let voteString = ""; const riskClass = RISK_CLASS_MAP[riskText] || "risk-low";
if (item.agreeVote !== null && item.agreeVote !== undefined && item.disagreeVote !== null && item.disagreeVote !== undefined) {
voteString = `${item.agreeVote}票赞成 : ${item.disagreeVote}票反对`;
}
return { ...item, x, y, row, formattedDate, voteString }; return {
...item,
formattedDate,
riskText,
riskClass
};
}); });
},
lines() {
const arr = [];
const gap = 0; // 间隔长度
for (let i = 0; i < this.nodes.length - 1; i++) {
const x1 = this.nodes[i].x;
const y1 = this.nodes[i].y;
const x2 = this.nodes[i + 1].x;
const y2 = this.nodes[i + 1].y;
const dx = x2 - x1;
const dy = y2 - y1;
const len = Math.sqrt(dx * dx + dy * dy);
// 计算起点和终点都缩进 gap
const ratioStart = gap / len;
const ratioEnd = (len - gap) / len;
const sx = x1 + dx * ratioStart;
const sy = y1 + dy * ratioStart;
const tx = x1 + dx * ratioEnd;
const ty = y1 + dy * ratioEnd;
arr.push({
x1: sx,
y1: sy,
x2: tx,
y2: ty
});
}
return arr;
},
arcPaths() {
if (this.nodes.length < 2) return "";
let path = `M ${this.nodes[0].x} ${this.nodes[0].y}`;
for (let i = 1; i < this.nodes.length; i++) {
const prev = this.nodes[i - 1];
const curr = this.nodes[i];
// 判断是否是行尾转折点
const isTurnPoint = i % this.maxPerRow === 0;
if (isTurnPoint) {
// 计算半圆路径
const radius = 10 / 2;
if (prev.row % 2 === 0) {
// 偶数行向右凸半圆
path += `
L ${prev.x + radius} ${prev.y}
A ${radius} ${radius} 0 0 1 ${curr.x - radius} ${curr.y}
L ${curr.x} ${curr.y}`;
} else {
// 奇数行向左凸半圆
path += `
L ${prev.x - radius} ${prev.y}
A ${radius} ${radius} 0 0 0 ${curr.x + radius} ${curr.y}
L ${curr.x} ${curr.y}`;
}
} else {
// 同行节点间用直线连接
path += ` L ${curr.x} ${curr.y}`;
}
}
return path;
},
svgWidth() {
return this.leftMargin + this.maxPerRow * this.nodeGapX + 50;
},
svgHeight() {
// SVG高度
const listLength = (this.dataList && Array.isArray(this.dataList)) ? this.dataList.length : 0;
return Math.ceil(listLength / this.maxPerRow) * this.nodeGapY + 40;
} }
} }
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .bill-progress-list {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; padding: 2px 0 0;
justify-content: center; box-sizing: border-box;
align-items: center;
} .list-wrapper {
.svg-timeline { height: 100%;
width: 100%; overflow: hidden;
// background-size: 100% 100%; padding-right: 2px;
// position: relative; position: relative;
// .title { padding-bottom: 48px;
// position: absolute; }
// top: 0;
// left: 32px;
// width: 100%;
// height: 100%;
// font-family: YouSheBiaoTiHei;
// font-size: 16px;
// color: #ffffff;
// line-height: 24px;
// background: linear-gradient(90deg, #ffffff 0%, #79c2ff 100%);
// -webkit-background-clip: text;
// -webkit-text-fill-color: transparent;
// display: flex;
// align-items: center;
// height: 40px;
// img {
// width: 12px;
// height: 24px;
// }
// }
.node-content {
font-family: Microsoft YaHei, sans-serif;
text-align: left;
padding-left: 4px;
.date { .list-item {
color: rgb(5, 95, 194); height: 58px;
font-weight: 700; box-sizing: border-box;
font-size: 16px; }
line-height: 22px;
margin-bottom: 0px; .item-main {
margin-top: 6px; height: 100%;
display: flex;
align-items: center;
gap: 20px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
}
.left {
display: flex;
align-items: center;
width: 110px;
flex-shrink: 0;
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #ff4d4f;
margin-right: 12px;
} }
.title { .item-date {
color: rgb(59, 65, 75); font-family: Microsoft YaHei;
font-weight: 700;
font-size: 16px; font-size: 16px;
font-weight: 700;
line-height: 22px; line-height: 22px;
margin-bottom: 0px; color: #3b414b;
white-space: nowrap; /* Keep text on one line */ white-space: nowrap;
overflow: hidden; /* Hide overflow */
text-overflow: ellipsis; /* Show ... for overflow */
width: 100%; /* Ensure it takes full width of container */
display: block; /* Block level for ellipsis to work */
} }
}
.votes { .item-title {
color: rgb(95, 101, 108); flex: 1;
font-size: 16px; min-width: 0;
font-weight: 400; font-family: Microsoft YaHei;
line-height: 22px; font-size: 16px;
} font-weight: 400;
line-height: 22px;
color: #3b414b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
}
</style>
<style> .right {
.common-prompt-popper.el-popper { display: flex;
padding: 8px 16px !important; align-items: center;
border-radius: 10px !important; gap: 16px;
background-color: rgb(59, 65, 75) !important; margin-left: auto;
font-size: 16px !important; padding-right: 6px;
font-weight: 400 !important; flex-shrink: 0;
font-family: "Microsoft YaHei" !important; }
line-height: 30px !important;
color: #fff !important; .risk-tag {
border: none !important; height: 32px;
max-width: 600px; padding: 0 14px;
} border-radius: 16px;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 32px;
white-space: nowrap;
}
.risk-critical {
color: #ff4d4f;
background: #fff1f0;
}
.risk-high {
color: #fa8c16;
background: #fff7e6;
}
.risk-medium {
color: #d4b106;
background: #fffbe6;
}
.risk-normal {
color: #389e0d;
background: #f6ffed;
}
.common-prompt-popper.el-popper .el-popper__arrow::before { .risk-low {
background-color: rgb(59, 65, 75) !important; color: #1677ff;
border-color: rgb(59, 65, 75) !important; background: #e6f4ff;
}
.arrow {
font-size: 16px;
color: #8c8c8c;
line-height: 1;
}
.more {
display: flex;
gap: 5px;
position: absolute;
left: 50%;
bottom: 23px;
transform: translateX(-50%);
font-family: Microsoft YaHei;
font-size: 14px;
line-height: 22px;
color: #1677ff;
cursor: pointer;
}
.empty {
font-family: Microsoft YaHei;
font-size: 16px;
line-height: 24px;
color: rgba(95, 101, 108, 1);
text-align: center;
padding-top: 40px;
}
} }
</style> </style>
\ No newline at end of file
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="16.000000" fill="none" customFrame="#000000">
<rect id="Icon图标/Line/double right" width="16.000000" height="16.000000" x="0.000000" y="0.000000" transform="matrix(0,1,-1,0,16,0)" />
<path id="形状" d="M5.519 5.19219L1.52994 0.0953125C1.48307 0.034375 1.40963 0 1.33307 0L0.125253 0C0.0205652 0 -0.0372473 0.120312 0.0268152 0.201562L4.17369 5.5L0.0268152 10.7984C-0.0372473 10.8797 0.0205652 11 0.125253 11L1.33307 11C1.40963 11 1.48307 10.9641 1.52994 10.9047L5.519 5.80937C5.66119 5.62656 5.66119 5.37344 5.519 5.19219ZM6.27994 0.0953125L10.269 5.19219C10.4112 5.37344 10.4112 5.62656 10.269 5.80937L6.27994 10.9047C6.23306 10.9641 6.15963 11 6.08307 11L4.87525 11C4.77056 11 4.71275 10.8797 4.77681 10.7984L8.92369 5.5L4.77681 0.201562C4.71275 0.120312 4.77056 0 4.87525 0L6.08307 0C6.15963 0 6.23306 0.034375 6.27994 0.0953125Z" fill="rgb(5,95,194)" fill-rule="evenodd" transform="matrix(0,1,-1,0,13.5,3.8125)" />
</svg>
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
<div class="item-left">相关领域:</div> <div class="item-left">相关领域:</div>
<div class="item-right1"> <div class="item-right1">
<!-- <div class="right1-item" v-for="item in basicInfo.hylyList" :key="item">{{ item }}</div> --> <!-- <div class="right1-item" v-for="item in basicInfo.hylyList" :key="item">{{ item }}</div> -->
<AreaTag v-for="item, index in basicInfo.hylyList" :key="index" :tagName="item"></AreaTag> <AreaTag v-for="item, index in basicInfo.hylyList" :key="index" :tagName="item"/>
</div> </div>
</div> </div>
<div class="box1-right-item"> <div class="box1-right-item">
...@@ -144,9 +144,19 @@ ...@@ -144,9 +144,19 @@
</div> </div>
</div> --> </div> -->
<AnalysisBox title="法案进展" :showAllBtn="false"> <AnalysisBox title="法案进展" :showAllBtn="false">
<template #header-btn>
<div class="progress-header-btns">
<div class="btn" :class="{ btnActive: progressMode === 'latest' }" @click="handleSwitchProgressMode('latest')">
最新进展
</div>
<div class="btn" :class="{ btnActive: progressMode === 'early' }" @click="handleSwitchProgressMode('early')">
前期进程
</div>
</div>
</template>
<div class="box2-main"> <div class="box2-main">
<div class="box2-main-center"> <div class="box2-main-center">
<STimeline :dataList="timelineData" /> <STimeline :dataList="timelineData" :mode="progressMode" :maxCount="5" />
</div> </div>
</div> </div>
</AnalysisBox> </AnalysisBox>
...@@ -261,7 +271,7 @@ ...@@ -261,7 +271,7 @@
</div> </div>
<div class="info-box"> <div class="info-box">
<div class="info-left"> <div class="info-left">
<img :src="defaultAvatar" alt="" @click="handleClickAvatar(curPerson)" /> <img class="person-avatar" :src="curPerson.imageUrl || defaultAvatar" alt="" @click="handleClickAvatar(curPerson)" />
<div class="usr-icon1"> <div class="usr-icon1">
<img src="./assets/images/usr-icon1.png" alt="" /> <img src="./assets/images/usr-icon1.png" alt="" />
</div> </div>
...@@ -292,13 +302,8 @@ ...@@ -292,13 +302,8 @@
</div> </div>
<div class="right-main-box2" v-if="curPerson.tagList && curPerson.tagList.length"> <div class="right-main-box2" v-if="curPerson.tagList && curPerson.tagList.length">
<!-- <WordCloudMap :data="wordCloudData" :shape="circle" /> --> <!-- <WordCloudMap :data="wordCloudData" :shape="circle" /> -->
<div class="tag-box" :class="{ <div class="tag-box status"v-for="(tag, index) in curPerson.tagList" :key="index">
status0: index % 4 === 0, {{ tag }}
status1: index % 4 === 1,
status2: index % 4 === 2,
status3: index % 4 === 3
}" v-for="(tag, index) in curPerson.tagList" :key="index">
{{ tag.industryName }}
</div> </div>
</div> </div>
<div class="right-main-box3"> <div class="right-main-box3">
...@@ -329,88 +334,10 @@ ...@@ -329,88 +334,10 @@
</el-timeline-item> --> </el-timeline-item> -->
</el-timeline> </el-timeline>
</div> </div>
<div class="right-main-box3-footer">
<div class="btn-more" @click="handleClickMore2">
<img src="../assets/images/btn-more.png" alt="" />
</div>
</div>
</div> </div>
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
<el-dialog v-model="isShowDialog" class="dialog-box" width="1280">
<div class="dialog-inner-box">
<div class="inner-left">
<div class="info-box">
<div class="info-top">
<img :src="defaultAvatar" alt="" />
<div class="usr-icon1">
<img src="./assets/images/usr-icon1.png" alt="" />
</div>
<div class="usr-icon2">
<img src="./assets/images/usr-icon2.png" alt="" />
</div>
</div>
<div class="info-footer">
<div class="info-right-title">{{ curPerson.name }}</div>
<div class="info-right-item">
<div class="item-left">英文名:</div>
<div class="item-right">{{ curPerson.ename }}</div>
</div>
<div class="info-right-item">
<div class="item-left">党派:</div>
<div class="item-right">{{ curPerson.dp }}</div>
</div>
<div class="info-right-item">
<div class="item-left">选区:</div>
<div class="item-right">{{ curPerson.xq }}</div>
</div>
<div class="info-right-item">
<div class="item-left">职位:</div>
<div class="item-right">{{ curPerson.zw }}</div>
</div>
</div>
</div>
<div class="tag-wrapper" v-if="curPerson.tagList && curPerson.tagList.length">
<div class="tag-box" :class="{
status0: index % 4 === 0,
status1: index % 4 === 1,
status2: index % 4 === 2,
status3: index % 4 === 3
}" v-for="(tag, index) in curPerson.tagList" :key="index">
{{ tag.industryName }}
</div>
</div>
<div class="more">更多议员 ></div>
</div>
<div class="inner-right">
<div class="btn-box">
<div class="btn" :class="{ btnActive: dialogBoxBtnActive === 0 }" @click="handleClcikDialogBoxBtn(0)">
新闻动态
</div>
<div class="btn" :class="{ btnActive: dialogBoxBtnActive === 1 }" @click="handleClcikDialogBoxBtn(1)">
人物履历
</div>
</div>
<div class="inner-right-main">
<el-timeline style="max-width: 840px">
<el-timeline-item :timestamp="item.newsDate" placement="top" v-for="(item, index) in curPerson.newsList"
:key="index">
<div class="timeline-content1">
<div class="text">
{{ item.newsContent }}
</div>
<div class="pic">
<img :src="defaultNew" alt="" />
</div>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</div>
</el-dialog>
</div> </div>
</template> </template>
...@@ -446,11 +373,10 @@ const handleClcikDialogBoxBtn = index => { ...@@ -446,11 +373,10 @@ const handleClcikDialogBoxBtn = index => {
}; };
const timelineData = ref([]); const timelineData = ref([]);
const progressMode = ref("latest");
const isShowDialog = ref(false); const handleSwitchProgressMode = mode => {
progressMode.value = mode;
const handleClickMore2 = () => {
isShowDialog.value = true;
}; };
// 获取基本信息 // 获取基本信息
...@@ -529,6 +455,32 @@ onMounted(() => { ...@@ -529,6 +455,32 @@ onMounted(() => {
height: 880px; height: 880px;
display: flex; display: flex;
.progress-header-btns {
display: flex;
gap: 8px;
.btn {
height: 28px;
padding: 0 12px;
box-sizing: border-box;
border: 1px solid var(--btn-plain-border-color);
border-radius: 4px;
background: var(--btn-plain-bg-color);
color: var(--btn-plain-text-color);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 26px;
cursor: pointer;
}
.btnActive {
border: 1px solid var(--btn-active-border-color);
background: var(--btn-active-bg-color);
color: var(--btn-active-text-color);
}
}
.box-header { .box-header {
height: 56px; height: 56px;
display: flex; display: flex;
...@@ -913,17 +865,6 @@ onMounted(() => { ...@@ -913,17 +865,6 @@ onMounted(() => {
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
z-index: 99; z-index: 99;
.btn-more {
width: 108px;
height: 32px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
} }
} }
} }
...@@ -1007,6 +948,13 @@ onMounted(() => { ...@@ -1007,6 +948,13 @@ onMounted(() => {
.info-left { .info-left {
cursor: pointer; cursor: pointer;
.person-avatar {
width: 128px;
height: 128px;
border-radius: 50%;
object-fit: cover;
}
img { img {
width: 128px; width: 128px;
height: 128px; height: 128px;
...@@ -1117,28 +1065,10 @@ onMounted(() => { ...@@ -1117,28 +1065,10 @@ onMounted(() => {
display: inline-block; display: inline-block;
} }
.status0 { .status {
border: 1px solid rgba(217, 247, 190, 1); // border: 1px solid rgba(217, 247, 190, 1);
color: rgba(82, 196, 26, 1); color: rgb(5, 95, 194);
background: rgba(246, 255, 237, 1); background: rgb(231, 243, 255);
}
.status1 {
border: 1px solid rgba(186, 224, 255, 1);
color: rgba(22, 119, 255, 1);
background: rgba(230, 244, 255, 1);
}
.status2 {
border: 1px solid rgba(255, 241, 184, 1);
color: rgba(250, 173, 20, 1);
background: rgba(255, 251, 230, 1);
}
.status3 {
border: 1px solid rgba(255, 204, 199, 1);
color: rgba(255, 77, 79, 1);
background: rgba(255, 241, 240, 1);
} }
} }
...@@ -1156,37 +1086,19 @@ onMounted(() => { ...@@ -1156,37 +1086,19 @@ onMounted(() => {
.title { .title {
margin-left: 12px; margin-left: 12px;
height: 22px; height: 24px;
color: rgba(95, 101, 108, 1); color: rgb(59, 65, 75);
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
font-size: 14px; font-size: 18px;
font-weight: 600; font-weight: 700;
line-height: 22px; line-height: 24px;
} }
} }
.right-main-box3-main { .right-main-box3-main {
margin-top: 18px; margin-top: 18px;
height: 412px;
overflow-y: auto; overflow-y: auto;
} }
.right-main-box3-footer {
margin-top: 1px;
display: flex;
justify-content: center;
.btn-more {
width: 108px;
height: 32px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
} }
} }
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论