提交 5df76eea authored 作者: 张烨's avatar 张烨

Merge branch 'pre' into zy-dev

流水线 #107 已取消 于阶段
in 8 分 25 秒
--- ---
description:
alwaysApply: true alwaysApply: true
--- ---
# Overview # Overview
Insert overview text here. The agent will only see this should they choose to apply the rule. Insert overview text here. The agent will only see this should they choose to apply the rule.
......
...@@ -2,10 +2,13 @@ stages: ...@@ -2,10 +2,13 @@ stages:
- build - build
- deploy - deploy
# cache: cache:
# key: "$CI_COMMIT_REF_SLUG" # cache:key 这里使用字符串,兼容性更好(部分 linter 不支持 key: { files: [...] })
# paths: # 预分支 pre 需要快速构建并实时同步,因此让 .npm 下载缓存跨分支复用
# - .npm/ key: "npm-cache-global"
paths:
- .npm/
policy: pull-push
build_pre: build_pre:
stage: build stage: build
...@@ -17,8 +20,13 @@ build_pre: ...@@ -17,8 +20,13 @@ build_pre:
script: script:
- node -v - node -v
- npm -v - npm -v
- npm config set cache .npm --global - echo "cache dir sizes:"
- npm ci --prefer-offline --no-audit --no-fund - du -sh "$CI_PROJECT_DIR/.npm" "$CI_PROJECT_DIR/.npm/_cacache" 2>/dev/null || true
- echo "=== npm ci start ==="
- date -Iseconds
- npm ci --cache "$CI_PROJECT_DIR/.npm" --no-audit --no-fund --loglevel=verbose --timing --foreground-scripts
- echo "=== npm ci end ==="
- date -Iseconds
- npm run build - npm run build
artifacts: artifacts:
paths: paths:
...@@ -35,5 +43,55 @@ deploy_pre: ...@@ -35,5 +43,55 @@ deploy_pre:
dependencies: dependencies:
- build_pre - build_pre
script: script:
- apk add --no-cache rsync - apk add --no-cache rsync curl jq
- rsync -av --delete dist/ /nas/kjb_service/zm/pre-project/html/ # 只允许“最新一次 pre pipeline”部署到 nginx(加二次确认,避免短时间多次推送导致重复 rsync)
\ No newline at end of file - >
LATEST_PIPELINE_ID="$(
curl --silent --show-error --fail
--header "JOB-TOKEN: $CI_JOB_TOKEN"
"$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/pipelines?ref=pre&order_by=id&sort=desc&per_page=1"
| jq -r '.[0].id'
)"
- >
if [ -z "$LATEST_PIPELINE_ID" ] || [ "$LATEST_PIPELINE_ID" != "$CI_PIPELINE_ID" ]; then
echo "skip deploy: not latest pipeline (latest=$LATEST_PIPELINE_ID current=$CI_PIPELINE_ID)";
exit 0;
fi
- sleep 20
- >
LATEST_PIPELINE_ID="$(
curl --silent --show-error --fail
--header "JOB-TOKEN: $CI_JOB_TOKEN"
"$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/pipelines?ref=pre&order_by=id&sort=desc&per_page=1"
| jq -r '.[0].id'
)"
- >
if [ -z "$LATEST_PIPELINE_ID" ] || [ "$LATEST_PIPELINE_ID" != "$CI_PIPELINE_ID" ]; then
echo "skip deploy: not latest pipeline after debounce (latest=$LATEST_PIPELINE_ID current=$CI_PIPELINE_ID)";
exit 0;
fi
- rsync -avz --delete dist/ /nas/kjb_service/zm/pre-project/html/
# 非 protected 分支:push 时先做 build 校验(避免合并 pre 时出现 build 报错)
build_check:
stage: build
image: node:20-bullseye
tags:
- risk-monitor-frontend
# 只在 push 时做构建校验,且排除 protected 分支与目标分支 pre
only:
- pushes
except:
- pre
- protected_branches
script:
- node -v
- npm -v
- echo "cache dir sizes:"
- du -sh "$CI_PROJECT_DIR/.npm" "$CI_PROJECT_DIR/.npm/_cacache" 2>/dev/null || true
- echo "=== npm ci start ==="
- date -Iseconds
- npm ci --cache "$CI_PROJECT_DIR/.npm" --no-audit --no-fund --loglevel=verbose --timing --foreground-scripts
- echo "=== npm ci end ==="
- date -Iseconds
- npm run build
\ No newline at end of file
差异被折叠。
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
"json5": "^2.2.3", "json5": "^2.2.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"pdfjs-dist": "^5.4.449", "pdfjs-dist": "^3.11.174",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"vue": "^3.4.0", "vue": "^3.4.0",
"vue-router": "^4.2.5" "vue-router": "^4.2.5"
......
...@@ -77,6 +77,58 @@ function extractInterpretationFromLooseText(text) { ...@@ -77,6 +77,58 @@ function extractInterpretationFromLooseText(text) {
return String(m[1]).replace(/\\n/g, "\n").trim(); return String(m[1]).replace(/\\n/g, "\n").trim();
} }
/**
* 从流式累积 buffer 中提取「解读」字符串的已生成部分(滤掉 ```json、[、{ 等外壳,避免界面出现 json\n[\n)
* 支持未闭合的字符串(流式进行中)
* @param {string} buffer
* @returns {string}
*/
function extractStreamingInterpretationFromBuffer(buffer) {
const s = String(buffer || "");
let rest = s.replace(/^\uFEFF/, "");
const fence = rest.match(/^```(?:json)?\s*/i);
if (fence) {
rest = rest.slice(fence[0].length);
}
const keyRe =
/["'](?:解读|interpretation|analysis|content)["']\s*:\s*"/i;
const m = rest.match(keyRe);
if (!m) {
return "";
}
let pos = m.index + m[0].length;
let out = "";
while (pos < rest.length) {
const ch = rest[pos];
if (ch === '"') {
break;
}
if (ch === "\\") {
pos += 1;
if (pos >= rest.length) {
break;
}
const esc = rest[pos];
if (esc === "n") {
out += "\n";
} else if (esc === "r") {
out += "\r";
} else if (esc === "t") {
out += "\t";
} else if (esc === '"' || esc === "\\") {
out += esc;
} else {
out += esc;
}
pos += 1;
continue;
}
out += ch;
pos += 1;
}
return out;
}
/** /**
* 图表解读(SSE 流式) * 图表解读(SSE 流式)
* @param {object} data - 请求体 * @param {object} data - 请求体
...@@ -94,6 +146,8 @@ export function getChartAnalysis(data, options = {}) { ...@@ -94,6 +146,8 @@ export function getChartAnalysis(data, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let buffer = ""; let buffer = "";
let latestInterpretation = ""; let latestInterpretation = "";
/** 已推给前端的「解读」正文长度,用于只增量回调 onChunk */
let lastStreamedInterpretationLen = 0;
let settled = false; let settled = false;
const abortController = new AbortController(); const abortController = new AbortController();
...@@ -175,9 +229,31 @@ export function getChartAnalysis(data, options = {}) { ...@@ -175,9 +229,31 @@ export function getChartAnalysis(data, options = {}) {
return; return;
} }
// 每收到一条消息即回调,用于流式渲染 // 流式渲染:不把 ```json、[、{ 等 markdown/JSON 外壳拼到界面
if (chunk && onDelta) { if (chunk && onDelta) {
onDelta(chunk); let parsedMsg = null;
try {
parsedMsg = JSON.parse(raw);
} catch (_) {
parsedMsg = null;
}
const isReasoningChunk =
parsedMsg &&
typeof parsedMsg === "object" &&
parsedMsg.type === "reasoning" &&
typeof parsedMsg.chunk === "string";
if (isReasoningChunk) {
onDelta(parsedMsg.chunk);
} else {
const visible = extractStreamingInterpretationFromBuffer(buffer);
if (visible.length > lastStreamedInterpretationLen) {
onDelta(
visible.slice(lastStreamedInterpretationLen)
);
lastStreamedInterpretationLen = visible.length;
}
}
} }
// 如果 buffer 已经拼完 markdown code fence,则提前解析并中断连接 // 如果 buffer 已经拼完 markdown code fence,则提前解析并中断连接
......
...@@ -328,16 +328,20 @@ export function getProgressPrediction(billId) { ...@@ -328,16 +328,20 @@ export function getProgressPrediction(billId) {
/** /**
* 获取相似法案列表 * 获取相似法案列表
* @param {Object} params - 查询参数 * @param {Object} params - 查询参数
* @param {string} params.patternType - 政府结构类型,如 "统一政府" * @param {string} params.billIds - 当前法案的ID
* @param {string} params.proposalType - 提案类型,默认 "两党共同提案" * @param {string[]} params.domains - 领域名称列表
* @param {string} params.patternType - 政府结构类型,如 "统一政府"/"分裂政府"/"微弱多数"
* @param {string} params.proposalType - 提案类型,如 "两党共同提案"/"单政党提案(共和党提案)"/"单政党提案(民主党提案)"
* @returns {Promise<Object>} 相似法案列表 * @returns {Promise<Object>} 相似法案列表
*/ */
export function getSimiBills(params = {}) { export function getSimiBills(params = {}) {
return request('/api/BillProgressPrediction/simiBills', { return request('/api/BillProgressPrediction/simiBills', {
method: 'GET', method: 'GET',
params: { params: {
patternType: params.patternType || '统一政府', billIds: params.billIds,
proposalType: params.proposalType || '两党共同提案', domains: params.domains,
patternType: params.patternType ,
proposalType: params.proposalType ,
...params ...params
} }
}) })
...@@ -400,31 +404,42 @@ export function transformSimiBillsData(apiData) { ...@@ -400,31 +404,42 @@ export function transformSimiBillsData(apiData) {
const transformedBills = bills.map(bill => ({ const transformedBills = bills.map(bill => ({
id: bill.bill_id, id: bill.bill_id,
title: bill.bill_name || bill.bill_id, title: bill.bill_name || bill.bill_id,
tags: [bill.poli_pattern_type, bill.bill_proposal_type].filter(Boolean), proposalDate: extractProposalDate(bill.poli_pattern_desc),
passTime: extractPassTime(bill.bill_actions), areas: bill.domain_name ? (Array.isArray(bill.domain_name) ? bill.domain_name : [bill.domain_name]) : [],
totalDays: calculateTotalDays(bill.bill_actions), proposer: extractProposer(bill.bill_sponsors),
selected: true, // 默认全选 coProposers: bill.bill_proposal_desc || '',
_raw: bill governmentType: formatGovernmentType(bill.poli_pattern_type, bill.poli_pattern_desc),
passDays: calculateTotalDays(bill.bill_actions),
selected: true // 默认全选
})) }))
return { stats, bills: transformedBills } return { stats, bills: transformedBills }
} }
/** /**
* 从法案动作中提取通过时间 * 从政治格局描述中提取提案时间
* @param {Array} actions - 法案动作列表 * @param {string} desc - 政治格局描述
* @returns {string} 通过时间 * @returns {string} 提案时间
*/ */
function extractPassTime(actions) { function extractProposalDate(desc) {
if (!actions || actions.length === 0) return '' if (!desc) return ''
// 找到最后一个动作的日期 const match = desc.match(/(\d{4})-(\d{2})-(\d{2})/)
const lastAction = actions[actions.length - 1] if (match) {
if (lastAction && lastAction.action_date) { return `${match[1]}${parseInt(match[2])}${parseInt(match[3])}日`
const date = new Date(lastAction.action_date)
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日`
} }
return '' return ''
} }
/**
* 从提案人列表中提取主提案人
* @param {Array} sponsors - 提案人列表
* @returns {string} 主提案人姓名
*/
function extractProposer(sponsors) {
if (!sponsors || sponsors.length === 0) return ''
const mainProposer = sponsors.find(s => s.sponsor_type === '提案人')
return mainProposer ? mainProposer.person_name : ''
}
/**
/** /**
* 计算法案总耗时 * 计算法案总耗时
...@@ -488,29 +503,30 @@ export function transformProposalInfo(apiData) { ...@@ -488,29 +503,30 @@ export function transformProposalInfo(apiData) {
} }
return { return {
// 提案标题 - 需要从其他 API 获取或使用默认值 // 提案标题
title: data.bill_title || 'H.R.1-大而美法案', title: data.bill_title || 'H.R.1-大而美法案',
// 提案时间 - 从政治格局描述中提取或使用默认值 // 提案时间 - 从政治格局描述中提取
date: extractDateFromDesc(data.poli_pattern_desc) || '2025年5月20日', date: extractDateFromDesc(data.poli_pattern_desc) || '2025年5月20日',
// 涉及领域 // 涉及领域 TAG - 使用 domain_name
areas: data.domain_name || [], areas: Array.isArray(data.domain_name) ? data.domain_name : (data.domain_name ? [data.domain_name] : []),
// 选举周期阶段 - 从政治格局描述推断 // 政策领域完整选项列表 - 使用 bill_domain(用于筛选下拉框)
electionPhase: inferElectionPhase(data.poli_pattern_desc) || '执政初期/蜜月期', billDomain: Array.isArray(data.bill_domain) ? data.bill_domain : [],
// 政策领域默认已选项 - 使用 domain_name
defaultDomains: Array.isArray(data.domain_name) ? data.domain_name : (data.domain_name ? [data.domain_name] : []),
// 提案人 // 提案人
proposer: mainProposer ? `${mainProposer.person_name}` : '', proposer: mainProposer ? mainProposer.person_name : '',
// 共同提案人 // 共同提案人
coProposers: coProposersDesc, coProposers: coProposersDesc,
// 提案人职务 - 需要从其他 API 获取
proposerPosition: data.proposer_position || '委员会主席',
// 政府结构类型 // 政府结构类型
governmentType: formatGovernmentType(data.poli_pattern_type, data.poli_pattern_desc), governmentType: formatGovernmentType(data.poli_pattern_type, data.poli_pattern_desc),
// 法案预算规模 - 需要从其他 API 获取 // 政治格局类型(用于筛选条件默认值)
budgetScale: data.budget_scale || '4万亿美元', patternType: data.poli_pattern_type || '统一政府',
// 原始数据,供筛选条件使用 // 原始数据,供筛选条件使用
_raw: data _raw: data
} }
} }
/** /**
* 从政治格局描述中提取日期 * 从政治格局描述中提取日期
* @param {string} desc - 政治格局描述 * @param {string} desc - 政治格局描述
...@@ -579,8 +595,6 @@ export function generateDefaultFilters(proposalInfo) { ...@@ -579,8 +595,6 @@ export function generateDefaultFilters(proposalInfo) {
proposerPosition: proposalInfo.proposerPosition === '委员会主席' ? ['chairman'] : [], proposerPosition: proposalInfo.proposerPosition === '委员会主席' ? ['chairman'] : [],
// 政府结构类型 // 政府结构类型
governmentType: proposalInfo.governmentType.includes('一致') ? ['unified'] : ['divided'], governmentType: proposalInfo.governmentType.includes('一致') ? ['unified'] : ['divided'],
// 选举周期阶段
electionPhase: proposalInfo.electionPhase.includes('蜜月') ? ['honeymoon'] : [],
// 法案预算规模 // 法案预算规模
budgetScale: ['trillion_plus'], budgetScale: ['trillion_plus'],
// 对方党派提案人 // 对方党派提案人
...@@ -589,3 +603,19 @@ export function generateDefaultFilters(proposalInfo) { ...@@ -589,3 +603,19 @@ export function generateDefaultFilters(proposalInfo) {
proposalTime: ['recent_5'] proposalTime: ['recent_5']
} }
} }
/**
* 获取预测分析结果
* @param {Object} params - 请求参数
* @param {string} params.bill_id - 当前法案ID
* @param {string} params.bill_name - 当前法案名称
* @param {Array} params.bill_actions - 当前法案动作列表
* @param {Array} params.bill_sponsors - 当前法案提案人列表
* @param {Array} params.simi_bills - 用户勾选的相似法案列表
* @returns {Promise<Object>} 预测分析结果
*/
export function getPassProd(params) {
return request('/api/BillProgressPrediction/passProd', {
method: 'POST',
data: params
})
}
\ No newline at end of file
...@@ -139,119 +139,117 @@ export function getPersonList(params) { ...@@ -139,119 +139,117 @@ export function getPersonList(params) {
}) })
} }
//创新主体科研实力:专利数量统计
export function getPatentList(params) {
//合作情况:与中国合作数量变化
export function getCooperateNumWithChina(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/innovateSubject/patentList/${params.id}`, url: `/api/innovateSubject/cooperateNumWithChina/${params.id}`,
params params
}) })
} }
// 合作情况:与中国合作类型变化
// 创新主体科研实力:论文数量统计 export function getCooperateTypeWithChina(params) {
export function getPaperList(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/innovateSubject/paperList/${params.id}`, url: `/api/innovateSubject/cooperateTypeWithChina/${params.year}/${params.id}`,
params params
}) })
} }
//创新主体科研实力:领域实力分布 // 合作情况:与中国合作领域变化
export function getStudyFieldList(params) { export function getCooperateAreaWithChina(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/innovateSubject/studyFieldList/${params.id}`, url: `/api/innovateSubject/cooperateAreaWithChina/${params.id}`,
params
}) })
} }
//创新主体科研实力:经费增长情况 //合作情况:与中国合作经费变化
export function getFundGrowth(params) { export function getCooperateFundWithChina(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/innovateSubject/fundGrowth/${params.id}`, url: `/api/innovateSubject/cooperateFundWithChina/${params.id}`,
params params
}) })
} }
//创新主体科研实力:经费来源 //合作情况:与中国合作事例
export function getFundFromList(params) { export function getCooperateExampleWithChina(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/innovateSubject/fundFromList/${params.id}`, url: `/api/innovateSubject/cooperateExampleWithChina/${params.id}`,
params params
}) })
} }
//创新主体科研实力:经费分配
export function getFundToList(params) {
// 专利数量统计
export function getPatentList(orgId) {
return request({ return request({
method: 'GET', method: "GET",
url: `/api/innovateSubject/fundToList/${params.id}`, url: `/api/innovateSubject/patentList/${orgId}`
params
}) })
} }
//合作情况:与中国合作数量变化 // 论文数量统计
export function getCooperateNumWithChina(params) { export function getPaperList(orgId) {
return request({ return request({
method: 'GET', method: "GET",
url: `/api/innovateSubject/cooperateNumWithChina/${params.id}`, url: `/api/innovateSubject/paperList/${orgId}`
params
}) })
} }
// 合作情况:与中国合作类型变化 // 领域实力分布
export function getCooperateTypeWithChina(params) { export function getStudyFieldList(orgId) {
return request({ return request({
method: 'GET', method: "GET",
url: `/api/innovateSubject/cooperateTypeWithChina/${params.year}/${params.id}`, url: `/api/innovateSubject/studyFieldList/${orgId}`
params
}) })
} }
// 合作情况:与中国合作领域变化 // 经费增长情况
export function getCooperateAreaWithChina(params) { export function getFundGrowth(orgId) {
return request({ return request({
method: 'GET', method: "GET",
url: `/api/innovateSubject/cooperateAreaWithChina/${params.id}`, url: `/api/innovateSubject/fundGrowth/${orgId}`
params
}) })
} }
//合作情况:与中国合作经费变化 // 经费来源
export function getCooperateFundWithChina(params) { export function getFundFromList(orgId) {
return request({ return request({
method: 'GET', method: "GET",
url: `/api/innovateSubject/cooperateFundWithChina/${params.id}`, url: `/api/innovateSubject/fundFromList/${orgId}`
params
}) })
} }
//合作情况:与中国合作事例 // 经费分配
export function getCooperateExampleWithChina(params) { export function getFundToList(orgId) {
return request({ return request({
method: 'GET', method: "GET",
url: `/api/innovateSubject/cooperateExampleWithChina/${params.id}`, url: `/api/innovateSubject/fundToList/${orgId}`
params
}) })
} }
// 获取实验室列表
//创新主体其他情况:重点实验室 export function getLabList(orgId) {
export function getLabList(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/innovateSubject/labList/${params.id}`, url: `/api/innovateSubject/labList/${orgId}`
}) })
} }
//创新主体其他情况:政策文件 // 获取政策文件列表
export function getPolicyList(params) { export function getPolicyList(orgId, currentPage = 1, pageSize = 6) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/innovateSubject/policyList/${params.id}`, url: `/api/innovateSubject/policyList/${orgId}`,
params params: { currentPage, pageSize }
}) })
} }
...@@ -46,7 +46,10 @@ export function getThinkTankPolicyIndustryChange(params) { ...@@ -46,7 +46,10 @@ export function getThinkTankPolicyIndustryChange(params) {
params: { params: {
startDate: params.startDate, startDate: params.startDate,
endDate: params.endDate endDate: params.endDate
} },
// 无数据年份(如 2026)后端可能返回 HTTP 400/500,避免走全局错误提示
validateStatus: (status) =>
(status >= 200 && status < 300) || status === 400 || status === 500
}); });
} }
...@@ -286,8 +289,7 @@ export function getThinkPolicyIndustryChange(params) { ...@@ -286,8 +289,7 @@ export function getThinkPolicyIndustryChange(params) {
/** /**
* 获取智库政策(政策追踪列表) * 获取智库政策(政策追踪列表)
* GET /api/thinkTankInfo/policy
* Query: thinkTankId, startDate, endDate, orgIds, domainIds(科技领域/智库领域,逗号分隔 id), pageNum, pageSize, sortField, sortOrder, sortFun, reportId 等
*/ */
export function getThinkPolicy(params) { export function getThinkPolicy(params) {
return request({ return request({
......
...@@ -35,7 +35,7 @@ const tipText = computed(() => props.text || `数据来源:${props.dataSource} ...@@ -35,7 +35,7 @@ const tipText = computed(() => props.text || `数据来源:${props.dataSource}
width: 100%; width: 100%;
display: flex; display: flex;
gap: 8px; gap: 8px;
justify-content: center; justify-content: flex-start;
align-items: center; align-items: center;
height: 22px; height: 22px;
......
...@@ -4,22 +4,20 @@ const InnovationInstitution = () => import('@/views/innovationSubject/innovative ...@@ -4,22 +4,20 @@ const InnovationInstitution = () => import('@/views/innovationSubject/innovative
const innovationSubjectRoutes = [ const innovationSubjectRoutes = [
//创新主体
{ {
path: "/innovationSubject", path: "/innovationSubject",
name: "InnovationSubject", name: "InnovationSubject",
component: InnovationSubject, component: InnovationSubject,
meta: { meta: {
title: "M国主要创新主体分析概览" title: "M "
} }
}, },
{ {
path: "/InnovativeInstitutions/:id", path: "/InnovativeInstitutions/:id/:type",
name: "InnovativeInstitutions", name: "InnovativeInstitutions",
component: InnovationInstitution, component: InnovationInstitution,
// meta: {
// title: "学校详情"
// },
} }
] ]
......
...@@ -15,22 +15,29 @@ const setChart = (option, chartId, allowClick, selectParam) => { ...@@ -15,22 +15,29 @@ const setChart = (option, chartId, allowClick, selectParam) => {
chart.on('click', function (params) { chart.on('click', function (params) {
switch (selectParam.moduleType) { switch (selectParam.moduleType) {
case '国会法案': case '国会法案':
if(selectParam.selectedDate && selectParam.length === 4) {
selectParam.selectedDate = JSON.stringify([selectParam.selectedDate+'-01-01',selectParam.selectedDate+'-12-31'])
}
// 判断点击的是否为饼图的数据项 // 判断点击的是否为饼图的数据项
if (params.componentType === 'series' && params.seriesType === 'pie') { if (params.componentType === 'series' && params.seriesType === 'pie') {
console.log('点击的扇形名称:', params.name); console.log('点击的扇形名称:', params.name);
if (selectParam.key === '领域') { if (selectParam.key === '领域') {
selectParam.domains = params.name selectParam.domains = params.name
if (selectParam.selectedDate.length === 4) {
selectParam.selectedDate = JSON.stringify([selectParam.selectedDate + '-01-01', selectParam.selectedDate + '-12-31'])
}
} else if (selectParam.key === '议院委员会') { } else if (selectParam.key === '议院委员会') {
if (params.name === '众议院' || params.name === '参议院') { if (params.name === '众议院' || params.name === '参议院') {
selectParam.selectedCongress = params.name selectParam.selectedCongress = params.name
selectParam.selectedOrg = '' selectParam.selectedOrg = ''
if (selectParam.selectedDate.length === 4) {
selectParam.selectedDate = JSON.stringify([selectParam.selectedDate + '-01-01', selectParam.selectedDate + '-12-31'])
}
} else { } else {
selectParam.selectedOrg = params.name selectParam.selectedOrg = params.name
selectParam.selectedCongress = '' selectParam.selectedCongress = ''
if (selectParam.selectedDate.length === 4) {
selectParam.selectedDate = JSON.stringify([selectParam.selectedDate + '-01-01', selectParam.selectedDate + '-12-31'])
}
} }
} }
const route = router.resolve({ const route = router.resolve({
...@@ -40,9 +47,12 @@ const setChart = (option, chartId, allowClick, selectParam) => { ...@@ -40,9 +47,12 @@ const setChart = (option, chartId, allowClick, selectParam) => {
window.open(route.href, "_blank"); window.open(route.href, "_blank");
} else if (params.componentType === 'series' && params.seriesType === 'bar') { } else if (params.componentType === 'series' && params.seriesType === 'bar') {
if (params.name === '已立法') { if (params.name === '已立法') {
selectParam.selectedStauts = 1 selectParam.selectedStatus = 1
} else { } else {
selectParam.selectedStauts = 0 selectParam.selectedStatus = 0
}
if (selectParam.selectedDate.length === 4) {
selectParam.selectedDate = JSON.stringify([selectParam.selectedDate + '-01-01', selectParam.selectedDate + '-12-31'])
} }
const route = router.resolve({ const route = router.resolve({
path: "/dataLibrary/countryBill", path: "/dataLibrary/countryBill",
...@@ -52,11 +62,13 @@ const setChart = (option, chartId, allowClick, selectParam) => { ...@@ -52,11 +62,13 @@ const setChart = (option, chartId, allowClick, selectParam) => {
} else { } else {
console.log('当前点击', selectParam, params.seriesName, params.name); console.log('当前点击', selectParam, params.seriesName, params.name);
if (params.seriesName !== '通过率') { if (params.seriesName !== '通过率') {
selectParam = { selectParam.selectedDate = JSON.stringify(getMonthRange(params.name))
selectedDate: JSON.stringify(getMonthRange(params.name)), if (params.seriesName === '通过法案') {
status: params.seriesName === '通过法案' ? 1 : 0, selectParam.selectedStatus = 1
...selectParam } else {
selectParam.selectedStatus = null
} }
const route = router.resolve({ const route = router.resolve({
path: "/dataLibrary/countryBill", path: "/dataLibrary/countryBill",
query: selectParam query: selectParam
......
...@@ -1006,7 +1006,7 @@ const handleBox9Data = async () => { ...@@ -1006,7 +1006,7 @@ const handleBox9Data = async () => {
moduleType: '国会法案', moduleType: '国会法案',
key: '领域', key: '领域',
selectedDate: box9selectetedTime.value, selectedDate: box9selectetedTime.value,
status: box9LegislativeStatus.value === '提出法案' ? 0 : 1, selectedStatus: box9LegislativeStatus.value === '提出法案' ? 0 : 1,
isInvolveCn: 1 isInvolveCn: 1
} }
box9ChartInstance = setChart(box9Chart, "box9Chart", true, selectParam); box9ChartInstance = setChart(box9Chart, "box9Chart", true, selectParam);
......
...@@ -3,20 +3,20 @@ import * as echarts from 'echarts' ...@@ -3,20 +3,20 @@ import * as echarts from 'echarts'
const getMultiLineChart = (dataX, dataY1, dataY2, dataY3) => { const getMultiLineChart = (dataX, dataY1, dataY2, dataY3) => {
return { return {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'item',
axisPointer: { axisPointer: {
type: 'cross', type: 'cross',
label: { label: {
backgroundColor: '#6a7985' backgroundColor: '#6a7985'
} }
}, },
formatter: function (params) { // formatter: function (params) {
let res = params[0].name + '<br/>'; // let res = params[0].name + '<br/>';
params.forEach(item => { // params.forEach(item => {
res += item.marker + item.seriesName + ': ' + item.value + (item.seriesName === '通过率' ? '%' : '') + '<br/>'; // res += item.marker + item.seriesName + ': ' + item.value + (item.seriesName === '通过率' ? '%' : '') + '<br/>';
}); // });
return res; // return res;
} // }
}, },
grid: { grid: {
width: '96%', width: '96%',
......
...@@ -384,7 +384,6 @@ ...@@ -384,7 +384,6 @@
</div> </div>
</div> --> </div> -->
<AnalysisBox title="投票分析"> <AnalysisBox title="投票分析">
<div class="analysis-ai-wrapper analysis-ai-wrapper--box3">
<div class="vote-legend"> <div class="vote-legend">
<div class="vote-legend-item"> <div class="vote-legend-item">
<span class="vote-legend-dot agree"></span> <span class="vote-legend-dot agree"></span>
...@@ -395,6 +394,7 @@ ...@@ -395,6 +394,7 @@
<span>反对票</span> <span>反对票</span>
</div> </div>
</div> </div>
<div class="analysis-ai-wrapper analysis-ai-wrapper--box3">
<div class="box3-main" :class="{ 'box3-main--full': !voteFooterText }"> <div class="box3-main" :class="{ 'box3-main--full': !voteFooterText }">
<div class="box3-main-center"> <div class="box3-main-center">
<div class="box3-main-center-header"> <div class="box3-main-center-header">
...@@ -405,14 +405,15 @@ ...@@ -405,14 +405,15 @@
<div class="box3-main-center-header-box5"> <div class="box3-main-center-header-box5">
倒戈人数<span style="font-weight: normal; display: inline-block">(平均区间)</span> 倒戈人数<span style="font-weight: normal; display: inline-block">(平均区间)</span>
</div> </div>
<div class="box3-main-center-header-box6">关键议员</div>
</div> </div>
<div class="box3-main-center-content"> <div class="box3-main-center-content">
<div class="box3-main-center-content-box" v-for="item in voteAnalysisList" :key="item.actionId"> <div class="box3-main-center-content-box" v-for="item in voteAnalysisList"
:key="item.actionId">
<div class="item"> <div class="item">
<div class="item-box1"> <div class="item-box1">
<div class="box1-left"> <div class="box1-left">
<div style="width: 100%; display: flex; flex-direction: column; align-items: flex-end"> <div
style="width: 100%; display: flex; flex-direction: column; align-items: flex-end">
<div class="name nameBlod" :title="item.actionTitle" style=" <div class="name nameBlod" :title="item.actionTitle" style="
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
...@@ -428,33 +429,34 @@ ...@@ -428,33 +429,34 @@
</div> </div>
<div class="box1-right"> <div class="box1-right">
<div class="box1-right-top"> <div class="box1-right-top">
<el-progress :percentage="Number(item.agreePercent)" :show-text="false" color="rgb(33, 129, 57)"> <el-progress :percentage="Number(item.agreePercent)"
:show-text="false" color="rgb(33, 129, 57)">
</el-progress> </el-progress>
</div> </div>
<div class="box1-right-bottom"> <div class="box1-right-bottom">
<el-progress :percentage="Number(item.againstPercent)" :show-text="false" color="rgb(206, 79, 81)"> <el-progress :percentage="Number(item.againstPercent)"
:show-text="false" color="rgb(206, 79, 81)">
</el-progress> </el-progress>
</div> </div>
</div> </div>
</div> </div>
<div class="item-box2"> <div class="item-box2">
<div class="box2-1" style="color: rgb(33, 129, 57)">{{ item.agreeCount + "票" }}</div> <div class="box2-1" style="color: rgb(33, 129, 57)">{{ item.agreeCount +
<div class="box2-2" style="color: rgb(206, 79, 81)">{{ item.againstCount + "票" }}</div> "票" }}</div>
<div class="box2-2" style="color: rgb(206, 79, 81)">{{ item.againstCount
+ "票" }}</div>
</div> </div>
<div class="item-box3"> <div class="item-box3">
<div class="box3-1"></div> <div class="box3-1"></div>
<div class="box3-2"></div> <div class="box3-2"></div>
</div> </div>
<div class="item-box4"> <div class="item-box4">
<div class="box4-1" style="color: rgb(33, 129, 57)">{{ item.agreePercent + "%" }}</div> <div class="box4-1" style="color: rgb(33, 129, 57)">{{ item.agreePercent
<div class="box4-2" style="color: rgb(206, 79, 81)">{{ item.againstPercent + "%" }}</div> + "%" }}</div>
<div class="box4-2" style="color: rgb(206, 79, 81)">{{
item.againstPercent + "%" }}</div>
</div> </div>
<div class="item-box5"></div> <div class="item-box5"></div>
<div class="item-box6">
<el-icon size="20" color="#555">
<ArrowDownBold />
</el-icon>
</div>
</div> </div>
<div class="item"> <div class="item">
<div class="item-box1"> <div class="item-box1">
...@@ -466,31 +468,33 @@ ...@@ -466,31 +468,33 @@
</div> </div>
<div class="box1-right"> <div class="box1-right">
<div class="box1-right-top"> <div class="box1-right-top">
<el-progress :percentage="Number(item.dagreePercent)" :show-text="false" color="rgb(33, 129, 57)"> <el-progress :percentage="Number(item.dagreePercent)"
:show-text="false" color="rgb(33, 129, 57)">
</el-progress> </el-progress>
</div> </div>
<div class="box1-right-bottom"> <div class="box1-right-bottom">
<el-progress :percentage="Number(item.dagainstPercent)" :show-text="false" color="rgb(206, 79, 81)"> <el-progress :percentage="Number(item.dagainstPercent)"
:show-text="false" color="rgb(206, 79, 81)">
</el-progress> </el-progress>
</div> </div>
</div> </div>
</div> </div>
<div class="item-box2"> <div class="item-box2">
<div class="box2-1" style="color: rgb(33, 129, 57)">{{ item.dagreeCount + "票" }}</div> <div class="box2-1" style="color: rgb(33, 129, 57)">{{ item.dagreeCount
<div class="box2-2" style="color: rgb(206, 79, 81)">{{ item.dagainstCount + "票" }}</div> + "票" }}</div>
<div class="box2-2" style="color: rgb(206, 79, 81)">{{
item.dagainstCount + "票" }}</div>
</div> </div>
<div class="item-box3"></div> <div class="item-box3"></div>
<div class="item-box4"> <div class="item-box4">
<div class="box4-1" style="color: rgb(33, 129, 57)">{{ item.dagreePercent + "%" }}</div> <div class="box4-1" style="color: rgb(33, 129, 57)">{{
<div class="box4-2" style="color: rgb(206, 79, 81)">{{ item.dagainstPercent + "%" }}</div> item.dagreePercent + "%" }}</div>
<div class="box4-2" style="color: rgb(206, 79, 81)">{{
item.dagainstPercent + "%" }}</div>
</div> </div>
<div class="item-box5"> <div class="item-box5">
<div class="box5-1" style="color: #ce4f51">{{ item.dreverseCount + "人" }}</div> <div class="box5-1" style="color: #ce4f51">{{ item.dreverseCount + "人"
</div> }}</div>
<div class="item-box6">
<div class="img-box" v-if="item.dpersonImageUrl">
<img :src="item.dpersonImageUrl" alt="" />
</div>
</div> </div>
</div> </div>
<div class="item"> <div class="item">
...@@ -503,31 +507,33 @@ ...@@ -503,31 +507,33 @@
</div> </div>
<div class="box1-right"> <div class="box1-right">
<div class="box1-right-top"> <div class="box1-right-top">
<el-progress :percentage="Number(item.ragreePercent)" :show-text="false" color="rgb(33, 129, 57)"> <el-progress :percentage="Number(item.ragreePercent)"
:show-text="false" color="rgb(33, 129, 57)">
</el-progress> </el-progress>
</div> </div>
<div class="box1-right-bottom"> <div class="box1-right-bottom">
<el-progress :percentage="Number(item.ragainstPercent)" :show-text="false" color="rgb(206, 79, 81)"> <el-progress :percentage="Number(item.ragainstPercent)"
:show-text="false" color="rgb(206, 79, 81)">
</el-progress> </el-progress>
</div> </div>
</div> </div>
</div> </div>
<div class="item-box2"> <div class="item-box2">
<div class="box2-1" style="color: rgb(33, 129, 57)">{{ item.ragreeCount + "票" }}</div> <div class="box2-1" style="color: rgb(33, 129, 57)">{{ item.ragreeCount
<div class="box2-2" style="color: rgb(206, 79, 81)">{{ item.ragainstCount + "票" }}</div> + "票" }}</div>
<div class="box2-2" style="color: rgb(206, 79, 81)">{{
item.ragainstCount + "票" }}</div>
</div> </div>
<div class="item-box3"></div> <div class="item-box3"></div>
<div class="item-box4"> <div class="item-box4">
<div class="box4-1" style="color: rgb(33, 129, 57)">{{ item.ragreePercent + "%" }}</div> <div class="box4-1" style="color: rgb(33, 129, 57)">{{
<div class="box4-2" style="color: rgb(206, 79, 81)">{{ item.ragainstPercent + "%" }}</div> item.ragreePercent + "%" }}</div>
<div class="box4-2" style="color: rgb(206, 79, 81)">{{
item.ragainstPercent + "%" }}</div>
</div> </div>
<div class="item-box5"> <div class="item-box5">
<div class="box5-1" style="color: #ce4f51">{{ item.rreverseCount + "人" }}</div> <div class="box5-1" style="color: #ce4f51">{{ item.rreverseCount + "人"
</div> }}</div>
<div class="item-box6">
<div class="img-box" v-if="item.rpersonImageUrl">
<img :src="item.rpersonImageUrl" alt="" />
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -701,7 +707,8 @@ ...@@ -701,7 +707,8 @@
<TipTab class="analysis-ai-tip" /> <TipTab class="analysis-ai-tip" />
<AiButton class="analysis-ai-tip-action" @mouseenter="handleShowAiPane('box3')" /> <AiButton class="analysis-ai-tip-action" @mouseenter="handleShowAiPane('box3')" />
</div> </div>
<div v-if="aiPaneVisible.box3" class="analysis-ai-pane" @mouseleave="handleHideAiPane('box3')"> <div v-if="aiPaneVisible.box3" class="analysis-ai-pane"
@mouseleave="handleHideAiPane('box3')">
<AiPane :aiContent="overviewAiContent.box3" /> <AiPane :aiContent="overviewAiContent.box3" />
</div> </div>
</div> </div>
...@@ -1248,6 +1255,7 @@ onMounted(async () => { ...@@ -1248,6 +1255,7 @@ onMounted(async () => {
.box1 { .box1 {
width: 792px; width: 792px;
height: 415px; height: 415px;
.box1-main { .box1-main {
height: 368px; height: 368px;
...@@ -1393,6 +1401,7 @@ onMounted(async () => { ...@@ -1393,6 +1401,7 @@ onMounted(async () => {
margin-top: 17px; margin-top: 17px;
width: 792px; width: 792px;
height: 415px; height: 415px;
.box2-main { .box2-main {
height: 359px; height: 359px;
...@@ -1640,6 +1649,7 @@ onMounted(async () => { ...@@ -1640,6 +1649,7 @@ onMounted(async () => {
margin-top: 16px; margin-top: 16px;
width: 792px; width: 792px;
height: 847px; height: 847px;
.box3 { .box3 {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -1647,7 +1657,7 @@ onMounted(async () => { ...@@ -1647,7 +1657,7 @@ onMounted(async () => {
.vote-legend { .vote-legend {
position: absolute; position: absolute;
top: 15px; top: 10px;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
display: inline-flex; display: inline-flex;
...@@ -1735,12 +1745,6 @@ onMounted(async () => { ...@@ -1735,12 +1745,6 @@ onMounted(async () => {
min-width: 0; min-width: 0;
text-align: center; text-align: center;
} }
.box3-main-center-header-box6 {
flex: 90 1 0;
min-width: 0;
text-align: center;
}
} }
.box3-main-center-content { .box3-main-center-content {
...@@ -1976,23 +1980,6 @@ onMounted(async () => { ...@@ -1976,23 +1980,6 @@ onMounted(async () => {
line-height: 14px; line-height: 14px;
} }
} }
.item-box6 {
flex: 90 1 0;
min-width: 0;
text-align: center;
.img-box {
width: 30px;
height: 30px;
margin-left: 30px;
img {
width: 100%;
height: 100%;
}
}
}
} }
} }
} }
......
...@@ -50,12 +50,9 @@ const emit = defineEmits<{ ...@@ -50,12 +50,9 @@ const emit = defineEmits<{
const billFields = [ const billFields = [
{ key: 'proposalDate', label: '提案时间:' }, { key: 'proposalDate', label: '提案时间:' },
{ key: 'areas', label: '涉及领域:' }, { key: 'areas', label: '涉及领域:' },
{ key: 'electionPhase', label: '选举周期阶段:' },
{ key: 'proposer', label: '提案人:' }, { key: 'proposer', label: '提案人:' },
{ key: 'coProposers', label: '共同提案人:' }, { key: 'coProposers', label: '共同提案人:' },
{ key: 'proposerPosition', label: '提案人职务:' },
{ key: 'governmentType', label: '政府结构类型:' }, { key: 'governmentType', label: '政府结构类型:' },
{ key: 'budgetScale', label: '法案预算规模:' },
{ key: 'passDays', label: '通过耗时:' } { key: 'passDays', label: '通过耗时:' }
] ]
</script> </script>
......
...@@ -5,56 +5,189 @@ ...@@ -5,56 +5,189 @@
<img src="../assets/fitller.svg" /> <img src="../assets/fitller.svg" />
<h2 class="section-title text-title-3-bold">核心相似度维度筛选</h2> <h2 class="section-title text-title-3-bold">核心相似度维度筛选</h2>
</div> </div>
<button class="btn-outline" @click="emit('setAsCurrent')">设置为当前提案</button> <button class="btn-outline" @click="setAsCurrent">设置为当前提案</button>
</div> </div>
<div class="divider" /> <div class="divider" />
<div class="fields-grid"> <div class="fields-grid">
<div
v-for="field in fields" <!-- 政策领域 -->
:key="field.id" <div class="field-item">
class="field-item" <span class="field-label text-tip-1 text-primary-65-clor">政策领域:</span>
<div class="field-content">
<el-select
v-model="localValues.policyArea"
multiple
placeholder="请选择"
style="width: 420px"
@change="handleChange"
> >
<div class="field-label-wrapper"> <el-option
<span class="field-label text-tip-1 text-primary-65-clor">{{ field.label }}</span> v-for="opt in fields.policyArea.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</div> </div>
</div>
<!-- 政府结构类型 -->
<div class="field-item">
<span class="field-label text-tip-1 text-primary-65-clor">政府结构类型:</span>
<div class="field-content"> <div class="field-content">
<FilterSelect <el-select
:options="field.options" v-model="localValues.governmentType"
:model-value="field.selectedValues" multiple
@update:model-value="(val) => handleFieldUpdate(field.id, val)" placeholder="请选择"
style="width: 420px"
@change="handleChange"
>
<el-option
v-for="opt in fields.governmentType.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/> />
<div v-if="field.hint" class="field-hint"> </el-select>
<div v-if="fields.governmentType.hint" class="field-hint">
<img src="../assets/importent.svg" /> <img src="../assets/importent.svg" />
<span class="text-tip-2 text-primary-50-clor">{{ field.hint }}</span> <span class="text-tip-2 text-primary-50-clor">{{ fields.governmentType.hint }}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- 对方党派提案人 -->
<div class="field-item">
<span class="field-label text-tip-1 text-primary-65-clor">对方党派提案人:</span>
<div class="field-content">
<el-select
v-model="localValues.oppositionProposer"
multiple
placeholder="请选择"
style="width: 420px"
@change="handleChange"
>
<el-option
v-for="opt in fields.oppositionProposer.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</div>
</div>
<!-- 提案时间 -->
<div class="field-item">
<span class="field-label text-tip-1 text-primary-65-clor">提案时间:</span>
<div class="field-content">
<el-select
v-model="localValues.proposalTime"
multiple
placeholder="请选择"
style="width: 420px"
@change="handleChange"
>
<el-option
v-for="opt in fields.proposalTime.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</div>
</div>
</div> </div>
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { FilterField } from '../api' import { ref, computed, watch } from 'vue'
import FilterSelect from './FilterSelect.vue' import type { ProposalInfo } from '../api'
const props = defineProps<{ const props = defineProps<{
fields: FilterField[] proposalInfo?: ProposalInfo | null
defaultFilters?: Record<string, string[]>
}>() }>()
const emit = defineEmits<{ // 本地 v-model 值
'update:fields': [fields: FilterField[]] const localValues = ref({
setAsCurrent: [] policyArea: [] as string[],
}>() governmentType: [] as string[],
oppositionProposer: [] as string[],
proposalTime: [] as string[],
})
// 更新字段选中值 // 根据 proposalInfo 计算初始筛选值(即"设置为当前提案"的目标状态)
function handleFieldUpdate(fieldId: string, newValues: string[]) { function buildInitialValues(info?: ProposalInfo | null): Record<string, string[]> {
const updatedFields = props.fields.map(f => if (!info) return { policyArea: [], governmentType: [], oppositionProposer: [], proposalTime: [] }
f.id === fieldId return {
? { ...f, selectedValues: newValues } policyArea: info.defaultDomains?.length ? [...info.defaultDomains] : [...(info.areas || [])],
: f governmentType: info.patternType ? [info.patternType] : [],
) oppositionProposer: [],
emit('update:fields', updatedFields) proposalTime: [],
}
} }
// 监听 proposalInfo 首次传入时设置初始值
watch(
() => props.proposalInfo,
(newInfo) => {
Object.assign(localValues.value, buildInitialValues(newInfo))
},
{ immediate: true }
)
// 重置:清空所有已选项
function reset() {
localValues.value = {
policyArea: [],
governmentType: [],
oppositionProposer: [],
proposalTime: [],
}
}
// 设置为当前提案:恢复为 proposalInfo 的初始状态
function setAsCurrent() {
Object.assign(localValues.value, buildInitialValues(props.proposalInfo))
}
defineExpose({ reset, setAsCurrent, getValues: () => localValues.value })
// 固定的字段配置,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 },
governmentType: {
options: [
{ value: '统一政府', label: '统一政府' },
{ value: '分裂政府', label: '分裂政府' },
{ value: '微弱多数', label: '微弱多数' }
],
hint: '总统所属政党同时控制国会参众两院'
},
oppositionProposer: {
options: [
{ value: '两党共同提案', label: '两党共同提案' },
{ value: '单政党提案(共和党提案)', label: '单政党提案(共和党提案)' },
{ value: '单政党提案(民主党提案)', label: '单政党提案(民主党提案)' }
]
},
proposalTime: {
options: [
{ value: '近五年', label: '近五年' },
{ value: '近十年', label: '近十年' },
{ value: '全部', label: '全部' }
]
}
}
})
function handleChange() {}
</script> </script>
<style scoped> <style scoped>
...@@ -75,12 +208,6 @@ function handleFieldUpdate(fieldId: string, newValues: string[]) { ...@@ -75,12 +208,6 @@ function handleFieldUpdate(fieldId: string, newValues: string[]) {
gap: 12px; gap: 12px;
} }
.section-icon {
width: 16px;
height: 16px;
color: var(--text-primary-80-color);
}
.btn-outline { .btn-outline {
padding: 6px 16px; padding: 6px 16px;
border: 1px solid var(--bg-black-10); border: 1px solid var(--bg-black-10);
...@@ -105,7 +232,7 @@ function handleFieldUpdate(fieldId: string, newValues: string[]) { ...@@ -105,7 +232,7 @@ function handleFieldUpdate(fieldId: string, newValues: string[]) {
.fields-grid { .fields-grid {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 24px 24px; gap: 24px;
padding-left: 30px; padding-left: 30px;
} }
...@@ -113,15 +240,14 @@ function handleFieldUpdate(fieldId: string, newValues: string[]) { ...@@ -113,15 +240,14 @@ function handleFieldUpdate(fieldId: string, newValues: string[]) {
width: 580px; width: 580px;
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
} gap: 0;
.field-label-wrapper {
padding-top: 4px;
} }
.field-label { .field-label {
display: block; display: block;
width: 150px; width: 140px;
flex-shrink: 0;
padding-top: 6px;
} }
.field-content { .field-content {
...@@ -135,11 +261,4 @@ function handleFieldUpdate(fieldId: string, newValues: string[]) { ...@@ -135,11 +261,4 @@ function handleFieldUpdate(fieldId: string, newValues: string[]) {
align-items: center; align-items: center;
gap: 8px; gap: 8px;
} }
.hint-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
color: var(--text-primary-50-color);
}
</style> </style>
...@@ -39,26 +39,19 @@ ...@@ -39,26 +39,19 @@
</p> </p>
</div> </div>
</div> </div>
<div class="facts-section"> <div v-if="phase.predictionBasis" class="facts-section">
<div class="box-header flex-display-start"> <div class="box-header flex-display-start">
<div class="box-title-row flex-display-center"> <div class="box-title-row flex-display-center">
<img src="../assets/icon1.svg"/> <img src="../assets/icon1.svg"/>
<span class="text-compact-bold">{{ phase.supportingFacts.title }}</span> <span class="text-compact-bold">通过性预测依据</span>
</div> </div>
<div class="box-hint flex-display-center text-tip-2 text-primary-50-clor"> <div class="box-hint flex-display-center text-tip-2 text-primary-50-clor">
<img src="../assets/importent.svg"/> <img src="../assets/importent.svg"/>
<span>{{ phase.supportingFacts.basedOn }}</span> <span>此阶段预测基于以下观点</span>
</div> </div>
</div> </div>
<div class="stats-grid"> <div class="prediction-basis-content">
<div <p class="text-tip-2 text-primary-65-clor">{{ phase.predictionBasis }}</p>
v-for="(stat, index) in phase.supportingFacts.stats"
:key="index"
class="stat-card"
>
<div class="stat-value main-color">{{ stat.value }}</div>
<div class="text-tip-3 text-primary-65-clor">{{ stat.label }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -260,6 +253,16 @@ const riskLabel = computed(() => { ...@@ -260,6 +253,16 @@ const riskLabel = computed(() => {
line-height: 1.6; line-height: 1.6;
} }
.prediction-basis-content {
background-color: var(--bg-black-2);
border-radius: var(--radius-10);
padding: 16px;
}
.prediction-basis-content p {
line-height: 1.6;
}
.stats-grid { .stats-grid {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
......
...@@ -33,13 +33,6 @@ ...@@ -33,13 +33,6 @@
<span class="info-label text-body-1">{{ field.label }}</span> <span class="info-label text-body-1">{{ field.label }}</span>
<template v-if="field.key === 'areas'"> <template v-if="field.key === 'areas'">
<div class="area-tags"> <div class="area-tags">
<!-- <span
v-for="area in info.areas"
:key="area"
class="area-tag"
>
{{ area }}
</span> -->
<AreaTag v-for="area in info.areas" :key="area" :tagName="area" /> <AreaTag v-for="area in info.areas" :key="area" :tagName="area" />
</div> </div>
...@@ -59,17 +52,14 @@ defineProps<{ ...@@ -59,17 +52,14 @@ defineProps<{
info: ProposalInfo info: ProposalInfo
}>() }>()
// 信息字段配置 // 信息字段配置 - 只保留:提案标题、提案时间、涉及领域、提案人、共同提案人、政府结构类型
const infoFields = [ const infoFields = [
{ key: 'title', label: '提案标题:' }, { key: 'title', label: '提案标题:' },
{ key: 'date', label: '提案时间:' }, { key: 'date', label: '提案时间:' },
{ key: 'areas', label: '涉及领域:' }, { key: 'areas', label: '涉及领域:' },
{ key: 'electionPhase', label: '选举周期阶段:' },
{ key: 'proposer', label: '提案人:' }, { key: 'proposer', label: '提案人:' },
{ key: 'coProposers', label: '共同提案人:' }, { key: 'coProposers', label: '共同提案人:' },
{ key: 'proposerPosition', label: '提案人职务:' }, { key: 'governmentType', label: '政府结构类型:' }
{ key: 'governmentType', label: '政府结构类型:' },
{ key: 'budgetScale', label: '法案预算规模:' }
] ]
</script> </script>
......
...@@ -7,9 +7,9 @@ ...@@ -7,9 +7,9 @@
<div class="content-wrapper"> <div class="content-wrapper">
<ProposalInfoSection v-if="currentProposalInfo" :info="currentProposalInfo" /> <ProposalInfoSection v-if="currentProposalInfo" :info="currentProposalInfo" />
<FilterSection <FilterSection
:fields="filterFields" ref="filterSectionRef"
@update:fields="filterFields = $event" :proposal-info="currentProposalInfo"
@set-as-current="handleSetAsCurrent" :default-filters="defaultFilters"
/> />
</div> </div>
<ActionButtons <ActionButtons
...@@ -22,8 +22,7 @@ ...@@ -22,8 +22,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, watch } from 'vue' import { ref, onMounted, watch } from 'vue'
import type { ProposalInfo, FilterField } from '../api' import type { ProposalInfo } from '../api'
import { fetchProposalInfo, fetchFilterFields } from '../api'
import ProposalInfoSection from './ProposalInfoSection.vue' import ProposalInfoSection from './ProposalInfoSection.vue'
import FilterSection from './FilterSection.vue' import FilterSection from './FilterSection.vue'
import ActionButtons from './ActionButtons.vue' import ActionButtons from './ActionButtons.vue'
...@@ -34,99 +33,42 @@ const props = defineProps<{ ...@@ -34,99 +33,42 @@ const props = defineProps<{
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
next: [] next: [selectedFilters: Record<string, string[]>]
}>() }>()
// 提案信息(优先使用 props,否则从 API 获取 // 提案信息(优先使用 props)
const currentProposalInfo = ref<ProposalInfo | null>(null) const currentProposalInfo = ref<ProposalInfo | null>(null)
// 筛选字段列表 // FilterSection 组件引用
const filterFields = ref<FilterField[]>([]) const filterSectionRef = ref<InstanceType<typeof FilterSection> | null>(null)
// 加载状态 // 加载状态
const loading = ref(true) const loading = ref(true)
// 初始筛选字段(用于重置) // 默认筛选条件
const initialFilterFields = ref<FilterField[]>([]) const defaultFilters = ref<Record<string, string[]>>({})
// 监听 props.proposalInfo 变化 // 监听 props.proposalInfo 变化
watch(() => props.proposalInfo, (newInfo) => { watch(() => props.proposalInfo, (newInfo) => {
if (newInfo) { if (newInfo) {
currentProposalInfo.value = newInfo currentProposalInfo.value = newInfo
// 根据提案信息生成默认筛选条件(仅用于 Step1 内部状态跟踪,不再传给 FilterSection)
defaultFilters.value = {
policyArea: newInfo.defaultDomains?.length ? newInfo.defaultDomains : newInfo.areas,
governmentType: newInfo.patternType ? [newInfo.patternType] : [],
oppositionProposer: [],
proposalTime: [],
} }
}, { immediate: true })
// 页面初始化时加载数据
onMounted(async () => {
try {
// 如果没有从 props 获取到提案信息,则从 API 获取
if (!props.proposalInfo) {
currentProposalInfo.value = await fetchProposalInfo()
}
// 获取筛选字段配置
const fields = await fetchFilterFields()
// 如果有默认筛选条件,应用到筛选字段
if (props.defaultFilters && Object.keys(props.defaultFilters).length > 0) {
filterFields.value = fields.map(field => ({
...field,
selectedValues: props.defaultFilters?.[field.id] || field.selectedValues
}))
} else {
filterFields.value = fields
} }
// 保存初始状态用于重置
initialFilterFields.value = JSON.parse(JSON.stringify(filterFields.value))
} finally {
loading.value = false loading.value = false
} }, { immediate: true })
})
// 重置所有筛选条件 // 重置所有筛选条件:清空所有已选项
function handleReset() { function handleReset() {
filterFields.value = JSON.parse(JSON.stringify(initialFilterFields.value)) filterSectionRef.value?.reset()
}
// 设置为当前提案
function handleSetAsCurrent() {
// 根据当前提案信息重新设置筛选条件
if (currentProposalInfo.value) {
filterFields.value = filterFields.value.map(field => {
// 根据提案信息设置默认值
const defaultValues = getDefaultValuesForField(field.id, currentProposalInfo.value!)
return {
...field,
selectedValues: defaultValues.length > 0 ? defaultValues : field.selectedValues
}
})
}
}
// 根据字段 ID 和提案信息获取默认值
function getDefaultValuesForField(fieldId: string, info: ProposalInfo): string[] {
const areaMap: Record<string, string> = {
'能源': 'energy',
'集成电路': 'ic',
'人工智能': 'ai',
'生物技术': 'biotech'
}
switch (fieldId) {
case 'policyArea':
return info.areas.map(area => areaMap[area] || area.toLowerCase())
case 'proposerPosition':
return info.proposerPosition === '委员会主席' ? ['chairman'] : []
case 'governmentType':
return info.governmentType.includes('一致') ? ['unified'] : ['divided']
case 'electionPhase':
return info.electionPhase.includes('蜜月') ? ['honeymoon'] : []
default:
return []
}
} }
// 下一步 // 下一步:获取已选的筛选条件并传给父组件
function handleNext() { function handleNext() {
emit('next') const selectedFilters = filterSectionRef.value?.getValues() || {}
emit('next', selectedFilters)
} }
</script> </script>
......
...@@ -77,13 +77,12 @@ ...@@ -77,13 +77,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, inject, watch } from 'vue' import { ref, computed, onMounted, inject, watch } from 'vue'
import type { FilterStats, BillInfo } from '../api' import type { FilterStats, BillInfo } from '../api'
import { fetchFilterStats, fetchBillList } from '../api'
import { getSimiBills, transformSimiBillsData } from '@/api/bill/billHome' import { getSimiBills, transformSimiBillsData } from '@/api/bill/billHome'
import BillCard from './BillCard.vue' import BillCard from './BillCard.vue'
const emit = defineEmits<{ const emit = defineEmits<{
previous: [] previous: []
next: [] next: [selectedBills: any[]]
}>() }>()
// 从父组件注入筛选参数 // 从父组件注入筛选参数
...@@ -93,6 +92,8 @@ const filterParams = inject<any>('filterParams', null) ...@@ -93,6 +92,8 @@ const filterParams = inject<any>('filterParams', null)
const stats = ref<FilterStats | null>(null) const stats = ref<FilterStats | null>(null)
// 法案列表 // 法案列表
const bills = ref<BillInfo[]>([]) const bills = ref<BillInfo[]>([])
// 原始法案数据(用于传给第三页)
const rawBillsData = ref<any[]>([])
// 加载状态 // 加载状态
const loading = ref(true) const loading = ref(true)
...@@ -121,36 +122,30 @@ const selectedCount = computed(() => { ...@@ -121,36 +122,30 @@ const selectedCount = computed(() => {
async function loadData() { async function loadData() {
loading.value = true loading.value = true
try { try {
// 优先使用真实 API // 使用真实 API,传入 billIds、domains、patternType、proposalType
const params = { const params = {
patternType: filterParams?.governmentType || '统一政府', billIds: filterParams?.value.billIds,
proposalType: '两党共同提案' domains:JSON.stringify(filterParams?.value.domains) || [],
patternType: filterParams?.value.patternType || '统一政府',
proposalType: filterParams?.value.proposalType || '两党共同提案'
} }
const response = await getSimiBills(params) const response = await getSimiBills(params)
if (response && response.success && response.data) { if (response && response.data) {
// 保存原始数据
rawBillsData.value = response.data
const { stats: apiStats, bills: apiBills } = transformSimiBillsData(response) const { stats: apiStats, bills: apiBills } = transformSimiBillsData(response)
stats.value = apiStats stats.value = apiStats
bills.value = apiBills bills.value = apiBills
} else { } else {
// 如果 API 失败,使用模拟数据 stats.value = null
const [statsData, billsData] = await Promise.all([ bills.value = []
fetchFilterStats(),
fetchBillList()
])
stats.value = statsData
bills.value = billsData
} }
} catch (error) { } catch (error) {
console.error('获取相似法案失败:', error) console.error('获取相似法案失败:', error)
// 使用模拟数据作为 fallback stats.value = null
const [statsData, billsData] = await Promise.all([ bills.value = []
fetchFilterStats(),
fetchBillList()
])
stats.value = statsData
bills.value = billsData
} finally { } finally {
loading.value = false loading.value = false
} }
...@@ -189,7 +184,11 @@ function handleBack() { ...@@ -189,7 +184,11 @@ function handleBack() {
// 开始预测分析 // 开始预测分析
function handleStartAnalysis() { function handleStartAnalysis() {
emit('next') // 获取用户勾选的法案ID列表
const selectedIds = bills.value.filter(b => b.selected).map(b => b.id)
// 从原始数据中筛选出用户勾选的法案
const selectedBills = rawBillsData.value.filter(b => selectedIds.includes(b.bill_id))
emit('next', selectedBills)
} }
</script> </script>
......
<template> <template>
<div class="step-container"> <div class="step-container">
<div v-if="predictionData" class="content-wrapper"> <div v-if="props.loading" class="loading-wrapper flex-display-center">
<span class="text-tip-2 text-primary-50-clor">预测分析中...</span>
</div>
<div v-else-if="predictionData" class="content-wrapper">
<div class="header-section flex-display-start"> <div class="header-section flex-display-start">
<div class="header-left flex-display-start"> <div class="header-left flex-display-start">
...@@ -27,8 +30,8 @@ ...@@ -27,8 +30,8 @@
<div class="highlight-box text-regular"> <div class="highlight-box text-regular">
<div class="highlight-wave text-regular"></div> <div class="highlight-wave text-regular"></div>
<div class="highlight-content text-regular"> <div class="highlight-content text-regular">
<p class="highlight-title text-regular">《大而美法案》的通过概率非常高</p> <p class="highlight-title text-regular">该法案的通过概率为 {{ predictionData?.overallProbability || '—' }}</p>
<p class="highlight-text text-regular">该法案由众议院共和党领导层在5月正式提出,作为特朗普第二任期核心经济议程,旨在通过一揽子税收、贸易和预算改革提振经济。到6月初,法案已快速通过关键的筹款委员会和预算委员会审议,进入众议院全院辩论阶段。共和党当时控制众议院,且党内团结支持;白宫已明确表示强烈支持。虽然民主党普遍反对,但共和党凭借席位优势足以在众议院通过。主要不确定性在于参议院,但预计部分温和民主党议员可能支持,或通过预算和解程序(只需简单多数)规避阻挠议事。因此,该法案在6月初已势在必行,最终成法几无悬念。</p> <p class="highlight-text text-regular">{{ predictionData?.highlightText }}</p>
</div> </div>
</div> </div>
<div v-if="predictionData?.phases?.length" class="phases-list"> <div v-if="predictionData?.phases?.length" class="phases-list">
...@@ -59,23 +62,22 @@ ...@@ -59,23 +62,22 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue' import { ref, computed } from 'vue'
import { fetchPredictionAnalysis, type PredictionAnalysis } from '../api' import type { PredictionAnalysis } from '../api'
import PredictionPhaseCard from './PredictionPhaseCard.vue' import PredictionPhaseCard from './PredictionPhaseCard.vue'
const props = defineProps<{
data?: PredictionAnalysis | null
loading?: boolean
}>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'prev'): void (e: 'prev'): void
(e: 'repredict'): void (e: 'repredict'): void
}>() }>()
// 预测分析数据 // 预测分析数据 - 从 props 获取,暂无真实接口
const predictionData = ref<PredictionAnalysis | null>(null) const predictionData = computed(() => props.data || null)
// 获取预测分析数据
onMounted(async () => {
const data = await fetchPredictionAnalysis()
predictionData.value = data
})
// 根据索引和progressLevel返回进度条格子的类名 // 根据索引和progressLevel返回进度条格子的类名
function getOverallSegmentClass(index: number) { function getOverallSegmentClass(index: number) {
...@@ -140,15 +142,16 @@ function handleRepredict() { ...@@ -140,15 +142,16 @@ function handleRepredict() {
</script> </script>
<style scoped> <style scoped>
/* .text-title-2-bold{
color: #3b414b;
} */
.step-container { .step-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
} }
.loading-wrapper {
flex: 1;
}
.content-wrapper { .content-wrapper {
flex: 1; flex: 1;
overflow: auto; overflow: auto;
...@@ -157,7 +160,6 @@ function handleRepredict() { ...@@ -157,7 +160,6 @@ function handleRepredict() {
.header-section { .header-section {
justify-content: space-between; justify-content: space-between;
align-items: flex-start; align-items: flex-start;
/* margin-bottom: 24px; */
} }
.header-left { .header-left {
......
...@@ -417,11 +417,12 @@ onMounted(() => { ...@@ -417,11 +417,12 @@ onMounted(() => {
} }
.risk-signal-pane-top { .risk-signal-pane-top {
width: 1600px; // width: 1600px;
margin-top: 16px; margin-top: 16px;
margin-right: 18px; margin-right: 18px;
height: 116px; padding-bottom: 16px;
min-height: 116px; min-height: 116px;
height: auto;
flex-shrink: 0; flex-shrink: 0;
} }
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
<div class="meta-row"> <div class="meta-row">
<span class="meta-label">相关领域:</span> <span class="meta-label">相关领域:</span>
<div class="meta-tags"> <div class="meta-tags">
<TagBadge v-for="item in bill.industryList" :key="item.industryName" :label="item.industryName" tag-class="tag3" /> <AreaTag v-for="item in bill.industryList" :key="item.industryName" :tagName="item.industryName" />
</div> </div>
</div> </div>
<div class="meta-row"> <div class="meta-row">
...@@ -45,7 +45,6 @@ ...@@ -45,7 +45,6 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import DocumentPreview from './DocumentPreview.vue' import DocumentPreview from './DocumentPreview.vue'
import TagBadge from './TagBadge.vue'
import ProgressBar from './ProgressBar.vue' import ProgressBar from './ProgressBar.vue'
const props = defineProps({ const props = defineProps({
...@@ -179,23 +178,25 @@ const currentStageIndex = computed(() => { ...@@ -179,23 +178,25 @@ const currentStageIndex = computed(() => {
.bill-card-meta { .bill-card-meta {
width: 100%; width: 100%;
flex: 1; flex: 1;
position: relative; display: flex;
flex-direction: column;
gap: 12px;
min-height: 0; min-height: 0;
overflow: hidden;
} }
.meta-row { .meta-row {
display: flex; display: flex;
align-items: center; align-items: flex-start;
position: absolute;
left: 0;
width: 100%; width: 100%;
gap: 12px;
} }
.meta-row:nth-child(1) { top: 0; } .meta-row:nth-child(1) { }
.meta-row:nth-child(2) { top: 36px; } .meta-row:nth-child(2) { }
.meta-row:nth-child(3) { top: 72px; } .meta-row:nth-child(3) { }
.meta-row:nth-child(4) { top: 108px; } .meta-row:nth-child(4) { }
.meta-row:nth-child(5) { top: 144px; } .meta-row:nth-child(5) { }
.meta-label { .meta-label {
font-size: 16px; font-size: 16px;
...@@ -206,6 +207,7 @@ const currentStageIndex = computed(() => { ...@@ -206,6 +207,7 @@ const currentStageIndex = computed(() => {
white-space: nowrap; white-space: nowrap;
flex-shrink: 0; flex-shrink: 0;
width: 100px; width: 100px;
padding-top: 2px;
} }
.meta-value { .meta-value {
...@@ -213,6 +215,8 @@ const currentStageIndex = computed(() => { ...@@ -213,6 +215,8 @@ const currentStageIndex = computed(() => {
font-weight: 400; font-weight: 400;
color: rgba(95, 101, 108, 1); color: rgba(95, 101, 108, 1);
line-height: 24px; line-height: 24px;
flex: 1;
min-width: 0;
} }
.sponsor-name { .sponsor-name {
...@@ -225,11 +229,11 @@ const currentStageIndex = computed(() => { ...@@ -225,11 +229,11 @@ const currentStageIndex = computed(() => {
gap: 8px; gap: 8px;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
flex: 1;
min-width: 0;
} }
.meta-row-progress { .meta-row-progress {
left: 0;
right: 0;
width: 100%; width: 100%;
} }
.meta-row-progress :deep(.progress-bar) { .meta-row-progress :deep(.progress-bar) {
......
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
</div> </div>
</div> </div>
</div> </div>
<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>
...@@ -854,9 +854,11 @@ onMounted(() => { ...@@ -854,9 +854,11 @@ onMounted(() => {
margin-top: 10px; margin-top: 10px;
.main-item { .main-item {
width: 1014px; width: 1014px;
margin-bottom: 40px;
display: flex; display: flex;
flex-direction: row;
align-items: flex-start;
margin-bottom: 20px;
position: relative;
.time { .time {
width: 77px; width: 77px;
box-sizing: border-box; box-sizing: border-box;
...@@ -995,7 +997,16 @@ onMounted(() => { ...@@ -995,7 +997,16 @@ onMounted(() => {
} }
} }
} }
.main-item::after {
content: '';
position: absolute;
left: 109px;
top: 24px;
bottom: -20px;
width: 1px;
background-color: rgb(230, 231, 232);
z-index: -1;
}
.line-test { .line-test {
position: absolute; position: absolute;
top: 10px; top: 10px;
......
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
</div> </div>
</div> </div>
</div> </div>
<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>
...@@ -606,9 +606,12 @@ const companyList = ref([ ...@@ -606,9 +606,12 @@ const companyList = ref([
.main-item { .main-item {
width: 100%; width: 100%;
margin-bottom: 40px;
display: flex; display: flex;
display: flex;
flex-direction: row;
align-items: flex-start;
margin-bottom: 20px;
position: relative;
.time { .time {
width: 77px; width: 77px;
box-sizing: border-box; box-sizing: border-box;
...@@ -717,7 +720,16 @@ const companyList = ref([ ...@@ -717,7 +720,16 @@ const companyList = ref([
} }
} }
} }
.main-item::after {
content: '';
position: absolute;
left: 109px;
top: 24px;
bottom: -20px;
width: 1px;
background-color: rgb(230, 231, 232);
z-index: -1;
}
.line-test { .line-test {
position: absolute; position: absolute;
top: 10px; top: 10px;
......
...@@ -78,7 +78,7 @@ const props = defineProps({ ...@@ -78,7 +78,7 @@ const props = defineProps({
type: String, type: String,
default: '' default: ''
}, },
chartTypeList : { chartTypeList: {
type: Array, type: Array,
default: [] default: []
} }
...@@ -88,15 +88,19 @@ const chartItemList = computed(() => { ...@@ -88,15 +88,19 @@ const chartItemList = computed(() => {
let arr = [] let arr = []
props.chartTypeList.forEach(item => { props.chartTypeList.forEach(item => {
defaultChartTypeList.forEach(val => { defaultChartTypeList.forEach(val => {
if(val.name === item) { if (val.name === item) {
arr.push(val) arr.push(val)
} }
}) })
}) })
arr.forEach(item => { arr.forEach(item => {
item.active = false item.active = false
}) })
arr[0].active = true arr[0].active = true
// console.log('arr', arr);
return arr return arr
}) })
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论