diff --git a/web/public/report_template.docx b/web/public/report_template.docx index 82a661a..29b5d44 100644 Binary files a/web/public/report_template.docx and b/web/public/report_template.docx differ diff --git a/web/src/utils/report.js b/web/src/utils/report.js index 7f7b4f4..51e58d8 100644 --- a/web/src/utils/report.js +++ b/web/src/utils/report.js @@ -12,6 +12,11 @@ import { export const generateReport = async (detailData) => { try { + // Validate input + if (!detailData || typeof detailData !== 'object') { + throw new Error('无效的详情数据') + } + // Load the template const response = await fetch('/report_template.docx') if (!response.ok) { @@ -23,99 +28,146 @@ export const generateReport = async (detailData) => { const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true, - delimiters: { start: '[', end: ']' }, + delimiters: { start: '${', end: '}' }, + // 当字段不存在时,保持原始的 ${字段名} 格式,不替换 + nullGetter: (part) => { + if (!part.module) { + return '${' + part.value + '}' + } + return '' + }, }) - // Helper to get safe number - const getNum = (val) => (val || val === 0 ? Number(val) : '-') - const getWan = (val) => (val || val === 0 ? formatNumberValue(val) : '-') - - // Prepare data - const data = { - ...detailData, - // Summary - B: getWan(detailData.model_value_b), - B1: getWan(detailData.economic_value_b1), - B2: getWan(detailData.cultural_value_b2), - B3: getNum(detailData.risk_coefficient_b3), - C: getWan(detailData.market_value_c), - DPR: getNum(detailData.pledge_rate), - - // Basic Info - asset_name: detailData.asset_name || '-', - institution: detailData.institution || '-', - credit_code: detailData.credit_code || detailData.credit_code_or_id || '-', - industry: detailData.industry || '-', - business_heritage_intro: detailData.business_heritage_intro || detailData.biz_intro || '-', - - // Finance - annual_revenue: getWan(detailData.annual_revenue), - rd_investment: getWan(detailData.rd_investment), - three_year_income: Array.isArray(detailData.three_year_income) ? detailData.three_year_income.join(',') : '-', - funding_status: detailData.funding_status || '-', - - // Tech / Non-heritage attributes - heritage_level: detailData.heritage_level || detailData.heritage_asset_level || '-', - inheritor_age_count_50: detailData.inheritor_age_count?.[0] || 0, - inheritor_age_count_50_70: detailData.inheritor_age_count?.[1] || 0, - inheritor_age_count_70: detailData.inheritor_age_count?.[2] || 0, - - historical_evidence_artifacts: detailData.historical_evidence?.artifacts || 0, - historical_evidence_literature: detailData.historical_evidence?.ancient_literature || 0, - historical_evidence_testimony: detailData.historical_evidence?.inheritor_testimony || 0, - historical_evidence_research: detailData.historical_evidence?.modern_research || 0, - - offline_activities: getNum(detailData.offline_activities ?? detailData.offline_teaching_count), - online_clicks: getWan(detailData.online_clicks), // Assuming this field exists or needs mapping - - // Market - platform_accounts_bilibili: detailData.platform_accounts?.bilibili?.account || '-', - platform_accounts_douyin: detailData.platform_accounts?.douyin?.account || '-', - price_fluctuation_min: detailData.price_fluctuation?.[0] || '-', - price_fluctuation_max: detailData.price_fluctuation?.[1] || '-', - monthly_transaction: detailData.monthly_transaction || detailData.monthly_transaction_amount || '-', - circulation: detailData.circulation || detailData.scarcity_level || '-', - - // Detailed Parameters (Assuming these keys exist in detailData or calculation_result) - // Economic Value B1 - B11: getWan(detailData.basic_value_b11), - F: getWan(detailData.financial_value_p), - L: getNum(detailData.legal_strength_l), - D: getNum(detailData.development_potential_d), - - B12: getNum(detailData.traffic_factor_b12), - search_index_ratio: getNum(detailData.search_index_ratio), - S3: getNum(detailData.social_media_spread_s3), - - B13: getNum(detailData.policy_multiplier_b13), - policy_fit_score: getNum(detailData.policy_fit_score), - - // Cultural Value B2 - B21: getNum(detailData.living_inheritance_b21), - inheritor_level_score: getNum(detailData.inheritor_level_score), - teaching_frequency_score: getNum(detailData.teaching_frequency_score), - cooperation_depth_score: getNum(detailData.cooperation_depth_score), - - B22: getNum(detailData.pattern_entropy_b22), - SC: getNum(detailData.structure_complexity_sc), - H: getNum(detailData.info_entropy_h), - HI: getNum(detailData.history_inheritance_hi), - - // Risk - risk_market: getNum(detailData.risk_market), - risk_legal: getNum(detailData.risk_legal), - risk_inheritance: getNum(detailData.risk_inheritance), - - // Market Verification - C1: getWan(detailData.market_bidding_c1), - C2: getNum(detailData.heat_coefficient_c2), - C3: getNum(detailData.scarcity_multiplier_c3), - C4: getNum(detailData.time_decay_c4), - - // Current Date - current_date: new Date().toLocaleDateString('zh-CN'), + // Helper function to safely add field only if it exists + const addIfExists = (obj, key, value) => { + if (value !== undefined && value !== null) { + obj[key] = value + } } + // Extract calculation data + const calcInput = detailData.calculation_input || {} + const calcResult = detailData.calculation_result || {} + const modelData = calcInput.model_data || {} + const ecoData = modelData.economic_data || {} + const cultData = modelData.cultural_data || {} + const riskData = modelData.risky_data || {} + const marketData = calcInput.market_data || {} + + // Prepare data - only include fields that actually exist + const data = { + // Spread all existing detailData fields + ...detailData, + + // Date fields (always generated) + yyyy: new Date().getFullYear(), + mm: String(new Date().getMonth() + 1).padStart(2, '0'), + dd: String(new Date().getDate()).padStart(2, '0'), + yyyymmdd: `${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}`, + } + + // Handle inheritor_age_count array - format each item + if (detailData.inheritor_age_count && Array.isArray(detailData.inheritor_age_count)) { + const ageData = formatAgeDistribution(detailData.inheritor_age_count) + // 提供格式化后的完整文本 + data.inheritor_age_count_text = ageData.join('\n') + // 提供单独的数值,支持 ${inheritor_age_count[0]} 这样的用法 + if (detailData.inheritor_age_count[0] !== undefined) { + data['inheritor_age_count[0]'] = detailData.inheritor_age_count[0] + } + if (detailData.inheritor_age_count[1] !== undefined) { + data['inheritor_age_count[1]'] = detailData.inheritor_age_count[1] + } + if (detailData.inheritor_age_count[2] !== undefined) { + data['inheritor_age_count[2]'] = detailData.inheritor_age_count[2] + } + } + + // Handle historical_evidence object - format as text + if (detailData.historical_evidence && typeof detailData.historical_evidence === 'object') { + const evidenceData = formatHistoricalEvidence(detailData.historical_evidence) + // 提供格式化后的完整文本(换行分隔) + data.historical_evidence = evidenceData.join('\n') + } + + // Handle platform_accounts object - format as text + if (detailData.platform_accounts && typeof detailData.platform_accounts === 'object') { + const accountsData = formatPlatformAccounts(detailData.platform_accounts) + // 提供格式化后的完整文本(换行分隔) + data.platform_accounts = accountsData.join('\n') + } + + // Handle price_fluctuation array + if (detailData.price_fluctuation && Array.isArray(detailData.price_fluctuation)) { + const min = detailData.price_fluctuation[0] + const max = detailData.price_fluctuation[1] + + // 提供单独的数值,支持 ${price_fluctuation[0]} 这样的用法 + if (min !== undefined) { + data['price_fluctuation[0]'] = min + data.price_min = min + } + if (max !== undefined) { + data['price_fluctuation[1]'] = max + data.price_max = max + } + + // 提供多种格式的范围文本 + if (min !== undefined && max !== undefined) { + // 简单范围(数字-数字) + data.price_range = `${min}-${max}` + // 带"元"的范围 + data.price_range_yuan = `${min}-${max}元` + // 带货币符号的范围 + data.price_fluctuation_range = formatPriceRange(detailData.price_fluctuation) + // 精确匹配模板中的字段名(作为一个整体的字段名) + data['price_fluctuation[0]-price_fluctuation[1]'] = `${min}-${max}` + } + } + + // Add calculation results if they exist + if (calcResult.model_value_b !== undefined) data.B = formatNumberValue(calcResult.model_value_b) + if (calcResult.economic_value_b1 !== undefined) data.B1 = formatNumberValue(calcResult.economic_value_b1) + if (calcResult.cultural_value_b2 !== undefined) data.B2 = formatNumberValue(calcResult.cultural_value_b2) + if (calcResult.risk_adjustment_b3 !== undefined) data.B3 = calcResult.risk_adjustment_b3 + if (calcResult.market_value_c !== undefined) data.C = formatNumberValue(calcResult.market_value_c) + + // Add DPR if exists + const dpr = detailData.drp_result?.dynamic_pledge_rate || detailData.dynamic_pledge_rate + if (dpr !== undefined) data.DPR = dpr + + // Add economic data fields if they exist + if (ecoData.basic_value_b11 !== undefined) data.B11 = formatNumberValue(ecoData.basic_value_b11) + if (ecoData.financial_value !== undefined) data.F = formatNumberValue(ecoData.financial_value) + if (ecoData.legal_strength !== undefined) data.L = ecoData.legal_strength + if (ecoData.development_potential !== undefined) data.D = ecoData.development_potential + if (ecoData.traffic_factor_b12 !== undefined) data.B12 = ecoData.traffic_factor_b12 + if (ecoData.search_index_s1 !== undefined) data.search_index_ratio = ecoData.search_index_s1 + if (ecoData.social_media_spread_s3 !== undefined) data.S3 = ecoData.social_media_spread_s3 + if (ecoData.policy_multiplier_b13 !== undefined) data.B13 = ecoData.policy_multiplier_b13 + if (ecoData.policy_compatibility_score !== undefined) data.policy_fit_score = ecoData.policy_compatibility_score + + // Add cultural data fields if they exist + if (cultData.living_inheritance_b21 !== undefined) data.B21 = cultData.living_inheritance_b21 + if (cultData.inheritor_level_coefficient !== undefined) data.inheritor_level_score = cultData.inheritor_level_coefficient + if (cultData.teaching_frequency !== undefined) data.teaching_frequency_score = cultData.teaching_frequency + if (cultData.cross_border_depth !== undefined) data.cooperation_depth_score = cultData.cross_border_depth + if (cultData.pattern_entropy_b22 !== undefined) data.B22 = cultData.pattern_entropy_b22 + if (cultData.structure_complexity !== undefined) data.SC = cultData.structure_complexity + if (cultData.normalized_entropy !== undefined) data.H = cultData.normalized_entropy + if (cultData.historical_inheritance !== undefined) data.HI = cultData.historical_inheritance + + // Add risk data fields if they exist + if (riskData.risk_market !== undefined) data.risk_market = riskData.risk_market + if (riskData.risk_legal !== undefined) data.risk_legal = riskData.risk_legal + if (riskData.risk_inheritance !== undefined) data.risk_inheritance = riskData.risk_inheritance + + // Add market data fields if they exist + if (marketData.market_bidding_c1 !== undefined) data.C1 = formatNumberValue(marketData.market_bidding_c1) + if (marketData.heat_coefficient_c2 !== undefined) data.C2 = marketData.heat_coefficient_c2 + if (marketData.scarcity_multiplier_c3 !== undefined) data.C3 = marketData.scarcity_multiplier_c3 + if (marketData.time_decay_c4 !== undefined) data.C4 = marketData.time_decay_c4 + doc.render(data) const out = doc.getZip().generate({ diff --git a/web/src/views/valuation/audit/components/AuditDetail.vue b/web/src/views/valuation/audit/components/AuditDetail.vue index 64c0ad6..f754473 100644 --- a/web/src/views/valuation/audit/components/AuditDetail.vue +++ b/web/src/views/valuation/audit/components/AuditDetail.vue @@ -63,9 +63,9 @@ const detailSections = computed(() => { fields: [ { label: '资产名称', type: 'text', value: detail.asset_name || '-' }, { label: '所属机构/权利人', type: 'text', value: detail.institution || '-' }, - { label: '统一社会信用代码/身份证号', type: 'text', value: detail.credit_code || detail.credit_code_or_id || '-' }, + { label: '统一社会信用代码/身份证号', type: 'text', value: detail.credit_code_or_id || '-' }, { label: '所属行业', type: 'text', value: detail.industry || '-' }, - { label: '业务/传承介绍', type: 'text', value: detail.business_heritage_intro || detail.biz_intro || '-' }, + { label: '业务/传承介绍', type: 'text', value: detail.biz_intro || '-' }, ], }, { @@ -74,8 +74,7 @@ const detailSections = computed(() => { fields: [ { label: '近12个月机构营收/万元', type: 'text', value: formatNumberValue(detail.annual_revenue) }, { label: '近12个月机构研发投入/万元', type: 'text', value: formatNumberValue(detail.rd_investment) }, - { label: '近三年机构收益/万元', type: 'text', value: formatThreeYearIncome(detail.three_year_income) }, - // { label: '近三年机构收益/万元', type: 'list', value: formatThreeYearIncome(detail.three_year_income) }, + { label: '近三年机构收益/万元', type: 'list', value: formatThreeYearIncome(detail.three_year_income) }, { label: '资产受资助情况', type: 'text', value: detail.funding_status || '-' }, ], }, @@ -86,15 +85,13 @@ const detailSections = computed(() => { { label: '非遗传承人等级', type: 'text', value: detail.inheritor_level || '-' }, { label: '非遗传承人年龄水平及数量', - type: 'text', - // type: 'list', + type: 'list', value: formatAgeDistribution(detail.inheritor_age_count || detail.inheritor_ages), }, { label: '非遗传承人等级证书', type: 'images', value: detail.inheritor_certificates || [] }, { label: '非遗等级', type: 'text', value: detail.heritage_level || detail.heritage_asset_level || '-' }, { label: '非遗资产所用专利的申请号', type: 'text', value: detail.patent_application_no || '-' }, - { label: '非遗资产历史证明证据及数量', type: 'text', value: formatHistoricalEvidence(detail.historical_evidence) }, - // { label: '非遗资产历史证明证据及数量', type: 'list', value: formatHistoricalEvidence(detail.historical_evidence) }, + { label: '非遗资产历史证明证据及数量', type: 'list', value: formatHistoricalEvidence(detail.historical_evidence) }, { label: '非遗资产所用专利/纹样图片', type: 'images', @@ -114,8 +111,7 @@ const detailSections = computed(() => { type: 'text', value: formatNumberValue(detail.offline_activities ?? detail.offline_teaching_count), }, - { label: '线上相关宣传账号信息', type: 'text', value: formatPlatformAccounts(detail.platform_accounts) }, - // { label: '线上相关宣传账号信息', type: 'list', value: formatPlatformAccounts(detail.platform_accounts) }, + { label: '线上相关宣传账号信息', type: 'list', value: formatPlatformAccounts(detail.platform_accounts) }, ], }, { @@ -146,7 +142,7 @@ const detailSections = computed(() => { title: field.label, key: field.label, width: 200, - ellipsis: { + ellipsis: field.type === 'list' ? false : { tooltip: true, }, render: (row) => { @@ -155,7 +151,7 @@ const detailSections = computed(() => { if (fieldData.type === 'list') { if (fieldData.value && fieldData.value.length) { - return h('div', { class: 'cell-multi' }, + return h('div', { style: 'display: flex; flex-direction: column; gap: 4px;' }, fieldData.value.map(item => h('span', item)) ) } @@ -305,7 +301,9 @@ const mockFlowHtml = ref(` // 证书相关功能 const handleUploadCertificate = () => { certificateModalMode.value = 'upload' - certificateData.value = {} + certificateData.value = { + detailData: props.detailData + } certificateModalVisible.value = true } diff --git a/web/src/views/valuation/audit/components/CertificateModal.vue b/web/src/views/valuation/audit/components/CertificateModal.vue index 0cce982..441aec4 100644 --- a/web/src/views/valuation/audit/components/CertificateModal.vue +++ b/web/src/views/valuation/audit/components/CertificateModal.vue @@ -165,6 +165,10 @@ const isUploadMode = computed(() => props.mode === 'upload') // 下载报告 const handleDownloadReport = async () => { try { + if (!props.certificateData?.detailData) { + message.error('缺少详情数据,无法生成报告') + return + } message.loading('正在生成报告...') await generateReport(props.certificateData.detailData) message.success('报告生成并下载成功')