refactor(login): move match-route logic to dedicated service and fix import

- 提取登录回跳、产品对比的路由匹配逻辑至独立的 match-route.uts 服务
- 修复登录页面导入对比服务的错误,改为导入新的 match-route 模块
- 保持原有的路由参数校验和跳转逻辑不变,仅重构代码结构
This commit is contained in:
hujun
2026-10-12 09:28:09 +08:00
parent 25cb06b71c
commit b418969e92
39 changed files with 49277 additions and 119 deletions
+57 -13
View File
@@ -1,7 +1,9 @@
import { getSession, requestJson } from '@/services/api.uts'
import { assessmentApiPayload, draftText } from '@/services/assessment.uts'
import { assessmentRequestKey } from '@/services/assessment-flow.uts'
import { contributionLabels, contributionCodes, contributionLabel } from '@/services/contribution.uts'
import { yuanToWan } from '@/services/money.uts'
import { FinancingGradeView, financingGradeFrom } from '@/packages/finance/services/financing-grade.uts'
export type AssessmentRecord = { id: string, type: string, status: string, statusLabel: string, message: string, city: string, submittedAt: string, taskId: string, stage: string, canRetry: boolean, revisionId: string, revisionNo: number, hasReport: boolean, publishedRevisionId: string }
export type AssessmentPage = { items: AssessmentRecord[], hasMore: boolean, nextCursor: string }
@@ -44,10 +46,48 @@ export async function retryAssessment(id: string, requestId: string): Promise<vo
}
export type ReportMatching = { status: string, snapshotId: string, ruleVersion: string, orderingVersion: string, eligible: string, ineligible: string, unknown: string }
export type AnalysisReport = { revisionId: string, revisionNo: number, isPreviousResult: boolean, currentStatus: string, version: number, generatedAt: string, completenessPercent: number | null, matching: ReportMatching, summary: string, facts: AssessmentField[], observations: string[], suggestions: string[], suggestionSummaries: string[], dataGaps: string[], disclaimer: string, matchingMessage: string, modelCode: string, demand: AssessmentField[], completenessText: string, completenessItems: AssessmentField[], readinessLabel: string, readinessBasis: string[], warnings: string[] }
export type AnalysisReport = { revisionId: string, revisionNo: number, isPreviousResult: boolean, currentStatus: string, version: number, generatedAt: string, completenessPercent: number | null, matching: ReportMatching, summary: string, facts: AssessmentField[], observations: string[], suggestions: string[], priorityItems: string[], dataGaps: string[], disclaimer: string, matchingMessage: string, modelCode: string, demand: AssessmentField[], completenessText: string, completenessItems: AssessmentField[], readinessLabel: string, readinessBasis: string[], warnings: string[], financingGrade: FinancingGradeView }
function reportPriorityItems(improvements: UTSJSONObject[], fields: UTSJSONObject[], basis: UTSJSONObject[]): string[] {
const items: string[] = []; const covered: string[] = []
// 仅用冻结证据中的明确问题标识合并;普通事实引用不表示同一个问题。
improvements.forEach((item: UTSJSONObject) => {
const text = draftText(item, 'text').trim()
if (text.length == 0) return
const keys: string[] = []
const refs = item['evidenceRefs'] as string[]
refs.forEach((ref: string) => {
let related: string[] = []
if (ref.startsWith('fact:')) {
const field = fields.find((entry: UTSJSONObject): boolean => draftText(entry, 'fieldKey') == ref.slice(5))
if (field != null && ['missing', 'invalid'].includes(draftText(field, 'state'))) related = [draftText(field, 'state') + ':' + ref.slice(5)]
} else if (ref.startsWith('readiness:')) {
const entry = basis.find((value: UTSJSONObject): boolean => draftText(value, 'basisId') == ref.slice(10))
if (entry != null && ['MISSING_FIELD', 'INVALID_FIELD'].includes(draftText(entry, 'code'))) {
const kind = draftText(entry, 'code') == 'MISSING_FIELD' ? 'missing' : 'invalid'
related = (entry['fieldRefs'] as string[]).map((key: string): string => kind + ':' + key)
} else if (entry != null && (entry['issueRefs'] as string[]).length > 0) related = [ref]
}
related.forEach((key: string) => { if (!keys.includes(key)) keys.push(key) })
})
if (!items.includes(text) && (keys.length == 0 || keys.some((key: string): boolean => !covered.includes(key)))) items.push(text)
keys.forEach((key: string) => { if (!covered.includes(key)) covered.push(key) })
})
fields.forEach((field: UTSJSONObject) => {
const state = draftText(field, 'state'); const key = state + ':' + draftText(field, 'fieldKey')
const label = draftText(field, 'label').trim()
if (!['missing', 'invalid'].includes(state) || covered.includes(key) || label.length == 0) return
const text = (state == 'missing' ? '补充' : '核实') + label
if (!items.includes(text)) items.push(text)
covered.push(key)
})
return items
}
export async function fetchAnalysisReport(id: string, revisionId: string = ''): Promise<AnalysisReport> {
const result = await requestJson('/assessments/' + encodeURIComponent(id) + '/report', 'GET', revisionParams(revisionId))
const version = result['resultVersion'] as number
const financingGrade = financingGradeFrom(version == 2 ? result['financingGrade'] as UTSJSONObject | null : null)
if (revisionId.length > 0 && result.getString('revisionId') != revisionId) throw new Error('报告版本不一致,请重新选择。')
if (result['assessmentId'] != id || (version != 1 && version != 2) || (version == 1 && result['reportType'] != 'self_reported_analysis') || (version == 2 && result['reportType'] != 'financing_needs_analysis')) throw new Error('报告格式不支持')
const date = new Date(draftText(result, 'generatedAt'))
@@ -74,6 +114,7 @@ export async function fetchAnalysisReport(id: string, revisionId: string = ''):
if (fact['value'] != null && fact['unit'] == 'month') value += ' 个月'
if (fact['state'] == 'invalid') value = '格式需核实'
const enumLabels: UTSJSONObject = { six_months_plus: '连续缴纳6个月以上', under_six_months: '缴纳不足6个月', none: '无' }
if (['socialSecurity', 'housingFund'].includes(draftText(fact, 'fieldKey'))) value = contributionLabel(value)
if (enumLabels[value] != null) value = enumLabels[value] as string
// v2的需求在独立区块展示,避免用途代码和同一金额重复出现。
if (version == 2 && ['requestedAmountYuan', 'requestedTermMonths', 'purposeCode'].includes(draftText(fact, 'fieldKey'))) return
@@ -108,17 +149,18 @@ export async function fetchAnalysisReport(id: string, revisionId: string = ''):
}).filter((label: string): boolean => label.length > 0)
return draftText(item, 'text') + (labels.length > 0 ? '(依据:' + labels.join(';') + ')' : '')
})
return { revisionId: draftText(result, 'revisionId'), revisionNo: result.getNumber('revisionNo') ?? 1, isPreviousResult: result.getBoolean('isPreviousResult') ?? false, currentStatus: draftText(result, 'currentStatus'), version, generatedAt, completenessPercent, matching, summary: draftText(result, 'summary'), facts, observations: explain('strengths'), suggestions: explain('improvements'), suggestionSummaries: (result['improvements'] as UTSJSONObject[]).map((item: UTSJSONObject): string => draftText(item, 'text')), dataGaps: explain('riskWarnings'),
return { revisionId: draftText(result, 'revisionId'), revisionNo: result.getNumber('revisionNo') ?? 1, isPreviousResult: result.getBoolean('isPreviousResult') ?? false, currentStatus: draftText(result, 'currentStatus'), version, generatedAt, completenessPercent, matching, summary: draftText(result, 'summary'), facts, observations: explain('strengths'), suggestions: explain('improvements'), priorityItems: reportPriorityItems(result['improvements'] as UTSJSONObject[], items, basis), dataGaps: explain('riskWarnings'),
// 新报告展示服务端冻结结果的统计;旧报告无此字段时保留原提示,不重算历史。
disclaimer: draftText(result, 'disclaimer'), matchingMessage: draftText(result, 'matchingMessage').length > 0 ? draftText(result, 'matchingMessage') : '产品资料及匹配规则尚未就绪,本报告未执行产品匹配。', modelCode: draftText(result, 'modelCode'),
demand, completenessText, completenessItems, readinessLabel: readiness['status'] == 'evaluated' ? draftText(readiness, 'label') : '未评定',
demand, completenessText, completenessItems, financingGrade, readinessLabel: readiness['status'] == 'evaluated' ? draftText(readiness, 'label') : '未评定',
readinessBasis: basis.map((item: UTSJSONObject): string => draftText(item, 'message')), warnings: result['systemWarnings'] as string[] }
}
const observations = (result['observations'] as UTSJSONObject[]).map((item: UTSJSONObject): string => {
const fact = rawFacts.find((entry: UTSJSONObject): boolean => entry['id'] == item['factId'])
return (fact == null ? '' : draftText(fact, 'label') + ':') + draftText(item, 'text')
})
return { revisionId: draftText(result, 'revisionId'), revisionNo: result.getNumber('revisionNo') ?? 1, isPreviousResult: result.getBoolean('isPreviousResult') ?? false, currentStatus: draftText(result, 'currentStatus'), version, generatedAt, completenessPercent: null, matching, summary: draftText(result, 'summary'), facts, observations, suggestions: result['suggestions'] as string[], suggestionSummaries: result['suggestions'] as string[], dataGaps: result['dataGaps'] as string[], disclaimer: draftText(result, 'disclaimer'), matchingMessage: draftText(result, 'matchingMessage'), modelCode: draftText(result, 'modelCode'), demand: [], completenessText: '旧版报告未计算资料完整度', completenessItems: [], readinessLabel: '未评定', readinessBasis: [], warnings: [] }
const legacySuggestions = (result['suggestions'] as string[]).map((text: string): string => text.trim())
return { revisionId: draftText(result, 'revisionId'), revisionNo: result.getNumber('revisionNo') ?? 1, isPreviousResult: result.getBoolean('isPreviousResult') ?? false, currentStatus: draftText(result, 'currentStatus'), version, generatedAt, completenessPercent: null, matching, summary: draftText(result, 'summary'), facts, observations, suggestions: result['suggestions'] as string[], priorityItems: legacySuggestions.filter((text: string, index: number): boolean => text.length > 0 && legacySuggestions.indexOf(text) == index), dataGaps: result['dataGaps'] as string[], disclaimer: draftText(result, 'disclaimer'), matchingMessage: draftText(result, 'matchingMessage'), modelCode: draftText(result, 'modelCode'), demand: [], completenessText: '旧版报告未计算资料完整度', completenessItems: [], readinessLabel: '未评定', readinessBasis: [], warnings: [], financingGrade }
}
function enumValue(value: string, labels: string[], codes: string[]): string | null {
@@ -140,7 +182,7 @@ export async function createAssessment(payload: UTSJSONObject): Promise<string>
const values = assessmentApiPayload(payload)
const form = new UTSJSONObject()
const textKeys = ['name', 'idCard', 'phone', 'city', 'occupation', 'company', 'enterpriseName', 'businessCode']
const moneyKeys = ['monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit']
const moneyKeys = ['monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit', 'socialSecurityBase', 'housingFundBase']
textKeys.concat(moneyKeys).forEach((key: string) => { const value = draftText(values, key).trim(); form[key] = value.length == 0 ? null : value })
if (form['idCard'] != null) form['idCard'] = (form['idCard'] as string).toUpperCase()
if (form['businessCode'] != null) form['businessCode'] = (form['businessCode'] as string).toUpperCase()
@@ -152,7 +194,7 @@ export async function createAssessment(payload: UTSJSONObject): Promise<string>
})
form['maritalStatus'] = enumValue(draftText(values, 'maritalStatus'), ['已婚', '未婚', '离异', '丧偶'], ['married', 'single', 'divorced', 'widowed'])
const socialKeys = ['socialSecurity', 'housingFund']
socialKeys.forEach((key: string) => { form[key] = enumValue(draftText(values, key), ['连续缴纳 6 个月以上', '缴纳不足 6 个月', '未缴纳'], ['six_months_plus', 'under_six_months', 'none']) })
socialKeys.forEach((key: string) => { form[key] = enumValue(draftText(values, key), contributionLabels, contributionCodes) })
const ownershipKeys = ['house', 'car']
ownershipKeys.forEach((key: string) => { form[key] = enumValue(draftText(values, key), ['有', '无'], ['yes', 'no']) })
const cityNames = ['深圳市', '广州市', '北京市', '上海市', '杭州市']
@@ -221,20 +263,21 @@ export async function fetchAssessmentDetail(id: string, revisionId: string = '')
fields.push(field); sections[section].fields.push(field)
if (provided) sections[section].provided++
}
const keys = ['name', 'phone', 'city', 'idCard', 'enterpriseName', 'businessCode', 'occupation', 'company', 'operatingMonths', 'monthlyIncome', 'annualRevenue', 'creditLoanCount', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardCount', 'creditCardTotalLimit', 'creditCardUsedLimit', 'maritalStatus', 'socialSecurity', 'housingFund', 'house', 'car']
const labels = ['姓名 / 联系人', '手机号', '城市', '身份证号', '企业名称', '信用代码', '职业 / 行业', '工作单位', '经营月数', '月收入', '年营收', '信用贷款笔数', '欠款总余额', '贷款月还款金额', '信用卡张数', '信用卡总额度', '信用卡已用额度', '婚姻状况', '社保缴纳', '公积金缴纳', '是否有房', '是否有车']
const moneyKeys = ['monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit']
const keys = ['name', 'phone', 'city', 'idCard', 'enterpriseName', 'businessCode', 'occupation', 'company', 'operatingMonths', 'monthlyIncome', 'annualRevenue', 'creditLoanCount', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardCount', 'creditCardTotalLimit', 'creditCardUsedLimit', 'maritalStatus', 'socialSecurity', 'housingFund', 'house', 'car', 'socialSecurityBase', 'housingFundBase']
const labels = ['姓名 / 联系人', '手机号', '城市', '身份证号', '企业名称', '信用代码', '职业 / 行业', '工作单位', '经营月数', '月收入', '年营收', '信用贷款笔数', '欠款总余额', '贷款月还款金额', '信用卡张数', '信用卡总额度', '信用卡已用额度', '婚姻状况', '社保缴纳', '公积金缴纳', '是否有房', '是否有车', '社保月缴存基数', '公积金月缴存基数']
const moneyKeys = ['monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit', 'socialSecurityBase', 'housingFundBase']
const enumLabels: UTSJSONObject = { married: '已婚', single: '未婚', divorced: '离异', widowed: '丧偶', yes: '有', no: '无', six_months_plus: '连续缴纳6个月以上', under_six_months: '缴纳不足6个月', none: '未缴纳' }
keys.forEach((key: string, index: number) => {
const raw = form[key]
let value = raw == null ? '未填写' : (typeof raw == 'number' ? (raw as number).toString() : raw as string)
if (raw != null && moneyKeys.includes(key)) value = yuanToWan(value) + ' 万元'
if (raw != null && key == 'operatingMonths') value += ' 个月'
if (['socialSecurity', 'housingFund'].includes(key)) value = contributionLabel(value)
if (['maritalStatus', 'socialSecurity', 'housingFund', 'house', 'car'].includes(key) && enumLabels[value] != null) value = enumLabels[value] as string
const personalOnly = ['idCard', 'company', 'monthlyIncome', 'maritalStatus', 'socialSecurity', 'housingFund']
const personalOnly = ['idCard', 'company', 'monthlyIncome', 'maritalStatus', 'socialSecurity', 'housingFund', 'socialSecurityBase', 'housingFundBase']
const enterpriseOnly = ['enterpriseName', 'businessCode', 'operatingMonths', 'annualRevenue']
if ((result['type'] == 'enterprise' && personalOnly.includes(key)) || (result['type'] == 'personal' && enterpriseOnly.includes(key))) return
const incomeKeys = ['occupation', 'company', 'operatingMonths', 'monthlyIncome', 'annualRevenue', 'socialSecurity', 'housingFund']
const incomeKeys = ['occupation', 'company', 'operatingMonths', 'monthlyIncome', 'annualRevenue', 'socialSecurity', 'housingFund', 'socialSecurityBase', 'housingFundBase']
const assetKeys = ['creditLoanCount', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardCount', 'creditCardTotalLimit', 'creditCardUsedLimit', 'house', 'car']
// “无”及数值0都是已填写,不能用真值判断遗漏零负债、零经营月数。
addField(incomeKeys.includes(key) ? 1 : (assetKeys.includes(key) ? 2 : 0), labels[index], value, raw != null && value.length > 0)
@@ -255,14 +298,15 @@ export async function fetchAssessmentEdit(id: string): Promise<UTSJSONObject> {
const form = result['form'] as UTSJSONObject | null
if (form == null || Array.isArray(form) || typeof form != 'object' || result.getString('moneyUnit') != 'yuan') throw new Error('修改资料不完整,请返回档案重新读取。')
const payload: UTSJSONObject = { type: result['type'], editAssessmentId: id, baseRevisionId: result['revisionId'], step: 0 }
const money = ['monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit']
const money = ['monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit', 'socialSecurityBase', 'housingFundBase']
const labels: UTSJSONObject = { married: '已婚', single: '未婚', divorced: '离异', widowed: '丧偶', yes: '有', no: '无', six_months_plus: '连续缴纳 6 个月以上', under_six_months: '缴纳不足 6 个月', none: '未缴纳' }
const enums = ['maritalStatus', 'house', 'car', 'socialSecurity', 'housingFund']
const keys = ['name', 'idCard', 'phone', 'city', 'occupation', 'company', 'enterpriseName', 'businessCode', 'monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit', 'creditLoanCount', 'creditCardCount', 'operatingMonths', 'maritalStatus', 'house', 'car', 'socialSecurity', 'housingFund']
const keys = ['name', 'idCard', 'phone', 'city', 'occupation', 'company', 'enterpriseName', 'businessCode', 'monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit', 'creditLoanCount', 'creditCardCount', 'operatingMonths', 'maritalStatus', 'house', 'car', 'socialSecurity', 'housingFund', 'socialSecurityBase', 'housingFundBase']
keys.forEach((key: string) => {
const raw = form[key]
let value = raw == null ? '' : (typeof raw == 'number' ? (raw as number).toString() : raw as string)
if (value.length > 0 && money.includes(key)) value = yuanToWan(value)
if (['socialSecurity', 'housingFund'].includes(key)) value = contributionLabel(value)
if (enums.includes(key) && labels[value] != null) value = labels[value] as string
payload[key] = value
})