feat(报告): 实现评估报告生成与展示功能
- 新增报告模板文件及生成工具模块 - 在拦截器中添加 isRaw 配置支持原始响应 - 实现报告内容获取接口及前端展示逻辑 - 完善发票模块的附件处理功能 - 优化用户管理界面的默认值设置 - 移除用户列表中的备注列显示
This commit is contained in:
commit
97b872aa9b
Binary file not shown.
@ -68,5 +68,6 @@ export default {
|
||||
request.post(`/valuations/${data.valuation_id || data.id}/reject`, { admin_notes: data.admin_notes }),
|
||||
updateValuationNotes: (data = {}) =>
|
||||
request.put(`/valuations/${data.valuation_id || data.id}/admin-notes`, { admin_notes: data.admin_notes }),
|
||||
getValuationReport: (params = {}) => request.get(`/valuations/${params.valuation_id || params.id}/report`, { isRaw: true }),
|
||||
sendSmsReport: (data = {}) => request.post('/sms/send-report', data),
|
||||
}
|
||||
|
||||
@ -21,7 +21,10 @@ export function reqReject(error) {
|
||||
}
|
||||
|
||||
export function resResolve(response) {
|
||||
const { data, status, statusText } = response
|
||||
const { data, status, statusText, config } = response
|
||||
if (config?.isRaw) {
|
||||
return Promise.resolve(data)
|
||||
}
|
||||
if (data?.code !== 200) {
|
||||
const code = data?.code ?? status
|
||||
/** 根据code处理对应的操作,并返回处理后的message */
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
/**
|
||||
* 报告生成工具模块
|
||||
*
|
||||
* 功能说明:
|
||||
* 1. 基于 Word 模板生成非遗资产评估报告
|
||||
* 2. 使用 Docxtemplater 进行模板变量替换
|
||||
* 3. 自动格式化数值、枚举、日期等字段
|
||||
* 4. 支持复杂数据结构(数组、对象)的格式化
|
||||
*/
|
||||
|
||||
import PizZip from 'pizzip'
|
||||
import Docxtemplater from 'docxtemplater'
|
||||
import { saveAs } from 'file-saver'
|
||||
@ -7,29 +17,59 @@ import {
|
||||
formatAgeDistribution,
|
||||
formatHistoricalEvidence,
|
||||
formatPlatformAccounts,
|
||||
formatPriceRange
|
||||
formatPriceRange,
|
||||
formatEnumByKey
|
||||
} from '@/views/valuation/audit/utils'
|
||||
|
||||
/**
|
||||
* 生成评估报告
|
||||
*
|
||||
* @param {Object} detailData - 评估详情数据对象
|
||||
* @returns {Promise<void>} - 生成并下载报告文件
|
||||
*
|
||||
* 数据结构说明:
|
||||
* - detailData: 包含所有评估基础信息
|
||||
* - detailData.calculation_input: 计算输入数据
|
||||
* - model_data: 模型数据
|
||||
* - economic_data: 经济价值数据
|
||||
* - cultural_data: 文化价值数据
|
||||
* - risky_data: 风险数据
|
||||
* - market_data: 市场数据
|
||||
* - detailData.calculation_result: 计算结果数据
|
||||
*
|
||||
* 模板变量命名规则:
|
||||
* - 使用 ${变量名} 格式
|
||||
* - 如果数据不存在,保持原始占位符不变
|
||||
* - 支持数组索引访问,如 ${inheritor_age_count[0]}
|
||||
*/
|
||||
export const generateReport = async (detailData) => {
|
||||
try {
|
||||
// Validate input
|
||||
// ========== 1. 数据验证 ==========
|
||||
if (!detailData || typeof detailData !== 'object') {
|
||||
throw new Error('无效的详情数据')
|
||||
}
|
||||
|
||||
// Load the template
|
||||
// ========== 2. 加载 Word 模板文件 ==========
|
||||
const response = await fetch('/report_template.docx')
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load report template')
|
||||
}
|
||||
const content = await response.arrayBuffer()
|
||||
|
||||
// ========== 3. 初始化 Docxtemplater ==========
|
||||
const zip = new PizZip(content)
|
||||
const doc = new Docxtemplater(zip, {
|
||||
paragraphLoop: true,
|
||||
linebreaks: true,
|
||||
delimiters: { start: '${', end: '}' },
|
||||
// 当字段不存在时,保持原始的 ${字段名} 格式,不替换
|
||||
paragraphLoop: true, // 启用段落循环
|
||||
linebreaks: true, // 支持换行符
|
||||
delimiters: { start: '${', end: '}' }, // 模板变量分隔符
|
||||
|
||||
/**
|
||||
* nullGetter: 自定义空值处理器
|
||||
* 当模板中的变量在数据对象中不存在时的处理逻辑
|
||||
*
|
||||
* 策略:保持原始占位符格式 ${字段名},不进行替换
|
||||
* 这样可以在后续手动填写或调试时识别缺失的字段
|
||||
*/
|
||||
nullGetter: (part) => {
|
||||
if (!part.module) {
|
||||
return '${' + part.value + '}'
|
||||
@ -38,40 +78,66 @@ export const generateReport = async (detailData) => {
|
||||
},
|
||||
})
|
||||
|
||||
// Helper function to safely add field only if it exists
|
||||
// ========== 4. 辅助函数定义 ==========
|
||||
|
||||
/**
|
||||
* 安全添加字段(已定义但未使用,保留供未来扩展)
|
||||
* @param {Object} obj - 目标对象
|
||||
* @param {string} key - 字段名
|
||||
* @param {*} value - 字段值
|
||||
*/
|
||||
const addIfExists = (obj, key, value) => {
|
||||
if (value !== undefined && value !== null) {
|
||||
obj[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Extract calculation data
|
||||
// ========== 5. 提取计算相关数据 ==========
|
||||
|
||||
// 计算输入数据
|
||||
const calcInput = detailData.calculation_input || {}
|
||||
// 计算结果数据
|
||||
const calcResult = detailData.calculation_result || {}
|
||||
// 模型数据
|
||||
const modelData = calcInput.model_data || {}
|
||||
// 经济价值数据(B1 相关)
|
||||
const ecoData = modelData.economic_data || {}
|
||||
// 文化价值数据(B2 相关)
|
||||
const cultData = modelData.cultural_data || {}
|
||||
// 风险数据(B3 相关)
|
||||
const riskData = modelData.risky_data || {}
|
||||
// 市场数据(C 相关)
|
||||
const marketData = calcInput.market_data || {}
|
||||
|
||||
// Prepare data - only include fields that actually exist
|
||||
// ========== 6. 构建报告数据对象 ==========
|
||||
|
||||
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')}`,
|
||||
// ========== 6.1 日期字段(始终生成当前日期) ==========
|
||||
yyyy: new Date().getFullYear(), // 年份,如 2025
|
||||
mm: String(new Date().getMonth() + 1).padStart(2, '0'), // 月份,如 11
|
||||
dd: String(new Date().getDate()).padStart(2, '0'), // 日期,如 27
|
||||
yyyymmdd: `${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}`, // 完整日期,如 20251127
|
||||
}
|
||||
|
||||
// Handle inheritor_age_count array - format each item
|
||||
// ========== 6.2 处理非遗传承人年龄分布数据 ==========
|
||||
/**
|
||||
* inheritor_age_count: 数组格式 [≤50岁人数, 50-70岁人数, ≥70岁人数]
|
||||
*
|
||||
* 生成的字段:
|
||||
* - inheritor_age_count_text: 格式化后的完整文本(换行分隔)
|
||||
* - inheritor_age_count[0]: ≤50岁人数
|
||||
* - inheritor_age_count[1]: 50-70岁人数
|
||||
* - inheritor_age_count[2]: ≥70岁人数
|
||||
*/
|
||||
if (detailData.inheritor_age_count && Array.isArray(detailData.inheritor_age_count)) {
|
||||
const ageData = formatAgeDistribution(detailData.inheritor_age_count)
|
||||
// 提供格式化后的完整文本
|
||||
// 格式化后的完整文本,如 "≤50岁:10\n50-70岁:20\n≥70岁:5"
|
||||
data.inheritor_age_count_text = ageData.join('\n')
|
||||
// 提供单独的数值,支持 ${inheritor_age_count[0]} 这样的用法
|
||||
|
||||
// 提供单独的数值访问,支持模板中使用 ${inheritor_age_count[0]} 等
|
||||
if (detailData.inheritor_age_count[0] !== undefined) {
|
||||
data['inheritor_age_count[0]'] = detailData.inheritor_age_count[0]
|
||||
}
|
||||
@ -83,60 +149,121 @@ export const generateReport = async (detailData) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle historical_evidence object - format as text
|
||||
// ========== 6.3 处理历史证明证据数据 ==========
|
||||
/**
|
||||
* historical_evidence: 对象格式
|
||||
* {
|
||||
* artifacts: 出土实物数量,
|
||||
* ancient_literature: 古代文献数量,
|
||||
* inheritor_testimony: 传承人佐证数量,
|
||||
* modern_research: 现代研究数量
|
||||
* }
|
||||
*
|
||||
* 生成的字段:
|
||||
* - historical_evidence: 格式化后的文本(换行分隔)
|
||||
* 如 "出土实物:5\n古代文献:10\n传承人佐证:3\n现代研究:8"
|
||||
*/
|
||||
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
|
||||
// ========== 6.4 处理平台账号信息数据 ==========
|
||||
/**
|
||||
* platform_accounts: 对象格式
|
||||
* {
|
||||
* bilibili: { account: 'xxx', likes: 1000, comments: 200, shares: 50 },
|
||||
* douyin: { account: 'yyy', likes: 5000, comments: 800, shares: 300 },
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* 生成的字段:
|
||||
* - platform_accounts: 格式化后的文本(换行分隔)
|
||||
* 如 "B站账号:xxx(赞1,000 / 评200 / 转50)\n抖音账号:yyy(赞5,000 / 评800 / 转300)"
|
||||
*/
|
||||
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
|
||||
// ========== 6.5 处理价格波动区间数据 ==========
|
||||
/**
|
||||
* price_fluctuation: 数组格式 [最低价, 最高价]
|
||||
*
|
||||
* 生成的字段:
|
||||
* - price_fluctuation[0]: 最低价
|
||||
* - price_min: 最低价(别名)
|
||||
* - price_fluctuation[1]: 最高价
|
||||
* - price_max: 最高价(别名)
|
||||
* - price_range: 简单范围,如 "100-500"
|
||||
* - price_range_yuan: 带单位范围,如 "100-500元"
|
||||
* - price_fluctuation_range: 带货币符号范围,如 "¥100.00 - ¥500.00"
|
||||
* - price_fluctuation[0]-price_fluctuation[1]: 组合字段名,如 "100-500"
|
||||
*/
|
||||
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}`
|
||||
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
|
||||
// ========== 6.6 添加计算结果字段 ==========
|
||||
/**
|
||||
* 主要计算结果(顶层):
|
||||
* - B: 模型价值
|
||||
* - B1: 经济价值
|
||||
* - B2: 文化价值
|
||||
* - B3: 风险调整系数
|
||||
* - C: 市场价值
|
||||
*/
|
||||
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
|
||||
// ========== 6.7 添加动态质押率(DPR) ==========
|
||||
/**
|
||||
* DPR (Dynamic Pledge Rate): 动态质押率
|
||||
* 优先从 drp_result.dynamic_pledge_rate 获取,其次从 dynamic_pledge_rate 获取
|
||||
*/
|
||||
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
|
||||
// ========== 6.8 添加经济价值相关字段(B1 细分) ==========
|
||||
/**
|
||||
* 经济价值 B1 = B11 × B12 × B13
|
||||
*
|
||||
* B11: 基础价值 = F × L × D
|
||||
* - F: 财务价值
|
||||
* - L: 法律强度
|
||||
* - D: 发展潜力
|
||||
*
|
||||
* B12: 流量因子
|
||||
* - search_index_ratio (S1): 搜索指数比
|
||||
* - social_media_spread_s3 (S3): 社交媒体传播度
|
||||
*
|
||||
* B13: 政策乘数
|
||||
* - policy_fit_score: 政策契合度评分
|
||||
*/
|
||||
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
|
||||
@ -147,7 +274,20 @@ export const generateReport = async (detailData) => {
|
||||
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
|
||||
// ========== 6.9 添加文化价值相关字段(B2 细分) ==========
|
||||
/**
|
||||
* 文化价值 B2 = B21 × B22
|
||||
*
|
||||
* B21: 活态传承价值
|
||||
* - inheritor_level_score: 传承人等级系数
|
||||
* - teaching_frequency_score: 授课频次
|
||||
* - cooperation_depth_score: 跨界合作深度
|
||||
*
|
||||
* B22: 纹样熵值
|
||||
* - SC: 结构复杂度
|
||||
* - H: 归一化熵
|
||||
* - HI: 历史传承度
|
||||
*/
|
||||
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
|
||||
@ -157,25 +297,83 @@ export const generateReport = async (detailData) => {
|
||||
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
|
||||
// ========== 6.10 添加风险数据字段(B3 相关) ==========
|
||||
/**
|
||||
* 风险调整系数 B3 综合考虑:
|
||||
* - risk_market: 市场风险
|
||||
* - risk_legal: 法律风险
|
||||
* - risk_inheritance: 传承风险
|
||||
*/
|
||||
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
|
||||
// ========== 6.11 添加市场数据字段(C 细分) ==========
|
||||
/**
|
||||
* 市场价值 C = C1 × C2 × C3 × C4
|
||||
*
|
||||
* - C1: 市场竞价基准
|
||||
* - C2: 热度系数
|
||||
* - C3: 稀缺性乘数
|
||||
* - C4: 时间衰减系数
|
||||
*/
|
||||
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
|
||||
|
||||
// ========== 6.12 格式化枚举字段 ==========
|
||||
/**
|
||||
* 枚举字段需要从数字代码转换为中文描述
|
||||
*
|
||||
* 辅助函数:
|
||||
* - pickValue: 从多个可能的字段中选择第一个有效值(兼容不同版本的字段名)
|
||||
* - formatEnum: 将枚举值格式化为中文描述
|
||||
*
|
||||
* 枚举字段列表:
|
||||
* 1. funding_status: 资助情况(国家级/省级/无)
|
||||
* 2. inheritor_level: 传承人等级(国家级/省级/市级)
|
||||
* 3. heritage_level: 非遗等级(国家级/省级/国家文化数字化清单/无)
|
||||
* 4. application_maturity: 应用成熟度(成熟应用/推广阶段/试点阶段)
|
||||
* 5. application_coverage: 应用覆盖范围(全球/全国/区域)
|
||||
* 6. cooperation_depth: 跨界合作深度(无/品牌联名/科技载体/国家外交礼品)
|
||||
* 7. circulation: 发行量/稀缺性(孤品/限量/稀有/流通)
|
||||
* 8. last_market_activity: 最近市场活动时间(近一周/近一月/近一年/其他)
|
||||
* 9. monthly_transaction: 月交易额水平(<100万/100-500万/≥500万)
|
||||
*/
|
||||
const pickValue = (...args) => args.find(v => v !== undefined && v !== null && v !== '')
|
||||
const formatEnum = (key, ...values) => formatEnumByKey(pickValue(...values), key)
|
||||
|
||||
data.funding_status = formatEnum('fundingStatus', detailData.funding_status)
|
||||
data.inheritor_level = formatEnum('inheritorLevel', detailData.inheritor_level)
|
||||
data.heritage_level = formatEnum('heritageLevel', detailData.heritage_level, detailData.heritage_asset_level)
|
||||
data.application_maturity = formatEnum('applicationMaturity', detailData.application_maturity, detailData.implementation_stage)
|
||||
data.application_coverage = formatEnum('applicationCoverage', detailData.application_coverage, detailData.coverage_area)
|
||||
data.cooperation_depth = formatEnum('cooperationDepth', detailData.cooperation_depth, detailData.collaboration_type)
|
||||
data.circulation = formatEnum('circulation', detailData.circulation, detailData.scarcity_level)
|
||||
data.last_market_activity = formatEnum('marketActivity', detailData.last_market_activity, detailData.market_activity_time)
|
||||
data.monthly_transaction = formatEnum('monthlyTransaction', detailData.monthly_transaction, detailData.monthly_transaction_amount)
|
||||
|
||||
// ========== 7. 渲染模板 ==========
|
||||
/**
|
||||
* 使用准备好的数据对象渲染 Word 模板
|
||||
* Docxtemplater 会将模板中的 ${变量名} 替换为对应的值
|
||||
*/
|
||||
doc.render(data)
|
||||
|
||||
// ========== 8. 生成并下载文件 ==========
|
||||
/**
|
||||
* 将渲染后的文档生成为 Blob 对象
|
||||
* 使用 file-saver 库触发浏览器下载
|
||||
*/
|
||||
const out = doc.getZip().generate({
|
||||
type: 'blob',
|
||||
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
})
|
||||
|
||||
// 下载文件,文件名使用资产名称,默认为"评估报告"
|
||||
saveAs(out, `${detailData.asset_name || '评估报告'}.docx`)
|
||||
|
||||
} catch (error) {
|
||||
console.error('Report generation failed:', error)
|
||||
throw error
|
||||
|
||||
@ -40,12 +40,34 @@ watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
formData.value = {
|
||||
email: props.invoiceData?.email || '',
|
||||
content: '',
|
||||
attachments: [],
|
||||
// 如果是查看模式且有 extra 数据,则回显 extra 中的内容
|
||||
const extra = props.invoiceData?.extra
|
||||
if (props.mode === 'view' && extra) {
|
||||
formData.value = {
|
||||
email: extra.email || props.invoiceData?.email || '',
|
||||
content: extra.body || '',
|
||||
attachments: extra.file_urls || [],
|
||||
}
|
||||
// 回显附件列表
|
||||
if (extra.file_urls && Array.isArray(extra.file_urls)) {
|
||||
fileList.value = extra.file_urls.map((url, index) => ({
|
||||
id: `file-${index}`,
|
||||
name: url.split('/').pop() || `附件${index + 1}`,
|
||||
url: url,
|
||||
status: 'finished',
|
||||
}))
|
||||
} else {
|
||||
fileList.value = []
|
||||
}
|
||||
} else {
|
||||
// 开票模式,使用默认邮箱
|
||||
formData.value = {
|
||||
email: props.invoiceData?.email || '',
|
||||
content: '',
|
||||
attachments: [],
|
||||
}
|
||||
fileList.value = []
|
||||
}
|
||||
fileList.value = []
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -295,6 +295,7 @@ async function handleInvoiceConfirm(formData) {
|
||||
email: formData.email,
|
||||
subject: formData.email, // 用户要求 subject 传 email
|
||||
body: formData.content, // 映射 content -> body
|
||||
file_urls: formData.attachments, // 映射 attachments -> file_url
|
||||
file_url: formData.attachments, // 映射 attachments -> file_url
|
||||
status:'success'
|
||||
}
|
||||
|
||||
@ -36,8 +36,8 @@ const currentRemaining = computed(() => props.userData?.remaining_count ?? 0)
|
||||
watch(() => props.userData, (newData) => {
|
||||
if (newData && Object.keys(newData).length > 0) {
|
||||
limitForm.value = {
|
||||
targetCount: newData.remaining_count || 0,
|
||||
quotaType: newData.user_type || '免费体验',
|
||||
targetCount: 0,
|
||||
quotaType: '免费体验',
|
||||
remark: ''
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,16 +108,16 @@ const columns = [
|
||||
return row.created_at ? formatDate(row.created_at) : '-'
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
key: 'notes',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
ellipsis: { tooltip: true },
|
||||
render(row) {
|
||||
return row.notes || '-'
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: '备注',
|
||||
// key: 'notes',
|
||||
// align: 'center',
|
||||
// width: 120,
|
||||
// ellipsis: { tooltip: true },
|
||||
// render(row) {
|
||||
// return row.notes || '-'
|
||||
// },
|
||||
// },
|
||||
{
|
||||
title: '剩余体验次数',
|
||||
key: 'remaining_count',
|
||||
|
||||
@ -42,6 +42,8 @@ const emit = defineEmits(['back', 'approve', 'reject'])
|
||||
const $message = useMessage()
|
||||
|
||||
const activeDetailTab = ref('audit')
|
||||
const reportLoading = ref(false)
|
||||
const reportContent = ref('')
|
||||
|
||||
const pickFilledValue = (...values) => values.find((val) => val !== undefined && val !== null && val !== '')
|
||||
const formatEnumField = (key, ...values) => formatEnumByKey(pickFilledValue(...values), key)
|
||||
@ -55,9 +57,34 @@ watch(
|
||||
() => props.detailData?.id,
|
||||
() => {
|
||||
activeDetailTab.value = 'audit'
|
||||
reportContent.value = ''
|
||||
}
|
||||
)
|
||||
|
||||
// 监听 tab 切换,当切换到计算流程时加载报告
|
||||
watch(activeDetailTab, async (newTab) => {
|
||||
if (newTab === 'flow' && props.detailData?.id && !reportContent.value) {
|
||||
await fetchReport()
|
||||
}
|
||||
})
|
||||
|
||||
// 获取报告内容
|
||||
const fetchReport = async () => {
|
||||
if (!props.detailData?.id) return
|
||||
|
||||
reportLoading.value = true
|
||||
try {
|
||||
const response = await api.getValuationReport({ valuation_id: props.detailData.id })
|
||||
reportContent.value = response.data || response || ''
|
||||
} catch (error) {
|
||||
console.error('获取报告失败:', error)
|
||||
$message.error('获取报告失败')
|
||||
reportContent.value = '# 获取报告失败\n\n请稍后重试'
|
||||
} finally {
|
||||
reportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const detailSections = computed(() => {
|
||||
const detail = props.detailData
|
||||
if (!detail) return []
|
||||
@ -232,10 +259,8 @@ const calcFlow = computed(() => props.detailData?.calculation_result?.flow || []
|
||||
|
||||
|
||||
|
||||
const mockFlowHtml = ref(mockReportMarkdown)
|
||||
|
||||
const renderedFlowHtml = computed(() => {
|
||||
return marked.parse(mockFlowHtml.value)
|
||||
return marked.parse(reportContent.value || mockReportMarkdown)
|
||||
})
|
||||
|
||||
|
||||
@ -351,7 +376,7 @@ const handleCertificateConfirm = async (data) => {
|
||||
</NSpin>
|
||||
</NTabPane>
|
||||
<NTabPane name="flow" tab="计算流程">
|
||||
<NSpin :show="loading">
|
||||
<NSpin :show="reportLoading">
|
||||
<div class="markdown-body" v-html="renderedFlowHtml"></div>
|
||||
</NSpin>
|
||||
</NTabPane>
|
||||
|
||||
@ -1534,9 +1534,12 @@ const submit = () => {
|
||||
api.valuations(data).then((res) => {
|
||||
loading.value = false
|
||||
getHistoryList()
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 1000)
|
||||
message.success('评估完成 将于7个工作日内生成报告与证书 以短信形式通知')
|
||||
router.push('/user-center')
|
||||
// status.value = 'success'
|
||||
// setTimeout(() => {
|
||||
// window.location.reload()
|
||||
// }, 1000)
|
||||
})
|
||||
}
|
||||
const getHistoryList = () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user