feat: 支持评估版本隔离的报告、匹配及PDF导出功能

新增revisionId参数到路由、服务及导出接口,实现不同评估版本的隔离访问。删除已废弃的需求相关服务文件,移除取消采集的融资金额、期限和用途字段,更新相关页面提示文本。同时更新文档,说明评估版本、最新资料及产品展示规则的调整,支持前台正常业务名称显示,去除环境标签。
This commit is contained in:
hujun
2026-09-30 19:11:44 +08:00
parent 542c107317
commit 3b2a0139e4
34 changed files with 1389 additions and 338 deletions
+14 -14
View File
@@ -20,7 +20,6 @@
<template v-if="contextOpen">
<text v-if="context!.products.length == 0" class="context-note">围绕这份已保存报告解答,不重新评估</text>
<view v-for="item in context!.products" :key="item.id" class="context-product"><text class="context-product-name">{{ item.name }}</text><text :class="item.state == 'eligible' ? 'product-state good-state' : 'product-state'">{{ item.stateLabel }}</text></view>
<text v-if="hasDemo" class="context-note">演示产品 · 仅供功能验证</text>
<text class="context-link context-source" @click="source">{{ sourceLabel }}</text>
</template>
</view>
@@ -50,7 +49,7 @@
import { parseCompareIds } from '@/packages/finance/services/compare.uts'
import { AssistantProduct, AssistantContext, AssistantTurn, AssistantHistory, assistantKey, assistantRoute, previewAssistant, loadAssistant, createAssistant, assistantMessages, submitAssistant, assistantHistory } from '@/packages/finance/services/assistant.uts'
const { pageStyle, contentStyle, bottomStyle } = usePageLayout()
const assessmentId = ref(''); const productIds = ref<string[]>([]); const chatId = ref(''); const from = ref('')
const revisionId = ref(''); const assessmentId = ref(''); const productIds = ref<string[]>([]); const chatId = ref(''); const from = ref('')
const context = ref<AssistantContext | null>(null); const messages = ref<AssistantTurn[]>([])
// 展开只影响展示;切换会话或账户时恢复折叠,不改变已绑定的报告和产品。
const contextOpen = ref(false)
@@ -62,21 +61,20 @@
let historyCursor = ''; let createKey = assistantKey(); let bindingSpecified = false; let generation = 0; let scope = ''; let timer = 0; let polling = false; let pollCount = 0
let sendKey = ''; let sendText = ''; let sendAttempt = 0
const pending = computed((): boolean => messages.value.some((t: AssistantTurn): boolean => t.status == 'queued' || t.status == 'running'))
const hasDemo = computed((): boolean => context.value?.products.some((p: AssistantProduct): boolean => p.demo) ?? false)
const lastRequestId = computed((): string => messages.value.length > 0 ? messages.value[messages.value.length - 1].requestId : '')
const canSend = computed((): boolean => context.value != null && !sending.value && !pending.value && !uncertain.value && draft.value.trim().length > 0)
const sourceLabel = computed((): string => from.value == 'next-step' ? '返回下一步行动 ›' : (from.value == 'product' && productIds.value.length == 1 ? '返回产品 ›' : (from.value == 'compare' ? '返回对比 ›' : '查看报告 ›')))
const statusLabel = (item: AssistantTurn): string => item.status == 'queued' ? '问题已收到,等待AI回复…' : (item.status == 'running' ? '正在整理建议…' : (item.status == 'local' ? (sending.value ? '正在确认发送结果…' : '尚未确认发送结果,请查看下方提示。') : '回复暂未完成。' + (item.canRetry ? '您可以重新尝试。' : '已达到本条消息重试上限。')))
const source = () => {
if (assessmentId.value.length == 0) { uni.reLaunch({ url: '/pages/home/home' }); return }
let url = '/packages/finance/report/report?assessmentId=' + assessmentId.value
if (from.value == 'product' && productIds.value.length == 1) url = '/packages/finance/product/product?assessmentId=' + assessmentId.value + '&productId=' + productIds.value[0] + '&tab=explanation'
if (from.value == 'compare') url = '/packages/finance/compare/compare?assessmentId=' + assessmentId.value + '&productIds=' + productIds.value.join(',')
if (from.value == 'next-step') url = '/packages/finance/next-step/next-step?assessmentId=' + assessmentId.value + (productIds.value.length == 1 ? '&productId=' + productIds.value[0] + '&section=prepare' : '&section=actions')
let url = '/packages/finance/report/report?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '')
if (from.value == 'product' && productIds.value.length == 1) url = '/packages/finance/product/product?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&productId=' + productIds.value[0] + '&tab=explanation'
if (from.value == 'compare') url = '/packages/finance/compare/compare?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&productIds=' + productIds.value.join(',')
if (from.value == 'next-step') url = '/packages/finance/next-step/next-step?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + (productIds.value.length == 1 ? '&productId=' + productIds.value[0] + '&section=prepare' : '&section=actions')
uni.redirectTo({ url })
}
const back = () => { if (getCurrentPages().length > 1) uni.navigateBack(); else source() }
const login = () => { uni.redirectTo({ url: '/packages/account/login/login?resumeAssistant=1&assessmentId=' + assessmentId.value + '&productIds=' + productIds.value.join(',') + '&chatId=' + chatId.value + '&from=' + from.value }) }
const login = () => { uni.redirectTo({ url: '/packages/account/login/login?resumeAssistant=1&assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&productIds=' + productIds.value.join(',') + '&chatId=' + chatId.value + '&from=' + from.value }) }
const stopTimer = () => { if (timer != 0) { clearInterval(timer); timer = 0 } }
const clearContent = () => { context.value = null; contextOpen.value = false; messages.value = []; draft.value = ''; sendText = ''; sendKey = ''; uncertain.value = false; histories.value = []; historyCursor = ''; historyOpen.value = false; sendError.value = ''; queryError.value = '' }
// 请求代次与登录Token双重校验;离页、退出、换账号后旧响应不得回填个人对话。
@@ -112,13 +110,13 @@
scope = session.getString('token') ?? ''; const token = scope; loading.value = true
try {
if (chatId.value.length > 0) {
const chat = await loadAssistant(chatId.value, assessmentId.value, productIds.value, bindingSpecified)
const chat = await loadAssistant(chatId.value, assessmentId.value, productIds.value, bindingSpecified, revisionId.value)
if (!current(ticket, token)) return
const page = await assistantMessages(chatId.value, '')
if (!current(ticket, token)) return
context.value = chat.context; assessmentId.value = chat.context.assessmentId; productIds.value = chat.context.productIds
context.value = chat.context; revisionId.value = chat.context.revisionId; assessmentId.value = chat.context.assessmentId; productIds.value = chat.context.productIds
messages.value = page.items; more.value = page.more; cursor.value = page.cursor; scrollEnd()
} else { const data = await previewAssistant(assessmentId.value, productIds.value); if (!current(ticket, token)) return; context.value = data }
} else { const data = await previewAssistant(assessmentId.value, productIds.value, revisionId.value); if (!current(ticket, token)) return; context.value = data; revisionId.value = data.revisionId }
pollCount = 0; pollStopped.value = false; beginTimer()
} catch (error) { if (current(ticket, token)) { context.value = null; messages.value = []; pageError.value = error instanceof Error ? error.message : '对话读取失败' } }
finally { if (ticket == generation) loading.value = false }
@@ -133,14 +131,14 @@
const updateUrl = () => {
// H5仅更新地址中的ID,刷新后重新鉴权;不把消息、Token或报告写入URL/本地缓存。
// #ifdef WEB
history.replaceState(history.state, '', location.pathname + location.search + '#' + assistantRoute(assessmentId.value, productIds.value, from.value, chatId.value))
history.replaceState(history.state, '', location.pathname + location.search + '#' + assistantRoute(assessmentId.value, productIds.value, from.value, chatId.value, revisionId.value))
// #endif
}
const transmit = async () => {
if (sending.value || context.value == null) return
const ticket = generation; const token = scope; sending.value = true; sendError.value = ''; uncertain.value = true
try {
if (chatId.value.length == 0) { const chat = await createAssistant(assessmentId.value, productIds.value, createKey); if (!current(ticket, token)) return; chatId.value = chat.id; updateUrl() }
if (chatId.value.length == 0) { const chat = await createAssistant(assessmentId.value, productIds.value, createKey, revisionId.value); if (!current(ticket, token)) return; chatId.value = chat.id; updateUrl() }
const item = await submitAssistant(chatId.value, sendKey, sendText, sendAttempt)
if (!current(ticket, token)) return
merge([item]); uncertain.value = false; draft.value = ''; pollCount = 0; pollStopped.value = false; scrollEnd()
@@ -160,8 +158,10 @@
}
const showHistory = () => { historyOpen.value = !historyOpen.value; if (historyOpen.value && histories.value.length == 0) loadHistory() }
const openHistory = (id: string) => { uni.redirectTo({ url: assistantRoute('', [], 'archive', id) }) }
const newChat = () => { uni.redirectTo({ url: assistantRoute(assessmentId.value, productIds.value, from.value) }) }
const newChat = () => { uni.redirectTo({ url: assistantRoute(assessmentId.value, productIds.value, from.value, '', revisionId.value) }) }
onLoad((options) => {
revisionId.value = options?.['revisionId'] ?? '';
if (revisionId.value.length > 0 && !validRecordId(revisionId.value)) routeError.value = '版本编号无效,请返回重新选择。'
assessmentId.value = options?.['assessmentId'] ?? ''; chatId.value = options?.['chatId'] ?? ''; from.value = options?.['from'] ?? ''; bindingSpecified = options?.['assessmentId'] != null || options?.['productIds'] != null
try {
productIds.value = parseCompareIds(options?.['productIds'] ?? '')