提交 002c3693 authored 作者: 张烨's avatar 张烨

Merge remote-tracking branch 'origin/pre' into zy-dev

流水线 #374 已通过 于阶段
in 1 分 39 秒
......@@ -199,7 +199,6 @@
"resolved": "https://registry.npmmirror.com/@antv/g6/-/g6-4.8.25.tgz",
"integrity": "sha512-8mdTnN9QMVNQZtlXmftL8fvRsa4L+GajK58Zp51wyrGLFyjeop8R0QSkCALW45DWP2TaQeZAPtjhQUU/wf5hIg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@antv/g6-pc": "0.8.25"
}
......@@ -2172,7 +2171,6 @@
"resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/lodash": "*"
}
......@@ -2289,7 +2287,6 @@
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz",
"integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/parser": "^7.28.3",
"@vue/compiler-core": "3.5.21",
......@@ -2896,7 +2893,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
......@@ -3255,7 +3251,6 @@
"resolved": "https://registry.npmmirror.com/cytoscape/-/cytoscape-3.33.1.tgz",
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10"
}
......@@ -3653,7 +3648,6 @@
"resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
......@@ -3742,7 +3736,6 @@
"version": "0.8.5",
"resolved": "https://registry.npmmirror.com/dagre/-/dagre-0.8.5.tgz",
"integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==",
"peer": true,
"dependencies": {
"graphlib": "^2.1.8",
"lodash": "^4.17.15"
......@@ -3950,7 +3943,6 @@
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz",
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.6.1"
......@@ -5342,15 +5334,13 @@
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/lodash-unified": {
"version": "1.0.3",
......@@ -5398,7 +5388,6 @@
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"license": "MIT",
"peer": true,
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
......@@ -6478,7 +6467,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
......@@ -6575,7 +6563,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
......@@ -6977,7 +6964,6 @@
"integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
......@@ -8148,7 +8134,6 @@
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
......@@ -8208,7 +8193,6 @@
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.21.tgz",
"integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.21",
"@vue/compiler-sfc": "3.5.21",
......
......@@ -259,26 +259,7 @@ export async function getHistoryBillListWithStage(personId, params = {}) {
}
}
// /**
// * 获取潜在提案举措分析
// * GET /api/personHomepage/historyBill/clause/{personId}
// * @param {string} personId - 人物ID
// * @param {Object} params - 查询参数
// */
// export async function getPotentialClauseAnalysis(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',
// })
// }
/**
* 获取排序选项
......@@ -331,31 +312,32 @@ export function getProgressPrediction(billId) {
})
}
/**
* 获取相似法案列表
* @param {Object} params - 查询参数
* @param {string} params.billIds - 当前法案的ID
* @param {string[]} params.domains - 领域名称列表
* @param {string} params.patternType - 政府结构类型,如 "统一政府"/"分裂政府"/"微弱多数"
* @param {string} params.proposalType - 提案类型,如 "两党共同提案"/"单政党提案(共和党提案)"/"单政党提案(民主党提案)"
* @param {string} params.domains - 领域名称,多个用逗号拼接
* @param {string} params.patternType - 政府结构类型
* @param {string} params.proposalType - 提案类型
* @param {string} params.timeFrame - 时间范围(单个字符串)
* @param {number} params.maxNum - 返回最大数量
* @returns {Promise<Object>} 相似法案列表
*/
export function getSimiBills(params = {}) {
// domains 如果是数组则用逗号拼接
const domains = Array.isArray(params.domains)
? params.domains.join(',')
: params.domains
return request('/api/BillProgressPrediction/simiBills', {
method: 'GET',
params: {
billIds: params.billIds,
domains: domains,
domains: params.domains,
patternType: params.patternType,
proposalType: params.proposalType
proposalType: params.proposalType,
timeFrame: params.timeFrame,
maxNum: params.maxNum || 50
}
})
}
/**
* 格式化日期 YYYY-MM-DD -> YYYY年M月D日
* @param {string} dateStr - 日期字符串
......
......@@ -201,3 +201,16 @@ export async function getSubjectList(params) {
params
})
}
/**
* 获取调研项目列表
* @param {string} personId
* @param {Object} params
*/
export function getInvestigationProject(personId, params) {
return request({
method: 'GET',
url: `/api/personHomepage/investigationProject/${personId}`,
params,
})
}
\ No newline at end of file
......@@ -393,13 +393,13 @@ export function getDomainDistribution(sanctionDate = "2025-11-11") {
* startTime: string
* }[]>}
*/
export function getEntitiesList(sanTypeIds = ["1"], pageNum = 1, pageSize = 10, sanctionDate = "", isCn = false) {
export function getEntitiesList(sanTypeId="1", pageNum = 1, pageSize = 10, sanctionDate = "", isCn = false) {
return request200(
request({
method: "POST",
url: "/api/sanctionList/pageQuery",
data: {
sanTypeIds,
sanTypeId,
pageNum,
pageSize,
sanctionDate,
......
import { createRouter, createWebHistory } from "vue-router";
import { getIsLoggedIn } from "@/utils/auth";
const Home = () => import('@/views/home/index.vue')
const DataLibrary = () => import('@/views/dataLibrary/index.vue')
......@@ -21,6 +22,14 @@ const dataRoutes = Object.keys(datas).reduce((acc, path) => {
}, [])
const routes = [
{
path: "/login",
name: "Login",
component: () => import("@/views/login/index.vue"),
meta: {
title: "登录"
}
},
{
path: "/",
name: "Home",
......@@ -66,6 +75,19 @@ const router = createRouter({
// 路由守卫 - 设置页面标题
router.beforeEach((to, from, next) => {
// 登录态:同一次前端服务 BOOT_ID 内跨刷新/跨新标签有效;服务重启后自动失效
const isAuthed = getIsLoggedIn();
const isLoginRoute = to.name === "Login" || /^\/login\/?$/.test(String(to.path || ""));
if (!isLoginRoute && !isAuthed) {
next({
path: "/login",
query: { redirect: to.fullPath }
});
return;
}
if (to.meta.title) {
if (to.meta.dynamicTitle) {
console.log('to', to);
......
......@@ -4,7 +4,7 @@ const ZMOverview = () => import('@/views/ZMOverView/index.vue')
const ZMOverviewRoutes = [
{
path: "/",
redirect: "/ZMOverView"
redirect: "/login"
},
{
path: "/ZMOverView",
......
......@@ -123,19 +123,9 @@ const billRoutes = [
component: BillInfluenceScientificResearch,
// meta: { title: "对华科研影响" }
},
// {
// path: "ProgressForecast",
// name: "BillProgressForecast",
// component: BillProgressForecast,
// // meta: { title: "对华科研影响" }
// }
]
}, {
path: "ProgressForecast/:id",
name: "BillProgressForecast",
component: BillProgressForecast,
// meta: { title: "对华科研影响" }
},
},
{
path: "relevantCircumstance",
......@@ -155,6 +145,14 @@ const billRoutes = [
}
]
},
{
path: "ProgressForecast/:id",
name: "BillProgressForecast",
component: BillProgressForecast,
meta: {
title: "法案预测"
}
},
]
export default billRoutes
\ No newline at end of file
......@@ -16,7 +16,7 @@ const financeRoutes = [
{
path: "/finance/sdnlistoverview",
name: "sdnlistOverview",
component: () => import("@/views/finance/v2.0EntityList/index.vue"),
component: () => import("@/views/finance/entityList/index.vue"),
meta: {
title: "SDN制裁清单概览"
}
......@@ -24,8 +24,8 @@ const financeRoutes = [
// V2.0单条制裁详情
{
path: "/finance/singleSanction",
name: "singleSanction",
component: () => import("@/views/finance/v2.0SingleSanction/index.vue"),
name: "sdnSingleSanction",
component: () => import("@/views/finance/singleSanction/index.vue"),
meta: {
title: "单条制裁详情",
dynamicTitle: true
......@@ -34,8 +34,8 @@ const financeRoutes = [
// V2.0单条制裁详情-实体清单原文
{
path: "/exportControl/origin",
name: "entityListOrigin",
component: () => import("@/views/exportControl/v2.0SingleSanction/originPage/index.vue")
name: "financeEntityListOrigin",
component: () => import("@/views/finance/singleSanction/originPage/index.vue")
// meta: {
// title: "实体清单原文"
// }
......@@ -44,7 +44,7 @@ const financeRoutes = [
{
path: "/finance/cmccontrolList",
name: "cmccontrolList",
component: () => import("@/views/finance/v2.0CommercialControlList/index.vue"),
component: () => import("@/views/finance/commercialControlList/index.vue"),
meta: {
title: "涉军企业清单概览"
}
......
// 规则限制
const RuleRestriction = () => import('@/views/ruleRestriction/index.vue')
const RuleRestrictionDetail = () => import('@/views/ruleRestriction/detail/index.vue')
const RuleRestrictionsAlliance = () => import('@/views/ruleRestriction/alliance/index.vue')
const ruleRestrictionsRoutes = [
// 规则限制
{
......@@ -22,6 +23,14 @@ const ruleRestrictionsRoutes = [
title: "规则限制详情",
dynamicTitle: true
}
}, {
path: "/ruleRestrictions/alliance",
name: "RuleRestrictionsAlliance",
component: RuleRestrictionsAlliance,
meta: {
title: "规则限制联盟详情",
dynamicTitle: true
}
},
]
......
const STORAGE_LOGIN_FLAG = "isLoggedIn";
const STORAGE_LOGIN_BOOT_ID = "loginBootId";
const getBootId = () => {
// 由 vite.config.js 的 define 注入:重启 dev server 会变化
try {
// eslint-disable-next-line no-undef
return String(__APP_BOOT_ID__ || "");
} catch (_) {
return "";
}
};
export const getIsLoggedIn = () => {
try {
const flag = window.localStorage.getItem(STORAGE_LOGIN_FLAG) === "1";
const savedBootId = window.localStorage.getItem(STORAGE_LOGIN_BOOT_ID) || "";
return flag && savedBootId && savedBootId === getBootId();
} catch (_) {
return false;
}
};
export const setIsLoggedIn = (val) => {
try {
if (val) {
window.localStorage.setItem(STORAGE_LOGIN_FLAG, "1");
window.localStorage.setItem(STORAGE_LOGIN_BOOT_ID, getBootId());
} else {
window.localStorage.removeItem(STORAGE_LOGIN_FLAG);
window.localStorage.removeItem(STORAGE_LOGIN_BOOT_ID);
}
} catch (_) {
// ignore
}
};
......@@ -125,7 +125,7 @@
<DivideHeader id="position2" class="divide2" :titleText="'资讯要闻'"></DivideHeader>
<div class="center-center">
<NewsList :newsList="newsList" img="newsImage" title="newsTitle" from="from" content="newsContent"
@item-click="handleClickNewsDetail" />
@item-click="handleClickNewsDetail" @more-click="handleToMoreNews" />
<MessageBubble :messageList="messageList" imageUrl="personImage" @more-click="handleToSocialDetail"
@person-click="handleClickToCharacter" name="personName" content="remarks" source="orgName" />
</div>
......@@ -297,22 +297,26 @@ import { ElMessage } from "element-plus";
import { Calendar } from "@element-plus/icons-vue";
import { useGotoNewsDetail } from "@/router/modules/news";
// 跳转人物主页
const handleClickToCharacter = async (id, name) => {
// 跳转人物主页(MessageBubble 的 person-click 传入整条列表项,需取 personId)
const handleClickToCharacter = async item => {
if (!item?.personId) {
ElMessage.warning("缺少人员信息,无法跳转");
return;
}
const personTypeList = JSON.parse(window.sessionStorage.getItem("personTypeList"));
let type = 0;
let personTypeName = "";
const params = {
personId: id
personId: item.personId
};
try {
const res = await getPersonSummaryInfo(params);
console.log("人物全局信息2", res);
if (res.code === 200 && res.data) {
const arr = personTypeList.filter(item => {
return item.typeId === res.data.personType;
const arr = personTypeList.filter(pt => {
return pt.typeId === res.data.personType;
});
console.log("arr", arr);
......@@ -331,12 +335,12 @@ const handleClickToCharacter = async (id, name) => {
ElMessage.warning("找不到当前人员的类型值!");
return;
}
window.sessionStorage.setItem("curTabName", name);
window.sessionStorage.setItem("curTabName", item.personName);
const route = router.resolve({
path: "/characterPage",
query: {
type: type, // type=1为科技企业领袖,2为国会议员,3为智库研究人员
personId: id
personId: item.personId
}
});
window.open(route.href, "_blank");
......@@ -458,6 +462,12 @@ const handleToMoreRiskSignal = () => {
window.open(route.href, "_blank");
// router.push("/viewRiskSignal")
};
// 查看更多新闻资讯(新闻主页)
const handleToMoreNews = () => {
const route = router.resolve("/newsBrief");
window.open(route.href, "_blank");
};
// 风险信号
const warningList = ref([]);
......
......@@ -36,7 +36,7 @@ export default {
</script>
<style scoped lang="scss">
@import '@/styles/bill-tokens.scss';
@use '@/styles/bill-tokens.scss' as *;
.bill-page-shell {
width: 100%;
......
......@@ -87,7 +87,7 @@ export default {
</script>
<style scoped lang="scss">
@import '@/styles/bill-tokens.scss';
@use '@/styles/bill-tokens.scss' as *;
.bill-panel {
width: 100%;
......
......@@ -93,7 +93,7 @@ export default {
</script>
<style scoped lang="scss">
@import '@/styles/bill-tokens.scss';
@use '@/styles/bill-tokens.scss' as *;
.bill-two-column {
width: 100%;
......
<template>
<div class="action-buttons">
<button class="btn-secondary" @click="emit('reset')">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
<path d="M3 3v5h5" />
</svg>
<span>重置筛选条件</span>
</button>
<div class="action-left">
<button class="btn-secondary" @click="emit('reset')">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
<path d="M3 3v5h5" />
</svg>
<span>重置筛选条件</span>
</button>
<button v-if="showSetAsCurrent" class="btn-secondary" @click="emit('setAsCurrent')">
<span>设置为当前提案</span>
</button>
</div>
<div class="action-right">
<button
v-if="showPrevious"
......@@ -36,12 +41,14 @@ defineProps<{
previousText?: string
nextText?: string
nextDisabled?: boolean
showSetAsCurrent?: boolean
}>()
const emit = defineEmits<{
reset: []
previous: []
next: []
setAsCurrent: []
}>()
</script>
......@@ -53,6 +60,12 @@ const emit = defineEmits<{
padding-top: 24px;
}
.action-left {
display: flex;
align-items: center;
gap: 12px;
}
.action-right {
display: flex;
align-items: center;
......
......@@ -5,7 +5,7 @@
<img src="../assets/fitller.svg" />
<h2 class="section-title text-title-3-bold">核心相似度维度筛选</h2>
</div>
<button class="btn-outline" @click="setAsCurrent">设置为当前提案</button>
</div>
<div class="divider" />
<div class="fields-grid">
......@@ -62,6 +62,7 @@
<div class="field-content">
<el-select
v-model="localValues.oppositionProposer"
multiple
placeholder="请选择"
style="width: 420px"
@change="handleChange"
......@@ -109,21 +110,21 @@ const props = defineProps<{
defaultFilters?: Record<string, string[]>
}>()
// 本地 v-model 值
// 本地 v-model 值(proposalTime 为单选,用 string)
const localValues = ref({
policyArea: [] as string[],
governmentType: [] as string[],
oppositionProposer: '' as string,
oppositionProposer: [] as string[],
proposalTime: '' as string,
})
// 根据 proposalInfo 计算初始筛选值(即"设置为当前提案"的目标状态)
function buildInitialValues(info?: ProposalInfo | null): Record<string, string[] | string> {
if (!info) return { policyArea: [], governmentType: [], oppositionProposer: '', proposalTime: '' }
function buildInitialValues(info?: ProposalInfo | null) {
if (!info) return { policyArea: [], governmentType: [], oppositionProposer: [], proposalTime: '' }
return {
policyArea: info.defaultDomains?.length ? [...info.defaultDomains] : [...(info.areas || [])],
governmentType: info.patternType ? [info.patternType] : [],
oppositionProposer: '',
oppositionProposer: [],
proposalTime: '',
}
}
......@@ -142,7 +143,7 @@ function reset() {
localValues.value = {
policyArea: [],
governmentType: [],
oppositionProposer: '',
oppositionProposer: [],
proposalTime: '',
}
}
......@@ -152,14 +153,39 @@ function setAsCurrent() {
Object.assign(localValues.value, buildInitialValues(props.proposalInfo))
}
defineExpose({ reset, setAsCurrent, getValues: () => localValues.value })
defineExpose({
reset,
setAsCurrent,
getValues: () => ({
...localValues.value,
// proposalTime 单选,包装为数组保持传值一致
proposalTime: localValues.value.proposalTime ? [localValues.value.proposalTime] : []
})
})
// 固定的政策领域选项(15个)
const policyAreaOptions = [
{ value: '人工智能', label: '人工智能' },
{ value: '生物科技', label: '生物科技' },
{ value: '新一代通信网络', label: '新一代通信网络' },
{ value: '量子科技', label: '量子科技' },
{ value: '新能源', label: '新能源' },
{ value: '集成电路', label: '集成电路' },
{ value: '海洋', label: '海洋' },
{ value: '先进制造', label: '先进制造' },
{ value: '新材料', label: '新材料' },
{ value: '航空航天', label: '航空航天' },
{ value: '太空', label: '太空' },
{ value: '深海', label: '深海' },
{ value: '极地', label: '极地' },
{ value: '核', label: '核' },
{ value: '其他', label: '其他' }
]
// 固定的字段配置,options 由 proposalInfo 动态注入
// 固定的字段配置
const fields = computed(() => {
const billDomain = props.proposalInfo?.billDomain
const domainOptions = Array.isArray(billDomain) ? billDomain.map(d => ({ value: d, label: d })) : []
return {
policyArea: { options: domainOptions },
policyArea: { options: policyAreaOptions },
governmentType: {
options: [
{ value: '统一政府', label: '统一政府' },
......@@ -177,9 +203,9 @@ const fields = computed(() => {
},
proposalTime: {
options: [
{ value: '近三年', label: '近三年' },
{ value: '近五年', label: '近五年' },
{ value: '近十年', label: '近十年' },
{ value: '全部', label: '全部' }
{ value: '近十年', label: '近十年' }
]
}
}
......
......@@ -3,7 +3,7 @@
<div class="header-content">
<div class="header-left">
<div class="text-title-2-bold">科技法案立法进展预测</div>
<div class="text-tip-2 text-primary-65-clor">H.R.1-大而美法案(2025年5月20日)</div>
<div class="text-tip-2 text-primary-65-clor" v-if="props.proposalInfo">{{ props.proposalInfo.title+"("+ props.proposalInfo.date+")" }}</div>
</div>
<div class="header-actions">
<button class="action-btn">
......@@ -30,6 +30,11 @@
</template>
<script setup lang="ts">
import type { ProposalInfo } from '../api'
const props = defineProps<{
proposalInfo?: ProposalInfo | null
defaultFilters?: Record<string, string[]>
}>()
</script>
<style scoped>
......@@ -45,6 +50,7 @@
background: #ffffff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
margin-bottom: 16px;
margin-left: 0px;
}
.header-content {
......
......@@ -13,7 +13,9 @@
/>
</div>
<ActionButtons
show-set-as-current
@reset="handleReset"
@set-as-current="handleSetAsCurrent"
@next="handleNext"
/>
</template>
......@@ -65,6 +67,11 @@ function handleReset() {
filterSectionRef.value?.reset()
}
// 设置为当前提案:恢复初始状态
function handleSetAsCurrent() {
filterSectionRef.value?.setAsCurrent()
}
// 下一步:获取已选的筛选条件并传给父组件
function handleNext() {
const selectedFilters = filterSectionRef.value?.getValues() || {}
......
......@@ -122,26 +122,27 @@ const selectedCount = computed(() => {
async function loadData() {
loading.value = true
try {
// 使用真实 API,传入 billIds、domains、patternType、proposalType
// 标准 API 参数格式
const params = {
billIds: filterParams?.value.billIds,
domains: filterParams?.value.domains || [],
domains: (filterParams?.value.domains || []).join(','),
patternType: filterParams?.value.patternType || '统一政府',
proposalType: filterParams?.value.proposalType || '两党共同提案'
proposalType: filterParams?.value.proposalType || '两党共同提案',
timeFrame: filterParams?.value.timeFrame?.[0] || '',
maxNum: 50
}
const response = await getSimiBills(params)
if (response && response.data) {
// 保存原始数据(新 API 返回结构中 simi_bills 是法案数组)
rawBillsData.value = response.data.simi_bills || []
// 保存原始数据
rawBillsData.value = response.data
const { stats: apiStats, bills: apiBills } = transformSimiBillsData(response)
stats.value = apiStats
bills.value = apiBills
} else {
stats.value = null
bills.value = []
rawBillsData.value = []
}
} catch (error) {
console.error('获取相似法案失败:', error)
......@@ -187,8 +188,9 @@ function handleBack() {
function handleStartAnalysis() {
// 获取用户勾选的法案ID列表
const selectedIds = bills.value.filter(b => b.selected).map(b => b.id)
// 从原始数据中筛选出用户勾选的法案
const selectedBills = rawBillsData.value.filter(b => selectedIds.includes(b.bill_id))
// 从原始数据中筛选出用户勾选的法案(确保 rawBillsData 是数组)
const rawData = Array.isArray(rawBillsData.value) ? rawBillsData.value : []
const selectedBills = rawData.filter(b => selectedIds.includes(b.bill_id))
emit('next', selectedBills)
}
</script>
......@@ -210,12 +212,16 @@ function handleStartAnalysis() {
.content-wrapper {
flex: 1;
overflow: auto;
overflow-y: auto;
display: flex;
flex-direction: column;
min-height: 0;
}
.stats-section {
display: flex;
flex-direction: column;
height: 100%;
}
.stats-header {
......@@ -333,6 +339,10 @@ function handleStartAnalysis() {
display: flex;
flex-direction: column;
gap: 16px;
flex: 1;
overflow-y: auto;
min-height: 0;
padding-bottom: 16px;
}
.action-footer {
......
......@@ -31,6 +31,8 @@
<div class="highlight-wave text-regular"></div>
<div class="highlight-content text-regular">
<p class="highlight-title text-regular">该法案的通过概率为 {{ predictionData?.overallProbability || '—' }}</p>
<!-- {{ predictionData.factorAnalysis }} -->
<p class="highlight-text text-regular" v-for="value in predictionData.factorAnalysis" :key="value">{{ '*'+value }}</p>
<p class="highlight-text text-regular">{{ predictionData?.highlightText }}</p>
</div>
</div>
......
......@@ -78,12 +78,19 @@
<div class="introduction-wrap-right-main">
<div class="right-main-box1">
<div class="name-box">
<div class="person-box">
<div class="person-item" :class="{ nameItemActive: box3BtnActive === item.name }"
@click="handleClcikBox3Btn(item.name, index)" v-for="(item, index) in personList" :key="index">
{{ item.name }}
</div>
</div>
<el-select
v-model="box3BtnActive"
class="person-select"
placeholder="请选择提出人"
@change="handleClcikBox3Btn"
>
<el-option
v-for="item in personList"
:key="item.id || item.name"
:label="item.name"
:value="item.name"
/>
</el-select>
</div>
<div class="info-box">
<div class="info-left">
......@@ -302,9 +309,10 @@ const personList = ref([]);
const curPerson = ref({});
const box3BtnActive = ref("");
const handleClcikBox3Btn = (name, index) => {
const handleClcikBox3Btn = name => {
box3BtnActive.value = name;
curPerson.value = personList.value[index];
const targetPerson = personList.value.find(item => item.name === name);
curPerson.value = targetPerson || {};
};
// 法案提出人
......@@ -800,59 +808,8 @@ onMounted(() => {
margin-left: 22px;
margin-top: 10px;
.person-box {
width: 500px;
overflow-x: hidden;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
padding-bottom: 5px;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-thumb {
border-radius: 4px;
background: #e1e1e1;
}
&::-webkit-scrollbar-track {
background: transparent;
}
.person-item {
min-height: 28px;
height: auto;
box-sizing: border-box;
border: 1px solid var(--btn-plain-border-color);
border-radius: 4px;
background: var(--btn-plain-bg-color);
color: var(--btn-plain-text-color);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin-right: 8px;
padding: 1px 12px;
cursor: pointer;
white-space: normal;
word-break: break-all;
line-height: 18px;
flex-shrink: 1;
max-width: 170px;
text-align: center;
margin-bottom: 10px;
}
.nameItemActive {
border: 1px solid var(--btn-active-border-color);
background: var(--btn-active-bg-color);
color: var(--btn-active-text-color);
}
.person-select {
width: 100%;
}
}
......
......@@ -22,6 +22,12 @@
<span class="meta-value sponsor-name" :title="allSponsorNames">
{{ firstSponsorName }}
</span>
</div>
<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>
......@@ -114,6 +120,7 @@ const currentStageIndex = computed(() => {
gap: 13px;
align-items: center;
height: 100%;
margin:15px 1px
}
.bill-card-preview {
......
......@@ -23,9 +23,10 @@
<template v-else>
<BillCard
v-for="bill in bills"
:key="bill.id"
:key="bill.billId"
:bill="bill"
:progress-stages="progressStages"
@click="handleBillMoreClick(bill.billId)"
/>
<div v-if="total > 0" class="bill-pagination">
......@@ -46,7 +47,10 @@
<script setup>
import BillCard from './BillCard.vue'
import { useRouter } from "vue-router";
const router = useRouter();
import { useRoute } from "vue-router";
const route = useRoute();
defineProps({
bills: { type: Array, required: true },
loading: { type: Boolean, default: false },
......@@ -56,27 +60,45 @@ defineProps({
progressStages: { type: Array, default: () => ['提出', '众议院通过', '参议院通过', '分歧协调', '提交总统', '法案通过'] },
})
defineEmits(['page-change'])
/** 政策建议关联法案:新标签页打开法案介绍页,billId 随接口 id 变化 */
const handleBillMoreClick = (bill) => {
const billId = bill;
// debugger
if (!billId) {
return;
}
const route = router.resolve({
path: "/billLayout/bill/introduction",
query: { billId: String(billId) }
});
window.open(route.href, "_blank");
};
</script>
<style scoped>
/* Bill List */
.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;
background: var(--bg-white-100);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
padding: 24px;
}
.skeleton-left {
width: 120px;
height: 190px;
......@@ -84,30 +106,35 @@ defineEmits(['page-change'])
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;
background: #eaeced;
}
.bill-list-empty {
background: #fff;
border: 1px solid #ebedf0;
border-radius: 8px;
background: var(--bg-white-100);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
padding: 48px;
text-align: center;
color: #bfbfbf;
......@@ -117,6 +144,7 @@ defineEmits(['page-change'])
align-items: center;
gap: 12px;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
......@@ -129,6 +157,7 @@ defineEmits(['page-change'])
justify-content: space-between;
padding: 16px 0 0;
}
.bill-pagination-total {
font-size: 14px;
font-family: 'Microsoft YaHei', sans-serif;
......@@ -139,10 +168,11 @@ defineEmits(['page-change'])
/* Override el-pagination to match design */
.bill-pagination :deep(.el-pagination) {
--el-pagination-bg-color: #fff;
--el-pagination-bg-color: var(--bg-white-100);
--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) {
......@@ -151,20 +181,23 @@ defineEmits(['page-change'])
line-height: 32px;
border-radius: 4px;
border: 1px solid rgba(234, 236, 238, 1);
background: #fff;
background: var(--bg-white-100);
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;
background: var(--bg-white-100);
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);
......
......@@ -29,7 +29,7 @@
</div>
<div class="doc-info">
<div class="doc-number">{{ billNumber }}</div>
<div class="doc-serial">{{ billSerial }}</div>
<div class="doc-serial" :title="billSerial">{{ billSerial }}</div>
</div>
</div>
</template>
......@@ -147,5 +147,13 @@ defineProps({
color: rgba(95, 101, 108, 1);
line-height: 24px;
text-align: center;
width:15em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 6px 10px;
font-size: 14px;
cursor: pointer;
}
</style>
<template>
<div class="news-pagination-bar">
<div class="news-pagination-bar" v-if="total>1">
<span class="news-pagination-total">{{ total }}篇新闻报告</span>
<div class="news-pagination">
<button class="pg-btn" :disabled="currentPage <= 1" @click="$emit('update:currentPage', currentPage - 1)">
......
......@@ -20,11 +20,11 @@
<button
:class="['news-tab', { active: activeTab === 'local' }]"
@click="$emit('update:activeTab', 'local')"
>智库报告</button>
> 报告</button>
<button
:class="['news-tab', { active: activeTab === 'capital' }]"
@click="$emit('update:activeTab', 'capital')"
>调查项目</button>
> 项目</button>
</div>
<div class="news-sort" ref="sortDropdownRef">
......@@ -172,9 +172,10 @@ onUnmounted(() => document.removeEventListener('click', handleClickOutside))
margin-left: auto;
position: relative;
flex-shrink: 0;
margin-right: 10px;
}
.news-sort-btn {
width: 120px;
width: 150px;
height: 32px;
display: flex;
align-items: center;
......@@ -185,6 +186,7 @@ onUnmounted(() => document.removeEventListener('click', handleClickOutside))
background: rgba(255, 255, 255, 1);
cursor: pointer;
box-sizing: border-box;
margin-top: 15px;
}
.news-sort-btn:hover {
border-color: rgba(5, 95, 194, 0.4);
......
......@@ -18,13 +18,13 @@
<div class="news-main">
<div class="news-grid">
<NewsCard
v-for="item in newsList"
v-for="item in (activeTab === 'local' ? newsList : projectList)"
:key="item.reportId"
:item="item"
/>
</div>
<NewsPagination
:total="totalNews"
:total="activeTab === 'local' ? totalNews : totalProjects"
v-model:current-page="currentPage"
:page-size="pageSize"
/>
......@@ -41,7 +41,7 @@ import NewsSidebar from './NewsSidebar.vue'
import NewsCard from './NewsCard.vue'
import NewsPagination from './NewsPagination.vue'
import { getIndustryKeyList } from '@/api/bill/billHome.js'
import { getFindingsReport } from '@/api/characterPage/characterPage.js'
import { getFindingsReport, getInvestigationProject } from '@/api/characterPage/characterPage.js'
const route = useRoute()
const personId = computed(() => route.params.personId || route.query.personId || '')
......@@ -51,7 +51,9 @@ const activeTab = ref('local')
const currentPage = ref(1)
const pageSize = 12
const totalNews = ref(0)
const totalProjects = ref(0)
const newsList = ref([])
const projectList = ref([])
const loading = ref(false)
const sortBy = ref('publishTimeDesc')
......@@ -79,17 +81,16 @@ async function loadFilterOptions() {
]
}
function buildParams() {
function buildParams({ includeSearchText = false } = {}) {
const params = {
currentPage: currentPage.value - 1,
pageSize,
sortFun: sortBy.value === 'publishTimeAsc',
industryIds: selectedDomains.value.includes('all') ? '' : JSON.stringify(selectedDomains.value),
years: selectedTimes.value.includes('all') ? '' : JSON.stringify(selectedTimes.value),
}
if (!selectedDomains.value.includes('all')) {
params.industryIds = selectedDomains.value
}
if (!selectedTimes.value.includes('all')) {
params.years = selectedTimes.value
if (includeSearchText) {
params.searchText = searchText.value
}
return params
}
......@@ -97,14 +98,18 @@ function buildParams() {
function updateSort(value) {
sortBy.value = value
currentPage.value = 1
loadNews()
if (activeTab.value === 'local') {
loadNews()
} else {
loadProjects()
}
}
async function loadNews() {
if (!personId.value) return
loading.value = true
try {
const params = buildParams()
const params = buildParams({ includeSearchText: true })
const res = await getFindingsReport(personId.value, params)
if (res.code === 200 && res.data) {
newsList.value = res.data.content || []
......@@ -118,6 +123,32 @@ async function loadNews() {
}
}
async function loadProjects() {
if (!personId.value) return
loading.value = true
try {
const params = buildParams()
const res = await getInvestigationProject(personId.value, params)
if (res.code === 200 && res.data) {
projectList.value = (res.data.content || []).map(item => ({
reportId: item.projectId,
title: item.name,
content: item.description,
imageUrl: item.imageUrl,
time: item.time,
sourceName: item.sourceName,
industryList: item.industryList,
}))
totalProjects.value = res.data.totalElements || 0
} else {
projectList.value = []
totalProjects.value = 0
}
} finally {
loading.value = false
}
}
function toggleDomain(id) {
if (id === 'all') {
selectedDomains.value = ['all']
......@@ -142,17 +173,43 @@ function toggleTime(id) {
}
}
watch(searchText, () => {
currentPage.value = 1
if (activeTab.value === 'local') {
loadNews()
} else {
loadProjects()
}
})
watch(activeTab, (val) => {
currentPage.value = 1
if (val === 'local') {
loadNews()
} else {
loadProjects()
}
})
watch(
() => [selectedDomains.value, selectedTimes.value],
() => {
currentPage.value = 1
loadNews()
if (activeTab.value === 'local') {
loadNews()
} else {
loadProjects()
}
},
{ deep: true }
)
watch(currentPage, () => {
loadNews()
if (activeTab.value === 'local') {
loadNews()
} else {
loadProjects()
}
})
onMounted(async () => {
......
......@@ -104,10 +104,17 @@
<div class="baseInfo-item-content">{{ characterBasicInfo.country }}</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item">
<div class="baseInfo-item-title">教育背景:</div>
<div class="baseInfo-item-content long"
v-for="item in characterBasicInfo.educationList">
{{ item.school + '--' + item.major }}<br>
<div class="baseInfo-item-content">
<span
class="education-item"
v-for="value in characterBasicInfo.educationList"
:key="value.school"
>
{{ value.school + "(" + value.major + ")" }}
</span>
</div>
</div>
</div>
<div class="baseInfo-item">
......@@ -1220,4 +1227,32 @@ const companyList = ref([
color: rgba(170, 173, 177, 1);
font-size: 14px;
}
.baseInfo-item {
display: flex;
align-items: flex-start;
}
.baseInfo-item-title {
flex: 0 0 88px; /* 固定左侧宽度 */
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
white-space: nowrap; /* 左侧不换行 */
}
.baseInfo-item-content {
flex: 1;
min-width: 0;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
display: flex;
flex-wrap: wrap; /* 右侧满了再换行 */
gap: 8px 12px; /* 行列间距可调 */
}
.education-item {
white-space: nowrap; /* 每个学校+专业作为一个整体不拆行 */
}
</style>
\ No newline at end of file
......@@ -39,6 +39,7 @@ const getCharacterGlobalInfoFn = async () => {
const res = await getCharacterGlobalInfo(params);
if (res.code === 200) {
console.log("人物全局信息111", res);
document.title=res.data.name
if (res.data) {
characterInfo.value = res.data;
const personType = characterInfo.value.personType;
......
......@@ -15,8 +15,8 @@
<div class="main-content" ref="homeMainRef" :class="{ 'scroll-main': isShow }">
<div class="home-top-bg"></div>
<!-- 搜索栏部分 -->
<SearchContainer v-if="homeMainRef" :countInfo="cooperationCountInfo" placeholder="搜索合作限制"
:containerRef="homeMainRef" areaName="" />
<SearchContainer v-if="homeMainRef" placeholder="搜索合作限制" :containerRef="homeMainRef" areaName=""
style="height: fit-content" />
<!-- 最新动态 -->
<div class="newdata" id="position1">
......@@ -164,7 +164,7 @@ const handleToPosi = id => {
background-size: 100% 100%;
position: absolute;
width: 100%;
height: 100%;
z-index: -100;
top: -64px;
}
......@@ -177,7 +177,7 @@ const handleToPosi = id => {
.search {
width: 960px;
height: 168px;
margin: 0 auto 68px auto;
.search-main {
......@@ -368,12 +368,12 @@ const handleToPosi = id => {
.reslib {
width: 1600px;
height: 1633px;
margin: 0 auto 0px auto;
.reslib-main {
width: 1600px;
height: 1565px;
margin-top: 26px;
}
}
......
......@@ -105,13 +105,13 @@
></el-image>
<div v-else class="box1-bottom-content-item-imgUndefined">
{{
(ett.entityNameZh || ett.enName)?.match(
(ett.orgName || ett.orgNameZh)?.match(
/[\u4e00-\u9fa5a-zA-Z0-9]/
)?.[0]
}}
</div>
<div class="box1-bottom-content-item-txt">
{{ ett.name || ett.entityNameZh }}
{{ ett.orgName || ett.orgNameZh }}
</div>
</div>
</div>
......@@ -1494,7 +1494,7 @@ watch(
const fetchEntitiesList = async (page = 1, size = 10) => {
try {
console.log("activeResourceTabItem.value.id", activeResourceTabItem.value.id);
const res = await getEntitiesList(activeResourceTabItem.value.id.join(","), page, size);
const res = await getEntitiesList(activeResourceTabItem.value.id, page, size);
if (res) {
entitiesList.value = res.content.map(item => ({
...item,
......
......@@ -25,8 +25,12 @@
<div class="left-center">
<AnalysisBox title="出口管制分类编码(ECCN)" :showAllBtn="false">
<div class="button-list">
<div :class="['button', { click: item.isClick }]" @click="changeECCN(item)" v-for="(item, i) in ECCNList"
:key="i">
<div
:class="['button', { click: item.isClick }]"
@click="changeECCN(item)"
v-for="(item, i) in ECCNList"
:key="i"
>
<span>{{ item.ranking }}{{ item.name }}</span>
</div>
</div>
......@@ -44,9 +48,17 @@
<AnalysisBox title="商业管制清单更新历史" :showAllBtn="false">
<template #header-btn>
<div class="filters">
<el-select v-model="selectedDomain" placeholder="Select"
style="width: 150px; height: 32px; margin-right: 16px">
<el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value" />
<el-select
v-model="selectedDomain"
placeholder="Select"
style="width: 150px; height: 32px; margin-right: 16px"
>
<el-option
v-for="item in domainOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<!-- <el-checkbox v-model="onlyChina">只看涉华动态</el-checkbox> -->
</div>
......@@ -59,15 +71,21 @@
</div>
<div class="img-zone">
<img :src="item.icon || title" alt />
<div v-if="i < sanctionList.length - 1"
:class="['img-line', { 'img-line-last': i === sanctionList.length - 1 }]">
</div>
<div
v-if="i < sanctionList.length - 1"
:class="['img-line', { 'img-line-last': i === sanctionList.length - 1 }]"
></div>
</div>
<div class="main">
<div class="main-title">{{ item.name }}</div>
<!-- <div class="main-title" @click="handleClick(item)">{{ item.name }}</div> -->
<el-tooltip effect="dark" :content="item.summary" popper-class="common-prompt-popper" placement="top"
:show-after="500">
<el-tooltip
effect="dark"
:content="item.summary"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="main-desc">{{ item.summary }}</div>
</el-tooltip>
<div class="tag-box">
......@@ -82,8 +100,14 @@
</div>
<div class="left-footer">
<div class="total-count">共 {{ totalAll }} 项</div>
<el-pagination v-model:current-page="currentPageAll" :page-size="pageSizeAll" :total="totalAll"
layout="prev, pager, next" background @current-change="handlePageChangeAll" />
<el-pagination
v-model:current-page="currentPageAll"
:page-size="pageSizeAll"
:total="totalAll"
layout="prev, pager, next"
background
@current-change="handlePageChangeAll"
/>
</div>
</AnalysisBox>
</div>
......@@ -111,17 +135,31 @@
<span>关键人物</span>
</div>
<div class="key-person-list">
<div class="person-item" v-for="(item, index) in publishInfo.personList" :key="index"
@click="handlePerClick(item)">
<div
class="person-item"
v-for="(item, index) in publishInfo.personList"
:key="index"
@click="handlePerClick(item)"
>
<img :src="item.imageUrl" alt />
<div class="person-info">
<el-tooltip effect="dark" :content="item.name" popper-class="common-prompt-popper" placement="top"
:show-after="500">
<el-tooltip
effect="dark"
:content="item.name"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="name">{{ item.name }}</div>
</el-tooltip>
<el-tooltip effect="dark" :content="item.position" popper-class="common-prompt-popper" placement="top"
:show-after="500">
<el-tooltip
effect="dark"
:content="item.position"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="title1">{{ item.position }}</div>
</el-tooltip>
</div>
......@@ -189,7 +227,6 @@ const handleClickOrg = item => {
}
});
window.open(route.href, "_blank");
};
// 处理点击关键人物的方法
......@@ -209,13 +246,12 @@ const handlePerClick = item => {
const handleClick = item => {
console.log("点击了实体名称:", item);
const route = router.resolve({
path: "/exportControl/singleSanction",
path: "/finance/singleSanction",
query: {
id: item.id
}
});
window.open(route.href, "_blank");
};
const selectedDomain = ref(0);
......
......@@ -376,29 +376,156 @@ const getRegionCountData = async () => {
};
// 实体清单-数据统计- 制裁实体领域数量变化趋势
const domainNumChartOption = ref({});
const domainNumChartOption = ref({
color: [],
tooltip: {
trigger: "axis",
backgroundColor: "rgba(255, 255, 255, 0.9)",
textStyle: {
color: "#666"
},
extraCssText: "box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); border-radius: 4px;"
},
grid: {
top: "15%",
right: "2%",
bottom: "5%",
left: "2%",
containLabel: true
},
legend: {
type: "scroll",
show: true,
top: 0,
left: "center",
icon: "circle",
itemWidth: 12,
itemHeight: 12,
data: [],
textStyle: {
fontFamily: "Microsoft YaHei",
fontSize: 16,
fontWeight: 400,
lineHeight: 24,
color: "rgb(95, 101, 108)"
}
},
xAxis: [
{
type: "category",
boundaryGap: false,
data: [],
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: "#999",
fontSize: 12,
margin: 15
}
}
],
yAxis: [
{
type: "value",
min: 0,
// max: 100,
// interval: 20,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: "#999",
fontSize: 12
},
splitLine: {
lineStyle: {
type: "dashed",
color: "#E0E6F1"
}
}
}
],
series: []
});
const activeDomainTab = ref("year");
const handleDomainTabChange = tab => {
activeDomainTab.value = tab;
getDomainNumData();
};
// 处理领域趋势数据的方法
const processDomainTrendData = rawData => {
// 提取所有的月份作为标题
const titles = rawData.map(item => item.year).reverse();
// 收集所有不重复的领域名称
const domainNamesSet = new Set();
rawData.forEach(item => {
item.domainCountInfo.forEach(domain => {
domainNamesSet.add(domain.name);
});
});
const domainNames = Array.from(domainNamesSet);
// 定义颜色映射
const colorMap = {
人工智能: "#E34D59",
新一代通信网络: "#FF9F1C",
: "#FFB3B3",
生物科技: "#00A79D",
量子科技: "#7B61FF",
先进制造: "#363B42",
新能源: "#2BA471",
太空: "#3762F0",
集成电路: "#0052D9",
新材料: "#FFD900",
航空航天: "#3762F0",
海洋: "#76D1FF",
深海: "#002060",
其他: "#A6A6A6"
};
// 生成数据系列
const dataSeries = domainNames.map(domainName => {
const values = rawData
.map(yearData => {
const domainItem = yearData.domainCountInfo.find(d => d.name === domainName);
return domainItem ? domainItem.count : 0;
})
.reverse(); // 数据值也需要跟随标题反转顺序
return {
name: domainName,
color: colorMap[domainName] || `#${Math.floor(Math.random() * 16777215).toString(16)}`, // 如果没有预定义颜色,则随机生成
value: values
};
});
return {
title: titles,
data: dataSeries
};
};
const getDomainNumData = async () => {
// 参数
const param = {
IDsanTypeId: activeDomainTab.value === "year" ? "year" : "record",
type: sanTypeId.value
countType: activeDomainTab.value === "year" ? "year" : "record",
sanTypeId: sanTypeId.value
};
try {
const res = await getDomainNum(param);
if (res && res.code === 200) {
domainNumChartOption.value = getMultiLineChart({
data: res.data || [],
xAxis: res.xAxis || [],
yAxis: res.yAxis || [],
title: "制裁实体领域数量变化趋势",
xAxisName: "时间",
yAxisName: "数量"
});
if (res && res.length > 0) {
const processedData = processDomainTrendData(res);
domainNumChartOption.value = getMultiLineChart(processedData);
console.log("获取实体清单-数据统计-processedData:", processedData);
console.log("获取实体清单-数据统计-domainNumChartOption:", domainNumChartOption.value);
}
} catch (error) {
console.error("获取实体清单-数据统计-制裁实体领域数量变化趋势失败:", error);
......
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论