提交 592d75b3 authored 作者: 刘宇琪's avatar 刘宇琪

刘宇琪 人物详情页修改 科技巨头

上级 2d832f9c
...@@ -150,3 +150,99 @@ export function getPostMemberList() { ...@@ -150,3 +150,99 @@ export function getPostMemberList() {
url: `/api/BillDict/member`, url: `/api/BillDict/member`,
}) })
} }
/**
* 获取筛选项配置 - 行业列表
* GET /api/billImpactAnalysis/industry/hylyList
*/
export async function getIndustryKeyList() {
return request('/api/billImpactAnalysis/industry/hylyList', {
method: 'GET',
})
}
/**
* 获取进度阶段配置
* GET /api/commonDict/bill/stage
*/
export async function getBillStageConfig() {
return request('/api/commonDict/bill/stage', {
method: 'GET',
})
}
/**
* 获取法案列表
* GET /api/personHomepage/historyBill/{personId}
* @param {string} personId - 人物ID
* @param {Object} params - 查询参数(不包含分页参数)
*/
export async function getHistoryBillList(personId, params = {}) {
const queryString = Object.entries(params)
.filter(([, value]) => value !== undefined && value !== null && value !== '')
.map(([key, value]) => {
if (Array.isArray(value)) {
return value.map(v => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`).join('&')
}
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
})
.join('&')
const url = queryString
? `/api/personHomepage/historyBill/${personId}?${queryString}`
: `/api/personHomepage/historyBill/${personId}`
return request(url, {
method: 'GET',
})
}
/**
* 获取法案列表(含阶段字典)
* @param {string} personId - 人物ID
* @param {Object} params - 查询参数
*/
export async function getHistoryBillListWithStage(personId, params = {}) {
const stageRes = await getBillStageConfig()
const billRes = await getHistoryBillList(personId, params)
return {
stageConfig: stageRes,
billList: billRes,
}
}
export function getSortOptions() {
return Promise.resolve({
code: 200,
data: [
{ value: 'latestMotionTimeDesc', label: '最新修议时间倒序' },
{ value: 'latestMotionTimeAsc', label: '最新修议时间正序' },
],
})
}
export async function getPotentialNewsList(personId, params = {}) {
const queryString = Object.entries(params)
.filter(([, value]) => value !== undefined && value !== null && value !== '')
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&')
const url = queryString
? `/api/personHomepage/historyBill/clause/${personId}?${queryString}`
: `/api/personHomepage/historyBill/clause/${personId}`
return request(url, {
method: 'GET',
})
}
export async function getPotentialNewsKeywords(personId) {
return request(`/api/personHomepage/historyBill/clauseKeyword/${personId}`, {
method: 'GET',
})
}
\ No newline at end of file
...@@ -112,7 +112,7 @@ export function getCharacterView(params) { ...@@ -112,7 +112,7 @@ export function getCharacterView(params) {
export function getCharacterFundSource(params) { export function getCharacterFundSource(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/personHomepage/personFunds/${params.personId}/${params.year}`, url: `/api/personHomepage/personFunds/`,
params, params,
}) })
} }
......
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32.000000" height="32.000000" fill="none" customFrame="#000000">
<g id="组合 275">
<rect id="矩形 264" width="32.000000" height="32.000000" x="0.000000" y="0.000000" opacity="0" fill="rgb(255,0,0)" fill-opacity="0.319999993" />
<path id="合并" d="M9.49992 8.50003L9.4999 2.36022e-06L8.49991 0L8.49993 8.50003L0 8.50003L0 9.50003L8.49993 9.50003L8.49995 17.9999L9.49995 17.9999L9.49993 9.50003L18 9.50003L18 8.50003L9.49992 8.50003Z" fill="rgb(59,65,75)" fill-rule="evenodd" transform="matrix(0.707107,0.707107,-0.707107,0.707107,15.9996,3.27197)" />
</g>
</svg>
<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">
<rect id="icon-圆形布局 1" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="矢量 423" d="M13.81 6.6C13.67 5.97 13.39 5.41 13.04 4.85C13.18 4.64 13.25 4.36 13.25 4.15C13.25 3.38 12.62 2.75 11.85 2.75C11.57 2.75 11.36 2.82 11.15 2.96C10.59 2.61 10.03 2.4 9.4 2.19C9.33 1.49 8.7 1 8 1C7.3 1 6.74 1.56 6.6 2.19C5.97 2.4 5.41 2.61 4.85 2.96C4.64 2.82 4.36 2.75 4.15 2.75C3.38 2.75 2.75 3.38 2.75 4.15C2.75 4.43 2.82 4.64 2.96 4.85C2.61 5.41 2.4 5.97 2.19 6.6C1.56 6.74 1 7.3 1 8C1 8.7 1.56 9.26 2.19 9.4C2.33 10.03 2.61 10.59 2.96 11.15C2.82 11.36 2.75 11.64 2.75 11.85C2.75 12.62 3.38 13.25 4.15 13.25C4.43 13.25 4.64 13.18 4.85 13.04C5.41 13.39 5.97 13.6 6.6 13.81C6.67 14.51 7.3 15 8 15C8.7 15 9.26 14.44 9.4 13.81C10.03 13.67 10.59 13.39 11.15 13.04C11.36 13.18 11.64 13.25 11.85 13.25C12.62 13.25 13.25 12.62 13.25 11.85C13.25 11.57 13.18 11.36 13.04 11.15C13.39 10.59 13.6 10.03 13.81 9.4C14.51 9.33 15 8.7 15 8C15 7.3 14.44 6.74 13.81 6.6L13.81 6.6ZM12.55 10.66C12.34 10.52 12.13 10.45 11.85 10.45C11.08 10.45 10.45 11.08 10.45 11.85C10.45 12.13 10.52 12.34 10.66 12.55C10.24 12.76 9.82 12.97 9.33 13.11C9.12 12.62 8.63 12.2 8 12.2C7.44 12.2 6.88 12.55 6.67 13.11C6.18 12.97 5.76 12.83 5.34 12.55C5.48 12.34 5.55 12.13 5.55 11.85C5.55 11.08 4.92 10.45 4.15 10.45C3.87 10.45 3.66 10.52 3.45 10.66C3.24 10.24 3.03 9.82 2.89 9.33C3.45 9.12 3.8 8.56 3.8 8C3.8 7.44 3.45 6.88 2.96 6.74C3.1 6.25 3.24 5.83 3.52 5.41C3.73 5.55 3.94 5.62 4.22 5.62C4.99 5.62 5.62 4.99 5.62 4.22C5.62 3.94 5.55 3.73 5.41 3.52C5.83 3.31 6.25 3.1 6.74 2.96C6.88 3.45 7.44 3.8 8 3.8C8.56 3.8 9.12 3.45 9.33 2.89C9.82 3.03 10.24 3.17 10.66 3.45C10.52 3.66 10.45 3.87 10.45 4.15C10.45 4.92 11.08 5.55 11.85 5.55C12.13 5.55 12.34 5.48 12.55 5.34C12.76 5.76 12.97 6.18 13.11 6.67C12.55 6.88 12.2 7.44 12.2 8C12.2 8.56 12.55 9.12 13.04 9.26C12.97 9.75 12.76 10.24 12.55 10.66L12.55 10.66Z" fill="rgb(59,65,75)" fill-rule="nonzero" />
</svg>
<svg viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="28.000000" height="28.000000" fill="none">
<defs>
<clipPath id="clipPath_1">
<rect width="18.000000" height="15.000000" x="5.000000" y="5.000000" fill="rgb(255,255,255)" />
</clipPath>
</defs>
<rect id="导出数据" width="28.000000" height="28.000000" x="0.000000" y="0.000000" />
<g id="容器 742" customFrame="url(#clipPath_1)">
<rect id="容器 742" width="18.000000" height="15.000000" x="5.000000" y="5.000000" />
<rect id="矩形 347" width="2.000000" height="6.000000" x="13.000000" y="5.000000" fill="rgb(132,136,142)" />
<path id="矢量 600" d="M18 11L10 11L14 16L18 11Z" fill="rgb(132,136,142)" fill-rule="evenodd" />
<path id="矢量 601" d="M22 19.9996L22.9999 15.0012L19.9999 12.0011L18.9999 12L21.0003 15.001L17.9999 15.0015L16.9998 17.9987L14 17.9996L11.0001 17.9997L9.99998 15.002L7.00017 15.0028L8.99996 12.0008L8 12.0004L5 15.0023L6.00016 20L22 19.9996Z" fill="rgb(132,136,142)" fill-rule="evenodd" />
</g>
</svg>
<svg viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="28.000000" height="28.000000" fill="none">
<defs>
<clipPath id="clipPath_0">
<rect width="20.000000" height="20.000000" x="4.000000" y="4.000000" fill="rgb(255,255,255)" />
</clipPath>
</defs>
<rect id="数据源" width="28.000000" height="28.000000" x="0.000000" y="0.000000" />
<g id="数据库 1" clip-path="url(#clipPath_0)" customFrame="url(#clipPath_0)">
<rect id="数据库 1" width="20.000000" height="20.000000" x="4.000000" y="4.000000" />
<path id="合并" d="M10.6426 6.48828C11.6719 6.28906 12.791 6.1875 14 6.1875C15.209 6.1875 16.3281 6.28906 17.3574 6.48828C18.3867 6.6875 19.2012 6.96094 19.7988 7.30469C20.3965 7.64844 20.6973 8.01953 20.6973 8.42188L20.6973 9.53906C20.6973 9.94141 20.3984 10.3125 19.7988 10.6563C19.1992 11 18.3867 11.2715 17.3574 11.4727C16.3281 11.6699 15.209 11.7695 14 11.7695C12.791 11.7695 11.6719 11.668 10.6426 11.4688C9.61328 11.2695 8.79883 10.9961 8.20117 10.6523C7.60156 10.3086 7.30273 9.9375 7.30273 9.53516L7.30273 8.41797C7.30273 8.01563 7.60156 7.64453 8.20117 7.30078C8.80078 6.96094 9.61328 6.68945 10.6426 6.48828ZM10.1387 12.5078C11.3359 12.7578 12.623 12.8828 14 12.8828C15.377 12.8828 16.6641 12.7578 17.8613 12.5078C19.0586 12.2578 20.0039 11.8887 20.6953 11.4004L20.6953 12.8828C20.6953 13.2852 20.3965 13.6563 19.7969 14C19.1973 14.3438 18.3848 14.6152 17.3555 14.8164C16.3281 15.0156 15.209 15.1152 14 15.1152C12.791 15.1152 11.6719 15.0137 10.6426 14.8145C9.61328 14.6152 8.79883 14.3418 8.20117 13.998C7.60156 13.6543 7.30273 13.2832 7.30273 12.8809L7.30273 11.3984C7.99609 11.8906 8.94141 12.2598 10.1387 12.5078ZM10.1387 15.8574C11.3359 16.1074 12.623 16.2324 14 16.2324C14.6624 16.2324 15.3041 16.2035 15.9249 16.1456C14.2088 16.4715 12.8443 17.3161 12.2805 18.3935C11.7114 18.3432 11.1654 18.2672 10.6426 18.166C9.61328 17.9668 8.80078 17.6934 8.20117 17.3496C7.60156 17.0059 7.30273 16.6348 7.30273 16.2324L7.30273 14.75C7.9961 15.2383 8.94141 15.6074 10.1387 15.8574ZM17.5 16C17.3281 16 17.1581 16.005 16.9902 16.0148C17.2857 15.9695 17.5761 15.917 17.8613 15.8574C19.0586 15.6074 20.0039 15.2383 20.6953 14.75L20.6953 16.2324C20.6953 16.3614 20.6646 16.4872 20.6031 16.6099C19.7199 16.2251 18.6512 16 17.5 16ZM13 19.5C13 18.1193 15.0147 17 17.5 17C19.9853 17 22 18.1193 22 19.5C22 20.8807 19.9853 22 17.5 22C15.0147 22 13 20.8807 13 19.5ZM17.5 18C18.3284 18 19 18.6716 19 19.5C19 20.3284 18.3284 21 17.5 21C16.6716 21 16 20.3284 16 19.5C16 18.6716 16.6716 18 17.5 18ZM12 19.5L12 19.4861C11.3572 19.4236 10.7367 19.33 10.1387 19.2051C8.94141 18.9551 7.99609 18.5859 7.30273 18.0957L7.30273 19.5781C7.30273 19.9805 7.60156 20.3516 8.20117 20.6953C8.79883 21.0391 9.61328 21.3125 10.6426 21.5117C11.4872 21.6752 12.3923 21.7729 13.3579 21.8027C12.5123 21.1873 12 20.3817 12 19.5Z" fill="rgb(132,136,142)" fill-rule="evenodd" />
</g>
</svg>
<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">
<rect id="拓扑 1" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="矢量 422" d="M12.7727 11.142C12.4273 11.142 12.107 11.2495 11.8433 11.4327L9.42154 9.6837C9.65586 9.26852 9.78974 8.7891 9.78974 8.27836C9.78974 8.18728 9.7853 8.09723 9.77697 8.00833L12.257 6.54325C12.6396 6.77576 13.1451 6.72689 13.4758 6.39622C13.8641 6.00791 13.8641 5.37835 13.4758 4.99004C13.0875 4.60173 12.4579 4.60173 12.0696 4.99004C11.7835 5.27612 11.7085 5.69309 11.844 6.04812L9.63655 7.35217C9.40869 6.68522 8.9416 6.12922 8.33762 5.78637L8.77881 3.58047C8.81046 3.5835 8.84253 3.58519 8.87497 3.58519C9.42413 3.58519 9.8693 3.14002 9.8693 2.59087C9.8693 2.04173 9.42413 1.59656 8.87497 1.59656C8.32582 1.59656 7.88065 2.04173 7.88065 2.59087C7.88065 2.87182 7.99736 3.12535 8.18469 3.30617L7.73951 5.53213C7.48164 5.45587 7.2087 5.41473 6.9261 5.41473C6.388 5.41473 5.88464 5.56327 5.45458 5.8214L4.08134 4.61294C4.32918 4.12086 4.24801 3.50548 3.83714 3.09461C3.32457 2.58205 2.49355 2.58205 1.98098 3.09461C1.46841 3.60717 1.46842 4.4382 1.98098 4.95078C2.43973 5.40952 3.15341 5.45735 3.66574 5.09492L4.93964 6.21596C4.39895 6.73684 4.06247 7.46826 4.06247 8.27836C4.06247 9.15881 4.45994 9.94631 5.0851 10.4716L3.95324 11.6792C3.74531 11.5408 3.49572 11.4602 3.22724 11.4602C2.50236 11.4602 1.91474 12.0478 1.91474 12.7727C1.91474 13.4975 2.50236 14.0852 3.22724 14.0852C3.95212 14.0852 4.53974 13.4975 4.53974 12.7727C4.53974 12.5477 4.48308 12.3359 4.38332 12.1508L5.62251 10.8288C6.01351 11.029 6.45662 11.142 6.9261 11.142C7.76819 11.142 8.52534 10.7785 9.04931 10.1999L11.3976 11.8958C11.2358 12.149 11.142 12.4499 11.142 12.7727C11.142 13.6733 11.8721 14.4034 12.7727 14.4034C13.6733 14.4034 14.4034 13.6733 14.4034 12.7727C14.4034 11.8721 13.6733 11.142 12.7727 11.142L12.7727 11.142Z" fill="rgb(59,65,75)" fill-rule="nonzero" />
</svg>
<svg viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="28.000000" height="28.000000" fill="none">
<rect id="收藏" width="28.000000" height="28.000000" x="0.000000" y="0.000000" />
<path id="星形 2" d="M15.9534 11.0113C15.9936 11.1349 16.1088 11.2186 16.2388 11.2186L21.6363 11.2188C21.9269 11.2188 22.0478 11.5907 21.8127 11.7615L17.446 14.9343C17.3409 15.0107 17.2969 15.1461 17.3371 15.2697L19.0048 20.4031C19.0946 20.6795 18.7783 20.9094 18.5432 20.7385L14.1763 17.5661C14.0712 17.4897 13.9288 17.4897 13.8237 17.5661L9.45683 20.7385C9.22171 20.9094 8.90539 20.6795 8.99518 20.4031L10.6629 15.2697C10.7031 15.1461 10.6591 15.0107 10.554 14.9343L6.18734 11.7615C5.95224 11.5907 6.07307 11.2188 6.36368 11.2188L11.7612 11.2186C11.8912 11.2186 12.0064 11.1349 12.0466 11.0113L13.7147 5.87799C13.8045 5.60161 14.1955 5.60161 14.2853 5.87799L15.9534 11.0113Z" fill="rgb(132,136,142)" fill-rule="evenodd" />
</svg>
<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">
<rect id="绿洲_拓扑图 1" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="矢量 422" d="M15.0147 9.88449L14.1867 9.88449L14.1867 8.65516C14.1867 7.53912 13.312 6.63116 12.2368 6.63116L8.37334 6.63116L8.37334 5.08449L9.2317 5.08449C9.42605 5.08449 9.58222 4.94394 9.58222 4.74957L9.58222 2.34115C9.58222 2.11642 9.40148 1.92004 9.17669 1.92004L6.76829 1.92004C6.57396 1.92004 6.41778 2.09179 6.41778 2.28616L6.41778 4.69459C6.41778 4.91931 6.59853 5.08449 6.82329 5.08449L7.62667 5.08449L7.62667 6.63116L3.59268 6.63116C2.62816 6.63116 1.81334 7.45731 1.81334 8.43519L1.81334 9.88449L0.930314 9.88449C0.735986 9.88449 0.58667 10.0572 0.58667 10.2515L0.58667 12.66C0.58667 12.8847 0.760554 13.0489 0.985319 13.0489L3.39371 13.0489C3.58806 13.0489 3.75111 12.9093 3.75111 12.7149L3.75111 10.3066C3.75111 10.0818 3.56351 9.88451 3.33874 9.88451L2.52445 9.88451L2.52445 8.43521C2.52445 7.84276 3.01364 7.34227 3.59268 7.34227L7.62667 7.34227L7.62667 9.88449L6.76829 9.88449C6.57396 9.88449 6.41778 10.0572 6.41778 10.2515L6.41778 12.66C6.41778 12.8847 6.59853 13.0489 6.82329 13.0489L9.2317 13.0489C9.42605 13.0489 9.58222 12.9093 9.58222 12.7149L9.58222 10.3066C9.58222 10.0818 9.40148 9.88451 9.1767 9.88451L8.37334 9.88451L8.37334 7.34227L12.2368 7.34227C12.9199 7.34227 13.4756 7.93123 13.4756 8.65516L13.4756 9.88449L12.6063 9.88449C12.4119 9.88449 12.2489 10.0572 12.2489 10.2515L12.2489 12.66C12.2489 12.8847 12.4365 13.0489 12.6613 13.0489L15.0697 13.0489C15.264 13.0489 15.4133 12.9093 15.4133 12.7149L15.4133 10.3066C15.4133 10.0818 15.2395 9.88449 15.0147 9.88449Z" fill="rgb(59,65,75)" fill-rule="nonzero" />
</svg>
...@@ -5,13 +5,17 @@ ...@@ -5,13 +5,17 @@
v-for="(item, index) in list" v-for="(item, index) in list"
:key="index" :key="index"
class="headerItem" class="headerItem"
:class="{ active: item === activeIndex }" :class="{ active: item.value === activeIndex }"
@click="activeIndex = item" @click="handleChangeLayout(item.value)"
>{{ item }}</span
> >
<img :src="item.icon" :alt="item.label" />
</span>
</div>
<div class="headerBtnBox">
<img src="./assets/icon-expand.svg" alt="" class="btn-icon" />
<img src="./assets/icon-download.svg" alt="" class="btn-icon" />
<img src="./assets/icon-star.svg" alt="" class="btn-icon" />
</div> </div>
<div class="headerBtnBox"><img src="./assets/下载按钮.png" alt="" /><img src="./assets/收藏按钮.png" alt="" /></div>
<!-- 主要内容,人物关系图 -->
<div class="mainBox"> <div class="mainBox">
<div class="graph" id="relGraph"></div> <div class="graph" id="relGraph"></div>
</div> </div>
...@@ -21,139 +25,94 @@ ...@@ -21,139 +25,94 @@
<script setup> <script setup>
import { ref, onMounted, onBeforeUnmount, watch } from "vue"; import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import {getCharacterGlobalInfo, getCharacterRelation } from "@/api/characterPage/characterPage.js"; import { getCharacterGlobalInfo, getCharacterRelation } from "@/api/characterPage/characterPage.js";
import "default-passive-events";
import 'default-passive-events';
import { useRoute } from 'vue-router'; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
const personId = ref(route.query.personId || "Y000064"); const personId = ref(route.query.personId || "Y000064");
// ========== 图片代理 ==========
// const nodes = [
// {
// id: "c",
// name: "埃隆·马斯克",
// category: 0,
// symbolSize: 80,
// symbol: `image://${Center}`,
// label: {
// show: true,
// position: "bottom",
// formatter: "{n|{b}}",
// rich: {
// n: {
// color: "rgba(5,95,194,1)",
// fontSize: 24,
// fontWeight: 700,
// fontFamily: "Microsoft YaHei",
// lineHeight: 36
// }
// }
// }
// },
// // 从三点钟方向顺时针排序
// { id: "n11", name: "贾斯汀・马斯克", category: 1, symbolSize: 80, symbol: `image://${P11}` },
// { id: "n7", name: "杰弗里·凯斯勒", category: 1, symbolSize: 80, symbol: `image://${P7}`, r: 80 },
// { id: "n6", name: "斯科特·贝森特", category: 1, symbolSize: 80, symbol: `image://${P6}` },
// { id: "n9", name: "道格·伯格姆", category: 1, symbolSize: 80, symbol: `image://${P9}` },
// { id: "n12", name: "史蒂夫・尤尔韦松", category: 1, symbolSize: 80, symbol: `image://${PS}` },
// { id: "n5", name: "拉里・埃里森", category: 1, symbolSize: 80, symbol: `image://${P5}`, r: 80 },
// { id: "n8", name: "马尔科·卢比奥", category: 1, symbolSize: 80, symbol: `image://${P8}` },
// { id: "n10", name: "艾拉・埃伦普里斯", category: 1, symbolSize: 80, symbol: `image://${P10}`, r: 80 },
// { id: "n2", name: "詹姆斯・默多克", category: 1, symbolSize: 80, symbol: `image://${P2}` },
// { id: "n1", name: "唐纳德・特朗普", category: 1, symbolSize: 80, symbol: `image://${P1}` },
// { id: "n4", name: "金博尔・马斯克", category: 1, symbolSize: 80, symbol: `image://${P4}` },
// { id: "n3", name: "格温・肖特韦尔", category: 1, symbolSize: 80, symbol: `image://${P3}`, r: 80 }
// ];
// const links = [
// { source: "n11", target: "c", label: { show: true, formatter: "第一任妻子" } },
// { source: "n7", target: "c", label: { show: true, formatter: "风险投资家" } },
// { source: "n6", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n9", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n12", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n5", target: "c", label: { show: true, formatter: "早期重要投资人" } },
// { source: "n8", target: "c", label: { show: true, formatter: "Boring Company 总裁" } },
// { source: "n10", target: "c", label: { show: true, formatter: "特斯拉独立董事" } },
// { source: "n2", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n1", target: "c", label: { show: true, formatter: "美国总统" } },
// { source: "n4", target: "c", label: { show: true, formatter: "马斯克弟弟" } },
// { source: "n3", target: "c", label: { show: true, formatter: "SpaceX 总裁" } }
// ];
// 处理图片代理
const getProxyUrl = (url) => { const getProxyUrl = (url) => {
if (!url) return ""; if (!url) return "";
const urlStr = String(url); const urlStr = String(url);
// 排除非 http 开头(相对路径)、已经是代理链接、或者是本地链接 if (
if (!urlStr.startsWith('http') || urlStr.includes('images.weserv.nl') || urlStr.includes('localhost') || urlStr.includes('127.0.0.1')) { !urlStr.startsWith("http") ||
urlStr.includes("images.weserv.nl") ||
urlStr.includes("localhost") ||
urlStr.includes("127.0.0.1")
) {
return url; return url;
} }
// 移除协议头 http:// 或 https:// const cleanUrl = urlStr.replace(/^https?:\/\//i, "");
const cleanUrl = urlStr.replace(/^https?:\/\//i, '');
return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`; return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`;
}; };
// ========== 数据 ==========
const nodes = ref([]); const nodes = ref([]);
const links = ref([]); const links = ref([]);
// 人物全局信息
const characterInfo = ref({}); const characterInfo = ref({});
// 人物关系
const CharacterRelation = ref([]); const CharacterRelation = ref([]);
const getCharacterGlobalInfoFn = async () => { const list = ref([
const params = { {
personId: personId.value value: "圆形布局",
}; label: "圆形布局",
icon: new URL("./assets/icon-circle.svg", import.meta.url).href,
},
{
value: "力导向布局",
label: "力导向布局",
icon: new URL("./assets/icon-force.svg", import.meta.url).href,
},
{
value: "树形布局",
label: "树形布局",
icon: new URL("./assets/icon-tree.svg", import.meta.url).href,
},
]);
const activeIndex = ref("圆形布局");
let chart = null;
// ========== 切换布局 ==========
const handleChangeLayout = (value) => {
activeIndex.value = value;
renderChart();
};
try{ // ========== 接口:人物全局信息 ==========
const getCharacterGlobalInfoFn = async () => {
const params = { personId: personId.value };
try {
const res = await getCharacterGlobalInfo(params); const res = await getCharacterGlobalInfo(params);
if (res.code === 200 && res.data) {
if (res.code === 200) {
console.log("人物全局信息", res);
if (res.data) {
characterInfo.value = res.data; characterInfo.value = res.data;
} }
} catch (error) {
} console.error("获取人物信息失败", error);
}catch(error){
} }
}; };
// ========== 接口:人物关系 ==========
const getCharacterRelationFn = async () => { const getCharacterRelationFn = async () => {
const params = { const params = { personId: personId.value };
personId: personId.value try {
};
try{
const res = await getCharacterRelation(params); const res = await getCharacterRelation(params);
if (res.code === 200) { if (res.code === 200 && res.data) {
console.log("人物关系", res);
if (res.data) {
CharacterRelation.value = res.data; CharacterRelation.value = res.data;
} }
} catch (error) {
console.error("获取人物关系失败", error);
} }
if(CharacterRelation.value.length > 0){ // ---------- 组装节点和连线 ----------
const centerNode = { const centerNode = {
id: "c", id: "c",
name: characterInfo.value.name, name: characterInfo.value.name || "",
category: 0, category: 0,
symbolSize: 80, symbolSize: 80,
symbol: `image://${characterInfo.value.imageUrl}`, symbol: `image://${getProxyUrl(characterInfo.value.imageUrl)}`,
label: { label: {
show: true, show: true,
position: "bottom", position: "bottom",
...@@ -164,64 +123,45 @@ const getCharacterRelationFn = async () => { ...@@ -164,64 +123,45 @@ const getCharacterRelationFn = async () => {
fontSize: 24, fontSize: 24,
fontWeight: 700, fontWeight: 700,
fontFamily: "Microsoft YaHei", fontFamily: "Microsoft YaHei",
lineHeight: 36 lineHeight: 36,
} },
} },
} },
} };
const newNodes = CharacterRelation.value.map((item,index) => { if (CharacterRelation.value.length > 0) {
return{ const newNodes = CharacterRelation.value.map((item, index) => ({
id: index, id: index,
name: item.name, name: item.name,
category: 1, category: 1,
symbolSize: 80, symbolSize: 80,
symbol: `image://${getProxyUrl(item.imageUrl)}` symbol: `image://${getProxyUrl(item.imageUrl)}`,
} }));
});
newNodes.push(centerNode); newNodes.push(centerNode);
const newLinks = CharacterRelation.value.map((item,index) =>({ const newLinks = CharacterRelation.value.map((item, index) => ({
source:index, source: index,
target:"c", target: "c",
label: { label: { show: true, formatter: item.relation },
show: true,
formatter: item.relation }
})); }));
nodes.value = newNodes; nodes.value = newNodes;
links.value = newLinks; links.value = newLinks;
} else {
}else{ nodes.value = [centerNode];
nodes.value = [{
id: "c",
name: characterInfo.value.name,
category: 0,
symbolSize: 80,
symbol: `image://${characterInfo.value.imageUrl}`,
label: {
show: true,
position: "bottom",
formatter: "{n|{b}}",
rich: {
n: {
color: "rgba(5,95,194,1)",
fontSize: 24,
fontWeight: 700,
fontFamily: "Microsoft YaHei",
lineHeight: 36
}
}
}
}];
links.value = []; links.value = [];
} }
};
// ========== 渲染图表 ==========
const renderChart = () => {
const el = document.getElementById("relGraph"); const el = document.getElementById("relGraph");
if (!el) return; if (!el) return;
if (!chart) {
chart = echarts.init(el); chart = echarts.init(el);
}
const setOption = () => {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
const cx = rect.width / 2; const cx = rect.width / 2;
const cy = rect.height / 2; const cy = rect.height / 2;
...@@ -231,16 +171,17 @@ const getCharacterRelationFn = async () => { ...@@ -231,16 +171,17 @@ const getCharacterRelationFn = async () => {
if (n.id === "c") { if (n.id === "c") {
return { ...n, x: cx, y: cy, fixed: true }; return { ...n, x: cx, y: cy, fixed: true };
} }
// 均匀环形分布 const total = nodes.value.length - 1;
const idx = i - 1; const idx = nodes.value.slice(0, i).filter((nd) => nd.id !== "c").length;
const angle = (idx / (nodes.value.length - 1)) * Math.PI * 2; const angle = (idx / (total || 1)) * Math.PI * 2;
const rLocal = radius + (n.r || 0); const rLocal = radius + (n.r || 0);
const x = cx + rLocal * Math.cos(angle); const x = cx + rLocal * Math.cos(angle);
const y = cy + rLocal * Math.sin(angle); const y = cy + rLocal * Math.sin(angle);
return { ...n, x, y }; return { ...n, x, y };
}); });
chart.setOption({ chart.setOption(
{
tooltip: {}, tooltip: {},
series: [ series: [
{ {
...@@ -257,115 +198,45 @@ const getCharacterRelationFn = async () => { ...@@ -257,115 +198,45 @@ const getCharacterRelationFn = async () => {
edgeLabel: { edgeLabel: {
show: true, show: true,
position: "middle", position: "middle",
distance: -18, distance: 0,
formatter: ({ data }) => data?.label?.formatter || "", formatter: ({ data }) => data?.label?.formatter || "",
color: "rgb(5, 95, 194)", color: "rgb(5, 95, 194)",
fontSize: 12, fontSize: 14,
fontWeight: 400, fontWeight: 400,
fontFamily: "Microsoft YaHei", fontFamily: "Microsoft YaHei",
lineHeight: 24, backgroundColor: "#E7F3FF",
backgroundColor: "rgba(231, 243, 255, 1)", borderRadius: 14,
borderRadius: 24, padding: [4, 12],
padding: [0, 12] borderWidth: 1,
borderColor: "rgba(174,214,255,1)",
}, },
label: { show: true, position: "bottom", color: "rgb(59,65,75)", fontSize: 16 }, label: { show: true, position: "bottom", color: "rgb(59,65,75)", fontSize: 16 },
itemStyle: { color: "rgba(5,95,194,1)" }, itemStyle: { color: "rgba(5,95,194,1)" },
emphasis: { focus: "adjacency" } emphasis: { focus: "adjacency" },
} },
] ],
}); },
}; true
setOption(); );
}catch(error){
}
}; };
// ========== 生命周期 ==========
onMounted(async () => {
await getCharacterGlobalInfoFn();
await getCharacterRelationFn();
renderChart();
const list = ref(["圆形布局", "力导向布局", "树形布局"]);
const activeIndex = ref("圆形布局");
let chart;
onMounted(() => {
getCharacterGlobalInfoFn();
getCharacterRelationFn();
const el = document.getElementById("relGraph");
if (!el) return;
chart = echarts.init(el);
const setOption = () => {
const rect = el.getBoundingClientRect();
const cx = rect.width / 2;
const cy = rect.height / 2;
const radius = Math.min(cx, cy) - 140;
const dataNodes = nodes.value.map((n, i) => {
if (n.id === "c") {
return { ...n, x: cx, y: cy, fixed: true };
}
// 均匀环形分布
const idx = i - 1;
const angle = (idx / (nodes.value.length - 1)) * Math.PI * 2;
const rLocal = radius + (n.r || 0);
const x = cx + rLocal * Math.cos(angle);
const y = cy + rLocal * Math.sin(angle);
return { ...n, x, y };
});
chart.setOption({
tooltip: {},
series: [
{
type: "graph",
layout: activeIndex.value === "圆形布局" ? "none" : "force",
circular: { rotateLabel: true },
force: { repulsion: 800, edgeLength: [80, 160] },
roam: true,
data: activeIndex.value === "圆形布局" ? dataNodes : nodes.value,
links: links.value,
edgeSymbol: ["none", "arrow"],
edgeSymbolSize: [4, 10],
lineStyle: { color: "rgba(174,214,255,1)", width: 2, opacity: 0.8 },
edgeLabel: {
show: true,
position: "middle",
distance: -18,
formatter: ({ data }) => data?.label?.formatter || "",
color: "rgb(5, 95, 194)",
fontSize: 12,
fontWeight: 400,
fontFamily: "Microsoft YaHei",
lineHeight: 24,
backgroundColor: "rgba(231, 243, 255, 1)",
borderRadius: 24,
padding: [0, 12]
},
label: { show: true, position: "bottom", color: "rgb(59,65,75)", fontSize: 16 },
itemStyle: { color: "rgba(5,95,194,1)" },
emphasis: { focus: "adjacency" }
}
]
});
};
setOption();
console.log("node1", nodes);
const onResize = () => chart && chart.resize(); const onResize = () => chart && chart.resize();
window.addEventListener("resize", onResize); window.addEventListener("resize", onResize);
watch(activeIndex, () => setOption());
watch(activeIndex, () => renderChart());
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener("resize", onResize); window.removeEventListener("resize", onResize);
chart && chart.dispose(); if (chart) {
chart.dispose();
chart = null;
}
}); });
}); });
</script> </script>
...@@ -377,53 +248,64 @@ onMounted(() => { ...@@ -377,53 +248,64 @@ onMounted(() => {
} }
.character-relationships { .character-relationships {
width: 1600px; width: 1600px;
height: 688px; height: 738px;
background-color: #fff; background-color: #fff;
margin: 0 auto; margin: 0 auto;
position: relative; position: relative;
border-radius: 4px; border-radius: 10px;
border: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.headerBox { overflow: hidden;
background-image: url("./assets/bg-grid.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.headerBox {
position: absolute; position: absolute;
top: 14px; top: 14px;
right: 96px; left: 12px;
.headerItem { display: flex;
padding: 1px 8px; gap: 4px;
border-radius: 4px; z-index: 10;
font-size: 16px; }
font-weight: 400; .headerItem {
line-height: 30px; width: 28px;
height: 28px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer; cursor: pointer;
color: rgb(59, 65, 75); border-radius: 4px;
font-family: "Microsoft YaHei"; border: 1px solid rgba(234, 236, 238, 1);
margin-right: 8px; background: rgba(255, 255, 255, 1);
border: 1px solid rgb(230, 231, 232); }
} .headerItem img {
.active { width: 18px;
background-color: rgba(246, 250, 255, 1); height: 18px;
border-color: rgb(5, 95, 194); }
color: rgb(5, 95, 194); .headerItem.active {
} background: rgba(231, 243, 255, 1);
} }
.headerBtnBox { .headerBtnBox {
position: absolute; position: absolute;
top: 14px; top: 14px;
right: 12px; right: 12px;
img { display: flex;
gap: 4px;
z-index: 10;
}
.btn-icon {
width: 28px; width: 28px;
height: 28px; height: 28px;
margin-right: 4px;
cursor: pointer; cursor: pointer;
} }
} .mainBox {
.mainBox {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding-top: 42px; }
.graph { .graph {
width: 100%; width: 100%;
height: 100%; height: 100%;
}
}
} }
</style> </style>
\ No newline at end of file
/**
* Mock API - 法案数据模拟接口
* 所有数据集中管理,方便后续替换为真实 API
*/
// ===== 筛选项配置数据 =====
const DOMAIN_OPTIONS = [
{ id: 'all', label: '全部领域' },
{ id: 'ai', label: '人工智能' },
{ id: 'ic', label: '集成电路' },
{ id: 'quantum', label: '量子科技' },
{ id: 'network', label: '新一代通信网络' },
{ id: 'biotech', label: '生物技术' },
{ id: 'ocean', label: '海洋' },
{ id: 'deep-sea', label: '深海' },
{ id: 'polar', label: '极地' },
{ id: 'nuclear', label: '核' },
{ id: 'other', label: '其他' },
]
const TIME_OPTIONS = [
{ id: 'all', label: '全部时间' },
{ id: '2025', label: '2025年' },
{ id: '2024', label: '2024年' },
{ id: '2023', label: '2023年' },
{ id: '2022', label: '2022年' },
{ id: '2021', label: '2021年' },
{ id: 'earlier', label: '更早' },
]
// ===== 进度阶段配置 =====
const PROGRESS_STAGES = ['提出', '众议院通过', '参议院通过', '分歧协调', '提交总统', '法案通过']
// ===== 法案模拟数据 =====
const MOCK_BILLS = [
{
id: 1,
billNumber: 'H.R.1',
billSerial: '第1234',
title: '国家人工智能安全法案',
titleEn: 'One Big Beautiful Bill Act',
importance: '特别重大',
proposer: { name: '乔迪', avatar: null },
committee: '众议院科技委员会',
relatedDomains: ['人工智能', '集成电路'],
latestMotion: '2025.07.04',
latestMotionResult: '成',
currentStage: 5,
congressSession: '119th',
},
{
id: 2,
billNumber: 'H.R.2648',
billSerial: '第2648',
title: '半导体供应链安全法',
titleEn: 'CHIPS and Science Act Extension',
importance: '重大',
proposer: { name: '佩洛西', avatar: null },
committee: '参议院商务委员会',
relatedDomains: ['集成电路'],
latestMotion: '2025.06.18',
latestMotionResult: '通过',
currentStage: 3,
congressSession: '119th',
},
{
id: 3,
billNumber: 'S.1052',
billSerial: '第1052',
title: '量子计算出口管制法',
titleEn: 'Quantum Computing Export Control Act',
importance: '关注',
proposer: { name: '沃纳', avatar: null },
committee: '参议院情报委员会',
relatedDomains: ['量子科技', '人工智能'],
latestMotion: '2025.05.22',
latestMotionResult: '通过',
currentStage: 1,
congressSession: '119th',
},
{
id: 4,
billNumber: 'H.R.4217',
billSerial: '第4217',
title: '清洁能源核技术推进法案',
titleEn: 'Clean Nuclear Energy Advancement Act',
importance: '一般',
proposer: { name: '格兰姆', avatar: null },
committee: '众议院能源委员会',
relatedDomains: ['核', '生物技术'],
latestMotion: '2025.04.11',
latestMotionResult: '提出',
currentStage: 0,
congressSession: '119th',
},
{
id: 5,
billNumber: 'S.3301',
billSerial: '第3301',
title: '深海战略资源保护法',
titleEn: 'Deep Sea Strategic Resources Protection Act',
importance: '特别重大',
proposer: { name: '卢比奥', avatar: null },
committee: '参议院外交委员会',
relatedDomains: ['深海', '海洋'],
latestMotion: '2025.07.01',
latestMotionResult: '通过',
currentStage: 4,
congressSession: '119th',
},
]
// ===== 潜在新闻模拟数据 =====
const MOCK_POTENTIAL_NEWS = [
{ id: 1, title: '我国新一代人工智能大模型正式开放公众体验。', tags: [{ label: '人工智能', color: 'blue' }, { label: '科技突破', color: 'green' }] },
{ id: 2, title: '2026 年全国春运客流总量再创新高', tags: [{ label: '春运', color: 'blue' }] },
{ id: 3, title: '我国新一代人工智能大模型正式开放公众体验。', tags: [{ label: '人工智能', color: 'blue' }, { label: '科技突破', color: 'green' }] },
{ id: 4, title: '2026 年全国春运客流总量再创新高', tags: [{ label: '人工智能', color: 'blue' }, { label: '科技突破', color: 'green' }] },
{ id: 5, title: '我国新一代人工智能大模型正式开放公众体验。', tags: [{ label: '人工智能', color: 'blue' }, { label: '科技突破', color: 'green' }] },
{ id: 6, title: '2026 年全国春运客流总量再创新高', tags: [{ label: '人工智能', color: 'blue' }, { label: '科技突破', color: 'green' }] },
{ id: 7, title: '我国新一代人工智能大模型正式开放公众体验。', tags: [{ label: '人工智能', color: 'blue' }, { label: '科技突破', color: 'green' }] },
{ id: 8, title: '2026 年全国春运客流总量再创新高', tags: [{ label: '人工智能', color: 'blue' }, { label: '科技突破', color: 'green' }] },
{ id: 9, title: '我国新一代人工智能大模型正式开放公众体验。', tags: [{ label: '人工智能', color: 'blue' }, { label: '科技突破', color: 'green' }] },
{ id: 10, title: '2026 年全国春运客流总量再创新高', tags: [{ label: '人工智能', color: 'blue' }, { label: '科技突破', color: 'green' }] },
]
const MOCK_KEYWORDS = [
{ label: '财政责任', color: 'red', value: 48 },
{ label: '地球化改造', color: 'red', value: 36 },
{ label: '双边合作', color: 'cyan', value: 32 },
{ label: 'Neuralink+Grok', color: 'blue', value: 44 },
{ label: '能源政策', color: 'blue', value: 40 },
{ label: '保守派财政立场', color: 'red', value: 28 },
{ label: '农场法案倡导者', color: 'gold', value: 30 },
{ label: '环境议题', color: 'cyan', value: 34 },
{ label: '外交政策立场', color: 'gold', value: 26 },
{ label: '农业委员会成员', color: 'red', value: 22 },
{ label: '教育政策', color: 'cyan', value: 24 },
{ label: '工作可选项', color: 'gold', value: 20 },
{ label: '保守派价值观', color: 'blue', value: 38 },
{ label: '可持续能源', color: 'volcano', value: 42 },
{ label: '农业经济引擎', color: 'red', value: 18 },
{ label: '第一性原理', color: 'gold', value: 26 },
{ label: '多智能体协作', color: 'cyan', value: 28 },
{ label: '可持续富足', color: 'gold', value: 22 },
{ label: '为后代负责', color: 'gold', value: 32 },
]
// ===== 模拟网络延迟 =====
function delay(ms = 300) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
// ===== API 接口函数 =====
/**
* 获取筛选项配置
*/
export async function fetchFilterOptions() {
await delay(100)
return {
code: 200,
data: {
domains: DOMAIN_OPTIONS,
timeRanges: TIME_OPTIONS,
},
}
}
/**
* 获取进度阶段配置
*/
export async function fetchProgressStages() {
await delay(50)
return {
code: 200,
data: PROGRESS_STAGES,
}
}
/**
* 获取法案列表
* @param {Object} params - 查询参数
* @param {string} params.keyword - 搜索关键词
* @param {string[]} params.domains - 选中的领域
* @param {string[]} params.timeRanges - 选中的时间范围
* @param {boolean} params.chinaRelatedOnly - 只看涉华法案
* @param {string} params.sortBy - 排序方式
*/
export async function fetchBills(params = {}) {
await delay(400)
let result = [...MOCK_BILLS]
// 模拟关键词过滤
if (params.keyword) {
const kw = params.keyword.toLowerCase()
result = result.filter(
(b) =>
b.title.toLowerCase().includes(kw) ||
b.titleEn.toLowerCase().includes(kw) ||
b.billNumber.toLowerCase().includes(kw)
)
}
// 模拟领域过滤
if (params.domains && params.domains.length > 0 && !params.domains.includes('all')) {
result = result.filter((b) =>
b.relatedDomains.some((d) => params.domains.includes(d))
)
}
// 模拟总量 1149
const simulatedTotal = 1149
const page = params.page || 1
const pageSize = params.pageSize || 10
// 循环填充 mock 数据以模拟分页
const pagedList = []
for (let i = 0; i < pageSize; i++) {
const src = result[i % result.length]
pagedList.push({
...src,
id: (page - 1) * pageSize + i + 1,
})
}
return {
code: 200,
data: {
list: pagedList,
total: simulatedTotal,
},
}
}
/**
* 获取潜在新闻列表
*/
export async function fetchPotentialNews(params = {}) {
await delay(300)
const page = params.page || 1
const pageSize = params.pageSize || 10
// simulate 96 total items
const totalItems = 96
const list = MOCK_POTENTIAL_NEWS.map((item, i) => ({
...item,
id: (page - 1) * pageSize + i + 1,
}))
return {
code: 200,
data: {
list,
total: totalItems,
},
}
}
/**
* 获取潜在新闻分析关键词
*/
export async function fetchPotentialNewsKeywords() {
await delay(200)
return {
code: 200,
data: MOCK_KEYWORDS,
}
}
/**
* 获取排序选项
*/
export async function fetchSortOptions() {
await delay(50)
return {
code: 200,
data: [
{ value: 'publishTimeDesc', label: '发布时间倒序' },
{ value: 'publishTimeAsc', label: '发布时间正序' },
],
}
}
<template>
<div class="bill-card">
<div class="bill-card-inner">
<div class="bill-card-preview">
<DocumentPreview :bill-number="bill.billId" :bill-serial="bill.name" />
</div>
<div class="bill-card-detail">
<div class="bill-card-upper">
<div class="bill-card-title-row">
<h3 class="bill-card-title">{{ bill.name }}</h3>
</div>
<p class="bill-card-desc">{{ bill.ename }}</p>
</div>
<div class="bill-card-divider" />
<div class="bill-card-meta">
<div class="meta-row">
<span class="meta-label">提案人:</span>
<span class="meta-value sponsor-name" :title="allSponsorNames">
{{ firstSponsorName }}
</span>
</div>
<div class="meta-row">
<span class="meta-label">相关领域:</span>
<div class="meta-tags">
<TagBadge v-for="item in bill.industryList" :key="item.industryName" :label="item.industryName" tag-class="tag3" />
</div>
</div>
<div class="meta-row">
<span class="meta-label">最新动议:</span>
<span class="meta-value">{{ latestActionText }}</span>
</div>
<div class="meta-row meta-row-progress">
<ProgressBar :stages="stageNames" :current="currentStageIndex" />
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import DocumentPreview from './DocumentPreview.vue'
import TagBadge from './TagBadge.vue'
import ProgressBar from './ProgressBar.vue'
const props = defineProps({
bill: { type: Object, required: true },
progressStages: { type: Array, default: () => [] },
})
// 提案人:只展示第一个,悬浮 title 显示全部
const firstSponsorName = computed(() => {
if (!props.bill.sponsorList || props.bill.sponsorList.length === 0) return ''
return props.bill.sponsorList[0] || ''
})
const allSponsorNames = computed(() => {
if (!props.bill.sponsorList || props.bill.sponsorList.length === 0) return ''
return props.bill.sponsorList.filter(Boolean).join('\n')
})
// 最新动议:null 替换为至今
const latestActionText = computed(() => {
if (!props.bill.latestAction) return ''
// 将 "null" 字符串替换为 "至今"
return props.bill.latestAction.replace(/\bnull\b/gi, '至今').trim()
})
// 法案进展:stageList 是对象数组 [{id, name}],提取 name 列表
const stageNames = computed(() => {
if (!props.progressStages || props.progressStages.length === 0) return []
// progressStages 是全量阶段字典 [{id, name}],提取 name
return props.progressStages.map(s => s.name || s)
})
// 当前阶段:找到 stageList 最后一个阶段在全量字典中的位置
const currentStageIndex = computed(() => {
if (!props.bill.stageList || props.bill.stageList.length === 0) return 0
if (!props.progressStages || props.progressStages.length === 0) return 0
// stageList 是已走过的阶段对象数组,取最后一个
const lastStage = props.bill.stageList[props.bill.stageList.length - 1]
const lastStageName = lastStage?.name || lastStage
const idx = props.progressStages.findIndex(s => (s.name || s) === lastStageName)
return idx > -1 ? idx : 0
})
</script>
<style scoped>
.bill-card {
width: 1224px;
max-width: 100%;
height: 320px;
background: #fff;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
box-sizing: border-box;
padding: 12px 16px;
cursor: pointer;
overflow: hidden;
transition: box-shadow 0.2s;
}
.bill-card:hover {
box-shadow: 0px 0px 24px 0px rgba(25, 69, 130, 0.16);
}
.bill-card-inner {
display: flex;
flex-direction: row;
gap: 13px;
align-items: center;
height: 100%;
}
.bill-card-preview {
flex-shrink: 0;
width: 240px;
height: 296px;
}
.bill-card-detail {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 12px;
align-items: flex-start;
height: 100%;
}
.bill-card-upper {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
}
.bill-card-title-row {
display: flex;
align-items: flex-start;
gap: 20px;
width: 100%;
}
.bill-card-title {
font-size: 20px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
margin: 0;
line-height: 26px;
flex: 1;
min-width: 0;
}
.bill-card-desc {
font-size: 16px;
font-weight: 400;
color: rgba(59, 65, 75, 1);
margin: 0;
line-height: 24px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.bill-card-divider {
width: 100%;
height: 1px;
background: rgba(234, 236, 238, 1);
flex-shrink: 0;
}
.bill-card-meta {
width: 100%;
flex: 1;
position: relative;
min-height: 0;
}
.meta-row {
display: flex;
align-items: center;
position: absolute;
left: 0;
width: 100%;
}
.meta-row:nth-child(1) { top: 0; }
.meta-row:nth-child(2) { top: 36px; }
.meta-row:nth-child(3) { top: 72px; }
.meta-row:nth-child(4) { top: 108px; }
.meta-row:nth-child(5) { top: 144px; }
.meta-label {
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
letter-spacing: 1px;
line-height: 24px;
white-space: nowrap;
flex-shrink: 0;
width: 100px;
}
.meta-value {
font-size: 16px;
font-weight: 400;
color: rgba(95, 101, 108, 1);
line-height: 24px;
}
.sponsor-name {
cursor: default;
white-space: nowrap;
}
.meta-tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.meta-row-progress {
left: 0;
right: 0;
width: 100%;
}
.meta-row-progress :deep(.progress-bar) {
width: 100%;
}
.meta-progress {
flex: 1;
min-width: 0;
}
</style>
<template>
<div class="bill-list">
<div v-if="loading" class="bill-list-loading">
<div v-for="i in 3" :key="i" class="bill-list-skeleton">
<div class="skeleton-left" />
<div class="skeleton-right">
<div class="skeleton-line w30" />
<div class="skeleton-line w50" />
<div class="skeleton-divider" />
<div class="skeleton-line w60" />
<div class="skeleton-line w40" />
</div>
</div>
</div>
<div v-else-if="bills.length === 0" class="bill-list-empty">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#bfbfbf" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<p>暂无相关法案</p>
</div>
<template v-else>
<BillCard
v-for="bill in bills"
:key="bill.id"
:bill="bill"
:progress-stages="progressStages"
/>
<div v-if="total > 0" class="bill-pagination">
<span class="bill-pagination-total">{{ '\u5171' + total + '\u9879\u6cd5\u6848' }}</span>
<el-pagination
:current-page="currentPage"
:page-size="pageSize"
:total="total"
:pager-count="5"
layout="prev, pager, next"
background
@current-change="$emit('page-change', $event)"
/>
</div>
</template>
</div>
</template>
<script setup>
import BillCard from './BillCard.vue'
defineProps({
bills: { type: Array, required: true },
loading: { type: Boolean, default: false },
total: { type: Number, default: 0 },
currentPage: { type: Number, default: 1 },
pageSize: { type: Number, default: 10 },
progressStages: { type: Array, default: () => ['提出', '众议院通过', '参议院通过', '分歧协调', '提交总统', '法案通过'] },
})
defineEmits(['page-change'])
</script>
<style scoped>
.bill-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.bill-list-loading {
display: flex;
flex-direction: column;
gap: 16px;
}
.bill-list-skeleton {
display: flex;
gap: 24px;
background: #fff;
border: 1px solid #ebedf0;
border-radius: 8px;
padding: 24px;
}
.skeleton-left {
width: 120px;
height: 190px;
background: #f0f0f0;
border-radius: 4px;
animation: pulse 1.5s ease-in-out infinite;
}
.skeleton-right {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
}
.skeleton-line {
height: 16px;
background: #f0f0f0;
border-radius: 4px;
animation: pulse 1.5s ease-in-out infinite;
}
.skeleton-line.w30 { width: 30%; }
.skeleton-line.w40 { width: 40%; }
.skeleton-line.w50 { width: 50%; }
.skeleton-line.w60 { width: 60%; }
.skeleton-divider {
height: 1px;
background: #ebedf0;
}
.bill-list-empty {
background: #fff;
border: 1px solid #ebedf0;
border-radius: 8px;
padding: 48px;
text-align: center;
color: #bfbfbf;
font-size: 13px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Pagination */
.bill-pagination {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0 0;
}
.bill-pagination-total {
font-size: 14px;
font-family: 'Microsoft YaHei', sans-serif;
font-weight: 400;
color: rgba(95, 101, 108, 1);
white-space: nowrap;
}
/* Override el-pagination to match design */
.bill-pagination :deep(.el-pagination) {
--el-pagination-bg-color: #fff;
--el-pagination-hover-color: rgba(5, 95, 194, 1);
padding: 0;
}
.bill-pagination :deep(.el-pagination.is-background .btn-prev),
.bill-pagination :deep(.el-pagination.is-background .btn-next),
.bill-pagination :deep(.el-pagination.is-background .el-pager li) {
min-width: 32px;
height: 32px;
line-height: 32px;
border-radius: 4px;
border: 1px solid rgba(234, 236, 238, 1);
background: #fff;
color: rgba(59, 65, 75, 1);
font-size: 14px;
font-family: 'Microsoft YaHei', sans-serif;
margin: 0 3px;
}
.bill-pagination :deep(.el-pagination.is-background .el-pager li:not(.is-disabled).is-active) {
background: #fff;
color: rgba(5, 95, 194, 1);
border-color: rgba(5, 95, 194, 1);
}
.bill-pagination :deep(.el-pagination.is-background .el-pager li:not(.is-disabled):hover) {
color: rgba(5, 95, 194, 1);
}
.bill-pagination :deep(.el-pagination.is-background .btn-prev:hover),
.bill-pagination :deep(.el-pagination.is-background .btn-next:hover) {
color: rgba(5, 95, 194, 1);
}
</style>
<template>
<div class="bill-tracker-root" ref="trackerRoot">
<div class="bt-topbar">
<TopBar
:keyword="filters.searchText"
:china-related-only="filters.isCN"
:sort-by="filters.sortBy"
:sort-options="sortOptions"
@update:keyword="updateKeyword"
@toggle-china-related="updateChinaRelated(!filters.isCN)"
@update:sort="updateSort"
/>
</div>
<div class="bt-main">
<div class="bt-sidebar">
<SidebarFilter
:domains="filterOptions.domains"
:time-ranges="filterOptions.timeRanges"
:selected-domains="filters.selectedDomains"
:selected-time-ranges="filters.selectedTimeRanges"
@toggle-domain="toggleDomain"
@toggle-time="toggleTimeRange"
/>
</div>
<div class="bt-list-area">
<BillList
:bills="bills"
:loading="loading"
:total="total"
:current-page="currentPage"
:page-size="pageSize"
:progress-stages="stageConfig"
@page-change="onPageChange"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import TopBar from './TopBar.vue'
import SidebarFilter from './SidebarFilter.vue'
import BillList from './BillList.vue'
import { useBills } from '../composables/useBills.js'
const trackerRoot = ref(null)
const {
bills,
stageConfig,
total,
loading,
currentPage,
pageSize,
filters,
filterOptions,
sortOptions,
init,
updateKeyword,
toggleDomain,
toggleTimeRange,
updateSort,
updateChinaRelated,
handlePageChange,
} = useBills()
const onPageChange = async (page) => {
await handlePageChange(page)
// 数据加载完成后滚动到顶部
if (trackerRoot.value) {
trackerRoot.value.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
onMounted(async () => {
try {
await init()
} catch (err) {
console.error('初始化失败:', err)
}
})
</script>
<style scoped>
.bill-tracker-root {
width: 100%;
font-family: 'Source Han Sans CN', 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
-webkit-font-smoothing: antialiased;
color: rgba(59, 65, 75, 1);
}
.bt-topbar {
padding: 12px 0;
}
.bt-main {
display: flex;
gap: 16px;
}
.bt-sidebar {
flex-shrink: 0;
}
.bt-list-area {
flex: 1;
min-width: 0;
}
</style>
<template>
<div class="doc-preview">
<div class="doc-image">
<div class="doc-frame">
<div class="doc-seal">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<circle cx="8" cy="8" r="7" stroke="#999" stroke-width="0.8" fill="none"/>
<circle cx="8" cy="8" r="4" stroke="#999" stroke-width="0.5" fill="none"/>
<line x1="8" y1="1" x2="8" y2="3" stroke="#999" stroke-width="0.5"/>
<line x1="8" y1="13" x2="8" y2="15" stroke="#999" stroke-width="0.5"/>
<line x1="1" y1="8" x2="3" y2="8" stroke="#999" stroke-width="0.5"/>
<line x1="13" y1="8" x2="15" y2="8" stroke="#999" stroke-width="0.5"/>
</svg>
</div>
<div class="doc-text">
<div class="doc-line-italic">One Hundred Nineteenth Congress</div>
<div class="doc-line-small">of the</div>
<div class="doc-line-italic">United States of America</div>
<div class="doc-body-lines">
<div class="doc-body-line" style="width: 100%" />
<div class="doc-body-line" style="width: 83%" />
<div class="doc-body-line" style="width: 92%" />
<div class="doc-body-line" style="width: 75%" />
<div class="doc-body-line" style="width: 100%" />
<div class="doc-body-line" style="width: 67%" />
</div>
</div>
</div>
</div>
<div class="doc-info">
<div class="doc-number">{{ billNumber }}</div>
<div class="doc-serial">{{ billSerial }}</div>
</div>
</div>
</template>
<script setup>
defineProps({
billNumber: { type: String, required: true },
billSerial: { type: String, required: true },
})
</script>
<style scoped>
.doc-preview {
width: 240px;
height: 296px;
position: relative;
}
.doc-image {
width: 222px;
height: 296px;
position: absolute;
left: 13px;
top: 0;
}
.doc-frame {
width: 100%;
height: 100%;
background: #fff;
border: 1px solid #ebedf0;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 12px 12px;
overflow: hidden;
box-sizing: border-box;
}
.doc-seal {
position: absolute;
top: 8px;
left: 8px;
}
.doc-text {
margin-top: 20px;
width: 100%;
display: flex;
flex-direction: column;
gap: 3px;
padding: 0 4px;
}
.doc-line-italic {
font-size: 7px;
color: rgba(95, 101, 108, 1);
text-align: center;
font-style: italic;
line-height: 1.4;
}
.doc-line-small {
font-size: 6px;
color: rgba(95, 101, 108, 1);
text-align: center;
line-height: 1.4;
}
.doc-body-lines {
margin-top: 10px;
border-top: 1px solid #ebedf0;
padding-top: 8px;
display: flex;
flex-direction: column;
gap: 5px;
}
.doc-body-line {
height: 2.5px;
background: #e0e0e0;
border-radius: 1px;
margin: 0 auto;
}
.doc-info {
position: absolute;
left: 0;
bottom: 0;
width: 240px;
height: 78px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
padding-bottom: 4px;
background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0.95) 40%);
border-radius: 0 0 4px 4px;
}
.doc-number {
font-size: 20px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
line-height: 36px;
text-align: center;
}
.doc-serial {
font-size: 16px;
font-weight: 400;
color: rgba(95, 101, 108, 1);
line-height: 24px;
text-align: center;
}
</style>
<template>
<span class="importance-badge" :class="'importance-' + level">
<span class="importance-dot"></span>
{{ label }}
</span>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
label: { type: String, required: true },
})
const level = computed(() => {
if (props.label.includes('特别重大') || props.label.includes('高')) return 'high'
if (props.label.includes('重大') || props.label.includes('中') || props.label.includes('橙')) return 'medium'
if (props.label.includes('关注') || props.label.includes('黄')) return 'warning'
return 'low'
})
</script>
<style scoped>
.importance-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 2px 8px;
border-radius: 20px;
font-size: 16px;
font-weight: 400;
white-space: nowrap;
flex-shrink: 0;
line-height: 24px;
}
.importance-dot {
display: inline-block;
width: 4px;
height: 4px;
border-radius: 50%;
flex-shrink: 0;
}
.importance-high {
background: rgba(206, 79, 81, 0.1);
color: rgba(206, 79, 81, 1);
}
.importance-high .importance-dot {
background: rgba(206, 79, 81, 1);
}
.importance-medium {
background: rgba(255, 149, 77, 0.1);
color: rgba(255, 149, 77, 1);
}
.importance-medium .importance-dot {
background: rgba(255, 149, 77, 1);
}
.importance-warning {
background: rgba(232, 189, 11, 0.1);
color: rgba(232, 189, 11, 1);
}
.importance-warning .importance-dot {
background: rgba(232, 189, 11, 1);
}
.importance-low {
background: rgba(33, 129, 57, 0.1);
color: rgba(33, 129, 57, 1);
}
.importance-low .importance-dot {
background: rgba(33, 129, 57, 1);
}
</style>
<template>
<div class="potential-news-root">
<div class="pn-analysis">
<PotentialNewsAnalysis :keywords="keywords" />
</div>
<div class="pn-list">
<PotentialNewsList
:news-list="newsList"
:total="total"
:current-page="currentPage"
:page-size="10"
@page-change="handlePageChange"
/>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import PotentialNewsList from './PotentialNewsList.vue'
import PotentialNewsAnalysis from './PotentialNewsAnalysis.vue'
import { getPotentialNewsList, getPotentialNewsKeywords } from '@/api/bill/billHome.js'
const route = useRoute()
const personId = computed(() => route.params.personId || route.query.personId || '')
const newsList = ref([])
const total = ref(0)
const currentPage = ref(1)
const keywords = ref([])
const COLORS = ['red', 'cyan', 'gold', 'blue', 'volcano']
async function loadNews(page = 1) {
if (!personId.value) return
currentPage.value = page
const res = await getPotentialNewsList(personId.value, {
currentPage: page - 1,
pageSize: 10,
})
if (res.code === 200 && res.data) {
newsList.value = res.data.content || []
total.value = res.data.totalElements || 0
} else {
newsList.value = []
total.value = 0
}
}
async function loadKeywords() {
if (!personId.value) return
const res = await getPotentialNewsKeywords(personId.value)
if (res.code === 200 && res.data) {
keywords.value = res.data.map((item, index) => ({
label: item.clause,
value: item.count,
color: COLORS[index % COLORS.length],
}))
}
}
async function handlePageChange(page) {
await loadNews(page)
}
onMounted(async () => {
if (!personId.value) return
await Promise.all([loadNews(), loadKeywords()])
})
</script>
<style scoped>
.potential-news-root {
display: flex;
gap: 16px;
width: 100%;
font-family: 'Source Han Sans CN', 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
-webkit-font-smoothing: antialiased;
color: rgba(59, 65, 75, 1);
}
.pn-analysis {
width: 430px;
flex-shrink: 0;
}
.pn-list {
flex: 1;
min-width: 0;
}
</style>
<template>
<div class="pna-container">
<!-- Header -->
<div class="pna-header">
<div class="pna-header-left">
<svg class="pna-header-icon" width="8" height="20" viewBox="0 0 8 20" fill="none">
<rect width="8" height="20" rx="2" fill="rgba(5,95,194,1)" />
</svg>
<span class="pna-header-title">潜在提案主题分析</span>
</div>
<div class="pna-header-actions">
<!-- 下载图标 (9_1305): 竖线 + 向下箭头 + 底座 -->
<svg class="pna-action-icon" width="28" height="28" viewBox="0 0 28 28" fill="none">
<rect x="13" y="7" width="2" height="6" rx="0.5" fill="rgba(132,136,142,1)" />
<path d="M10 13l4 4 4-4" stroke="rgba(132,136,142,1)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none" />
<path d="M7 19h14" stroke="rgba(132,136,142,1)" stroke-width="1.5" stroke-linecap="round" />
</svg>
<!-- 收藏/星标图标 (9_1303): 五角星 -->
<svg class="pna-action-icon" width="28" height="28" viewBox="0 0 28 28" fill="none">
<path d="M14 6l2.47 5.01L22 11.76l-4 3.9.94 5.5L14 18.51l-4.94 2.65.94-5.5-4-3.9 5.53-.75L14 6z" stroke="rgba(132,136,142,1)" stroke-width="1.5" stroke-linejoin="round" fill="none" />
</svg>
</div>
</div>
<!-- ECharts word cloud -->
<div ref="chartRef" class="pna-cloud"></div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import * as echarts from 'echarts'
import 'echarts-wordcloud'
const props = defineProps({
keywords: { type: Array, default: () => [] },
})
const chartRef = ref(null)
let chartInstance = null
// 设计稿色板
const COLOR_MAP = {
red: 'rgba(206, 79, 81, 1)',
cyan: 'rgba(19, 168, 168, 1)',
gold: 'rgba(250, 173, 20, 1)',
blue: 'rgba(22, 119, 255, 1)',
volcano: 'rgba(255, 122, 69, 1)',
}
function buildOption(data) {
const seriesData = data.map((item) => ({
name: item.label,
value: item.value || item.size || 18,
textStyle: {
color: COLOR_MAP[item.color] || COLOR_MAP.blue,
},
}))
return {
series: [
{
type: 'wordCloud',
shape: 'circle',
left: 'center',
top: 'center',
width: '90%',
height: '90%',
sizeRange: [12, 28],
rotationRange: [-45, 45],
rotationStep: 15,
gridSize: 8,
drawOutOfBound: false,
layoutAnimation: true,
textStyle: {
fontFamily: 'Microsoft YaHei, Source Han Sans CN, Noto Sans SC, sans-serif',
fontWeight: 400,
},
emphasis: {
textStyle: {
fontWeight: 700,
},
},
data: seriesData,
},
],
}
}
function initChart() {
if (!chartRef.value) return
chartInstance = echarts.init(chartRef.value)
if (props.keywords.length > 0) {
chartInstance.setOption(buildOption(props.keywords))
}
}
function handleResize() {
chartInstance?.resize()
}
watch(
() => props.keywords,
(val) => {
if (chartInstance && val.length > 0) {
chartInstance.setOption(buildOption(val))
}
},
{ deep: true }
)
onMounted(() => {
nextTick(() => {
initChart()
window.addEventListener('resize', handleResize)
})
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
chartInstance?.dispose()
chartInstance = null
})
</script>
<style scoped>
.pna-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 10px;
border: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
overflow: hidden;
}
.pna-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 24px;
flex-shrink: 0;
}
.pna-header-left {
display: flex;
align-items: center;
gap: 12px;
}
.pna-header-title {
font-size: 20px;
font-weight: 700;
line-height: 26px;
color: rgba(5, 95, 194, 1);
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
}
.pna-header-actions {
display: flex;
align-items: center;
gap: 4px;
}
.pna-action-icon {
cursor: pointer;
opacity: 0.7;
transition: opacity 0.2s;
}
.pna-action-icon:hover {
opacity: 1;
}
.pna-cloud {
flex: 1;
min-height: 400px;
}
</style>
<template>
<div class="pn-list-container">
<div v-if="loading" class="pn-loading-mask">
<div class="pn-spinner"></div>
</div>
<div class="pn-header">
<div class="pn-header-left">
<svg class="pn-header-icon" width="8" height="20" viewBox="0 0 8 20" fill="none">
<rect width="8" height="20" rx="2" fill="rgba(5,95,194,1)" />
</svg>
<span class="pn-header-title">潜在提案举措分析</span>
</div>
<div class="pn-header-actions">
<svg class="pn-action-icon" width="28" height="28" viewBox="0 0 28 28" fill="none">
<rect x="13" y="7" width="2" height="6" rx="0.5" fill="rgba(132,136,142,1)" />
<path d="M10 13l4 4 4-4" stroke="rgba(132,136,142,1)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none" />
<path d="M7 19h14" stroke="rgba(132,136,142,1)" stroke-width="1.5" stroke-linecap="round" />
</svg>
<svg class="pn-action-icon" width="28" height="28" viewBox="0 0 28 28" fill="none">
<path d="M14 6l2.47 5.01L22 11.76l-4 3.9.94 5.5L14 18.51l-4.94 2.65.94-5.5-4-3.9 5.53-.75L14 6z" stroke="rgba(132,136,142,1)" stroke-width="1.5" stroke-linejoin="round" fill="none" />
</svg>
</div>
</div>
<div class="pn-search-wrapper">
<div class="pn-search">
<span class="pn-search-placeholder">{{ keyword || '搜索新闻' }}</span>
<svg class="pn-search-icon" width="16" height="16" viewBox="0 0 16 16" fill="none">
<circle cx="7" cy="7" r="5.5" stroke="rgba(132,136,142,1)" stroke-width="1.2" fill="none" />
<path d="M11 11L14 14" stroke="rgba(132,136,142,1)" stroke-width="1.2" stroke-linecap="round" />
</svg>
</div>
</div>
<div class="pn-rows" :class="{ 'pn-rows-loading': loading }">
<div v-if="loading" class="pn-rows-spinner">
<div class="pn-spinner-icon"></div>
<span class="pn-spinner-text">加载中...</span>
</div>
<div
v-for="(item, index) in newsList"
v-show="!loading"
:key="item.tkid"
:class="['pn-row', { 'pn-row-alt': index % 2 === 0 }]"
>
<div class="pn-row-num">
<span class="pn-num-circle"></span>
<span class="pn-num-text">{{ index + 1 + (currentPage - 1) * pageSize }}</span>
</div>
<p class="pn-row-title">{{ item.fynr }}</p>
</div>
</div>
<div class="pn-footer">
<span class="pn-footer-total">{{ total }}条关键新闻</span>
<div class="pn-pagination">
<button
class="pn-page-btn pn-page-prev"
:disabled="currentPage <= 1"
@click="$emit('page-change', currentPage - 1)"
>
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
<path d="M6.5 1.5L3.5 5L6.5 8.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
<button
v-for="page in displayedPages"
:key="page"
:class="['pn-page-num', { active: page === currentPage }]"
@click="$emit('page-change', page)"
>{{ page }}</button>
<button
class="pn-page-btn pn-page-next"
:disabled="currentPage >= totalPages"
@click="$emit('page-change', currentPage + 1)"
>
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
<path d="M3.5 1.5L6.5 5L3.5 8.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import TagBadge from './TagBadge.vue'
const tagColorMap = {
blue: 'tag6',
green: '',
}
const props = defineProps({
newsList: { type: Array, default: () => [] },
total: { type: Number, default: 0 },
currentPage: { type: Number, default: 1 },
pageSize: { type: Number, default: 10 },
keyword: { type: String, default: '' },
loading: { type: Boolean, default: false },
})
defineEmits(['page-change'])
const totalPages = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)))
const displayedPages = computed(() => {
const pages = []
const tp = totalPages.value
const cp = props.currentPage
const start = Math.max(1, cp - 2)
const end = Math.min(tp, start + 4)
for (let i = start; i <= end; i++) {
pages.push(i)
}
return pages
})
</script>
<style scoped>
.pn-list-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 10px;
border: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
overflow: hidden;
}
.pn-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 24px;
flex-shrink: 0;
}
.pn-header-left {
display: flex;
align-items: center;
gap: 12px;
}
.pn-header-title {
font-size: 20px;
font-weight: 700;
line-height: 26px;
color: rgba(5, 95, 194, 1);
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
}
.pn-header-actions {
display: flex;
align-items: center;
gap: 4px;
}
.pn-action-icon {
cursor: pointer;
opacity: 0.7;
transition: opacity 0.2s;
}
.pn-action-icon:hover {
opacity: 1;
}
.pn-search-wrapper {
padding: 0 24px 0 24px;
flex-shrink: 0;
}
.pn-search {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
padding: 0 12px;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: #fff;
}
.pn-search-placeholder {
font-size: 16px;
line-height: 24px;
color: rgba(132, 136, 142, 1);
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
}
.pn-search-icon {
flex-shrink: 0;
}
.pn-rows {
flex: 1;
overflow-y: auto;
margin-top: 12px;
position: relative;
}
.pn-rows-loading {
display: flex;
align-items: center;
justify-content: center;
}
.pn-rows-spinner {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.pn-spinner-icon {
width: 40px;
height: 40px;
border: 4px solid rgba(22, 119, 255, 0.2);
border-top-color: rgba(22, 119, 255, 1);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.pn-spinner-text {
font-size: 14px;
color: rgba(132, 136, 142, 1);
}
.pn-row {
position: relative;
height: 60px;
display: flex;
align-items: center;
padding: 0 24px;
border-top: 1px solid rgba(234, 236, 238, 1);
background: #fff;
}
.pn-row-alt {
background: rgba(247, 248, 249, 1);
}
.pn-row-num {
position: relative;
width: 24px;
height: 24px;
flex-shrink: 0;
margin-right: 12px;
}
.pn-num-circle {
position: absolute;
inset: 0;
border-radius: 50%;
background: radial-gradient(circle at 40% 35%, rgba(5, 95, 194, 0.06) 0%, rgba(5, 95, 194, 0.12) 100%);
border: 1px solid rgba(5, 95, 194, 0.15);
}
.pn-num-text {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 400;
line-height: 12px;
color: rgba(5, 95, 194, 1);
font-family: 'Microsoft YaHei', sans-serif;
}
.pn-row-title {
flex: 1;
min-width: 0;
font-size: 16px;
font-weight: 700;
line-height: 30px;
color: rgba(59, 65, 75, 1);
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pn-row-tags {
display: flex;
gap: 8px;
flex-shrink: 0;
margin-left: 24px;
}
.pn-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 24px;
border-top: 1px solid rgba(234, 236, 238, 1);
flex-shrink: 0;
}
.pn-footer-total {
font-size: 16px;
font-weight: 400;
line-height: 24px;
color: rgba(132, 136, 142, 1);
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
}
.pn-pagination {
display: flex;
align-items: center;
gap: 4px;
}
.pn-page-btn {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 4px;
background: #fff;
cursor: pointer;
color: rgba(0, 0, 0, 0.65);
transition: border-color 0.2s, color 0.2s;
}
.pn-page-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.pn-page-num {
min-width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 4px;
background: #fff;
cursor: pointer;
font-size: 14px;
font-weight: 400;
line-height: 22px;
color: rgba(0, 0, 0, 0.88);
font-family: 'Microsoft YaHei', sans-serif;
padding: 0 4px;
transition: border-color 0.2s, color 0.2s;
}
.pn-page-num.active {
border-color: rgba(22, 119, 255, 1);
color: rgba(22, 119, 255, 1);
}
.pn-page-num:hover:not(.active) {
border-color: rgba(22, 119, 255, 0.5);
color: rgba(22, 119, 255, 0.8);
}
.pn-loading-mask {
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
backdrop-filter: blur(2px);
}
.pn-spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(22, 119, 255, 0.2);
border-top-color: rgba(22, 119, 255, 1);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
<template>
<div class="progress-bar">
<span class="progress-title">法案进展:</span>
<div class="progress-steps">
<div
v-for="(stage, idx) in [...visibleStages].reverse()"
:key="idx"
class="tag"
:style="{ zIndex: visibleStages.length - idx }"
>
{{ stage }}
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
stages: { type: Array, required: true },
current: { type: Number, default: 0 },
})
/**
* 只渲染到当前步骤为止的阶段
* @returns {Array} 可见的阶段列表
*/
const visibleStages = computed(() => {
return props.stages.slice(0, props.current + 1)
})
</script>
<style scoped>
.progress-bar {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
height: 26px;
}
.progress-title {
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
letter-spacing: 1px;
line-height: 24px;
white-space: nowrap;
flex-shrink: 0;
}
.progress-steps {
display: flex;
align-items: center;
}
.tag {
height: 24px;
line-height: 22px;
padding: 0 10px 0 30px;
background: rgba(255, 255, 255, 1);
color: rgb(95, 101, 108);
border-top: 1px solid rgb(234, 236, 238);
border-bottom: 1px solid rgb(234, 236, 238);
position: relative;
margin-left: -10px;
font-family: 'Microsoft YaHei', sans-serif;
font-size: 14px;
font-weight: 400;
white-space: nowrap;
box-sizing: border-box;
}
.tag::after {
content: '';
position: absolute;
top: 50%;
right: -8.485px;
width: 16.97px;
height: 16.97px;
background: inherit;
border-top: 1px solid rgb(234, 236, 238);
border-right: 1px solid rgb(234, 236, 238);
transform: translateY(-50%) rotate(45deg);
z-index: 1;
box-shadow: 2px -2px 2px rgba(0, 0, 0, 0.05);
box-sizing: border-box;
}
.tag:first-child {
margin-left: 0;
padding-left: 10px;
border-left: 1px solid rgb(234, 236, 238);
border-radius: 4px 0 0 4px;
}
.tag:last-child {
background: rgb(59, 65, 75);
color: rgba(255, 255, 255, 1);
border-color: rgb(59, 65, 75);
padding-right: 10px;
border-radius: 0;
border-right: none;
}
.tag:last-child::after {
display: block;
border-color: rgb(59, 65, 75);
box-shadow: none;
}
.tag:first-child:last-child {
margin-left: 0;
padding: 0 10px;
border-radius: 4px 0 0 4px;
background: rgb(59, 65, 75);
color: rgba(255, 255, 255, 1);
border: 1px solid rgb(59, 65, 75);
border-right: none;
}
</style>
<template>
<aside class="sidebar-filter">
<div class="sf-section">
<div class="sf-title-row">
<span class="sf-title-bar"></span>
<span class="sf-title-text">科技领域</span>
</div>
<div class="sf-options">
<label v-for="opt in domains" :key="opt.id" class="sf-label">
<span
class="sf-checkbox-icon"
:class="{ checked: selectedDomains.includes(opt.id) }"
@click.prevent="$emit('toggle-domain', opt.id)"
>
<svg v-if="selectedDomains.includes(opt.id)" width="14" height="14" viewBox="0 0 14 14" fill="none">
<rect width="14" height="14" rx="4" fill="#055FC2"/>
<path d="M3.5 7L6 9.5L10.5 5" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span v-else class="sf-checkbox-empty"></span>
</span>
<span class="sf-text" @click="$emit('toggle-domain', opt.id)">{{ opt.label }}</span>
</label>
</div>
</div>
<div class="sf-section">
<div class="sf-title-row">
<span class="sf-title-bar"></span>
<span class="sf-title-text">发布时间</span>
</div>
<div class="sf-options">
<label v-for="opt in timeRanges" :key="opt.id" class="sf-label">
<span
class="sf-checkbox-icon"
:class="{ checked: selectedTimeRanges.includes(opt.id) }"
@click.prevent="$emit('toggle-time', opt.id)"
>
<svg v-if="selectedTimeRanges.includes(opt.id)" width="14" height="14" viewBox="0 0 14 14" fill="none">
<rect width="14" height="14" rx="4" fill="#055FC2"/>
<path d="M3.5 7L6 9.5L10.5 5" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span v-else class="sf-checkbox-empty"></span>
</span>
<span class="sf-text" @click="$emit('toggle-time', opt.id)">{{ opt.label }}</span>
</label>
</div>
</div>
</aside>
</template>
<script setup>
defineProps({
domains: { type: Array, default: () => [] },
timeRanges: { type: Array, default: () => [] },
selectedDomains: { type: Array, default: () => ['all'] },
selectedTimeRanges: { type: Array, default: () => ['all'] },
})
defineEmits(['toggle-domain', 'toggle-time'])
</script>
<style scoped>
.sidebar-filter {
width: 360px;
background: #fff;
border-radius: 10px;
border: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
box-sizing: border-box;
padding: 16px 0 24px 0;
display: flex;
flex-direction: column;
gap: 16px;
align-items: flex-start;
overflow: hidden;
}
.sf-section {
width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
align-items: flex-start;
}
.sf-title-row {
display: flex;
align-items: center;
gap: 17px;
width: 100%;
padding: 0 0 0 0;
box-sizing: border-box;
}
.sf-title-bar {
display: inline-block;
width: 8px;
height: 16px;
background: rgba(5, 95, 194, 1);
border-radius: 0 2px 2px 0;
flex-shrink: 0;
}
.sf-title-text {
font-size: 16px;
font-weight: 700;
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
color: rgba(5, 95, 194, 1);
letter-spacing: 1px;
line-height: 24px;
}
.sf-options {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 8px 4px;
align-content: flex-start;
align-items: flex-start;
padding: 0 0 0 24px;
width: 100%;
box-sizing: border-box;
}
.sf-label {
width: 160px;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
flex-shrink: 0;
}
.sf-checkbox-icon {
display: flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
flex-shrink: 0;
cursor: pointer;
}
.sf-checkbox-empty {
display: block;
width: 14px;
height: 14px;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
box-sizing: border-box;
background: #fff;
}
.sf-text {
font-size: 16px;
font-weight: 400;
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
color: rgba(95, 101, 108, 1);
line-height: 24px;
cursor: pointer;
white-space: nowrap;
}
.sf-label:hover .sf-text {
color: rgba(59, 65, 75, 1);
}
</style>
<template>
<AreaTag :tag-name="label" :class="tagClass" />
</template>
<script setup>
import { h, defineComponent } from 'vue'
const AreaTag = defineComponent({
name: 'AreaTag',
props: {
tagName: { type: String, default: '标签名称' },
},
setup(props) {
return () => h('div', { class: 'tag-wrapper' }, props.tagName)
},
})
defineProps({
label: { type: String, required: true },
tagClass: { type: String, default: 'tag3' },
})
</script>
<style scoped>
.tag-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
height: 24px;
padding: 0 8px;
line-height: 24px;
text-align: center;
font-family: Microsoft YaHei, sans-serif;
font-size: 14px;
font-weight: 400;
letter-spacing: 0px;
box-sizing: border-box;
border-radius: 4px;
border: 1px solid rgba(183, 235, 143, 1);
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
}
.tag3 .tag-wrapper,
:deep(.tag3) {
border: 1px solid rgba(174, 214, 255, 1);
background: rgba(246, 250, 255, 1);
color: rgba(5, 95, 194, 1);
}
.tag1 .tag-wrapper,
:deep(.tag1) {
border: 1px solid rgba(255, 163, 158, 1);
background: rgba(255, 241, 240, 1);
color: rgba(245, 34, 45, 1);
}
.tag6 .tag-wrapper,
:deep(.tag6) {
border: 1px solid rgba(145, 202, 255, 1);
background: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1);
}
.tag8 .tag-wrapper,
:deep(.tag8) {
border: 1px solid rgba(255, 229, 143, 1);
background: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1);
}
.tag9 .tag-wrapper,
:deep(.tag9) {
border: 1px solid rgba(255, 213, 145, 1);
background: rgba(255, 247, 230, 1);
color: rgba(250, 140, 22, 1);
}
.tag4 .tag-wrapper,
:deep(.tag4) {
border: 1px solid rgba(211, 173, 247, 1);
background: rgba(249, 240, 255, 1);
color: rgba(114, 46, 209, 1);
}
</style>
<template>
<header class="topbar">
<div class="topbar-search">
<input
type="text"
:value="keyword"
@input="$emit('update:keyword', $event.target.value)"
placeholder="搜索法案"
class="topbar-search-input"
/>
<svg class="topbar-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
</div>
<div class="topbar-spacer"></div>
<div class="topbar-sort" ref="sortDropdownRef">
<button class="topbar-sort-btn" @click="showSortDropdown = !showSortDropdown">
<svg class="topbar-sort-icon" width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="4" y="3" width="2" height="10" rx="1" fill="rgba(95,101,108,1)" />
<rect x="10" y="6" width="2" height="7" rx="1" fill="rgba(95,101,108,1)" />
</svg>
<span class="topbar-sort-label">{{ currentSortLabel }}</span>
<svg class="topbar-sort-arrow" :class="{ open: showSortDropdown }" width="11" height="6" viewBox="0 0 11 6" fill="none">
<path d="M1 1l4.5 4L10 1" stroke="rgba(95,101,108,1)" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
<div v-if="showSortDropdown" class="topbar-sort-dropdown">
<button
v-for="opt in sortOptions"
:key="opt.value"
@click="selectSort(opt.value)"
class="topbar-sort-option"
:class="{ active: sortBy === opt.value }"
>
{{ opt.label }}
</button>
</div>
</div>
<label class="topbar-checkbox-label">
<input
type="checkbox"
:checked="chinaRelatedOnly"
@change="$emit('toggle-china-related')"
class="topbar-checkbox"
/>
<span>只看涉华法案</span>
</label>
</header>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
const props = defineProps({
keyword: { type: String, default: '' },
chinaRelatedOnly: { type: Boolean, default: true },
sortBy: { type: String, default: 'publishTimeDesc' },
sortOptions: { type: Array, default: () => [] },
})
const emit = defineEmits(['update:keyword', 'toggle-china-related', 'update:sort'])
const showSortDropdown = ref(false)
const sortDropdownRef = ref(null)
const currentSortLabel = computed(() => {
const opt = props.sortOptions.find((o) => o.value === props.sortBy)
return opt ? opt.label : '发布时间倒序'
})
function selectSort(value) {
emit('update:sort', value)
showSortDropdown.value = false
}
function handleClickOutside(e) {
if (sortDropdownRef.value && !sortDropdownRef.value.contains(e.target)) {
showSortDropdown.value = false
}
}
onMounted(() => document.addEventListener('click', handleClickOutside))
onUnmounted(() => document.removeEventListener('click', handleClickOutside))
</script>
<style scoped>
.topbar {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.topbar-search {
position: relative;
flex: 1;
min-width: 180px;
max-width: 400px;
}
.topbar-search-input {
width: 100%;
height: 32px;
padding: 0 32px 0 12px;
font-size: 14px;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
outline: none;
color: rgba(59, 65, 75, 1);
background: #fff;
box-sizing: border-box;
line-height: 22px;
}
.topbar-search-input::placeholder {
color: rgba(132, 136, 142, 1);
}
.topbar-search-input:focus {
border-color: rgba(5, 95, 194, 1);
box-shadow: 0 0 0 2px rgba(5, 95, 194, 0.1);
}
.topbar-search-icon {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
color: rgba(132, 136, 142, 1);
pointer-events: none;
}
.topbar-spacer {
flex: 1;
}
.topbar-sort {
position: relative;
flex-shrink: 0;
}
.topbar-sort-btn {
width: 170px;
height: 32px;
display: flex;
align-items: center;
padding: 0 12px 0 7px;
gap: 0;
border-radius: 4px;
border: 1px solid rgba(230, 231, 232, 1);
background: rgba(255, 255, 255, 1);
cursor: pointer;
box-sizing: border-box;
}
.topbar-sort-btn:hover {
border-color: rgba(5, 95, 194, 0.4);
}
.topbar-sort-icon {
flex-shrink: 0;
margin-right: 6px;
}
.topbar-sort-label {
flex: 1;
font-size: 14px;
font-family: 'Microsoft YaHei', sans-serif;
font-weight: 400;
color: rgba(95, 101, 108, 1);
white-space: nowrap;
}
.topbar-sort-arrow {
flex-shrink: 0;
transition: transform 0.2s;
}
.topbar-sort-arrow.open {
transform: rotate(180deg);
}
.topbar-sort-dropdown {
position: absolute;
right: 0;
top: 100%;
margin-top: 4px;
background: #fff;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 8px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
padding: 4px 0;
z-index: 20;
min-width: 140px;
}
.topbar-sort-option {
display: block;
width: 100%;
text-align: left;
padding: 8px 12px;
font-size: 14px;
background: none;
border: none;
cursor: pointer;
color: rgba(95, 101, 108, 1);
}
.topbar-sort-option:hover {
background: rgba(247, 248, 249, 1);
}
.topbar-sort-option.active {
color: rgba(5, 95, 194, 1);
font-weight: 500;
}
.topbar-checkbox-label {
display: inline-flex;
align-items: center;
gap: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 400;
color: rgba(132, 136, 142, 1);
white-space: nowrap;
line-height: 24px;
}
.topbar-checkbox {
width: 16px;
height: 16px;
accent-color: rgba(5, 95, 194, 1);
cursor: pointer;
}
</style>
<template>
<div class="user-avatar">
<div class="user-avatar-circle" :style="{ backgroundColor: avatarColor }">
{{ initial }}
</div>
<span class="user-avatar-name">{{ name }}</span>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
name: { type: String, required: true },
avatar: { type: String, default: null },
})
const COLORS = ['#CE4F51', '#055FC2', '#E8BD0B', '#218139', '#FF954D', '#3B414B']
const avatarColor = computed(() => {
let hash = 0
for (let i = 0; i < props.name.length; i++) {
hash = props.name.charCodeAt(i) + ((hash << 5) - hash)
}
return COLORS[Math.abs(hash) % COLORS.length]
})
const initial = computed(() => props.name.charAt(0))
</script>
<style scoped>
.user-avatar {
display: inline-flex;
align-items: center;
gap: 8px;
}
.user-avatar-circle {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 10px;
font-weight: 500;
flex-shrink: 0;
}
.user-avatar-name {
font-size: 16px;
font-weight: 400;
color: rgba(59, 65, 75, 1);
line-height: 24px;
}
</style>
import { ref, reactive, watch, computed } from 'vue'
import { useRoute } from 'vue-router'
import { getHistoryBillList, getBillStageConfig, getIndustryKeyList, getSortOptions } from '@/api/bill/billHome.js'
/**
* 法案数据管理 composable
*/
export function useBills() {
const route = useRoute()
const personId = computed(() => route.params.personId || route.query.personId || '')
const bills = ref([])
const stageConfig = ref([])
const total = ref(0)
const loading = ref(false)
const filterOptions = reactive({
domains: [],
timeRanges: [],
})
const sortOptions = ref([])
const currentPage = ref(1)
const pageSize = ref(10)
const filters = reactive({
searchText: '',
selectedDomains: ['all'],
selectedTimeRanges: ['all'],
sortBy: 'latestMotionTimeDesc',
isCN: true,
})
// 获取筛选项配置
async function loadFilterOptions() {
try {
const res = await getIndustryKeyList()
if (res.code === 200 && res.data) {
// 行业列表
filterOptions.domains = [
{ id: 'all', label: '全部' },
...res.data.map(item => ({
id: item.id,
label: item.name,
}))
]
// 发布时间:从当前年份向前推5年动态生成
const currentYear = new Date().getFullYear()
filterOptions.timeRanges = [
{ id: 'all', label: '全部' },
...Array.from({ length: 5 }, (_, i) => {
const year = currentYear - i
return { id: String(year), label: String(year) }
}),
]
}
} catch (error) {
console.error('加载筛选项失败:', error)
}
}
// 获取排序选项
async function loadSortOptions() {
try {
const res = await getSortOptions()
if (res.code === 200 && res.data) {
sortOptions.value = res.data
}
} catch (error) {
console.error('加载排序选项失败:', error)
}
}
// 构建请求参数(不包含分页)
function buildParams() {
const params = {}
if (filters.searchText) {
params.searchText = filters.searchText
}
if (filters.selectedDomains.length > 0 && !filters.selectedDomains.includes('all')) {
params.industryId = filters.selectedDomains
}
if (filters.selectedTimeRanges.length > 0 && !filters.selectedTimeRanges.includes('all')) {
params.years = filters.selectedTimeRanges
}
params.isCN = filters.isCN
params.sortFun = filters.sortBy === 'latestMotionTimeAsc'
return params
}
// 获取阶段字典(仅初始化时调用一次)
async function loadStageConfig() {
try {
const res = await getBillStageConfig()
if (res.code === 200 && res.data) {
stageConfig.value = res.data
}
} catch (error) {
console.error('加载阶段字典失败:', error)
}
}
// 根据当前筛选条件和分页参数加载法案列表
async function loadBills() {
if (!personId.value) return
loading.value = true
try {
const params = buildParams()
params.currentPage = currentPage.value - 1
params.pageSize = pageSize.value
const res = await getHistoryBillList(personId.value, params)
if (res.code === 200 && res.data) {
bills.value = res.data.content || []
total.value = res.data.totalElements || 0
} else {
// 接口返回错误时清空数据
bills.value = []
total.value = 0
}
} catch (error) {
console.error('加载法案列表失败:', error)
bills.value = []
total.value = 0
} finally {
loading.value = false
}
}
// 更新搜索关键词
function updateKeyword(keyword) {
filters.searchText = keyword
}
// 切换领域筛选项
function toggleDomain(domainId) {
if (domainId === 'all') {
filters.selectedDomains = ['all']
} else {
// 去掉 all,切换具体项
const withoutAll = filters.selectedDomains.filter(d => d !== 'all')
const idx = withoutAll.indexOf(domainId)
if (idx > -1) {
withoutAll.splice(idx, 1)
} else {
withoutAll.push(domainId)
}
// 没有选中任何具体项时回到全部
filters.selectedDomains = withoutAll.length > 0 ? withoutAll : ['all']
}
}
// 切换时间范围筛选项
function toggleTimeRange(timeId) {
if (timeId === 'all') {
filters.selectedTimeRanges = ['all']
} else {
const withoutAll = filters.selectedTimeRanges.filter(d => d !== 'all')
const idx = withoutAll.indexOf(timeId)
if (idx > -1) {
withoutAll.splice(idx, 1)
} else {
withoutAll.push(timeId)
}
filters.selectedTimeRanges = withoutAll.length > 0 ? withoutAll : ['all']
}
}
// 更新排序方式
function updateSort(sortBy) {
filters.sortBy = sortBy
}
function updateChinaRelated(isCN) {
filters.isCN = isCN
}
// 翻页处理
async function handlePageChange(page) {
currentPage.value = page
await loadBills()
}
// 初始化(阶段字典只在这里获取一次)
async function init() {
await Promise.all([loadFilterOptions(), loadSortOptions(), loadStageConfig()])
await loadBills()
}
// 监听筛选变化自动重新获取
watch(
() => ({
searchText: filters.searchText,
domains: [...filters.selectedDomains],
timeRanges: [...filters.selectedTimeRanges],
sortBy: filters.sortBy,
isCN: filters.isCN,
}),
() => {
currentPage.value = 1
loadBills()
},
{ deep: true }
)
return {
bills,
stageConfig,
total,
loading,
currentPage,
pageSize,
filters,
filterOptions,
sortOptions,
init,
loadBills,
updateKeyword,
toggleDomain,
toggleTimeRange,
updateSort,
updateChinaRelated,
handlePageChange,
}
}
<template>
<div class="historical-proposal">
<div class="nav">
<el-input placeholder="搜索关键词" v-model="searchText" :suffix-icon="Search" class="search-input"></el-input>
<div class="select-box">
<el-select v-model="value1" placeholder="请选择" class="select">
<el-option label="全部法案" value="全部法案"></el-option>
</el-select>
<el-select v-model="value2" placeholder="请选择" class="select">
<el-option label="全部领域" value="全部领域"></el-option>
</el-select>
</div>
</div>
<div class="main">
<div v-for="item in CharacterProposal" :key="item.id" class="item">
<div class="img-box">
<img :src="item.img" alt="" class="img" />
<div class="info">
<div class="title">{{ item.title }}</div>
<div>
<span
v-for="tag in item.tie"
:key="tag"
class="tag"
:class="{ 'tag-1': tag.status == 1, 'tag-2': tag.status == 8, 'tag-3': tag.status == 4 }"
>{{ tag.industryName }}</span
>
</div>
</div>
</div>
<div class="info-box">
<div class="box">
<div class="label">法案描述:</div>
<div class="content">{{ item.disc }}</div>
</div>
<div class="box">
<div class="label">法案状态:</div>
<div class="content">{{ item.state }}</div>
</div>
<div class="box">
<div class="label">提案日期:</div>
<div class="content">{{ item.time }}</div>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
@current-change="handleCurrentChange"
:page-size="pageSize"
:current-page="currentPage"
background
layout="prev, pager, next"
:total="total"
class="custom-pagination"
/>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
import img from "./assets/img.png";
import { getCharacterProposal } from "@/api/characterPage/characterPage.js";
import { useRoute } from 'vue-router';
const route = useRoute();
const personId = ref(route.query.personId || "Y000064");
const currentPage = ref(1);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
getCharacterProposalFn();
};
// 获取历史提案
const CharacterProposal = ref({});
const total = ref(0);
const pageSize = ref(7);
const loading = ref(false);
const abortController = ref(null);
const getCharacterProposalFn = async () => {
// 取消上一次未完成的请求
if (abortController.value) {
abortController.value.abort();
}
// 创建新的 AbortController
abortController.value = new AbortController();
loading.value = true;
const params = {
personId: personId.value,
industryId: 1,
currentPage: currentPage.value - 1,
pageSize: pageSize.value
};
try{
const res = await getCharacterProposal(params, abortController.value.signal);
console.log("历史提案", res);
if (res.code === 200) {
if (res.data&& res.data.content) {
CharacterProposal.value = res.data.content.map(item => ({
id: item.billId,
title: item.name,
tie: item.industryList,
disc: item.description,
state: item.status,
time: item.time,
img: item.imageUrl || img
}));
total.value = res.data.totalElements;
}else {
CharacterProposal.value = [];
total.value = 0;
}
}else {
CharacterProposal.value = [];
total.value = 0;
}
loading.value = false;
}catch (error) {
if (error.name !== "AbortError") {
console.error(error);
loading.value = false;
}
}
};
onMounted(() => {
getCharacterProposalFn();
});
const searchText = ref("");
const value1 = ref("全部法案");
const value2 = ref("全部领域");
const list = ref([
{
id: 1,
title: "FY2025 National Defense Authorization Act (NDAA, S.4638)",
tie: ["法案", "国防与军事"],
disc: "提供2.82亿美元用于Ellsworth空军基地B-21轰炸机任务和军事建设,包括现代化指令。图恩强调这是其领导的条款,确保南达科他州国防投资。",
state: "2024年12月参议院通过,待总统签署。",
time: "2024年6月",
img: img
},
{
id: 2,
title: "Bipartisan Appropriations Bills",
tie: ["法案", "政府预算"],
disc: "推动农业、商务、司法和军事等四项两党拨款法案,恢复“常规程序”以允许修正案辩论,避免年底“全包”法案或继续决议(CR)。图恩公开敦促民主党合作。",
state: "委员会报告,部分进入辩论阶段。",
time: "2025年7月",
img: img
},
{
id: 3,
title: "Tax Relief, Unemployment Insurance Reauthorization, and Job Creation Act",
tie: ["法案", "税收与经济"],
disc: "续推失业保险和就业创造措施,图恩在2024报告中排名共和党第8位影响力,推动小企业税收减免。",
state: "纳入2024报告卡,影响2025税收辩论",
time: "2024年续推",
img: img
},
{
id: 4,
title: "Farm Bill Renewal Provisions",
tie: ["修正案", "农业与农村"],
disc: "加强农村基础设施和作物保险,支持南达科他州农民。图恩强调两党合作,避免拖延。",
state: "纳入2024 Farm Bill讨论,部分通过",
time: "2024年",
img: img
},
{
id: 5,
title: "Regular Order Restoration",
tie: ["修正案", "能源"],
disc: "承诺恢复委员会起草、修正案辩论和投票规范,避免领导层封闭谈判。图恩表示将允许100多项修正案投票。",
state: "实施中,2025年夏季拨款辩论",
time: "2025年1月",
img: img
},
{
id: 6,
title: "S.Amdt.3946 和 S.Amdt.3947",
tie: ["法案", "政府预算"],
disc: "改进政府资金法案,允许参议员就电话记录扣押提起诉讼,保护国会隐私(源于2020选举调查争议)。图恩提议将法院赔偿退回财政部以调整条款。",
state: "2025年11月10日失败",
time: "2025年11月10日",
img: img
}
]);
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.historical-proposal {
width: 1600px;
height: 644px;
.nav {
width: 100%;
height: 32px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.search-input {
width: 300px;
height: 32px;
color: rgb(132, 136, 142);
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 22px;
}
.select-box {
width: 332px;
height: 32px;
display: flex;
justify-content: space-between;
align-items: center;
.select {
width: 160px;
height: 32px;
color: rgb(132, 136, 142);
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 22px;
}
}
}
.main {
width: 1600px;
height: 540px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
.item {
width: 523px;
height: 262px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
padding: 16px 19px 17px 16px;
.img-box {
width: 488px;
height: 77px;
display: flex;
margin-bottom: 12px;
img {
width: 57px;
height: 77px;
margin-right: 15px;
}
.info {
width: 420px;
height: 77px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
.title {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(5, 95, 194);
}
.tag {
display: inline-block;
padding: 1px 8px;
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 20px;
border-radius: 4px;
border: 1px solid;
margin-right: 8px;
}
.tag-1 {
background-color: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
border-color: rgba(217, 247, 190, 1);
}
.tag-2 {
background-color: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1);
border-color: rgba(255, 241, 184, 1);
}
.tag-3 {
background-color: rgba(255, 241, 240, 1);
color: rgba(245, 34, 45, 1);
border-color: rgba(255, 163, 158, 1);
}
}
}
.info-box {
width: 488px;
height: 140px;
.box {
width: 100%;
margin-bottom: 10px;
display: flex;
.label {
width: 84px;
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
}
.content {
width:400px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}
.pagination-container {
width: 100%;
display: flex;
justify-content: center;
margin-top: 20px;
:deep(.el-pagination.is-background .el-pager li) {
background-color: #fff;
border: 1px solid rgba(229, 230, 235, 1);
border-radius: 4px;
color: rgba(29, 33, 41, 1);
font-weight: 400;
margin: 0 4px;
}
:deep(.el-pagination.is-background .el-pager li.is-active) {
background-color: #fff;
border-color: #165DFF;
color: #165DFF;
}
:deep(.el-pagination.is-background .btn-prev),
:deep(.el-pagination.is-background .btn-next) {
background-color: #fff;
border: 1px solid rgba(229, 230, 235, 1);
border-radius: 4px;
margin: 0 4px;
}
}
}
</style>
...@@ -14,19 +14,14 @@ ...@@ -14,19 +14,14 @@
<p>{{ characterInfo.description }}</p> <p>{{ characterInfo.description }}</p>
</div> </div>
<div class="domain"> <div class="domain">
<p <p v-for="item in characterInfo.industryList" :key="item" class="cl1" :class="{
v-for="item in characterInfo.industryList"
:key="item"
class="cl1"
:class="{
cl1: item.status === '1', cl1: item.status === '1',
cl2: item.status === '2', cl2: item.status === '2',
cl3: item.status === '3', cl3: item.status === '3',
cl4: item.status === '4', cl4: item.status === '4',
cl5: item.status === '5', cl5: item.status === '5',
cl6: item.status === '6' cl6: item.status === '6'
}" }">
>
{{ item.industryName }} {{ item.industryName }}
</p> </p>
</div> </div>
...@@ -39,115 +34,80 @@ ...@@ -39,115 +34,80 @@
</div> </div>
</div> </div>
<!-- 人物详情 --> <!-- 人物详情 -->
<div class="info-content" v-if="infoActive === '人物详情'"> <div class="info-content" v-show="infoActive === '人物详情'">
<div class="left"> <div class="left">
<div class="left-top"> <AnalysisBox title="科技观点" width="1064px" height="300px" :show-all-btn="false" class="left-top">
<div class="title"> <template #header-btn>
<div class="box"></div> <el-select v-model="numActive" class="tab-select" :teleported="true" @change="handleChangeYear">
<div class="text">政治观点</div> <el-option v-for="item in num" :key="item" :label="item" :value="item" />
<div class="btn"> </el-select>
<img src="./assets/下载按钮.png" alt="" /> </template>
<img src="./assets/收藏按钮.png" alt="" />
</div>
</div>
<!-- 主要内容 -->
<div class="num-list">
<div v-for="item in num" :key="item" :class="{ active: item === numActive }" @click="handleChangeYear(item)">
{{ item }}
</div>
</div>
<!-- echarts 图表 -->
<div class="echarts" id="wordCloudChart"> <div class="echarts" id="wordCloudChart">
<!-- <div class="row" v-for="(row, index) in wordCloudData" :key="index">
<span
v-for="(item, idx) in row"
:key="idx"
:style="{
color: item.color,
fontSize: item.fontSize || '16px',
fontWeight: item.fontWeight || 'normal'
}"
>
{{ item.text }}
</span>
</div> -->
</div>
</div> </div>
<div class="left-center"> </AnalysisBox>
<div class="title">
<div class="box"></div>
<div class="text">资金来源</div>
<div class="input">
<el-select v-model="selectedOption" placeholder="请选择" class="select">
<el-option @click="handleChangeYearList()" v-for="item in yearList" :key="item.value"
:label="item.label" :value="item.value" />
<AnalysisBox title=" 金钱来源" width="1064px" height="512px" :show-all-btn="false" class="left-center">
<template #header-btn>
<div class="input">
<el-select v-model="selectedOption" placeholder="请选择" class="select" :teleported="true">
<el-option v-for="item in yearList" :key="item.value" :label="item.label"
:value="item.value" @click="handleChangeYearList()" />
</el-select> </el-select>
</div> </div>
<div class="btn"> </template>
<img src="./assets/下载按钮.png" alt="" />
<img src="./assets/收藏按钮.png" alt="" />
</div>
</div>
<!-- 主要内容 -->
<div class="main"> <div class="main">
<el-table
:data="CharacterFundSource" <el-table :data="CharacterFundSource" style="width: 100%" :header-cell-style="{
style="width: 100%"
:header-cell-style="{
background: 'transparent', background: 'transparent',
color: 'rgba(59, 65, 75, 1)', color: 'rgba(59, 65, 75, 1)',
fontWeight: 700, fontWeight: 700,
fontSize: '16px' fontSize: '16px'
}" }" :cell-style="{
:cell-style="{
fontSize: '16px', fontSize: '16px',
fontWeight: 400, fontWeight: 400,
color: 'rgba(59, 65, 75, 1)' color: 'rgba(59, 65, 75, 1)'
}" }" :row-class-name="tableRowClassName" :row-style="{ height: '60px' }" size="large">
:row-class-name="tableRowClassName"
:row-style="{ height: '60px' }"
size="large"
>
<el-table-column prop="rank" label="排名" width="100" align="center" /> <el-table-column prop="rank" label="排名" width="100" align="center" />
<el-table-column prop="contributor" label="贡献者" min-width="300" /> <el-table-column prop="contributor" label="贡献者" min-width="300" />
<el-table-column prop="totalAmount" label="总捐款" width="150" /> <el-table-column prop="totalAmount" label="总捐款" width="150" />
<el-table-column prop="individualAmount" label="个人捐款" width="150" /> <el-table-column prop="individualAmount" label="个人捐款" width="150" />
<el-table-column prop="pacsAmount" label="PACs捐款" width="150" /> <el-table-column prop="pacsAmount" label="PACs捐款" width="150" />
</el-table> </el-table>
<div class="table-pagination">
<span class="table-pagination-total">共{{ fundTotal }}项</span>
<el-pagination v-if="fundTotal / fundPageSize >= 2" :current-page="fundCurrentPage"
:page-size="fundPageSize" :total="fundTotal" :pager-count="4" layout="prev, pager, next"
background @current-change="handleFundPageChange" />
</div>
</div> </div>
<div class="bottom"> <div class="bottom">
<img src="./assets/ai.png" alt="" class="icon"> <img src="./assets/ai.png" alt="" class="icon">
<div class="text1">约翰·图恩的竞选资金前五大贡献者以美国以色列公共事务委员会(AIPAC)为主导,总捐款超61.8万美元,主要来自个人捐款,凸显其在外交、国防和行业游说方面的强大支持网络。</div> <div class="text1">
<img src="./assets/right.png" alt="" class="icon1">
</div> </div>
<img src="./assets/right.png" alt="" class="icon1">
</div> </div>
<div class="left-bottom"> </AnalysisBox>
<div class="title"> <AnalysisBox title="最新动态" width="1064px" height="1330px" :show-all-btn="false" class="left-bottom">
<div class="box"></div> <template #header-btn>
<div class="text">最新动态</div> <div class="input">
<div class="input"><input type="checkbox" v-model="isChecked" @change="handleChange"/>只看涉华动态</div> <input type="checkbox" v-model="isChecked" @change="handleChange"
<div class="btn"> style="padding-top: 2px;" /><span style="margin-left: 8px ">只看涉华动态</span>
<img src="./assets/下载按钮.png" alt="" />
<img src="./assets/收藏按钮.png" alt="" />
</div>
</div> </div>
<!-- 主要内容 --> </template>
<div class="main"> <div class="main">
<div v-for="item in CharacterLatestDynamic" :key="item" class="main-item"> <div v-for="item in CharacterLatestDynamic" :key="item" class="main-item">
<div class="time"> <div class="time">
<div class="year">{{ item.time.split("-")[0] }}</div> <div class="year">{{ item.time.split("-")[0] }}</div>
<div class="date">{{ item.time.split("-")[1] + "月" + item.time.split("-")[2] + "日"}}</div> <div class="date">{{ item.time.split("-")[1] + "月" + item.time.split("-")[2] + "日" }}
</div>
</div> </div>
<div class="image"> <div class="image">
<img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img <img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img
src="./assets/type2.png" src="./assets/type2.png" alt="" v-else />
alt=""
v-else
/>
</div> </div>
<div class="content"> <div class="content">
<div :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }"> <div
:class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }">
<p v-if="item.remarks === true" class="content-title1"> <p v-if="item.remarks === true" class="content-title1">
{{ item.content }} {{ item.content }}
...@@ -159,20 +119,9 @@ ...@@ -159,20 +119,9 @@
</div> </div>
<p v-if="item.remarks === false" class="content-contentcontent">{{ item.content }}</p> <p v-if="item.remarks === false" class="content-contentcontent">{{ item.content }}</p>
<div class="content-tag"> <div class="content-tag">
<div> <div style="display: flex; flex-wrap: wrap; gap: 8px;">
<span <AreaTag v-for="tag in item.industryList" :key="tag"
v-for="tag in item.industryList" :tag-name="tag.industryName" @click="handleClickTag(tag.industryName)" />
:key="tag"
class="tag"
:class="{
dl1: tag === '人工智能',
dl2: tag === '量子科技',
dl3: tag === '新能源',
dl4: tag === '集成电路'
}"
@click="handleClickTag(tag)"
>{{ tag }}</span
>
</div> </div>
<div class="origin">来源:{{ item.orgName }}</div> <div class="origin">来源:{{ item.orgName }}</div>
</div> </div>
...@@ -181,67 +130,57 @@ ...@@ -181,67 +130,57 @@
<div class="line-test"></div> <div class="line-test"></div>
</div> </div>
<div class="pagination"> <div class="pagination">
<div class="total">{{`共 ${total} 项`}}</div> <div class="total">{{ `共 ${total} 项` }}</div>
<el-pagination <el-pagination @current-change="handleCurrentChange" :page-size="pageSize"
@current-change="handleCurrentChange" :current-page="currentPage" background layout="prev, pager, next" :total="total"
:page-size="pageSize" class="custom-pagination" />
:current-page="currentPage"
background
layout="prev, pager, next"
:total="total"
class="custom-pagination"
/>
</div>
</div> </div>
</AnalysisBox>
</div> </div>
<div class="right"> <div class="right">
<div class="right-top"> <AnalysisBox title="基本信息" width="520px" :height="boxHeight" :show-all-btn="false" class="right-top" v-if="characterBasicInfo">
<div class="title">
<div class="box"></div>
<div class="text">基本信息</div>
<div class="btn">
<img src="./assets/下载按钮.png" alt="" />
<img src="./assets/收藏按钮.png" alt="" />
</div>
</div>
<div class="main-content"> <div class="main-content">
<div class="baseInfo"> <div class="baseInfo">
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">出生日期:</div> <div class="baseInfo-item-title">出生日期:</div>
<div class="baseInfo-item-content">{{characterBasicInfo.birthday}}</div> <div class="baseInfo-item-content">{{ characterBasicInfo.birthday }}</div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">现任职位:</div> <div class="baseInfo-item-title">现任职位:</div>
<div class="baseInfo-item-content">{{characterBasicInfo.positionTitle}}</div> <div class="baseInfo-item-content">{{ characterBasicInfo.positionTitle }}</div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">党派归属:</div> <div class="baseInfo-item-title">党派归属:</div>
<div class="baseInfo-item-content">{{characterBasicInfo.party}}</div> <div class="baseInfo-item-content">{{ characterBasicInfo.party }}</div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">教育背景:</div> <div class="baseInfo-item-title">教育背景:</div>
<div class="baseInfo-item-content" v-for="item in characterBasicInfo.educationList">{{item.school+item.major}}</div> <div class="baseInfo-item-content" v-for="item in characterBasicInfo.educationList">
{{ item.school + item.major }}</div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title address">代表州/选区:</div> <div class="baseInfo-item-title address">代表州/选区:</div>
<div class="baseInfo-item-content">{{characterBasicInfo.state}}</div> <div class="baseInfo-item-content">{{ characterBasicInfo.state }}</div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">政治立场:</div> <div class="baseInfo-item-title">政治立场:</div>
<div class="baseInfo-item-content long"> <div class="baseInfo-item-content long">
{{characterBasicInfo.political}} {{ characterBasicInfo.political }}
</div> </div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">出生地:</div> <div class="baseInfo-item-title">出生地:</div>
<div class="baseInfo-item-content">{{characterBasicInfo.birthPlace}}</div> <div class="baseInfo-item-content">{{ characterBasicInfo.birthPlace }}</div>
</div> </div>
</div> </div>
<div class="company"> <div class="company">
<div class="company-title">社交媒体</div> <div class="company-title">社交媒体</div>
<div class="company-content"> <div class="company-content">
<div v-for="item in characterBasicInfo.organizationList" :key="item" class="company-item"> <div v-for="item in characterBasicInfo.organizationList" :key="item"
<img :src="item.imageUrl?item.imageUrl:DefaultIcon2" alt="" /> class="company-item">
<img :src="item.imageUrl ? item.imageUrl : DefaultIcon2" alt="" />
<div> <div>
<div class="company-cn">{{ item.name }}</div> <div class="company-cn">{{ item.name }}</div>
<div class="company-name">{{ item.ename }}</div> <div class="company-name">{{ item.ename }}</div>
...@@ -250,83 +189,177 @@ ...@@ -250,83 +189,177 @@
</div> </div>
</div> </div>
</div> </div>
</AnalysisBox>
<AnalysisBox title="职业履历" width="520px" height="1556px" :show-all-btn="false" class="right-bottom">
<template #header-btn>
<div class="resume-tabs">
<button class="resume-tab" :class="{ active: resumeType === 'career' }"
@click="switchResumeType('career')">职业履历</button>
<button class="resume-tab" :class="{ active: resumeType === 'education' }"
@click="switchResumeType('education')">教育履历</button>
</div> </div>
<div class="right-bottom"> </template>
<div class="title">
<div class="box"></div>
<div class="text">政治履历</div>
<div class="btn">
<img src="./assets/下载按钮.png" alt="" />
<img src="./assets/收藏按钮.png" alt="" />
</div>
</div>
<div class="content-main"> <div class="content-main">
<div v-for="item in CharacterResume" class="content-item"> <!-- 职业履历 -->
<template v-if="resumeType === 'career'">
<div v-for="item in currentResumeList" :key="item.startTime" class="content-item">
<img src="./assets/icon01.png" alt="" class="image01" /> <img src="./assets/icon01.png" alt="" class="image01" />
<div class="content-item-time">{{ item.startTime +'-' + item.endTime}}</div> <div class="content-item-time">{{ item.startTime + '-' + item.endTime }}</div>
<div class="content-item-title">{{ item.orgName +'|' + item.jobName}}</div> <div class="content-item-title">{{ item.orgName + '|' + item.jobName }}</div>
<div class="content-item-content">{{ item.content }}</div> <div class="content-item-content">{{ item.content }}</div>
<div class="content-item-door" v-if="item.door"> <div class="content-item-door" v-if="item.door">
<img src="./assets/icon02.png" alt="" /> <img src="./assets/icon02.png" alt="" />
<span>{{ item.door }}</span> <span>{{ item.door }}</span>
</div> </div>
</div> </div>
</template>
<!-- 教育履历 -->
<template v-else>
<div v-for="(item, index) in currentResumeList" :key="index" class="content-item">
<img src="./assets/icon01.png" alt="" class="image01" />
<div class="content-item-time">{{ item.startTime + '-' + item.endTime }}</div>
<div class="content-item-title">{{ item.school }}</div>
<div class="content-item-content">{{ item.description }}</div>
<div class="content-item-door" v-if="item.country">
<img src="./assets/icon02.png" alt="" />
<span>{{ item.country }}</span>
</div>
</div> </div>
</template>
</div> </div>
</AnalysisBox>
</div> </div>
</div> </div>
<!-- 历史提案 --> <!-- 历史提案 -->
<HistoricalProposal v-if="infoActive === '历史提案'" /> <!-- 在 member-of-congress 同级的左侧添加标签栏 -->
<!-- 人物关系 --> <!-- 历史提案 tab 对应的内容区 -->
<div v-if="infoActive === '历史提案'" class="proposal-wrapper">
<div class="proposal-tab-switcher">
<button :class="['proposal-tab', { active: newsTab === 'history' }]" @click="newsTab = 'history'">
<span>历史提案</span>
<svg v-if="newsTab === 'history'" class="proposal-tab-arrow" width="12" height="12"
viewBox="0 0 12 12" fill="currentColor">
<path d="M4 2l5 4-5 4V2z" />
</svg>
</button>
<button :class="['proposal-tab', { active: newsTab === 'potential' }]" @click="newsTab = 'potential'">
<span>潜在提案</span>
<svg v-if="newsTab === 'potential'" class="proposal-tab-arrow" width="12" height="12"
viewBox="0 0 12 12" fill="currentColor">
<path d="M4 2l5 4-5 4V2z" />
</svg>
</button>
</div>
<HistoricalProposal v-if="newsTab === 'history'" />
<PotentialNews v-else />
</div>
<CharacterRelationships v-if="infoActive === '人物关系'" /> <CharacterRelationships v-if="infoActive === '人物关系'" />
<!-- 相关情况 -->
<RelevantSituation v-if="infoActive === '相关情况'" /> <RelevantSituation v-if="infoActive === '相关情况'" />
<!-- 弹框 --> <!-- 弹框 -->
<el-dialog <el-dialog v-model="dialogVisible" width="761px" class="viewpoint-dialog" :modal="false" :draggable="true"
v-model="dialogVisible" :lock-scroll="false" :show-close="false">
width="761px"
class="viewpoint-dialog"
:modal="false"
:draggable="true"
:lock-scroll="false"
>
<template #header> <template #header>
<div class="viewpoint-header"> <div class="viewpoint-header">
<div class="viewpoint-title">领域观点</div> <div class="viewpoint-title">
<span class="viewpoint-tag">#{{ currentTag }}</span>
<span class="viewpoint-label">相关领域标签</span>
</div>
<div class="viewpoint-close" @click="dialogVisible = false">
<el-icon :size="16">
<Close />
</el-icon>
</div>
</div> </div>
</template> </template>
<div class="viewpoint-body"> <div class="viewpoint-body">
<div class="viewpoint-body-title">#人工智能</div> <div v-for="item in CharacterFieldView" :key="item.id" class="viewpoint-item">
<div v-for="item in CharacterFieldView" class="viewpoint-item"> <div class="viewpoint-avatar">
<!-- <img :src="item.imageUrl ? getProxyUrl(item.imageUrl) : DefaultIcon1" alt="" /> --> <el-avatar :size="48" shape="circle"
<el-avatar :size="42" shape="circle" :src="item.imageUrl ? getProxyUrl(item.imageUrl) : DefaultIcon1" class="viewpoint-item-img" /> :src="item.imageUrl ? getProxyUrl(item.imageUrl) : DefaultIcon1" />
<div class="viewpoint-item-content"> </div>
<div class="viewpoint-item-name">{{ item.name }}</div> <div class="viewpoint-content">
<div class="viewpoint-item-desc">{{ item.remarks }}</div> <div class="viewpoint-arrow"></div>
<div class="viewpoint-item-job">{{ item.jobName }}</div> <div class="viewpoint-card">
<div class="viewpoint-card-header">
<span class="viewpoint-name">{{ item.name }}</span>
<span class="viewpoint-job">{{ item.jobName }}</span>
</div>
<div class="viewpoint-desc">{{ item.remarks }}</div>
</div> </div>
</div> </div>
</div> </div>
<div v-if="CharacterFieldView.length === 0" class="viewpoint-empty">暂无数据</div>
</div>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted, computed } from "vue";
import CharacterRelationships from "./components/characterRelationships/index.vue"; import CharacterRelationships from "./components/characterRelationships/index.vue";
import RelevantSituation from "./components/relevantSituation/index.vue"; import RelevantSituation from "./components/relevantSituation/index.vue";
import HistoricalProposal from "./components/historicalProposal/index.vue"; import HistoricalProposal from "./components/historicalProposal/components/BillTracker.vue";
import PotentialNews from './components/historicalProposal/components/PotentialNews.vue'
import getWordCloudChart from "../../utils/worldCloudChart"; import getWordCloudChart from "../../utils/worldCloudChart";
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
import { getCharacterGlobalInfo, import {
getCharacterGlobalInfo,
getCharacterBasicInfo, getCharacterBasicInfo,
getCharacterView, getCharacterView,
getCharacterLatestDynamic, getCharacterLatestDynamic,
getCharacterResume, getCharacterResume,
getCharacterFieldView, getCharacterFieldView,
getCharacterFundSource } from "@/api/characterPage/characterPage.js"; getCharacterFundSource,
getCharacterReducationResume
} from "@/api/characterPage/characterPage.js";
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'
const fundCurrentPage = ref(1)
const fundPageSize = ref(4)
const fundTotal = ref(0)
const allFundSource = ref([])
const boxHeight = computed(() => {
if(characterBasicInfo.value.organizationList==undefined) return '545px'
if(characterBasicInfo.value.organizationList.length==0) return '495px'
if(characterBasicInfo.value.organizationList.length<=2) return '445px'
return '545px'
})
// const CharacterFundSource = computed(() => {
// const start = (fundCurrentPage.value - 1) * fundPageSize.value
// return allFundSource.value.slice(start, start + fundPageSize.value)
// })
const handleFundPageChange = (page) => {
fundCurrentPage.value = page
getCharacterFundSourceFn()
}
const resumeType = ref('career')
const CharacterEducationResume = ref([])
const currentResumeList = computed(() => {
return resumeType.value === 'career' ? CharacterResume.value : CharacterEducationResume.value
})
const getCharacterEducationResumeFn = async () => {
const params = { personId: personId.value }
try {
const res = await getCharacterReducationResume(params)
if (res.code === 200 && res.data) {
CharacterEducationResume.value = res.data
}
} catch (error) { }
}
const switchResumeType = (type) => {
resumeType.value = type
if (type === 'education' && CharacterEducationResume.value.length === 0) {
getCharacterEducationResumeFn()
}
}
import Musk from "./assets/Musk.png"; import Musk from "./assets/Musk.png";
import cp1 from "./assets/cp1.png"; import cp1 from "./assets/cp1.png";
import cp2 from "./assets/cp2.png"; import cp2 from "./assets/cp2.png";
...@@ -345,7 +378,7 @@ import DefaultIcon2 from '@/assets/icons/default-icon2.png' ...@@ -345,7 +378,7 @@ import DefaultIcon2 from '@/assets/icons/default-icon2.png'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
const route = useRoute(); const route = useRoute();
const personId = ref(route.query.personId || "Y000064"); const personId = ref(route.query.personId || "Y000064");
const newsTab = ref('history')
// 处理图片代理 // 处理图片代理
const getProxyUrl = (url) => { const getProxyUrl = (url) => {
if (!url) return ""; if (!url) return "";
...@@ -369,15 +402,15 @@ const getCharacterGlobalInfoFn = async () => { ...@@ -369,15 +402,15 @@ const getCharacterGlobalInfoFn = async () => {
const params = { const params = {
personId: personId.value personId: personId.value
}; };
try{ try {
const res = await getCharacterGlobalInfo(params); const res = await getCharacterGlobalInfo(params);
if (res.code === 200) { if (res.code === 200) {
console.log("人物全局信息", res); console.log("人物全局信息1", res);
if (res.data) { if (res.data) {
characterInfo.value = res.data; characterInfo.value = res.data;
} }
} }
}catch(error){ } catch (error) {
} }
...@@ -389,7 +422,7 @@ const getCharacterBasicInfoFn = async () => { ...@@ -389,7 +422,7 @@ const getCharacterBasicInfoFn = async () => {
const params = { const params = {
personId: personId.value personId: personId.value
}; };
try{ try {
const res = await getCharacterBasicInfo(params); const res = await getCharacterBasicInfo(params);
if (res.code === 200) { if (res.code === 200) {
console.log("人物基本信息", res); console.log("人物基本信息", res);
...@@ -397,7 +430,7 @@ const getCharacterBasicInfoFn = async () => { ...@@ -397,7 +430,7 @@ const getCharacterBasicInfoFn = async () => {
characterBasicInfo.value = res.data; characterBasicInfo.value = res.data;
} }
} }
}catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}; };
...@@ -406,23 +439,25 @@ const getCharacterBasicInfoFn = async () => { ...@@ -406,23 +439,25 @@ const getCharacterBasicInfoFn = async () => {
const characterView = ref({}); const characterView = ref({});
const getCharacterViewFn = async () => { const getCharacterViewFn = async () => {
const params = { const params = {
personId: personId.value, personId: personId.value
year: numActive.value
}; };
try{ if (numActive.value !== '全部') {
params.year = numActive.value;
}
try {
const res = await getCharacterView(params); const res = await getCharacterView(params);
if (res.code === 200) { if (res.code === 200) {
console.log("人物观点", res); console.log("人物观点", res);
if (res.data) { if (res.data) {
characterView.value = res.data.map(item=>{ characterView.value = res.data.map(item => {
return{ return {
name:item.option, name: item.option,
value:item.count value: item.count
}; };
}); });
} }
} }
}catch(error){ } catch (error) {
} }
...@@ -434,16 +469,19 @@ const handleCharacterView = async () => { ...@@ -434,16 +469,19 @@ const handleCharacterView = async () => {
setChart(wordCloudChart, "wordCloudChart"); setChart(wordCloudChart, "wordCloudChart");
}; };
const handleChangeYear = (item) => { const handleChangeYear = () => {
numActive.value = item; characterView.value = []
characterView.value = []; handleCharacterView()
handleCharacterView();
}; }
// 获取资金来源
const yearList = ref([ const yearList = ref([
{
label: "全部",
value: 'all'
},
{ {
label: "2025", label: "2025",
value: 2025 value: 2025
...@@ -462,15 +500,20 @@ const CharacterFundSource = ref([]); ...@@ -462,15 +500,20 @@ const CharacterFundSource = ref([]);
const getCharacterFundSourceFn = async () => { const getCharacterFundSourceFn = async () => {
const params = { const params = {
personId: personId.value, personId: personId.value,
year: selectedOption.value || "2025" pageSize: 4,
currentPage: fundCurrentPage.value - 1,
}; };
try{ if (selectedOption.value !== '全部') {
params.year = selectedOption.value;
}
try {
const res = await getCharacterFundSource(params); const res = await getCharacterFundSource(params);
if (res.code === 200) { if (res.code === 200) {
console.log("资金来源", res);
if (res.data) { if (res.data) {
CharacterFundSource.value = res.data.map((item,index)=>{ fundTotal.value = res.data.totalElements
return{ CharacterFundSource.value = res.data.content.map((item, index) => {
return {
rank: index + 1, rank: index + 1,
contributor: item.orgName, contributor: item.orgName,
totalAmount: item.totalDonation, totalAmount: item.totalDonation,
...@@ -481,7 +524,7 @@ const getCharacterFundSourceFn = async () => { ...@@ -481,7 +524,7 @@ const getCharacterFundSourceFn = async () => {
}); });
} }
} }
}catch(error){ } catch (error) {
} }
...@@ -498,7 +541,7 @@ const getCharacterFieldViewFn = async () => { ...@@ -498,7 +541,7 @@ const getCharacterFieldViewFn = async () => {
const params = { const params = {
areaId: window.sessionStorage.getItem("areaId") || "20", areaId: window.sessionStorage.getItem("areaId") || "20",
}; };
try{ try {
const res = await getCharacterFieldView(params); const res = await getCharacterFieldView(params);
if (res.code === 200) { if (res.code === 200) {
console.log("领域观点", res); console.log("领域观点", res);
...@@ -506,7 +549,7 @@ const getCharacterFieldViewFn = async () => { ...@@ -506,7 +549,7 @@ const getCharacterFieldViewFn = async () => {
CharacterFieldView.value = res.data; CharacterFieldView.value = res.data;
} }
} }
}catch(error){ } catch (error) {
} }
...@@ -519,9 +562,9 @@ const isChecked = ref(false); ...@@ -519,9 +562,9 @@ const isChecked = ref(false);
const related = ref('N'); const related = ref('N');
const handleChange = event => { const handleChange = event => {
if(isChecked.value){ if (isChecked.value) {
related.value = 'Y'; related.value = 'Y';
}else{ } else {
related.value = 'N'; related.value = 'N';
} }
getCharacterLatestDynamicFn(); getCharacterLatestDynamicFn();
...@@ -533,6 +576,7 @@ const currentPage = ref(1); ...@@ -533,6 +576,7 @@ const currentPage = ref(1);
const handleCurrentChange = page => { const handleCurrentChange = page => {
currentPage.value = page; currentPage.value = page;
getCharacterLatestDynamicFn(); getCharacterLatestDynamicFn();
}; };
// 获取最新动态 // 获取最新动态
...@@ -558,11 +602,11 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -558,11 +602,11 @@ const getCharacterLatestDynamicFn = async () => {
pageSize: pageSize.value pageSize: pageSize.value
}; };
try{ try {
const res = await getCharacterLatestDynamic(params, abortController.value.signal); const res = await getCharacterLatestDynamic(params, abortController.value.signal);
console.log("最新动态", res); console.log("最新动态", res);
if (res.code === 200) { if (res.code === 200) {
if (res.data&& res.data.content) { if (res.data && res.data.content) {
CharacterLatestDynamic.value = res.data.content.map(item => ({ CharacterLatestDynamic.value = res.data.content.map(item => ({
title: item.title, title: item.title,
content: item.content, content: item.content,
...@@ -573,16 +617,16 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -573,16 +617,16 @@ const getCharacterLatestDynamicFn = async () => {
remarks: item.remarks remarks: item.remarks
})); }));
total.value = res.data.totalElements; total.value = res.data.totalElements;
}else { } else {
CharacterLatestDynamic.value = []; CharacterLatestDynamic.value = [];
total.value = 0; total.value = 0;
} }
}else { } else {
CharacterLatestDynamic.value = []; CharacterLatestDynamic.value = [];
total.value = 0; total.value = 0;
} }
loading.value = false; loading.value = false;
}catch (error) { } catch (error) {
if (error.name !== "AbortError") { if (error.name !== "AbortError") {
console.error(error); console.error(error);
loading.value = false; loading.value = false;
...@@ -597,7 +641,7 @@ const getCharacterResumeFn = async () => { ...@@ -597,7 +641,7 @@ const getCharacterResumeFn = async () => {
const params = { const params = {
personId: personId.value personId: personId.value
}; };
try{ try {
const res = await getCharacterResume(params); const res = await getCharacterResume(params);
if (res.code === 200) { if (res.code === 200) {
console.log("人物职业履历", res); console.log("人物职业履历", res);
...@@ -605,7 +649,7 @@ const getCharacterResumeFn = async () => { ...@@ -605,7 +649,7 @@ const getCharacterResumeFn = async () => {
CharacterResume.value = res.data; CharacterResume.value = res.data;
} }
} }
}catch(error){ } catch (error) {
} }
...@@ -623,56 +667,6 @@ onMounted(() => { ...@@ -623,56 +667,6 @@ onMounted(() => {
}); });
// const wordCloudData = ref([
// [
// { text: "Boring Company", color: "rgba(19, 168, 168, 1)" },
// { text: "地球化改造", color: "rgb(206, 79, 81)" },
// { text: "减少燃料对外依赖", color: "rgba(250, 173, 20, 1)" },
// { text: "外交政策立场", color: "rgba(250, 173, 20, 1)" },
// { text: "Neuralink+Grok", color: "rgba(22, 119, 255, 1)" },
// { text: "预算纪律", color: "rgba(19, 168, 168, 1)" }
// ],
// [
// { text: "保守派财政立场", color: "rgb(206, 79, 81)" },
// { text: "能源政策", color: "rgba(22, 119, 255, 1)" },
// { text: "项目改革", color: "rgba(250, 173, 20, 1)" },
// { text: "农业委员会成员", color: "rgb(206, 79, 81)" },
// { text: "工作可选项", color: "rgba(250, 173, 20, 1)" },
// { text: "教育政策", color: "rgba(19, 168, 168, 1)" },
// { text: "可持续能源", color: "rgba(255, 122, 69, 1)" }
// ],
// [
// { text: "减少政府支出", color: "rgba(255, 122, 69, 1)" },
// { text: "可持续能源", color: "rgba(255, 122, 69, 1)" },
// { text: "第一性原理", color: "rgba(250, 173, 20, 1)" },
// { text: "可持续能源", color: "rgba(255, 122, 69, 1)" },
// { text: "农业经济引擎", color: "rgb(206, 79, 81)" },
// { text: "有限政府", color: "rgba(255, 122, 69, 1)" }
// ],
// [
// { text: "Neuralink+Grok", color: "rgba(22, 119, 255, 1)" },
// { text: "财政责任", color: "rgb(206, 79, 81)", fontSize: "26px", fontWeight: "700" },
// { text: "农业优先", color: "rgba(255, 122, 69, 1)" },
// { text: "保守派价值观", color: "rgba(22, 119, 255, 1)" },
// { text: "可持续富足", color: "rgba(250, 173, 20, 1)" },
// { text: "为后代负责", color: "rgba(250, 173, 20, 1)" }
// ],
// [
// { text: "双边合作", color: "rgba(19, 168, 168, 1)" },
// { text: "农场法案倡导者", color: "rgba(250, 173, 20, 1)" },
// { text: "超音速海啸", color: "rgba(250, 173, 20, 1)" },
// { text: "环境议题", color: "rgba(19, 168, 168, 1)" },
// { text: "医疗保健改革", color: "rgb(206, 79, 81)" },
// { text: "多智能体协作", color: "rgba(19, 168, 168, 1)" }
// ]
// ]);
// const musk = ref({
// nameCn: "约翰·伦道夫·图恩",
// nameEn: "John Randolph Thune",
// introduction: "美国南达科他州共和党籍联邦参议员,现任参议院多数党领袖,以财政保守主义和对华强硬立场著称。",
// domain: ["共和党", "电信改革", "美国参议员", "对华强硬派", "参议院多数党领袖"],
// avatar: Musk
// });
const tableRowClassName = ({ row, rowIndex }) => { const tableRowClassName = ({ row, rowIndex }) => {
if (rowIndex % 2 === 0) { if (rowIndex % 2 === 0) {
return "highlight-row"; return "highlight-row";
...@@ -716,190 +710,23 @@ const contributorData = ref([ ...@@ -716,190 +710,23 @@ const contributorData = ref([
pacsAmount: "$8,500" pacsAmount: "$8,500"
} }
]); ]);
const info = ref(["人物详情", "历史提案", "人物关系", "相关情况"]); // const info = ref(["人物详情", "历史提案", "人物关系", "相关情况"]);
const info = ref(["人物详情", "历史提案", "人物关系" ]);
const infoActive = ref("人物详情"); const infoActive = ref("人物详情");
const num = ref(["2025", "2024", "2023", "2022", "2021", "2020"]); const num = ref(['全部', "2025", "2024", "2023", "2022", "2021", "2020"]);
const numActive = ref("2025"); const numActive = ref("全部");
const selectedOption = ref("2025"); const selectedOption = ref("全部");
const dialogVisible = ref(false); const dialogVisible = ref(false);
const handleClickTag = tag => { const currentTag = ref(null)
dialogVisible.value = true; const handleClickTag = async (tag) => {
}; currentTag.value = tag
const newList = ref([ dialogVisible.value = true
{ await getCharacterFieldViewFn(tag)
id: 1, }
title: "我敦促共和党人优先支持特朗普议程:打击移民、降低物价、增加国内能源生产,并通过税收立法。作为下一任多数党领袖,我致力于迅速推进这些优先事项。参议院必须成为人民授权的引擎,而不是障碍。",
titleEn:
"I urged Republicans to prioritize backing Trump’s agenda: cracking down on immigration, bringing down prices, increasing domestic energy production, and passing tax legislation. As the next majority leader, I’m committed to moving these priorities swiftly. The Senate must be an engine for the people’s mandate.",
pie: ["人工智能"],
origin: "X",
time: "2025年10月10日",
type: 1
},
{
id: 2,
title: "约翰·图恩对白宫推动的人工智能禁令表示怀疑",
content:
"参议院多数党领袖约翰·图恩表示,白宫在人工智能禁令方面的进展不足以将其纳入本月晚些时候参议院审议的国防政策法案中。他强调,需要与参议员和众议员合作制定方案,同时保护各州权利,避免联邦过度干预。",
pie: ["量子科技"],
origin: "华盛顿观察家报",
time: "2025年10月10日",
type: 2
},
{
id: 3,
title: "约翰·图恩呼吁医疗改革,大流行时期补贴即将到期",
content:
"图恩在参议院发言中指出,大流行时期临时扩展的《平价医疗法案》补贴已扭曲保险市场,并将于年底到期。他呼吁国会采取行动,建立可持续模式,提高可负担性和消费者选择,而不是简单延长补贴。",
pie: ["新能源"],
origin: "米切尔今报",
time: "2025年10月09日",
type: 2
},
{
id: 4,
title: "医疗谈判中堕胎条款成为障碍",
content:
"图恩表示,两党需要就到期中的奥巴马医改税收抵免达成策略共识,但堕胎相关条款已成为谈判的绊脚石。他强调需要平衡各方关切,以避免数百万美国人面临保费上涨。",
pie: ["量子科技"],
origin: "泰晤士报",
time: "2025年10月09日",
type: 2
},
{
id: 5,
title: "最近的医疗保健讨论聚焦于大流行时期临时补贴增强措施,这些措施将于年底到期。数百万美国人可能甚至不知道自己已报名补贴计划,因为保险公司会自动为符合零成本覆盖条件的人重新登记。",
titleEn:
"Recent discussions on health care have focused on the temporary subsidy enhancements from the pandemic era, which expire at year-end. Millions of Americans may not even know they’re enrolled in subsidized plans because insurers auto-reenroll those eligible for no-cost coverage.",
pie: ["人工智能"],
origin: "X",
time: "2025年10月08日",
type: 1
},
{
id: 6,
title: "通过‘一个大美丽法案’使特朗普减税永久化,将为家庭和企业提供所需的稳定性。这是关于创造就业、降低成本,并确保我们的经济比以往任何时候都更强劲复苏。",
titleEn:
"The phone records provision was a Senate issue we’re working through, but let’s be clear: transparency in investigations matters. On the Epstein files, the debate took on a life of its own, but passing the bill by unanimous consent was the right call to get it to the President without unnecessary delays.",
pie: ["集成电路"],
origin: "X",
time: "2025年10月08日",
type: 1
}
]);
const companyList = ref([
{
id: 1,
cn: "个人官网",
name: "thune.senate.gov ",
logo: cp1
},
{
id: 2,
cn: "Twitter/X",
name: "@johnthune",
logo: cp2
},
{
id: 3,
cn: "Instagram",
name: "@johnrthune",
logo: cp3
},
{
id: 4,
cn: "Facebook",
name: "johnthune",
logo: cp4
}
]);
// 政治履历
const resumeList = ref([
{
id: 1,
time: "2025年1月",
title: "参议院多数党领袖",
content:
"图恩创造历史,成为南达科他州历史上第二位当选第四届参议员任期的政客。4 2025年1月,他更进一步当选为参议院多数党领袖,成为美国国会中最具影响力的政治人物之一。",
door: ""
},
{
id: 2,
time: "2016年",
title: "美国参议院议员(第三届任期)",
content: "图恩成功当选第三届参议员任期,进一步巩固了其在南达科他州的政治地位。",
door: ""
},
{
id: 3,
time: "2005-2010年",
title: "美国参议院议员(第109届、110届、111届国会)",
content:
"图恩正式就任美国参议员,代表南达科他州,开始了他在参议院的长期服务。他在参议院迅速适应角色,关注农业、交通和财政等关键议题。",
door: ""
},
{
id: 4,
time: "2004年",
title: "美国参议院议员候选人",
content:
"图恩的政治生涯迎来历史性转折,他在选举中击败时任参议院多数党领袖汤姆·达施勒(Tom Daschle),这一胜利是近50年来首次有参议院多数党领袖在选举中落败。",
door: ""
},
{
id: 5,
time: "1997-2003年",
title: "美国众议院议员(第105届、106届、107届国会)",
content: "图恩在众议院轻松赢得两次连任,连续服务三个任期,展现了其在南达科他州的广泛支持基础。",
door: ""
},
{
id: 6,
time: "1996年",
title: "美国众议院议员候选人",
content: "图恩开启政治生涯,参与竞选南达科他州唯一的美国众议院席位。他成功赢得这场选举,为其政治生涯奠定了基础。",
door: ""
}
]);
// 弹框数据
const dialogData = ref([
{
id: 1,
name: "山姆・奥特曼",
content: "主张分级监管,反对 “一刀切” 审批;警告美国领先优势有限,电力与基建是关键瓶颈。",
img: img1,
job: "OpenAI CEO"
},
{
id: 2,
name: "布拉德・史密斯",
content: "主张全技术栈监管与基础建设投入,强调美国需在芯片、算法、数据各层保持领先。",
img: img2,
job: "微软副董事长"
},
{
id: 3,
name: "黄仁勋",
content: "认为 AI 处于 “智能基建起步期”,大模型为基础,代理式 AI 为下一阶段;否定 AI 泡沫,强调与实体经济融合。",
img: img3,
job: "NVIDIA CEO"
},
{
id: 4,
name: "杰弗里・辛顿",
content: "担忧 AI 发展过快,警告 “涌现能力” 超预期,可能导致存在性风险;呼吁放缓研发、加强安全。",
img: img4,
job: "图灵奖得主、深度学习先驱"
},
{
id: 5,
name: "李飞飞",
content: "关注 AI 伦理与公平性,强调安全与普惠并重,对 AGI 时间表持谨慎态度。",
img: img5,
job: "斯坦福大学教授"
}
]);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -907,12 +734,32 @@ const dialogData = ref([ ...@@ -907,12 +734,32 @@ const dialogData = ref([
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.news-tab-btn {
writing-mode: horizontal-tb;
font-size: 13px;
color: #8c8c8c;
background: none;
border: none;
cursor: pointer;
padding: 6px 12px;
white-space: nowrap;
}
.news-tab-btn.active {
background: #e8513f;
color: #fff;
border-radius: 20px;
font-weight: 500;
}
.member-of-congress { .member-of-congress {
width: 1600px; width: 1600px;
margin: 0 auto; margin: 0 auto;
padding-bottom: 50px; padding-bottom: 50px;
.header { .header {
width: 1600px; width: 100%;
height: 200px; height: 200px;
margin: 16px auto; margin: 16px auto;
background-color: rgba(255, 255, 255, 0.8); background-color: rgba(255, 255, 255, 0.8);
...@@ -921,19 +768,23 @@ const dialogData = ref([ ...@@ -921,19 +768,23 @@ const dialogData = ref([
padding: 20px; padding: 20px;
display: flex; display: flex;
align-items: center; align-items: center;
.avatar { .avatar {
width: 160px; width: 160px;
height: 160px; height: 160px;
margin-right: 24px; margin-right: 24px;
overflow: hidden; overflow: hidden;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
} }
.info { .info {
flex: 1; flex: 1;
.name-cn { .name-cn {
font-size: 32px; font-size: 32px;
font-weight: 700; font-weight: 700;
...@@ -942,6 +793,7 @@ const dialogData = ref([ ...@@ -942,6 +793,7 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 8px; margin-bottom: 8px;
} }
.name-en { .name-en {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -950,6 +802,7 @@ const dialogData = ref([ ...@@ -950,6 +802,7 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 6px; margin-bottom: 6px;
} }
.introduction { .introduction {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -958,8 +811,10 @@ const dialogData = ref([ ...@@ -958,8 +811,10 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 6px; margin-bottom: 6px;
} }
.domain { .domain {
font-size: 14px; font-size: 14px;
p { p {
display: inline-block; display: inline-block;
padding: 1px 8px; padding: 1px 8px;
...@@ -967,31 +822,37 @@ const dialogData = ref([ ...@@ -967,31 +822,37 @@ const dialogData = ref([
margin-right: 8px; margin-right: 8px;
border: 1px solid; border: 1px solid;
} }
.cl1 { .cl1 {
border-color: rgba(186, 224, 255, 1); border-color: rgba(186, 224, 255, 1);
background-color: rgba(230, 244, 255, 1); background-color: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1); color: rgba(22, 119, 255, 1);
} }
.cl2 { .cl2 {
border-color: rgba(217, 247, 190, 1); border-color: rgba(217, 247, 190, 1);
background-color: rgba(246, 255, 237, 1); background-color: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1); color: rgba(82, 196, 26, 1);
} }
.cl3 { .cl3 {
border-color: rgba(255, 241, 184, 1); border-color: rgba(255, 241, 184, 1);
background-color: rgba(255, 251, 230, 1); background-color: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1); color: rgba(250, 173, 20, 1);
} }
.cl4 { .cl4 {
border-color: rgba(255, 204, 199, 1); border-color: rgba(255, 204, 199, 1);
background-color: rgba(255, 241, 240, 1); background-color: rgba(255, 241, 240, 1);
color: rgba(255, 77, 79, 1); color: rgba(255, 77, 79, 1);
} }
.cl5 { .cl5 {
border-color: rgba(255, 241, 184, 1); border-color: rgba(255, 241, 184, 1);
background-color: rgba(255, 251, 230, 1); background-color: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1); color: rgba(250, 173, 20, 1);
} }
.cl6 { .cl6 {
border-color: rgba(255, 204, 199, 1); border-color: rgba(255, 204, 199, 1);
background-color: rgba(255, 241, 240, 1); background-color: rgba(255, 241, 240, 1);
...@@ -1000,8 +861,9 @@ const dialogData = ref([ ...@@ -1000,8 +861,9 @@ const dialogData = ref([
} }
} }
} }
.info-divide { .info-divide {
width: 1600px; width: 100%;
height: 64px; height: 64px;
margin: 16px auto; margin: 16px auto;
background-color: rgba(255, 255, 255, 0.65); background-color: rgba(255, 255, 255, 0.65);
...@@ -1011,6 +873,7 @@ const dialogData = ref([ ...@@ -1011,6 +873,7 @@ const dialogData = ref([
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
div { div {
width: 530px; width: 530px;
height: 54px; height: 54px;
...@@ -1022,10 +885,12 @@ const dialogData = ref([ ...@@ -1022,10 +885,12 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
border-radius: 10px; border-radius: 10px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: rgba(231, 243, 255, 1); background: rgba(231, 243, 255, 1);
} }
} }
.active { .active {
font-size: 24px; font-size: 24px;
font-weight: 700; font-weight: 700;
...@@ -1036,16 +901,23 @@ const dialogData = ref([ ...@@ -1036,16 +901,23 @@ const dialogData = ref([
border: 2px solid rgba(174, 214, 255, 1); border: 2px solid rgba(174, 214, 255, 1);
} }
} }
.info-content { .info-content {
width: 1600px; width: 1600px;
height: 2174px; height: 2174px;
margin-bottom: 50px; margin-bottom: 50px;
margin: 16px auto; margin: 16px auto;
display: flex; display: flex;
:deep(.header-btn) {
top: 10px;
}
.left { .left {
width: 1064px; width: 1064px;
height: 100%; height: 100%;
margin-right: 16px; margin-right: 16px;
.left-top { .left-top {
width: 100%; width: 100%;
height: 300px; height: 300px;
...@@ -1053,12 +925,14 @@ const dialogData = ref([ ...@@ -1053,12 +925,14 @@ const dialogData = ref([
background-color: rgba(255, 255, 255, 1); background-color: rgba(255, 255, 255, 1);
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
.title { .title {
width: 100%; width: 100%;
height: 56px; height: 56px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 14px 12px 16px 0; padding: 14px 12px 16px 0;
.box { .box {
width: 8px; width: 8px;
height: 20px; height: 20px;
...@@ -1067,6 +941,7 @@ const dialogData = ref([ ...@@ -1067,6 +941,7 @@ const dialogData = ref([
border-top-right-radius: 4px; border-top-right-radius: 4px;
margin-right: 14px; margin-right: 14px;
} }
.text { .text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -1074,43 +949,26 @@ const dialogData = ref([ ...@@ -1074,43 +949,26 @@ const dialogData = ref([
line-height: 26px; line-height: 26px;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
} }
.btn { .btn {
width: 60px; width: 60px;
height: 28px; height: 28px;
margin-left: auto; margin-left: auto;
img { img {
width: 28px; width: 28px;
height: 28px; height: 28px;
cursor: pointer; cursor: pointer;
} }
img:first-child { img:first-child {
margin-right: 4px; margin-right: 4px;
} }
} }
} }
.num-list {
display: flex;
align-items: center;
padding-left: 24px;
margin-bottom: 16px;
div {
padding: 1px 8px;
line-height: 30px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
cursor: pointer;
border: 1px solid rgb(230, 231, 232);
border-radius: 4px;
margin-right: 8px;
}
.active {
background-color: rgba(231, 243, 255, 1);
color: rgb(5, 95, 194);
border-color: rgb(5, 95, 194);
}
}
.echarts { .echarts {
width: 1009px; width: 1009px;
height: 170px; height: 170px;
...@@ -1141,6 +999,7 @@ const dialogData = ref([ ...@@ -1141,6 +999,7 @@ const dialogData = ref([
} }
} }
} }
.left-center { .left-center {
width: 100%; width: 100%;
height: 512px; height: 512px;
...@@ -1148,6 +1007,7 @@ const dialogData = ref([ ...@@ -1148,6 +1007,7 @@ const dialogData = ref([
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
margin-bottom: 16px; margin-bottom: 16px;
.title { .title {
width: 100%; width: 100%;
height: 56px; height: 56px;
...@@ -1155,6 +1015,7 @@ const dialogData = ref([ ...@@ -1155,6 +1015,7 @@ const dialogData = ref([
align-items: center; align-items: center;
padding: 14px 12px 16px 0; padding: 14px 12px 16px 0;
position: relative; position: relative;
.box { .box {
width: 8px; width: 8px;
height: 20px; height: 20px;
...@@ -1163,6 +1024,7 @@ const dialogData = ref([ ...@@ -1163,6 +1024,7 @@ const dialogData = ref([
border-top-right-radius: 4px; border-top-right-radius: 4px;
margin-right: 14px; margin-right: 14px;
} }
.text { .text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -1170,34 +1032,41 @@ const dialogData = ref([ ...@@ -1170,34 +1032,41 @@ const dialogData = ref([
line-height: 26px; line-height: 26px;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
} }
.btn { .btn {
width: 60px; width: 60px;
height: 28px; height: 28px;
margin-left: auto; margin-left: auto;
img { img {
width: 28px; width: 28px;
height: 28px; height: 28px;
cursor: pointer; cursor: pointer;
} }
img:first-child { img:first-child {
margin-right: 4px; margin-right: 4px;
} }
} }
.input { .input {
position: absolute; position: absolute;
top: 15px; top: 15px;
right: 114px; right: 114px;
.select { .select {
width: 120px; width: 120px;
} }
} }
} }
.main { .main {
width: 1016px; width: 1016px;
height: 360px; height: 360px;
margin-left: 24px; margin-left: 24px;
margin-bottom: 16px; margin-bottom: 16px;
} }
.bottom { .bottom {
width: 1016px; width: 1016px;
height: 64px; height: 64px;
...@@ -1208,11 +1077,13 @@ const dialogData = ref([ ...@@ -1208,11 +1077,13 @@ const dialogData = ref([
border-radius: 4px; border-radius: 4px;
border: 1px solid rgba(231, 243, 255, 1); border: 1px solid rgba(231, 243, 255, 1);
padding: 6px 12px; padding: 6px 12px;
.icon { .icon {
width: 19px; width: 19px;
height: 20px; height: 20px;
margin-right: 13px; margin-right: 13px;
} }
.text1 { .text1 {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -1221,18 +1092,45 @@ const dialogData = ref([ ...@@ -1221,18 +1092,45 @@ const dialogData = ref([
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
margin-right: 13px; margin-right: 13px;
} }
.icon1 { .icon1 {
width: 24px; width: 24px;
height: 24px; height: 24px;
} }
} }
} }
.left-center .input {
overflow: visible;
}
.left-center :deep(.el-select) {
width: 120px;
}
.left-center :deep(.el-select .el-input__inner) {
font-size: 14px;
color: rgba(59, 65, 75, 1);
}
.left-bottom { .left-bottom {
width: 100%; width: 100%;
height: 1330px; height: 1330px;
background-color: rgba(255, 255, 255, 1); background-color: rgba(255, 255, 255, 1);
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
:deep(.wrapper-header) {
position: relative;
display: flex;
align-items: center;
height: 56px;
/* 根据设计图调整 */
padding: 0 24px;
}
:deep(.header-btn){
top: 16px;
}
.title { .title {
width: 100%; width: 100%;
height: 85px; height: 85px;
...@@ -1240,6 +1138,7 @@ const dialogData = ref([ ...@@ -1240,6 +1138,7 @@ const dialogData = ref([
align-items: center; align-items: center;
padding: 14px 12px 45px 0; padding: 14px 12px 45px 0;
position: relative; position: relative;
.box { .box {
width: 8px; width: 8px;
height: 20px; height: 20px;
...@@ -1248,6 +1147,7 @@ const dialogData = ref([ ...@@ -1248,6 +1147,7 @@ const dialogData = ref([
border-top-right-radius: 4px; border-top-right-radius: 4px;
margin-right: 14px; margin-right: 14px;
} }
.text { .text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -1255,33 +1155,53 @@ const dialogData = ref([ ...@@ -1255,33 +1155,53 @@ const dialogData = ref([
line-height: 26px; line-height: 26px;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
} }
.btn { .btn {
width: 60px; width: 60px;
height: 28px; height: 28px;
margin-left: auto; margin-left: auto;
img { img {
width: 28px; width: 28px;
height: 28px; height: 28px;
cursor: pointer; cursor: pointer;
} }
img:first-child { img:first-child {
margin-right: 4px; margin-right: 4px;
} }
} }
.input { .input {
position: absolute; position: absolute;
top: 15px; top: 50%;
right: 114px; transform: translateY(-50%);
right: 120px;
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
line-height: 24px; line-height: 24px;
color: rgb(132, 136, 142); color: rgb(132, 136, 142);
input { display: flex;
margin-right: 8px; align-items: center;
gap: 8px;
input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: #1890ff;
cursor: pointer;
margin: 0;
}
span {
margin-left: 0 !important;
} }
} }
} }
.main { .main {
width: 1064px; width: 1064px;
height: 1133px; height: 1133px;
...@@ -1289,10 +1209,12 @@ const dialogData = ref([ ...@@ -1289,10 +1209,12 @@ const dialogData = ref([
padding-right: 50px; padding-right: 50px;
position: relative; position: relative;
z-index: 110; z-index: 110;
.main-item { .main-item {
width: 1014px; width: 1014px;
margin-bottom: 40px; margin-bottom: 40px;
display: flex; display: flex;
.time { .time {
width: 77px; width: 77px;
box-sizing: border-box; box-sizing: border-box;
...@@ -1300,6 +1222,7 @@ const dialogData = ref([ ...@@ -1300,6 +1222,7 @@ const dialogData = ref([
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; align-items: flex-end;
.year { .year {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1307,6 +1230,7 @@ const dialogData = ref([ ...@@ -1307,6 +1230,7 @@ const dialogData = ref([
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
line-height: 24px; line-height: 24px;
} }
.date { .date {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1315,20 +1239,25 @@ const dialogData = ref([ ...@@ -1315,20 +1239,25 @@ const dialogData = ref([
line-height: 24px; line-height: 24px;
} }
} }
.image { .image {
margin-right: 20px; margin-right: 20px;
img { img {
width: 24px; width: 24px;
height: 24px; height: 24px;
} }
} }
.content { .content {
width: 873px; width: 873px;
.content-type1 { .content-type1 {
background-color: rgba(246, 250, 255, 1); background-color: rgba(246, 250, 255, 1);
border-radius: 10px; border-radius: 10px;
border: 1px solid rgb(234, 236, 238); border: 1px solid rgb(234, 236, 238);
padding: 12px 14px 12px 15px; padding: 12px 14px 12px 15px;
.content-title1 { .content-title1 {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1339,6 +1268,7 @@ const dialogData = ref([ ...@@ -1339,6 +1268,7 @@ const dialogData = ref([
border-bottom: 1px solid rgb(234, 236, 238); border-bottom: 1px solid rgb(234, 236, 238);
cursor: pointer; cursor: pointer;
} }
.content-title-en { .content-title-en {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -1349,6 +1279,7 @@ const dialogData = ref([ ...@@ -1349,6 +1279,7 @@ const dialogData = ref([
cursor: pointer; cursor: pointer;
} }
} }
.content-type2 { .content-type2 {
.content-title2 { .content-title2 {
font-size: 20px; font-size: 20px;
...@@ -1360,6 +1291,7 @@ const dialogData = ref([ ...@@ -1360,6 +1291,7 @@ const dialogData = ref([
cursor: pointer; cursor: pointer;
} }
} }
.content-contentcontent { .content-contentcontent {
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
...@@ -1374,10 +1306,12 @@ const dialogData = ref([ ...@@ -1374,10 +1306,12 @@ const dialogData = ref([
margin-bottom: 8px; margin-bottom: 8px;
cursor: pointer; cursor: pointer;
} }
.content-tag { .content-tag {
width: 873px; width: 873px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
.tag { .tag {
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
...@@ -1389,26 +1323,31 @@ const dialogData = ref([ ...@@ -1389,26 +1323,31 @@ const dialogData = ref([
border: 1px solid; border: 1px solid;
cursor: pointer; cursor: pointer;
} }
.dl1 { .dl1 {
background-color: rgba(230, 255, 251, 1); background-color: rgba(230, 255, 251, 1);
color: rgba(19, 168, 168, 1); color: rgba(19, 168, 168, 1);
border-color: rgba(135, 232, 222, 1); border-color: rgba(135, 232, 222, 1);
} }
.dl2 { .dl2 {
background-color: rgba(255, 241, 240, 1); background-color: rgba(255, 241, 240, 1);
color: rgb(206, 79, 81); color: rgb(206, 79, 81);
border-color: rgba(255, 163, 158, 1); border-color: rgba(255, 163, 158, 1);
} }
.dl3 { .dl3 {
background-color: rgba(255, 247, 230, 1); background-color: rgba(255, 247, 230, 1);
color: rgba(250, 140, 22, 1); color: rgba(250, 140, 22, 1);
border-color: rgba(255, 213, 145, 1); border-color: rgba(255, 213, 145, 1);
} }
.dl4 { .dl4 {
background-color: rgba(249, 240, 255, 1); background-color: rgba(249, 240, 255, 1);
color: rgba(114, 46, 209, 1); color: rgba(114, 46, 209, 1);
border-color: rgba(211, 173, 247, 1); border-color: rgba(211, 173, 247, 1);
} }
.origin { .origin {
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
...@@ -1420,6 +1359,7 @@ const dialogData = ref([ ...@@ -1420,6 +1359,7 @@ const dialogData = ref([
} }
} }
} }
.line-test { .line-test {
position: absolute; position: absolute;
top: 10px; top: 10px;
...@@ -1429,6 +1369,7 @@ const dialogData = ref([ ...@@ -1429,6 +1369,7 @@ const dialogData = ref([
z-index: -1; z-index: -1;
} }
} }
.line { .line {
width: 100%; width: 100%;
height: 1px; height: 1px;
...@@ -1436,6 +1377,7 @@ const dialogData = ref([ ...@@ -1436,6 +1377,7 @@ const dialogData = ref([
margin-top: 30px; margin-top: 30px;
border: none; border: none;
} }
.pagination { .pagination {
width: 100%; width: 100%;
height: 76px; height: 76px;
...@@ -1445,16 +1387,19 @@ const dialogData = ref([ ...@@ -1445,16 +1387,19 @@ const dialogData = ref([
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-top: 1px solid rgb(234, 236, 238); border-top: 1px solid rgb(234, 236, 238);
.total { .total {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
} }
:deep(.custom-pagination) { :deep(.custom-pagination) {
display: flex; display: flex;
align-items: center; align-items: center;
} }
:deep(.custom-pagination.el-pagination.is-background .el-pager li) { :deep(.custom-pagination.el-pagination.is-background .el-pager li) {
min-width: 32px; min-width: 32px;
height: 32px; height: 32px;
...@@ -1468,11 +1413,13 @@ const dialogData = ref([ ...@@ -1468,11 +1413,13 @@ const dialogData = ref([
font-weight: 400; font-weight: 400;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
} }
:deep(.custom-pagination.el-pagination.is-background .el-pager li.is-active) { :deep(.custom-pagination.el-pagination.is-background .el-pager li.is-active) {
background-color: #fff; background-color: #fff;
color: rgba(22, 119, 255, 1); color: rgba(22, 119, 255, 1);
border-color: rgba(22, 119, 255, 1); border-color: rgba(22, 119, 255, 1);
} }
:deep(.custom-pagination.el-pagination.is-background .el-pager li.is-ellipsis) { :deep(.custom-pagination.el-pagination.is-background .el-pager li.is-ellipsis) {
border: none; border: none;
background-color: transparent; background-color: transparent;
...@@ -1480,6 +1427,7 @@ const dialogData = ref([ ...@@ -1480,6 +1427,7 @@ const dialogData = ref([
min-width: 16px; min-width: 16px;
margin: 0 6px; margin: 0 6px;
} }
:deep(.custom-pagination.el-pagination.is-background .btn-prev), :deep(.custom-pagination.el-pagination.is-background .btn-prev),
:deep(.custom-pagination.el-pagination.is-background .btn-next) { :deep(.custom-pagination.el-pagination.is-background .btn-next) {
min-width: 32px; min-width: 32px;
...@@ -1492,6 +1440,7 @@ const dialogData = ref([ ...@@ -1492,6 +1440,7 @@ const dialogData = ref([
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
margin: 0 6px; margin: 0 6px;
} }
:deep(.custom-pagination.el-pagination.is-background .btn-prev.is-disabled), :deep(.custom-pagination.el-pagination.is-background .btn-prev.is-disabled),
:deep(.custom-pagination.el-pagination.is-background .btn-next.is-disabled) { :deep(.custom-pagination.el-pagination.is-background .btn-next.is-disabled) {
color: rgba(95, 101, 108, 0.45); color: rgba(95, 101, 108, 0.45);
...@@ -1501,9 +1450,11 @@ const dialogData = ref([ ...@@ -1501,9 +1450,11 @@ const dialogData = ref([
} }
} }
} }
.right { .right {
width: 520px; width: 520px;
height: 100%; height: 100%;
.right-top { .right-top {
width: 520px; width: 520px;
height: 602px; height: 602px;
...@@ -1511,12 +1462,14 @@ const dialogData = ref([ ...@@ -1511,12 +1462,14 @@ const dialogData = ref([
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
margin-bottom: 16px; margin-bottom: 16px;
.title { .title {
width: 100%; width: 100%;
height: 60px; height: 60px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 14px 12px 20px 0; padding: 14px 12px 20px 0;
.box { .box {
width: 8px; width: 8px;
height: 20px; height: 20px;
...@@ -1525,6 +1478,7 @@ const dialogData = ref([ ...@@ -1525,6 +1478,7 @@ const dialogData = ref([
border-top-right-radius: 4px; border-top-right-radius: 4px;
margin-right: 14px; margin-right: 14px;
} }
.text { .text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -1532,32 +1486,39 @@ const dialogData = ref([ ...@@ -1532,32 +1486,39 @@ const dialogData = ref([
line-height: 26px; line-height: 26px;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
} }
.btn { .btn {
width: 60px; width: 60px;
height: 28px; height: 28px;
margin-left: auto; margin-left: auto;
img { img {
width: 28px; width: 28px;
height: 28px; height: 28px;
cursor: pointer; cursor: pointer;
} }
img:first-child { img:first-child {
margin-right: 4px; margin-right: 4px;
} }
} }
} }
.main-content { .main-content {
width: 520px; width: 520px;
height: 517px; height: 517px;
padding: 0 48px 50px 34px; padding: 0 48px 50px 34px;
margin-top: 16px;
.baseInfo { .baseInfo {
width: 438px; width: 438px;
height: 314px; height: 265px;
padding-bottom: 50px; padding-bottom: 50px;
border-bottom: 1px solid rgb(234, 236, 238); border-bottom: 1px solid rgb(234, 236, 238);
.baseInfo-item { .baseInfo-item {
display: flex; display: flex;
margin-bottom: 12px; margin-bottom: 12px;
.baseInfo-item-title { .baseInfo-item-title {
width: 88px; width: 88px;
font-size: 16px; font-size: 16px;
...@@ -1566,9 +1527,11 @@ const dialogData = ref([ ...@@ -1566,9 +1527,11 @@ const dialogData = ref([
line-height: 24px; line-height: 24px;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
} }
.address { .address {
width: 110px; width: 110px;
} }
.baseInfo-item-content { .baseInfo-item-content {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -1576,15 +1539,18 @@ const dialogData = ref([ ...@@ -1576,15 +1539,18 @@ const dialogData = ref([
line-height: 24px; line-height: 24px;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
} }
.long { .long {
width: 349px; width: 349px;
} }
} }
} }
.company { .company {
width: 438px; width: 438px;
height: 176px; height: 176px;
padding-top: 19px; padding-top: 19px;
.company-title { .company-title {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1593,12 +1559,21 @@ const dialogData = ref([ ...@@ -1593,12 +1559,21 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 19px; margin-bottom: 19px;
} }
.company-content { .company-content {
width: 409px; width: 409px;
height: 114px; height: 114px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden;
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.company-item { .company-item {
width: 180px; width: 180px;
height: 49px; height: 49px;
...@@ -1606,6 +1581,7 @@ const dialogData = ref([ ...@@ -1606,6 +1581,7 @@ const dialogData = ref([
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
img { img {
width: 48px; width: 48px;
height: 48px; height: 48px;
...@@ -1623,6 +1599,7 @@ const dialogData = ref([ ...@@ -1623,6 +1599,7 @@ const dialogData = ref([
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.company-name { .company-name {
width: 130px; width: 130px;
font-size: 16px; font-size: 16px;
...@@ -1635,9 +1612,11 @@ const dialogData = ref([ ...@@ -1635,9 +1612,11 @@ const dialogData = ref([
text-overflow: ellipsis; text-overflow: ellipsis;
} }
} }
.company-item:nth-child(2n-1) { .company-item:nth-child(2n-1) {
margin-right: 39px; margin-right: 39px;
} }
.company-item:last-child { .company-item:last-child {
.company-name { .company-name {
width: 150px; width: 150px;
...@@ -1647,18 +1626,53 @@ const dialogData = ref([ ...@@ -1647,18 +1626,53 @@ const dialogData = ref([
} }
} }
} }
.right-bottom { .right-bottom {
width: 520px; width: 520px;
height: 1556px; height: 1556px;
background-color: rgba(255, 255, 255, 1); background-color: rgba(255, 255, 255, 1);
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
.resume-tabs {
display: flex;
flex-direction: row;
gap: 8px;
align-items: flex-start;
}
.resume-tab {
width: 90px;
height: 28px;
display: flex;
justify-content: center;
align-items: center;
padding: 1px 8px;
border-radius: 4px;
border: 1px solid rgba(230, 231, 232, 1);
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-size: 16px;
font-family: 'Microsoft YaHei', sans-serif;
font-weight: 400;
line-height: 30px;
cursor: pointer;
box-sizing: border-box;
}
.resume-tab.active {
background: rgba(231, 243, 255, 1);
border-color: rgba(5, 95, 194, 1);
color: rgba(5, 95, 194, 1);
}
.title { .title {
width: 100%; width: 100%;
height: 80px; height: 80px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 14px 12px 40px 0; padding: 14px 12px 40px 0;
.box { .box {
width: 8px; width: 8px;
height: 20px; height: 20px;
...@@ -1667,6 +1681,7 @@ const dialogData = ref([ ...@@ -1667,6 +1681,7 @@ const dialogData = ref([
border-top-right-radius: 4px; border-top-right-radius: 4px;
margin-right: 14px; margin-right: 14px;
} }
.text { .text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -1674,29 +1689,35 @@ const dialogData = ref([ ...@@ -1674,29 +1689,35 @@ const dialogData = ref([
line-height: 26px; line-height: 26px;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
} }
.btn { .btn {
width: 60px; width: 60px;
height: 28px; height: 28px;
margin-left: auto; margin-left: auto;
img { img {
width: 28px; width: 28px;
height: 28px; height: 28px;
cursor: pointer; cursor: pointer;
} }
img:first-child { img:first-child {
margin-right: 4px; margin-right: 4px;
} }
} }
} }
.content-main { .content-main {
width: 480px; width: 480px;
height: 1020px; height: 1020px;
margin-left: 16px; margin-left: 16px;
.content-item { .content-item {
width: 454px; width: 454px;
margin-bottom: 60px; margin-bottom: 60px;
margin-left: 26px; margin-left: 26px;
position: relative; position: relative;
.image01 { .image01 {
width: 14px; width: 14px;
height: 12.13px; height: 12.13px;
...@@ -1704,6 +1725,7 @@ const dialogData = ref([ ...@@ -1704,6 +1725,7 @@ const dialogData = ref([
top: 8px; top: 8px;
left: -26px; left: -26px;
} }
.content-item-time { .content-item-time {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1712,6 +1734,7 @@ const dialogData = ref([ ...@@ -1712,6 +1734,7 @@ const dialogData = ref([
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
margin-bottom: 8px; margin-bottom: 8px;
} }
.content-item-title { .content-item-title {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1720,6 +1743,7 @@ const dialogData = ref([ ...@@ -1720,6 +1743,7 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 8px; margin-bottom: 8px;
} }
.content-item-content { .content-item-content {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -1728,6 +1752,7 @@ const dialogData = ref([ ...@@ -1728,6 +1752,7 @@ const dialogData = ref([
color: rgb(95, 101, 108); color: rgb(95, 101, 108);
margin-bottom: 8px; margin-bottom: 8px;
} }
.content-item-door { .content-item-door {
width: 300px; width: 300px;
height: 32px; height: 32px;
...@@ -1737,11 +1762,13 @@ const dialogData = ref([ ...@@ -1737,11 +1762,13 @@ const dialogData = ref([
border-radius: 4px; border-radius: 4px;
background-color: rgba(255, 246, 240, 1); background-color: rgba(255, 246, 240, 1);
border: 1px solid rgba(250, 140, 22, 0.4); border: 1px solid rgba(250, 140, 22, 0.4);
img { img {
width: 20px; width: 20px;
height: 24px; height: 24px;
margin-right: 10px; margin-right: 10px;
} }
span { span {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -1761,107 +1788,276 @@ const dialogData = ref([ ...@@ -1761,107 +1788,276 @@ const dialogData = ref([
background: rgba(248, 249, 250, 1); background: rgba(248, 249, 250, 1);
} }
:deep(.viewpoint-dialog) {
width: 761px !important;
height: 669px; .proposal-wrapper {
padding: 0; position: relative;
/* 作为定位参考 */
}
.proposal-tab.active {
background: #055FC2;
color: #fff;
}
.proposal-tab-switcher {
position: absolute;
right: calc(100% + 24px);
top: 0;
display: flex;
flex-direction: column;
gap: 12px;
z-index: 1;
}
.proposal-tab {
width: 120px;
height: 32px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px;
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
font-size: 16px;
font-weight: 400;
color: #8c8c8c;
background: none;
border: none;
border-radius: 20px;
cursor: pointer;
white-space: nowrap;
box-sizing: border-box;
}
.proposal-tab.active {
background: #055FC2;
color: #fff;
}
.proposal-tab-arrow {
flex-shrink: 0;
}
.table-pagination {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px 0;
}
.table-pagination-total {
font-size: 14px;
font-family: 'Microsoft YaHei', sans-serif;
font-weight: 400;
color: rgba(95, 101, 108, 1);
white-space: nowrap;
}
.table-pagination :deep(.el-pagination.is-background .btn-prev),
.table-pagination :deep(.el-pagination.is-background .btn-next),
.table-pagination :deep(.el-pagination.is-background .el-pager li) {
min-width: 32px;
height: 32px;
line-height: 32px;
border-radius: 4px; border-radius: 4px;
.el-dialog__body { border: 1px solid rgba(234, 236, 238, 1);
padding: 0; background: #fff;
} color: rgba(59, 65, 75, 1);
.el-dialog__header { font-size: 14px;
font-family: 'Microsoft YaHei', sans-serif;
margin: 0 3px;
}
.table-pagination :deep(.el-pagination.is-background .el-pager li:not(.is-disabled).is-active) {
background: #fff;
color: rgba(5, 95, 194, 1);
border-color: rgba(5, 95, 194, 1);
}
.table-pagination :deep(.el-pagination.is-background .el-pager li:not(.is-disabled):hover) {
color: rgba(5, 95, 194, 1);
}
.table-pagination :deep(.el-pagination.is-background .btn-prev:hover),
.table-pagination :deep(.el-pagination.is-background .btn-next:hover) {
color: rgba(5, 95, 194, 1);
}
.left-top :deep(.header-btn) {
overflow: visible;
}
.tab-select {
width: 120px;
}
.left-top :deep(.el-select .el-input__wrapper) {
height: 28px;
border-radius: 4px;
box-shadow: 0 0 0 1px rgba(230, 231, 232, 1) inset;
background: rgba(255, 255, 255, 1);
}
.left-top :deep(.el-select .el-input__inner) {
font-size: 14px;
font-family: 'Microsoft YaHei', sans-serif;
color: rgba(59, 65, 75, 1);
}
.left-top :deep(.el-select .el-input.is-focus .el-input__wrapper) {
box-shadow: 0 0 0 1px rgba(5, 95, 194, 1) inset;
}
.viewpoint-dialog {
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
}
.viewpoint-dialog.el-dialog {
width: 761px !important;
height: 669px !important;
max-height: 669px !important;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
overflow: hidden;
display: flex;
flex-direction: column;
}
.viewpoint-dialog .el-dialog__header {
padding: 0; padding: 0;
margin: 0; margin: 0;
position: relative; height: 52px;
height: 48px; flex-shrink: 0;
} }
.el-dialog__headerbtn {
top: 50%; .viewpoint-dialog .el-dialog__body {
transform: translateY(-50%); padding: 0;
right: 12px; flex: 1;
} overflow: hidden;
.viewpoint-header { }
width: 761px;
height: 48px; .viewpoint-header {
background: linear-gradient(180deg, rgba(31, 140, 252, 0.2) 0%, rgba(36, 144, 255, 0) 100%);
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
height: 52px;
padding: 0 24px; padding: 0 24px;
border-bottom: 1px solid rgb(234, 236, 238); border-bottom: 1px solid rgba(231, 243, 255, 1);
} box-sizing: border-box;
.viewpoint-title { }
font-size: 16px;
.viewpoint-title {
display: flex;
align-items: center;
}
.viewpoint-tag {
font-size: 18px;
font-family: 'Source Han Sans CN', 'Microsoft YaHei', sans-serif;
font-weight: 700; font-weight: 700;
font-family: "Microsoft YaHei"; color: rgba(206, 79, 81, 1);
line-height: 24px; line-height: 24px;
color: rgb(5, 95, 194); }
}
.viewpoint-body { .viewpoint-label {
padding: 24px 24px 35px 24px; font-size: 18px;
height: calc(669px - 48px); font-family: 'Source Han Sans CN', 'Microsoft YaHei', sans-serif;
box-sizing: border-box;
overflow: hidden;
.viewpoint-body-title {
font-size: 28px;
font-weight: 700; font-weight: 700;
font-family: "Microsoft YaHei"; color: rgba(59, 65, 75, 1);
line-height: 37px; line-height: 24px;
color: rgb(59, 65, 75); }
margin-bottom: 32px;
} .viewpoint-close {
.viewpoint-item { width: 32px;
width: 713px; height: 32px;
min-height: 81px; cursor: pointer;
display: block; }
margin-bottom: 12px;
position: relative; .viewpoint-body {
padding-left: 48px; padding: 18px 24px 24px;
.viewpoint-item-img { height: calc(669px - 52px);
position: absolute; max-height: calc(669px - 52px);
top: 0; overflow-y: auto;
left: -10px; display: flex;
width: 42px; flex-direction: column;
height: 42px; gap: 12px;
}
.viewpoint-item-content {
width: 665px;
min-height: 81px;
background-image: url("./assets/bg01.png");
background-size: 100% 100%;
position: relative;
top: 0;
left: 0;
padding-left: 23px;
padding-top: 12px;
padding-bottom: 13px;
box-sizing: border-box; box-sizing: border-box;
.viewpoint-item-name { }
.viewpoint-item {
display: flex;
gap: 8px;
align-items: flex-start;
}
.viewpoint-avatar {
flex-shrink: 0;
padding-top: 12px;
}
.viewpoint-content {
flex: 1;
display: flex;
align-items: flex-start;
}
.viewpoint-arrow {
width: 9px;
height: 72px;
flex-shrink: 0;
background: url('./assets/arrow-icon.png') no-repeat center / 100%;
}
.viewpoint-card {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
padding: 8px 12px;
background-color: rgba(246, 250, 255, 1);
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
}
.viewpoint-card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.viewpoint-name {
font-size: 16px; font-size: 16px;
font-family: 'Source Han Sans CN', 'Microsoft YaHei', sans-serif;
font-weight: 700; font-weight: 700;
font-family: "Microsoft YaHei"; color: rgba(59, 65, 75, 1);
line-height: 24px; line-height: 24px;
color: rgb(59, 65, 75); }
margin-bottom: 5px;
} .viewpoint-job {
.viewpoint-item-desc {
font-size: 16px; font-size: 16px;
font-family: 'Source Han Sans CN', 'Microsoft YaHei', sans-serif;
font-weight: 400; font-weight: 400;
font-family: "Microsoft YaHei"; color: rgba(95, 101, 108, 1);
line-height: 24px; line-height: 30px;
color: rgb(59, 65, 75); }
}
.viewpoint-item-job { .viewpoint-desc {
position: absolute;
top: 8px;
right: 22px;
font-size: 16px; font-size: 16px;
font-family: 'Source Han Sans CN', 'Microsoft YaHei', sans-serif;
font-weight: 400; font-weight: 400;
font-family: "Microsoft YaHei"; color: rgba(59, 65, 75, 1);
line-height: 30px; line-height: 24px;
color: rgb(132, 136, 142); text-align: justify;
} }
}
} .viewpoint-empty {
} text-align: center;
padding: 40px 0;
color: rgba(170, 173, 177, 1);
font-size: 14px;
} }
</style> </style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论