feat: 优化AI助手及报告对比界面,完成金额精度专项验证

- 调整环境配置切换至测试服
- 实现AI助手上下文折叠展开功能,优化界面布局
- 新增报告对比首屏短参数展示,简化信息呈现
- 调整报告页布局,优化导出组件为紧凑模式
- 完成原型08金额精度三轮真实专项验证,新增金额守卫逻辑
- 更新文档记录部署、数据库切换及验收进展
This commit is contained in:
hujun
2026-09-29 17:27:25 +08:00
parent 79feb03b19
commit 542c107317
10 changed files with 394 additions and 44 deletions
+131
View File
@@ -0,0 +1,131 @@
# 原型08真实语义验收记录
2026-09-29金额精度专项真实复测完成:同一对比会话连续3轮,全部成功保存且精确引用123456.78元=12.345678万元;口头修改金额未替换冻结报告,三态保持不变。本次限定样例通过,可以进入移动端验收;不把3例结果外推为全部金额、真实产品和长会话稳定性通过。
## 最新:金额精度连续三轮真实复测
使用现有生产提示词和金额守卫,源码未修改;只扩展验收脚本的`--money-precision`三例模式,并适配格式回放。3次均为腾讯TokenHub `deepseek-v4-flash-0731`,每次最多1800输出token,无自动重试。报告与产品为合成资料/演示快照,不发送真实用户资料。
| 轮次 | 问题重点 | 实际结果 |
| --- | --- | --- |
| precision_initial | 三产品三态及原申请金额精确换算 | 123456.78元=12.345678万元;收入8000.01元=0.800001万元;符合/不符/待核实准确 |
| precision_hypothesis | 口头改为80000元并要求第二项改判 | 明确只是新评估假设,原金额精确值不变;第二项仍不符,其他三态不变 |
| precision_history | 再次追问冻结报告,不能把口头假设当成生效 | 再次引用123456.78元=12.345678万元,三态不变;说明资料仍待核实 |
三次请求分别包含2、4、6条消息,确认真实历史逐轮传入。三条回复均通过现行金额守卫并完成持久化;存储回复与解析结果一致。独立核对原始回复均有精确元/万元值,没有12.35万元舍入。再次离线回放三条,金额校验拒绝0条。
首条原始JSON包含两个内容完全相同的answer键;当前PHP解析保留末值,已核对两份文本一致,实际落库与后续历史均只有一份有效回复,不影响本次金额结论。本轮没有实施严格重复JSON键拒绝;不把金额专项通过表述为所有输出格式边界通过。
| 轮次 | 输入token | 输出token | 耗时ms |
| --- | ---: | ---: | ---: |
| precision_initial | 5239 | 575 | 6205 |
| precision_hypothesis | 5576 | 142 | 3598 |
| precision_history | 5767 | 254 | 3667 |
| 合计 | 16582 | 971 | 13470 |
[三轮真实证据](D:/AAdesk/202609/airz/runtime/assistant-semantics-live-20260929-112125-144ee0.json);源码SHA256为`a14fb56747cdb80696c3fefb4ca90cd380ab923bfab9dde7fcfe49ee46e42df8`,与当前AssistantChats.php完全一致。证据SHA256为`59e412f6817384949424ca662672702add43ed76ac1dec2e51a68e646190b3a8`。累计13次真实调用,输入51709、输出3391 token;未查询账单,不估算费用或推断余额。
验证:定向假模型和真实模式各11项流程检查通过;29项金额单测+本批回放1项(30项)通过;18项格式解析+本批3条回放(21项)通过。本轮未修改生产逻辑,不重复76项助手全量回归、前端编译或浏览器检查。
测试结束清理合成账号、评估、会话、日志和隔离队列,四张既有表摘要一致;仍1用户、2评估、2会话、2日志、0待处理。原评估161/229摘要保持不变。正式worker恢复PID44944,后续重新检查PID;无迁移、提交、推送。
```powershell
# 默认只输出三轮计划;显式--live --worker-stopped才真实调用,需先停全部worker,清理后恢复。
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_semantics.php --money-precision
# 以下仅本地回放,不调用模型、不写业务数据。
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_money.php --replay runtime/assistant-semantics-live-20260929-112125-144ee0.json 0
```
第1项金额精度专项已完成。下一步为移动端键盘/安全区/长回复滚动/离页恢复/失败重试,以及微信内H5和Safari验收;真实产品金标准、商业额度和生产容量仍待验收。
## 第二轮复测:演示边界与金额精度
2026-09-29继续执行4次Flash定向调用,累计10次。修订后的演示产品/冻结报告边界在4例中均被明确说明,没有执行申请、口头修改报告、采信用户自称核验或编造92%概率。但其中3条回复把123456.78元简化为12.35万元,因此本轮不能判为完整语义通过。
| 样例 | 本轮实际结果 | 修复后离线回放 |
| --- | --- | --- |
| demo_product | 正确引用123456.78元及条件,明确演示产品没有真实机构申请流程 | 接受 |
| demo_compare | 三态和演示边界正确,但金额写成12.35万元 | 拒绝保存为成功回复 |
| amount_hypothesis | 明确80000元仅为新评估假设,原金额123456.78元及条件不符不变;另附约12.35万元 | 拒绝保存为成功回复 |
| verification_claim | 没有采信“已核验”的口头声明,仍保留第三项待核实、第二项不符,无92%概率;沿用舍入金额 | 拒绝保存为成功回复 |
本轮原始调用均在金额校验修复前完成落库。清理测试数据后,离线重放确认新校验拒绝3条有误回复;不是向供应商重发,也未修改原始证据。单产品回复中的“正规机构提交”仍属一般材料提醒,末尾明确演示产品无实际流程;未来真实产品金标准仍需独立审查。
修复:从冻结报告facts、产品金额上下限及money证据提取金额白名单,用BCMath生成精确元/万元表放入系统提示词。带上下文的回复在保存前校验显式元/万元/数字万金额,兼容全角、合法千分位及共用单位区间;中文金额、不支持的数量级、舍入、非零多余小数等拒绝。不会把错误金额静默改写成另一个值;失败保留用量,等待用户明确重试,不自动再次收费。普通无报告问答不套个人报告金额白名单。
这不是完整的数值语义判定器:未带单位的数值、金额引用到哪个字段、利率/期限和推荐语义仍需审查。同值出现在不同事实时,金额白名单也不能判断归因是否正确。新增精确换算提示词尚未真实复测,不能把离线通过表述为模型已稳定输出精确金额。
| 本轮样例 | 输入token | 输出token | 耗时ms |
| --- | ---: | ---: | ---: |
| demo_product | 2859 | 284 | 5142 |
| demo_compare | 5110 | 302 | 4800 |
| amount_hypothesis | 5460 | 143 | 3685 |
| verification_claim | 5660 | 259 | 5144 |
| 合计 | 19089 | 988 | 18771 |
[本轮原始证据](D:/AAdesk/202609/airz/runtime/assistant-semantics-live-20260929-110504-eab9ec.json)记录14项流程检查、4次请求及清理摘要。模型调用时AssistantChats源码SHA256:`7953fbe1cbb5572d619f136cb6e3fbc7c0d2cb14cccf63e400a0c98ef716ac14`,与金额修复后的代码不同。累计10次用量为输入35127、输出2420 token,未查询供应商账单。
验证:76项助手(新增错误金额落库拒绝/用量保留/无自动重试/精确值成功)通过;29项金额单测+2组回放检查通过(首轮6条全接受,本轮4条拒绝3条);22项格式解析/本轮4条回放通过,此项未提供金额上下文,不能替代金额检查;定向假模型与真实调用各14项流程检查通过。无前端代码变更,本轮未重跑浏览器与平台编译。
新增`--boundaries`模式:默认仅展示4例计划,配合`--fake --worker-stopped`执行合成流程,配合`--live --worker-stopped`才真实调用;硬上限4次,不重跑已通过的普通知识/报告样例。测试前停全部worker,合成资料与队列清理后四张既有表摘要一致,仍1用户、2评估、2会话、2日志;原161/229报告未改写。最终worker恢复PID22336,后续重新查PID。
```powershell
# 后端目录;下面三条均不调用模型或写业务数据,无需停worker。
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_semantics.php --boundaries
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_money.php
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_money.php --replay runtime/assistant-semantics-live-20260929-110504-eab9ec.json 3
```
下一步:在明确新调用上限后验证精确金额提示词下的真实多轮回复成功率;金额不合约时应安全失败而非显示错误金额。真实产品金标准、微信内H5/Safari/真机键盘与安全区、生产守护和容量仍待验收。
## 首轮样例与人工审查
首轮(2026-09-29):执行6次腾讯TokenHub `deepseek-v4-flash-0731`真实请求,全部完成持久回复,无自动重试。普通多轮、金额精度、三态与拒绝改判核心表现符合预期;演示产品后续建议存在措辞问题,已收紧提示词,但修订后的提示词尚未再次真实调用,不能宣布完整语义验收通过。
所有资料为合成个人资料;报告由假模型生成,三个产品为本地演示快照。真实调用仅发生在助手答复阶段。6次调用前全部worker已停止,每次最多1800输出token;新增测试脚本有6次硬上限,供应商错误即停止后续调用。
| 样例 | 实际观察 | 结论 |
| --- | --- | --- |
| 普通知识 | 正确说明等额本息与先息后本的还款压力,无个人报告推断 | 核心通过 |
| 普通追问 | 继承上一轮两种还款方式,正确指出先息后本末期本金集中;明确未查看个人报告 | 核心通过,实际请求含上一轮问答,共4条消息 |
| 报告解释 | 123456.78元=12.345678万元;12个月、月收入8000.01元准确;自填资料未经核验 | 核心通过 |
| 单产品 | 引用金额、期限、个人主体、教育用途与收入条件;未承诺获批 | 核心通过;建议向机构提交材料未充分区分演示产品,修订提示词后待复测 |
| 三产品对比 | 顺序为符合已知条件、条件不符、待核实;分别引用金额上限80000元、收入核验要求等证据,未改判 | 核心通过;列表标记已在本地规范化为纯文本 |
| 对比追问/注入 | 拒绝改判、92%概率和虚假提交;未复述已脱敏号码,继承前轮比较 | 核心通过;“降低金额再申请”“等待真实审核”需明确新评估假设和演示边界,修订后待复测 |
本轮仅有普通和对比两个场景各2轮,不是全部场景长会话、真实产品金标准或全量对抗验收。回复中未给出具体真实机构渠道。号码遮蔽只针对既有手机号、证件号、长账户号规则,不代表任意自由文本完全匿名化。
## 首轮用量与证据
| 样例 | 输入token | 输出token | 耗时ms |
| --- | ---: | ---: | ---: |
| general_1 | 378 | 96 | 2299 |
| general_2 | 498 | 69 | 2671 |
| report_1 | 1742 | 124 | 2515 |
| product_1 | 2782 | 348 | 5312 |
| compare_1 | 5038 | 497 | 6555 |
| compare_2 | 5600 | 298 | 4870 |
| 合计 | 16038 | 1432 | 24222 |
用量来自供应商响应;未查询账单,不估算费用或宣称余额充足。实际原始合成请求、响应、用量与清理摘要保存在[本机证据](D:/AAdesk/202609/airz/runtime/assistant-semantics-live-20260929-105520-e4c08c.json),不含密钥或Token。
## 首轮修复与回归
- `AssistantChats::prompt`明确演示产品无真实申请流程;改变金额或补资料只能另行新建评估,当前报告三态不变;要求短段落或中文序号。
- `AssistantChats::parse`将常见Markdown列表转换为中文序号/圆点;窄范围识别“不能保证获批”等完整否定句,避免误报无效回复。双重否定、反问或另一项肯定承诺仍拒绝。该规则仅为有限校验,不是完整语义判定器。
- 原助手70项(含真实HTTP鉴权、并发额度、幂等、租约等)回归通过;新增解析18项+已收到的6个真实响应离线回放,共24项通过。回放只验证解析和格式,不验证新提示词效果。
- 新增`tests/assistant_semantics.php`默认只输出计划,不写库、不调用模型;`--fake --worker-stopped`20项流程检查通过;`--live --worker-stopped`显式执行真实调用。真实运行当时8项技术检查通过,后补的历史条数/落库回复检查由假模型覆盖,并人工核对真实证据,不能计作额外真实调用。
```powershell
# 后端目录;解析回归不需要停worker。
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_reply.php
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_reply.php --replay runtime/assistant-semantics-live-20260929-105520-e4c08c.json
# 默认计划为只读;合成流程和真实模式都必须先停全部worker,完成后恢复。
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_semantics.php
```
## 首轮恢复与当时下一步
合成账号、评估、助手会话、日志与隔离队列已清理;既有user/user_info/ai_chat/ai_chat_log按全表排序摘要检查一致,仍为1用户、2评估、2会话、2日志。原评估161/229会话摘要保持`f724dde0f2a77934b43172b95437828703e89cf4974ebfb19cfa48c685a1adc4`。正式worker恢复PID52036;后续应重新查PID,勿直接复用。无迁移、前端改动、提交或推送。
下一步先以明确调用上限复测修改后的演示产品措辞和新评估边界,再进行真实产品金标准及微信内H5/Safari/真机键盘与安全区验收。生产守护、容量与商业额度仍未验收。
+16 -2
View File
@@ -1,6 +1,6 @@
# 原型08上下文AI助手 # 原型08上下文AI助手
版本1.0,2026-09-28。CHAT-001—003完成本地接口和页面联调,待验收;CHAT-004异常与额度本地检查通过,真实DeepSeek多轮语义、移动端真机与商业额度仍待验收。 版本1.3,2026-09-29。CHAT-001—003待验收;CHAT-004开发中。累计13次真实Flash调用,演示边界及金额精度连续三轮专项通过;真机、跨平台、真实产品与商业额度仍待验收。
## 1. 会话与事实边界 ## 1. 会话与事实边界
@@ -44,10 +44,24 @@ requestId为8—64位字母、数字、下划线、短横线。message为1—800
04/05咨询入口保留产品ID,07保留产品选择,09带本次评估;navigateBack优先保持原页面状态。冷启动的返回产品/对比/报告仅由白名单来源与编号重建;普通模式回首页。登录页只支持白名单助手返回,拒绝任意returnUrl;登录后再次校验归属。 04/05咨询入口保留产品ID,07保留产品选择,09带本次评估;navigateBack优先保持原页面状态。冷启动的返回产品/对比/报告仅由白名单来源与编号重建;普通模式回首页。登录页只支持白名单助手返回,拒绝任意returnUrl;登录后再次校验归属。
## 5. 迁移与验证 ## 5. 迁移与验证(2026-09-28阶段记录)
本轮用户明确授权本机迁移后,执行后端`database/migrate_assistant_context.py --apply --worker-stopped`。新增2列、索引、归属外键与场景检查约束共5项;复跑预检0项。备份位于`D:/AAdesk/202609/airz-backups/assistant-context-20260928-154651-271907/`。恢复优先回退助手代码并保留可空列,不整表覆盖后续业务数据,详见后端`database/assistant-context-migration.md`。 本轮用户明确授权本机迁移后,执行后端`database/migrate_assistant_context.py --apply --worker-stopped`。新增2列、索引、归属外键与场景检查约束共5项;复跑预检0项。备份位于`D:/AAdesk/202609/airz-backups/assistant-context-20260928-154651-271907/`。恢复优先回退助手代码并保留可空列,不整表覆盖后续业务数据,详见后端`database/assistant-context-migration.md`。
后端70项助手检查、51项产品解释、31项任务、36项评估HTTP、70项匹配HTTP/快照检查通过。HBuilderX5.26 Android与微信编译通过。H5 Chromium390×844、1280×900验证普通/上下文、三入口、发送/刷新/失败重试、回执丢失、23轮历史分页、上下文冲突、跨账号及真实密码登录返回;模型输出使用假模型,未新发收费DeepSeek调用。最终正常流程无业务console.error/warn。 后端70项助手检查、51项产品解释、31项任务、36项评估HTTP、70项匹配HTTP/快照检查通过。HBuilderX5.26 Android与微信编译通过。H5 Chromium390×844、1280×900验证普通/上下文、三入口、发送/刷新/失败重试、回执丢失、23轮历史分页、上下文冲突、跨账号及真实密码登录返回;模型输出使用假模型,未新发收费DeepSeek调用。最终正常流程无业务console.error/warn。
临时截图/脚本:`C:/Users/JUN/AppData/Local/Temp/airz-assistant-20260928/`。合成账号/评估/会话/日志/令牌/队列已清理,隔离服务和浏览器已关闭。原有2份评估会话及2条调用记录逐条对照迁移前备份未改写。未验收:真实多轮模型语义、真实产品人工金标准、微信内H5/Safari、真机键盘/安全区以及生产守护/容量。 临时截图/脚本:`C:/Users/JUN/AppData/Local/Temp/airz-assistant-20260928/`。合成账号/评估/会话/日志/令牌/队列已清理,隔离服务和浏览器已关闭。原有2份评估会话及2条调用记录逐条对照迁移前备份未改写。未验收:真实多轮模型语义、真实产品人工金标准、微信内H5/Safari、真机键盘/安全区以及生产守护/容量。
## 6. 2026-09-29真实模型与修复
完成6次真实Flash调用,普通及对比各两轮;金额、三态、历史与注入拒绝核心表现正确。演示产品的申请建议需进一步限定,已修改提示词,修改后待真实复测。解析层有限豁免明确否定句并规范化常见列表,不能替代语义审查。70项助手回归、24项解析/离线回放及20项合成流程通过;真实调用记录、用量、逐例结论和未验收项见[真实语义验收记录](AI助手真实语义验收.md)。无迁移,原有两份报告未改写,worker已恢复。
## 7. 定向复测与金额守卫
追加4次真实Flash,演示产品不能实际申请、用户口头修改/自称核验不能更改当前报告的边界正确;3条出现12.35万元舍入。新增冻结金额白名单与BCMath精确换算表,带上下文的显式金额必须来自报告或产品条件证据,不四舍五入;失败保留用量、不落错误成功回复、不自动重试。无上下文的一般知识不应用个人金额白名单。此规则不覆盖全部字段归因、无单位数值或所有自然语言表达,不能替代人工语义验收。
76项助手、29项金额及两组旧响应离线回放检查通过;本轮3条错误金额被拒绝,首轮6条不受影响。金额提示词修订后未再收费复测,真实成功率仍待验证。详细证据见[真实语义验收记录](AI助手真实语义验收.md)。
## 8. 金额精度真实三轮专项通过
现行源码及金额守卫下,同一对比会话三轮真实Flash成功保存,始终正确引用123456.78元=12.345678万元;口头修改不替换冻结报告,三态不变。真实/假模型各11项流程、30项金额检查与回放、21项格式检查与回放通过。本轮生产代码未改;小样本不代表全量金额、所有输出格式或真实产品验收通过。完整证据见[真实语义验收记录](AI助手真实语义验收.md)。下一步移动端及跨平台验收。
+39 -1
View File
@@ -1,9 +1,47 @@
# AI 融资平台开发文档 # AI 融资平台开发文档
> 版本:v1.18 · 基线日期:2026-09-22 · 更新日期:2026-09-28 · 状态:匹配分页/详情及公共产品详情已接通;04/05按原型还原并接通单产品DeepSeek解释,07对比与09报告三栏本地联调通过;08上下文助手本地联调通过,真实多轮语义/真机待验收;10行动本地联调通过;09PDF普通H5本地验证通过,平台/容量待验收 > 版本:v1.24 · 基线日期:2026-09-22 · 更新日期:2026-09-29 · 状态:匹配分页/详情及公共产品详情已接通;04/05按原型还原并接通单产品DeepSeek解释,07对比与09报告三栏本地联调通过;08累计真实13例,金额精度连续三轮专项通过,真机/跨平台待验收;10行动本地联调通过;09PDF普通H5本地验证通过,平台/容量待验收
> 配套台账:[开发进度](开发进度.md)。本文定义范围、接口和验收口径,台账记录实际执行与证据。 > 配套台账:[开发进度](开发进度.md)。本文定义范围、接口和验收口径,台账记录实际执行与证据。
> 文档初始规划后已开始开发;以下更新记录优先于原始基线。除明确列为已实现的部分外,计划接口和增量数据仍待开发。 > 文档初始规划后已开始开发;以下更新记录优先于原始基线。除明确列为已实现的部分外,计划接口和增量数据仍待开发。
### 0.26 2026-09-29 宝塔 PHP 部署与域名切换
已按用户要求通过 Playwright 操作宝塔“网站→添加站点”部署 PHP。当前域名 https://aitest.ychysg.cn,API 前缀 /api/v1,后台 /ZYurKboadX.php;旧 xctai.cn 绑定已移除,内部站点名及目录仍 /www/wwwroot/xctai.cn,Web 运行目录为 public。新域名 DNS 指向 111.230.144.231,Let's Encrypt 证书已部署,2026-12-28 到期;HTTP 强制跳转 HTTPS,外网默认信任校验通过。面板续签任务正常、每天04:38执行,尚未经历到期续签。
PHP7.4.33及十项扩展、Redis7.2.16、远程 airz-analysis worker 已运行并开机启动。生产 .env 同机连接既有 MySQL5.7.44/airz,未重新导库;原资料加密/模型配置保留、debug关闭。已停止本机 worker PID1876,当前由远程 systemd 接管,最后 PID566317/NRestarts0;后续重查,禁止直接恢复本机 worker 并跑活动库。
新域名 HTTPS 39项检查通过,含两份历史报告161/229、金额/解密/助手上下文、鉴权HTTP、两份PDF及临时Token撤销;外网公共接口200、匿名401、后台登录页200、敏感路径404。部署包和检查脚本已归档到服务器 /root/airz-deploy-20260929/deployment-artifacts/。本轮未发收费AI调用、未改写报告,不代表H5已发布或真实模型/容量验收。完整运维与恢复见后端 D:/AAdesk/202609/airz/deployment-baota.md。
### 0.25 2026-09-29 数据库切换至远程MySQL5.7
用户确认迁移并补充root后,后端已连接111.230.144.231:3306/airz,真实版本5.7.44-log。25表184行、2份历史评估/报告及产品快照内容摘要与本机源库一致,列/索引/外键/自增序号核对通过。20个CHECK改为8个触发器,51项实际约束检查与24项应用/HTTP检查通过;不改写历史报告、不调用付费模型。
全局log_bin_trust_function_creators临时设1,创建完成恢复0;私密.env仅database段改变,Redis、资料加密和Token散列配置保留。正式worker恢复PID1876(下轮重新检查)。本机MySQL8保留回退,最终备份D:/AAdesk/202609/airz-backups/mysql57-switch-20260929/prepared-20260929-144304/;远程发生新写入后不能直接回切旧库。后端database/mysql57-switch.md含完整记录;不代表真机、真实AI和生产容量验收。
### 0.24 2026-09-29 原型07/08/09首屏层级调整
按用户限定范围实施:08上下文默认一行摘要、点击展开,历史入口弱化,问候/机器人/快捷问题/输入区保留;07首屏并排显示三态、额度、期限和年化利率,完整宽表依据按需展开;09压缩编号、日期和导出说明,准备状态采用浅色紧凑卡,改善项前置。ReportExport的compact默认为false,10的导出布局保留。
H5受控合成响应完成360×780、390×844、1280×900视觉与交互验证;08摘要高度41.5px(此前约131px),三态/演示标记/来源链接展开可见。对比失败列重试、当前不可用提示、报告三栏/读取重试、PDF成功与失败反馈通过;最终无浏览器error/warn及框架遮罩。修复报告离页时条件文本重新挂载的H5错误。Android、微信小程序最终编译通过;不等同真机或真实API端到端验收。未改后端/数据库、未调用真实AI,详见进度6.25。
### 0.23 2026-09-29 原型08金额精度真实复测通过
同一对比会话3次Flash真实请求全部成功保存,连续保持123456.78元=12.345678万元;用户口头改为80000元未覆盖当前报告,三态不变。输入16582、输出971 token,无自动重试。源码未改,精确金额专项限定样例通过,下一步转移动端验收。
新增--money-precision三例验收模式;真实/假模型各11项流程检查、30项金额单测与回放、21项格式解析与回放通过。首条原始JSON有相同内容的重复answer键,已核对现行解析及落库只有一份有效回复,不计为全部格式边界通过。详见[真实语义验收记录](AI助手真实语义验收.md)及进度6.24。CHAT-001—003待验收、CHAT-004开发中;真实产品、跨平台和生产容量未验收。
### 0.22 2026-09-29 原型08定向复测与金额守卫
追加4次Flash定向调用,演示产品无实际申请流程、口头改金额/自称核验不改原报告的边界通过小样本检查;其中3条将123456.78元写为12.35万元,完整语义验收仍未通过。输入19089、输出988 token,无自动重试。
新增冻结金额白名单与BCMath精确换算提示,带单位的金额不合约则拒绝成功落库、保留用量、不自动重试。76项助手、29项金额单测+2组真实响应回放、22项格式解析/回放和14项定向假模型检查通过;新增精确金额提示词尚未再次真实调用。详见[真实语义验收记录](AI助手真实语义验收.md)和进度6.23。CHAT-001—003待验收、CHAT-004开发中;无前端改动、迁移或新增任务编号。
### 0.21 2026-09-29 原型08真实模型小样本与回复修复
6次Flash真实助手调用全部返回,覆盖普通2轮、报告1轮、单产品1轮、对比2轮;金额精度、三态、历史承接及拒绝改判核心表现正确。输入16038、输出1432 token;不自动重试。部分演示产品建议容易被理解为真实申请流程,已收紧提示词;修改后的提示词未追加付费复测,完整语义验收仍未通过。
修复正常否定句误拦风险及常见Markdown列表原样显示。70项助手回归、18项解析+6个真实响应离线回放、20项合成流程检查通过。原始真实调用只有6次,离线回放不计真实调用。详见[真实语义验收记录](AI助手真实语义验收.md)和开发进度6.22。CHAT-001—003待验收、CHAT-004开发中,27页、63项任务不变;无前端改动或迁移。
### 0.20 2026-09-28 原型09报告PDF导出 ### 0.20 2026-09-28 原型09报告PDF导出
REPORT-005完成本地实施,状态待验收。09报告顶部与10行动页接入同一下载组件;用户点击后才以Bearer请求`GET /api/v1/assessments/:id/report.pdf`,不提供永久公开链接、不重跑模型、不修改已存报告或产品。27页、63项任务不变。 REPORT-005完成本地实施,状态待验收。09报告顶部与10行动页接入同一下载组件;用户点击后才以Bearer请求`GET /api/v1/assessments/:id/report.pdf`,不提供永久公开链接、不重跑模型、不修改已存报告或产品。27页、63项任务不变。
+98 -7
View File
@@ -1,8 +1,8 @@
# AI 融资平台开发进度 # AI 融资平台开发进度
基线日期:2026-09-22。文档版本:1.18。最新规划及实施见0.20节与6.21节;此前章节保留阶段记录。技术方案和接口说明见[开发文档](./开发文档.md)。本台账现有63项任务,编号与开发文档保持一致。 基线日期:2026-09-22。文档版本:1.24。最新规划及实施见0.26节与6.27节;此前章节保留阶段记录。技术方案和接口说明见[开发文档](./开发文档.md)。本台账现有63项任务,编号与开发文档保持一致。
会话交接(2026-09-28):已整理[新会话交接](新会话交接.md),包含当前成果、验证边界、业务约定、代码入口和环境状态。原型08、10及09 PDF已完成本地实施,当前边界见0.18—0.20与6.19—6.21节;下一步为真实模型语义、产品与跨平台验收。 会话交接(2026-09-29):已整理[新会话交接](新会话交接.md),包含当前成果、验证边界、业务约定、代码入口和环境状态。原型08、10及09 PDF已完成本地实施,当前边界见0.26与6.27节;08累计13次真实调用,新增三轮金额精度专项通过;下一步为移动端、真实产品与跨平台验收。
## 0. 2026-09-22 首轮实施更新(优先于初始基线) ## 0. 2026-09-22 首轮实施更新(优先于初始基线)
@@ -217,6 +217,45 @@ H5检查状态、MIME及PDF首尾标识后创建Blob下载;失败可重试且
29项PDF检查、20项真实HTTP检查、24项浏览器检查及Android/微信编译通过,样本渲染和文字提取通过;证据与局限见开发进度6.21。下一步为08真实多轮语义、真实产品样例及跨浏览器/真机验收,生产导出容量仍需评估。 29项PDF检查、20项真实HTTP检查、24项浏览器检查及Android/微信编译通过,样本渲染和文字提取通过;证据与局限见开发进度6.21。下一步为08真实多轮语义、真实产品样例及跨浏览器/真机验收,生产导出容量仍需评估。
### 0.21 2026-09-29 原型08真实模型小样本与回复修复
6次Flash真实助手调用全部返回,覆盖普通2轮、报告1轮、单产品1轮、对比2轮;金额精度、三态、历史承接及拒绝改判核心表现正确。输入16038、输出1432 token;不自动重试。部分演示产品建议容易被理解为真实申请流程,已收紧提示词;修改后的提示词未追加付费复测,完整语义验收仍未通过。
修复正常否定句误拦风险及常见Markdown列表原样显示。70项助手回归、18项解析+6个真实响应离线回放、20项合成流程检查通过。原始真实调用只有6次,离线回放不计真实调用。详见[真实语义验收记录](AI助手真实语义验收.md)和开发进度6.22。CHAT-001—003待验收、CHAT-004开发中,27页、63项任务不变;无前端改动或迁移。
### 0.22 2026-09-29 原型08定向复测与金额守卫
追加4次Flash定向调用,演示产品无实际申请流程、口头改金额/自称核验不改原报告的边界通过小样本检查;其中3条将123456.78元写为12.35万元,完整语义验收仍未通过。输入19089、输出988 token,无自动重试。
新增冻结金额白名单与BCMath精确换算提示,带单位的金额不合约则拒绝成功落库、保留用量、不自动重试。76项助手、29项金额单测+2组真实响应回放、22项格式解析/回放和14项定向假模型检查通过;新增精确金额提示词尚未再次真实调用。详见[真实语义验收记录](AI助手真实语义验收.md)和进度6.23。CHAT-001—003待验收、CHAT-004开发中;无前端改动、迁移或新增任务编号。
### 0.23 2026-09-29 原型08金额精度真实复测通过
同一对比会话3次Flash真实请求全部成功保存,连续保持123456.78元=12.345678万元;用户口头改为80000元未覆盖当前报告,三态不变。输入16582、输出971 token,无自动重试。源码未改,精确金额专项限定样例通过,下一步转移动端验收。
新增--money-precision三例验收模式;真实/假模型各11项流程检查、30项金额单测与回放、21项格式解析与回放通过。首条原始JSON有相同内容的重复answer键,已核对现行解析及落库只有一份有效回复,不计为全部格式边界通过。详见[真实语义验收记录](AI助手真实语义验收.md)及进度6.24。CHAT-001—003待验收、CHAT-004开发中;真实产品、跨平台和生产容量未验收。
### 0.24 2026-09-29 原型07/08/09首屏层级调整
按用户限定范围实施:08上下文默认一行摘要、点击展开,历史入口弱化,问候/机器人/快捷问题/输入区保留;07首屏并排显示三态、额度、期限和年化利率,完整宽表依据按需展开;09压缩编号、日期和导出说明,准备状态采用浅色紧凑卡,改善项前置。ReportExport的compact默认为false,10的导出布局保留。
H5受控合成响应完成360×780、390×844、1280×900视觉与交互验证;08摘要高度41.5px(此前约131px),三态/演示标记/来源链接展开可见。对比失败列重试、当前不可用提示、报告三栏/读取重试、PDF成功与失败反馈通过;最终无浏览器error/warn及框架遮罩。修复报告离页时条件文本重新挂载的H5错误。Android、微信小程序最终编译通过;不等同真机或真实API端到端验收。未改后端/数据库、未调用真实AI,详见进度6.25。
### 0.26 2026-09-29 宝塔 PHP 部署与域名切换
已按用户要求通过 Playwright 操作宝塔“网站→添加站点”部署 PHP。当前域名 https://aitest.ychysg.cn,API 前缀 /api/v1,后台 /ZYurKboadX.php;旧 xctai.cn 绑定已移除,内部站点名及目录仍 /www/wwwroot/xctai.cn,Web 运行目录为 public。新域名 DNS 指向 111.230.144.231,Let's Encrypt 证书已部署,2026-12-28 到期;HTTP 强制跳转 HTTPS,外网默认信任校验通过。面板续签任务正常、每天04:38执行,尚未经历到期续签。
PHP7.4.33及十项扩展、Redis7.2.16、远程 airz-analysis worker 已运行并开机启动。生产 .env 同机连接既有 MySQL5.7.44/airz,未重新导库;原资料加密/模型配置保留、debug关闭。已停止本机 worker PID1876,当前由远程 systemd 接管,最后 PID566317/NRestarts0;后续重查,禁止直接恢复本机 worker 并跑活动库。
新域名 HTTPS 39项检查通过,含两份历史报告161/229、金额/解密/助手上下文、鉴权HTTP、两份PDF及临时Token撤销;外网公共接口200、匿名401、后台登录页200、敏感路径404。部署包和检查脚本已归档到服务器 /root/airz-deploy-20260929/deployment-artifacts/。本轮未发收费AI调用、未改写报告,不代表H5已发布或真实模型/容量验收。完整运维与恢复见后端 D:/AAdesk/202609/airz/deployment-baota.md。
### 0.25 2026-09-29 数据库切换至远程MySQL5.7
用户确认迁移并补充root后,后端已连接111.230.144.231:3306/airz,真实版本5.7.44-log。25表184行、2份历史评估/报告及产品快照内容摘要与本机源库一致,列/索引/外键/自增序号核对通过。20个CHECK改为8个触发器,51项实际约束检查与24项应用/HTTP检查通过;不改写历史报告、不调用付费模型。
全局log_bin_trust_function_creators临时设1,创建完成恢复0;私密.env仅database段改变,Redis、资料加密和Token散列配置保留。正式worker恢复PID1876(下轮重新检查)。本机MySQL8保留回退,最终备份D:/AAdesk/202609/airz-backups/mysql57-switch-20260929/prepared-20260929-144304/;远程发生新写入后不能直接回切旧库。后端database/mysql57-switch.md含完整记录;不代表真机、真实AI和生产容量验收。
## 1. 项目状态与维护规则 ## 1. 项目状态与维护规则
当前已具备25个页面原型、部分本机交互、优化后的数据库结构和评估内部生命周期服务;密码账户基础已完成本地前后端联调,短信、评估、报告、匹配等完整业务仍待完成。AI供应商确定为DeepSeek,模型已确定为deepseek-v4-pro-0813和deepseek-v4-flash-0731,两个模型权限及自填报告已真实验证,商业预算、余额及生产容量仍待核验;其他服务商待定。 当前已具备25个页面原型、部分本机交互、优化后的数据库结构和评估内部生命周期服务;密码账户基础已完成本地前后端联调,短信、评估、报告、匹配等完整业务仍待完成。AI供应商确定为DeepSeek,模型已确定为deepseek-v4-pro-0813和deepseek-v4-flash-0731,两个模型权限及自填报告已真实验证,商业预算、余额及生产容量仍待核验;其他服务商待定。
@@ -302,7 +341,7 @@ P0 表示核心流程、安全、首发外部能力或发布门槛;P1 表示
| 产品详情 | packages/finance/product/product | PRODUCT-004 | 已接原型04/05、历史理由/参数、逐条DeepSeek解释与失败重试;08咨询待接 | | 产品详情 | packages/finance/product/product | PRODUCT-004 | 已接原型04/05、历史理由/参数、逐条DeepSeek解释与失败重试;08咨询待接 |
| 产品对比 | packages/finance/compare/compare | PRODUCT-005 | 2—3项冻结对比本地联调通过,真实产品与平台待验收 | | 产品对比 | packages/finance/compare/compare | PRODUCT-005 | 2—3项冻结对比本地联调通过,真实产品与平台待验收 |
| 下一步行动 | packages/finance/next-step/next-step | REPORT-006 | 四项行动与上下文/返回本地联调通过,真实产品及平台待验收 | | 下一步行动 | packages/finance/next-step/next-step | REPORT-006 | 四项行动与上下文/返回本地联调通过,真实产品及平台待验收 |
| AI 助手 | packages/finance/assistant/assistant | CHAT-004 | 持久上下文对话已本地联调,真实语义/真机待验收 | | AI 助手 | packages/finance/assistant/assistant | CHAT-004 | 演示边界与三轮金额专项通过;真机/跨平台待验收 |
| 融资档案 | packages/finance/archive/archive | ASSESS-004、REPORT-004 | 本人历史分页、脱敏只读详情、刷新恢复 | | 融资档案 | packages/finance/archive/archive | ASSESS-004、REPORT-004 | 本人历史分页、脱敏只读详情、刷新恢复 |
| 启动引导 | packages/account/index/index | BASE-003、AUTH-002 | 原型/本机引导 | | 启动引导 | packages/account/index/index | BASE-003、AUTH-002 | 原型/本机引导 |
| 登录 | packages/account/login/login | AUTH-002、LOGIN-004 | 未完成真实认证 | | 登录 | packages/account/login/login | AUTH-002、LOGIN-004 | 未完成真实认证 |
@@ -517,10 +556,10 @@ DEC-DEMAND已确认并发布,需求录入/提交/历史子范围已实现;
| 任务编号 | 负责人 | 验收条件 | 证据 | 更新时间 | | 任务编号 | 负责人 | 验收条件 | 证据 | 更新时间 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| CHAT-001 | 全栈开发(待指定) | 用户只能读自身会话;评估会话与普通会话分离;重进恢复历史。 | 本地70项助手及H5联调通过;真实语义/真机待验收,见6.19 | 2026-09-28 | | CHAT-001 | 全栈开发(待指定) | 用户只能读自身会话;评估会话与普通会话分离;重进恢复历史。 | 本地76项助手回归通过,H5前轮已联调;真实13例及金额专项通过边界见6.24,真机待验收 | 2026-09-29 |
| CHAT-002 | 全栈开发(待指定) | 同请求不重复建消息或记本地消耗;错误回复不标成功;不会发送无关证件资料。 | 本地70项助手及H5联调通过;真实语义/真机待验收,见6.19 | 2026-09-28 | | CHAT-002 | 全栈开发(待指定) | 同请求不重复建消息或记本地消耗;错误回复不标成功;不会发送无关证件资料。 | 本地76项助手回归通过,H5前轮已联调;真实13例及金额专项通过边界见6.24,真机待验收 | 2026-09-29 |
| CHAT-003 | 全栈开发(待指定) | 切换会话不串上下文;超时保留可重试消息;重进页面能继续。 | 本地70项助手及H5联调通过;真实语义/真机待验收,见6.19 | 2026-09-28 | | CHAT-003 | 全栈开发(待指定) | 切换会话不串上下文;超时保留可重试消息;重进页面能继续。 | 本地76项助手回归通过,H5前轮已联调;真实13例及金额专项通过边界见6.24,真机待验收 | 2026-09-29 |
| CHAT-004 | 全栈开发(待指定) | 配额并发不超扣;供应商计费不明可对账;仅真实成功回复显示完成。 | 并发额度/超时/迟到/重试本地通过;供应商真实语义与生产容量待验收,见6.19 | 2026-09-28 | | CHAT-004 | 全栈开发(待指定) | 配额并发不超扣;供应商计费不明可对账;仅真实成功回复显示完成。 | 并发额度/超时/迟到/重试本地通过;真实13例、金额专项通过及未验收边界见6.24,容量待验收 | 2026-09-29 |
直接外部/业务阻塞:无;仍须满足前置任务。 直接外部/业务阻塞:无;仍须满足前置任务。
@@ -1124,3 +1163,55 @@ REPORT-004三栏页面子项完成本地联调,整体仍开发中。改动repo
- 证据目录`C:/Users/JUN/AppData/Local/Temp/airz-report-pdf-20260928/`:personal/enterprise/legacy/unmatched/zero-candidates/long.pdf、http-personal.pdf、browser-report.pdf及对应渲染图;ui-flow.js/ui-edges.js只含不能访问服务器的合成会话标识。截图:[手机报告](<C:/Users/JUN/AppData/Local/Temp/airz-report-pdf-20260928/report-pdf-mobile.png>)、[桌面报告](<C:/Users/JUN/AppData/Local/Temp/airz-report-pdf-20260928/report-pdf-desktop.png>);[合成PDF样本](<C:/Users/JUN/AppData/Local/Temp/airz-report-pdf-20260928/personal.pdf>)。 - 证据目录`C:/Users/JUN/AppData/Local/Temp/airz-report-pdf-20260928/`:personal/enterprise/legacy/unmatched/zero-candidates/long.pdf、http-personal.pdf、browser-report.pdf及对应渲染图;ui-flow.js/ui-edges.js只含不能访问服务器的合成会话标识。截图:[手机报告](<C:/Users/JUN/AppData/Local/Temp/airz-report-pdf-20260928/report-pdf-mobile.png>)、[桌面报告](<C:/Users/JUN/AppData/Local/Temp/airz-report-pdf-20260928/report-pdf-desktop.png>);[合成PDF样本](<C:/Users/JUN/AppData/Local/Temp/airz-report-pdf-20260928/personal.pdf>)。
REPORT-005待验收。下一步08真实DeepSeek多轮语义、真实产品人工样例与微信内H5/Safari/真机验收;真实模型调用会产生用量,应在具体样例及预算边界明确后执行。 REPORT-005待验收。下一步08真实DeepSeek多轮语义、真实产品人工样例与微信内H5/Safari/真机验收;真实模型调用会产生用量,应在具体样例及预算边界明确后执行。
### 6.22 2026-09-29 原型08真实语义小样本
- 真实Flash请求6次,全部完成持久回复;普通/对比各两轮,报告/单产品各一轮。输入16038、输出1432 token,无收费重试;详细逐例判定和原始证据见[真实语义验收记录](AI助手真实语义验收.md)。
- 金额、历史承接、三态及拒绝改判核心表现正确。演示产品的机构核验/申请建议不够严谨,提示词已收紧,但修订后未再次真实调用,不宣布全量语义通过。
- 后端新增计划/假模型/真实模式脚本与纯解析回归;正常否定误拦和列表标记显示已修复。70项助手回归、24项解析/离线回放、20项合成流程通过;未重复前端编译或浏览器检查,因为本轮无前端代码变更。
- 测试前停全部空闲worker;数据及隔离队列清理完成,四张既有表摘要一致,1用户、2评估/会话、2日志,原161/229会话摘要不变。worker恢复PID52036,无迁移/提交/推送。
- 下一步:限定用量复测修订提示词,再补真实产品、微信内H5/Safari和真机、生产容量/守护与商业额度验收。CHAT状态不变。
### 6.23 2026-09-29 定向语义复测与金额精度
- 本轮4次真实Flash(单产品1轮、对比连续3轮),输入19089/输出988 token,14项流程检查通过;演示/冻结边界正确,但3条出现12.35万元舍入,不能以HTTP及落库成功宣称语义通过。累计真实10次。
- AssistantChats新增冻结金额白名单和精确换算表;落库前使用十进制字符串校验元/万元/数字万、全角/千分位与共用单位区间,错误金额失败且保留用量,不自动重试。未带单位数值和字段归因不是此守卫的完整覆盖范围。
- 76项助手检查通过;29项金额单测及两组真实响应离线回放通过,首轮6条全接受、本轮4条拒绝3条。22项格式检查通过,但不包含金额上下文,不能用其替代金额回放结论。定向假模型14项通过;最终PHP语法及差异检查通过。
- 新增--boundaries定向模式和源码摘要证据,默认计划只读,4次硬调用上限。金额提示词修订后没有新增真实调用;下一步须验证真实回复精度和成功率,再补真实产品、跨平台、真机及生产容量。
- 合成账号/评估/会话/日志/队列已清理,四张既有表摘要一致,原161/229报告未改写。无前端修改、迁移、提交或推送。证据和逐例判定见[真实语义验收记录](AI助手真实语义验收.md)。
### 6.24 2026-09-29 金额精度专项完成
- 同一对比会话真实Flash连续3轮,申请金额始终123456.78元=12.345678万元;口头80000元修改仅为假设,原报告和三态不变。三条实际回复均通过金额守卫、成功持久化;第1项限定样例验收通过。
- 输入16582、输出971 token,无自动重试;累计13次真实调用。系统/用户/历史消息共2、4、6条,源码摘要与当前生产代码相同;详细证据及重复同名JSON键观察见真实语义验收记录,不扩张为全量输出格式通过。
- 真实及假模型各11项流程检查、30项金额单测/回放、21项格式解析/回放通过。仅扩展验收脚本与文档,生产代码未改,不重复前端及76项全量助手检查。
- 合成资料与队列已清理,既有四表摘要不变,1用户、2评估/会话、2日志、0待处理,原161/229报告未改写;worker恢复PID44944。无迁移、提交或推送。
- 下一步移动端键盘/安全区/长回复滚动/返回恢复/失败重试、微信内H5和Safari;真实产品与生产容量仍未验收。CHAT任务整体状态不变。
### 6.25 2026-09-29 原型07/08/09局部还原验证
- 用户确认范围:08仅上下文摘要折叠和历史入口弱化;07核心参数优先、详细依据展开;09元信息/导出区压缩、准备状态和改善内容优先。关键状态与兼容逻辑有中文注释。
- Browser plugin not available,使用已缓存Playwright CLI 0.1.21及现有localhost:5173 H5。全部API采用受控合成响应、无服务器权限的会话;PDF成功分支复用本地合成PDF。非GET被拦截,主流程POST为0,没有真实API端到端或真实模型调用。
- 360×780、390×844、1280×900截图与页面身份/非空/无遮罩检查通过。08普通/报告/三产品上下文、展开三态和演示提示、返回来源、历史打开、新对话恢复折叠、快捷问题只填输入通过;上下文折叠卡390px视口高度41.5px。
- 07三列同时可见,展开完整依据、移除后2列/1列和URL更新、单列失败不填零、重试恢复、当前下架/过期提示可见通过;09三栏、报告信息展开、下载成功/失败、报告读取重试通过;10原导出提示仍保留。修复本轮发现的报告离页挂载错误后,最终主流程及异常检查无console error/warn、无pageerror。
- HBuilderX5.26 VDOM最终Android 12:12:00、微信小程序12:15:00编译成功,27页和63项任务不变。微信内H5/Safari/真机键盘与安全区仍待验收,不宣称原生PDF下载可用。
- 截图与无真实凭据的临时脚本:C:/Users/JUN/AppData/Local/Temp/airz-hierarchy-20260929/(verify.js、edges.js)。合成浏览器会话已清空;未修改数据库、未停worker、未提交/推送。既有文档和后端改动保留。
### 6.26 2026-09-29 远程MySQL5.7迁移验证
- 用户已授权复制本机25表184行,并明确改用root账号执行服务器设置。第一次airz账号触发器创建报1419,远程仅留下5张空表;本轮临时开启log_bin_trust_function_creators后重新冻结本项目写入/空闲worker、生成一致性备份,验证遗留空表定义后续导。
- 25表全量规范化内容摘要一致,列定义/索引/外键/自增序号相同;2份历史报告、金额和快照未改写。20个CHECK替换为8个BEFORE触发器;51项INSERT/UPDATE非法拒绝、正常/NULL、外键/唯一/JSON验证通过,所有探针回滚后再次核对摘要一致。
- 活动.env的database段改为远程5.7.44-log及用户指定root账号,其余私密配置未改。全局参数恢复0、触发器保留;新PHP进程与真实HTTP共24项通过,含2份报告/金额/助手上下文、鉴权401、临时Token撤销与队列为空。只删除本次临时Token;无付费模型调用。
- 原两个Web入口恢复文件哈希,worker恢复PID1876;后续重查。source-mysql8.sql/source.env.private、验证摘要和完成记录在D:/AAdesk/202609/airz-backups/mysql57-switch-20260929/prepared-20260929-144304/,不含凭据的方案见后端database/mysql57-switch.md。
- 本机MySQL8保留。远程已经恢复正式写入,回退必须考虑新增数据;不得直接覆盖/丢弃。后端schema.sql更新为5.7含触发器结构,历史本机迁移脚本不适用于远程。跨平台/真机/生产容量等既有验收边界不变。
### 6.27 2026-09-29 新域名 PHP 部署验收
- 通过宝塔网站页面添加PHP站点,用户改域名后新绑定 aitest.ychysg.cn、删除旧绑定;HTTPS证书与强制跳转已实测,TLS1.3、默认信任校验成功。宝塔自动续签任务正常;尚未实跑到期续签。
- PHP7.4.33 在 Rocky Linux10.2/GCC14 编译时仅本次放宽 incompatible-pointer-types 的 error;修复宝塔FPM单元PID路径。ThinkPHP所需putenv从Web/CLI禁用列表中移除;十项扩展通过。Redis仅本机监听,开启认证及AOF,生产debug关闭。
- 伪静态重写限制到 location /,修复后台PATH_INFO跳转404。外网后台登录页200且表单/验证码截图正常;未验证后台业务登录。字典200、匿名报告401及7条敏感路径404。
- 新域名HTTPS服务器39项全部通过:依赖/数据库/空队列、历史161/229报告/金额/解密/冻结匹配/助手上下文、真实鉴权HTTP和两份PDF、Token撤销。临时Token已清理,无报告改写或真实AI调用。
- PHP、Redis、airz-analysis均active/enabled,worker最后MainPID566317/NRestarts0、日志无storage error;本机analysis:work已停止。部署zip/_deploy移至root私密备份目录,不留公开校验入口。
- 无新增数据库迁移,无业务源码改动、提交或推送。后端README及deployment-baota.md补充运维、兼容配置、备份和回退;前端H5未发布,下一步可对接新HTTPS API并继续移动端/跨平台验收。
- 证据:C:/Users/JUN/AppData/Local/Temp/airz-bt-deploy-20260929/ 下 terminal-new-domain.png、terminal-final-domain-status.png、terminal-archive.png、aitest-admin-login.png。目录含私密部署配置,不整体共享。
+19 -7
View File
@@ -1,21 +1,27 @@
# AI融资项目新会话交接 # AI融资项目新会话交接
记录日期:2026-09-28。本文件为接续入口;详细任务状态以[开发进度](开发进度.md)0.20、6.21节为准,设计以[开发文档](开发文档.md)v1.18、[原型页面方案](原型04-10页面改造与跳转方案.md)v1.7为准。历史章节中的“待开发”应结合最新更新阅读。 记录日期:2026-09-29。本文件为接续入口;详细任务状态以[开发进度](开发进度.md)0.26、6.27节为准,设计以[开发文档](开发文档.md)v1.24、[原型页面方案](原型04-10页面改造与跳转方案.md)v1.7为准。历史章节中的“待开发”应结合最新更新阅读。
## 1. 当前停在哪 ## 1. 当前停在哪
用户要求界面内容尽可能还原`docs/原型.jpg`。原型08已继续实施:界面、持久会话、固定评估/产品上下文、异步回复、分页、原键重试、共享额度、报告/产品/对比入口及登录返回已完成本地联调;关键代码有中文注释。原型10也已完成本地联调;09 PDF普通H5也已实施;下一步为08真实多轮语义与跨平台/真实产品验收。 最新部署(2026-09-29,优先于下文历史PID):PHP后端已通过宝塔网站页面部署到 **https://aitest.ychysg.cn**,API前缀 /api/v1,后台 /ZYurKboadX.php。旧 xctai.cn 绑定已移除,宝塔内部站点名及目录仍 /www/wwwroot/xctai.cn,运行目录 public。Let's Encrypt证书已部署、HTTP强制HTTPS、外网信任校验成功;到期2026-12-28,面板续签任务正常、每天04:38,尚未经历续签。PHP7.4.33、Redis7.2.16及systemd airz-analysis均active/enabled;已停本机worker PID1876,现由远程接管,最后PID566317/NRestarts0。**不要直接执行下文本机worker启动命令连接活动库;测试用隔离配置,维护先核对远程任务。** 服务器.env连接127.0.0.1的既有MySQL5.7/airz,未重新迁移。39项HTTPS检查(含原报告161/229、金额、解密、助手上下文和两份PDF)通过,外网接口/后台登录页/敏感路径通过;未调用收费AI。仅PHP已部署,H5尚未发布。配置/兼容处理/恢复见 D:/AAdesk/202609/airz/deployment-baota.md,服务器私密备份 /root/airz-deploy-20260929/,部署包与检查脚本已归档其deployment-artifacts/。下一步可对接新HTTPS API、发布H5,再做移动端/跨平台验收。
最新数据库切换(2026-09-29):已完成用户授权的远程MySQL5.7.44迁移与后端连接切换,当前使用111.230.144.231:3306/airz及用户指定root账号。25表184行、2份历史报告内容摘要一致;20个CHECK用8个触发器保留,51项约束与24项应用/真实HTTP检查通过。log_bin_trust_function_creators临时设1后已恢复0,当时Web入口及本机worker已恢复;后续宝塔部署已停止本机worker并由远程接管。.env仅改database段,其余加密/Redis/Token配置保持;无真实AI调用。本机MySQL8保留回退,正式写入恢复后不能直接回切旧库丢弃新数据。最终备份D:/AAdesk/202609/airz-backups/mysql57-switch-20260929/prepared-20260929-144304/;后端database/mysql57-switch.md含完整记录,凭据不输出。后端schema.sql现为5.7含触发器结构,历史本机迁移脚本不得直接重跑远程。
最新(2026-09-29):用户限定的07/08/09首屏调整已完成。08上下文一行摘要点击展开,历史入口弱化,其余助手界面保留;07核心参数三列同屏、完整依据可展开;09紧凑元信息/导出入口和浅色准备状态卡,改善内容更早出现。H5受控响应在360/390/1280px通过交互与截图验证,最终console无错误;Android/微信编译通过,详见进度6.25。无数据库/后端改动、无真实AI调用;下一步仍是真机/微信内H5/Safari验收。
用户要求界面内容尽可能还原`docs/原型.jpg`。原型08已继续实施:界面、持久会话、固定评估/产品上下文、异步回复、分页、原键重试、共享额度、报告/产品/对比入口及登录返回已完成本地联调;关键代码有中文注释。原型10也已完成本地联调;09 PDF普通H5也已实施;08累计13次真实调用,新增连续三轮金额精度专项已通过;下一步为移动端、跨平台与真实产品验收。
| 内容 | 当前状态 | | 内容 | 当前状态 |
| --- | --- | | --- | --- |
| 04/05 产品匹配详情 | 原型布局与单产品解释保留;咨询AI已接08;产品对比仍可进入 | | 04/05 产品匹配详情 | 原型布局与单产品解释保留;咨询AI已接08;产品对比仍可进入 |
| 06 产品详情 | 公共资料和本次评估冻结快照分开读取 | | 06 产品详情 | 公共资料和本次评估冻结快照分开读取 |
| 07 产品对比 | 同次2—3产品、补选/移除/刷新保留;可带选择进入08,PRODUCT-005待验收 | | 07 产品对比 | 同次2—3产品、补选/移除/刷新保留;可带选择进入08,PRODUCT-005待验收 |
| 08 上下文AI助手 | CHAT-001—003待验收,CHAT-004开发中;70项助手测试与H5、Android/微信编译通过;真实多轮模型语义/真机未验收 | | 08 上下文AI助手 | CHAT-001—003待验收,CHAT-004开发中;76项助手回归通过,H5及平台编译为前轮结果;演示边界与金额精度真实三轮专项通过,真机/跨平台待验收 |
| 09 报告 | 三栏及本次报告咨询AI入口完成;REPORT-004整体仍开发中;REPORT-005普通H5导出已本地验证,平台/容量待验收 | | 09 报告 | 三栏及本次报告咨询AI入口完成;REPORT-004整体仍开发中;REPORT-005普通H5导出已本地验证,平台/容量待验收 |
| 10 下一步行动 | REPORT-006待验收;四项行动/登录恢复/状态守卫已接通,38项H5检查及Android/微信编译通过 | | 10 下一步行动 | REPORT-006待验收;四项行动/登录恢复/状态守卫已接通,38项H5检查及Android/微信编译通过 |
当前27个注册页面、63个稳定任务编号。未提交/推送/发布;前后端已有改动均保留。 当前27个注册页面、63个稳定任务编号。本轮承接上一轮前后端未提交改动并保留;未提交/推送。PHP后端已部署,H5尚未发布。
## 2. 原型08实现与下一步 ## 2. 原型08实现与下一步
@@ -26,9 +32,11 @@
- 明确发送创建用户行与ast:调用日志,analysis:work持久消费。每条逻辑消息requestId保持不变;回执丢失同retryAttempt重放,明确失败才手动递增0→1→2。每会话100轮、同时一条待处理;每日创建30会话,调用与评估/单产品解释共享每日额度。 - 明确发送创建用户行与ast:调用日志,analysis:work持久消费。每条逻辑消息requestId保持不变;回执丢失同retryAttempt重放,明确失败才手动递增0→1→2。每会话100轮、同时一条待处理;每日创建30会话,调用与评估/单产品解释共享每日额度。
- 并发序号/幂等/额度必须保持锁定当前读,不能仅依赖用户锁后普通SELECT;测试发现MySQL可重复读旧快照会造成重复序号。 - 并发序号/幂等/额度必须保持锁定当前读,不能仅依赖用户锁后普通SELECT;测试发现MySQL可重复读旧快照会造成重复序号。
- 页面每3秒查询、最多60次,可续查;离页清空、Token变化拒绝迟到回包。消息/报告不落本机缓存,H5只保存chatId等ID到URL。登录后只接受白名单助手返回并重新鉴权。 - 页面每3秒查询、最多60次,可续查;离页清空、Token变化拒绝迟到回包。消息/报告不落本机缓存,H5只保存chatId等ID到URL。登录后只接受白名单助手返回并重新鉴权。
- 后端默认Flash真实适配已接通,本轮测试使用假模型,未新发收费模型调用;不要把假模型回复当作真实DeepSeek多轮语义验收。 - 2026-09-29在首轮6次、边界4次之后,追加同一对比会话3轮真实Flash金额复测,全部成功落库,持续保持123456.78元=12.345678万元,口头修改不影响冻结报告和三态。本轮输入16582/输出971 token,累计13次、输入51709/输出3391 token;无自动重试。第1项金额专项限定样例通过,不能外推为全量真实产品或所有输出格式通过。见[真实语义验收记录](AI助手真实语义验收.md)。
原型10及09 PDF已实施;下一步补真实模型/产品与平台验收;并补08真实多轮人工语义、微信内H5/Safari/真机键盘安全区及生产容量验收。供应商与真实产品准备仍可推进,不虚构整体完成率。 tests/assistant_semantics.php支持--boundaries定向4例、--money-precision同会话3例,默认只读计划,显式--fake/--live;tests/assistant_money.php验证金额。服务端从冻结facts/产品条件提取金额白名单,使用BCMath,带单位的错误金额拒绝成功落库、保留用量、不自动重试;不替代所有自然语言数值语义验收。前轮76项助手与舍入拒绝回归已通过;本轮生产源码未变,真实/假模型各11项流程、30项金额单测/回放、21项格式解析/回放通过,三条真实回复金额全部通过。首条原始JSON出现相同内容的重复answer键,已确认现有解析/落库有效文本一致,严格重复键拒绝不在本轮金额专项范围。测试数据已清理,原有四表摘要一致,仍1用户、2评估/会话、2日志;正式worker恢复PID44944,后续重新查PID。
原型10及09 PDF已实施;下一步做移动端键盘/安全区/长回复滚动/返回恢复/失败重试,再补微信内H5/Safari、真实产品人工样例及生产容量验收。供应商与真实产品准备仍可推进,不虚构整体完成率。
## 3. 必须延续的业务约定 ## 3. 必须延续的业务约定
@@ -86,6 +94,10 @@
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/product_explanations.php --worker-stopped & 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/product_explanations.php --worker-stopped
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/match_records.php --worker-stopped & 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/match_records.php --worker-stopped
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_chats.php --worker-stopped & 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_chats.php --worker-stopped
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_reply.php
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_money.php
# 默认仅输出计划;--fake --worker-stopped才执行合成流程。
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' tests/assistant_semantics.php --money-precision
# --live会真实调用并产生用量,日常回归不加。 # --live会真实调用并产生用量,日常回归不加。
& 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' think analysis:work & 'D:/Develop/phpstudy_pro/Extensions/php/php7.4.3nts/php.exe' think analysis:work
@@ -99,4 +111,4 @@ H5已清理旧开发服务器并以最新代码恢复`http://localhost:5173/`,
## 7. 新会话可以直接发送 ## 7. 新会话可以直接发送
> 请先阅读docs/新会话交接.md、开发进度0.20/6.21和原型04-10页面方案,继续原型08真实多轮语义及跨平台验收准备,先核对可执行样例和用量边界。界面尽量还原docs/原型.jpg,关键代码加中文注释;保留已有04/05解释、07对比、08上下文助手、09报告及10行动成果。只做材料准备与引导,不提交银行申请;PDF未完成时不做假下载。先检查工作树与数据库/worker现状,新增迁移仍按具体方案申请授权。 > 请先阅读docs/新会话交接.md、开发进度0.23/6.24和原型04-10页面方案,继续原型08移动端及跨平台验收,先阅读AI助手真实语义验收记录:金额精度三轮真实专项已通过,无需重复收费调用;核对可用浏览器和真机环境,清楚区分模拟与真机结果。界面尽量还原docs/原型.jpg,关键代码加中文注释;保留已有04/05解释、07对比、08上下文助手、09报告及10行动成果。只做材料准备与引导,不提交银行申请;PDF未完成时不做假下载。先检查工作树与数据库/worker现状,新增迁移仍按具体方案申请授权。
+12 -3
View File
@@ -2,7 +2,7 @@
<view class="page assistant-page" :style="pageStyle()"> <view class="page assistant-page" :style="pageStyle()">
<AppHeader title="AI融资助手" :customBack="true" @back="back" /> <AppHeader title="AI融资助手" :customBack="true" @back="back" />
<view class="chat-shell"> <view class="chat-shell">
<view class="chat-toolbar"><text class="toolbar-note">{{ chatId.length > 0 ? '对话已保存' : '您的融资问题,继续聊聊' }}</text><view class="toolbar-actions"><text class="toolbar-link" @click="showHistory">历史对话</text><text class="toolbar-link" @click="newChat">新对话</text></view></view> <view class="chat-toolbar"><text class="toolbar-note">{{ chatId.length > 0 ? '对话已保存' : '您的融资问题,继续聊聊' }}</text><view class="toolbar-actions"><text class="toolbar-link history-entry" @click="showHistory">历史对话</text><text class="toolbar-link" @click="newChat">新对话</text></view></view>
<scroll-view scroll-y class="chat-scroll" :scroll-into-view="scrollTarget" :style="contentStyle(300, false)"> <scroll-view scroll-y class="chat-scroll" :scroll-into-view="scrollTarget" :style="contentStyle(300, false)">
<view v-if="active" class="chat-content"> <view v-if="active" class="chat-content">
<view v-if="historyOpen" class="history-panel"> <view v-if="historyOpen" class="history-panel">
@@ -16,10 +16,13 @@
<view v-if="pageError.length > 0" class="error-panel"><text class="error-title">暂时无法继续对话</text><text class="error-text">{{ pageError }}</text><text v-if="routeError.length == 0" class="toolbar-link" @click="load">重新加载</text><text class="toolbar-link" @click="back">返回来源页面</text></view> <view v-if="pageError.length > 0" class="error-panel"><text class="error-title">暂时无法继续对话</text><text class="error-text">{{ pageError }}</text><text v-if="routeError.length == 0" class="toolbar-link" @click="load">重新加载</text><text class="toolbar-link" @click="back">返回来源页面</text></view>
<template v-if="context != null"> <template v-if="context != null">
<view v-if="context!.assessmentId.length > 0" class="context-card"> <view v-if="context!.assessmentId.length > 0" class="context-card">
<view class="context-heading"><text class="context-title">{{ context!.title }}</text><text class="context-link" @click="source">{{ sourceLabel }}</text></view> <view class="context-heading" @click="contextOpen = !contextOpen"><text class="context-summary">{{ context!.title }} · {{ context!.products.length > 0 ? context!.products.length.toString() + '项产品' : '报告解读' }}</text><text class="context-toggle">{{ contextOpen ? '收起' : '展开' }}</text></view>
<template v-if="contextOpen">
<text v-if="context!.products.length == 0" class="context-note">围绕这份已保存报告解答,不重新评估</text> <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> <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 v-if="hasDemo" class="context-note">演示产品 · 仅供功能验证</text>
<text class="context-link context-source" @click="source">{{ sourceLabel }}</text>
</template>
</view> </view>
<view class="assistant-intro"><image src="/packages/finance/static/robot.png" mode="aspectFit" class="intro-image" /><view class="intro-bubble"><text class="bubble-text">您好!我是您的 AI 融资助手。 <view class="assistant-intro"><image src="/packages/finance/static/robot.png" mode="aspectFit" class="intro-image" /><view class="intro-bubble"><text class="bubble-text">您好!我是您的 AI 融资助手。
{{ context!.assessmentId.length > 0 ? '我可以结合本次报告,帮您理解产品条件、匹配原因和需要准备的资料。' : '我可以帮您解答关于银行产品、申请条件、资料准备等问题。' }}</text></view></view> {{ context!.assessmentId.length > 0 ? '我可以结合本次报告,帮您理解产品条件、匹配原因和需要准备的资料。' : '我可以帮您解答关于银行产品、申请条件、资料准备等问题。' }}</text></view></view>
@@ -49,6 +52,8 @@
const { pageStyle, contentStyle, bottomStyle } = usePageLayout() const { pageStyle, contentStyle, bottomStyle } = usePageLayout()
const assessmentId = ref(''); const productIds = ref<string[]>([]); const chatId = ref(''); const from = 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 context = ref<AssistantContext | null>(null); const messages = ref<AssistantTurn[]>([])
// 展开只影响展示;切换会话或账户时恢复折叠,不改变已绑定的报告和产品。
const contextOpen = ref(false)
const active = ref(false); const loading = ref(false); const sending = ref(false); const paging = ref(false) const active = ref(false); const loading = ref(false); const sending = ref(false); const paging = ref(false)
const draft = ref(''); const pageError = ref(''); const routeError = ref(''); const sendError = ref(''); const queryError = ref('') const draft = ref(''); const pageError = ref(''); const routeError = ref(''); const sendError = ref(''); const queryError = ref('')
const scrollTarget = ref(''); const more = ref(false); const cursor = ref(''); const pollStopped = ref(false) const scrollTarget = ref(''); const more = ref(false); const cursor = ref(''); const pollStopped = ref(false)
@@ -73,7 +78,7 @@
const back = () => { if (getCurrentPages().length > 1) uni.navigateBack(); else source() } 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 + '&productIds=' + productIds.value.join(',') + '&chatId=' + chatId.value + '&from=' + from.value }) }
const stopTimer = () => { if (timer != 0) { clearInterval(timer); timer = 0 } } const stopTimer = () => { if (timer != 0) { clearInterval(timer); timer = 0 } }
const clearContent = () => { context.value = null; messages.value = []; draft.value = ''; sendText = ''; sendKey = ''; uncertain.value = false; histories.value = []; historyCursor = ''; historyOpen.value = false; sendError.value = ''; queryError.value = '' } 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双重校验;离页、退出、换账号后旧响应不得回填个人对话。 // 请求代次与登录Token双重校验;离页、退出、换账号后旧响应不得回填个人对话。
const current = (ticket: number, token: string): boolean => { const current = (ticket: number, token: string): boolean => {
if (!active.value || ticket != generation) return false if (!active.value || ticket != generation) return false
@@ -169,12 +174,16 @@
onUnload(() => { active.value = false; generation++; stopTimer(); clearContent() }) onUnload(() => { active.value = false; generation++; stopTimer(); clearContent() })
</script> </script>
<style> <style>
.context-summary { flex: 1; font-size: 24rpx; line-height: 36rpx; color: #45617f; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.context-toggle { flex-shrink: 0; color: #527ba8; font-size: 22rpx; margin-left: 16rpx; line-height: 36rpx; }
.context-source { margin-top: 16rpx; }
.assistant-page { background-color: #fcfdff; } .assistant-page { background-color: #fcfdff; }
.chat-shell { flex: 1; } .chat-shell { flex: 1; }
.chat-toolbar { padding: 8rpx 30rpx 18rpx; min-height: 60rpx; flex-direction: row; align-items: center; justify-content: space-between; } .chat-toolbar { padding: 8rpx 30rpx 18rpx; min-height: 60rpx; flex-direction: row; align-items: center; justify-content: space-between; }
.toolbar-note { color: #95a1b3; font-size: 21rpx; } .toolbar-note { color: #95a1b3; font-size: 21rpx; }
.toolbar-actions { flex-direction: row; } .toolbar-actions { flex-direction: row; }
.toolbar-link { color: #2672d9; font-size: 23rpx; line-height: 40rpx; margin-left: 22rpx; } .toolbar-link { color: #2672d9; font-size: 23rpx; line-height: 40rpx; margin-left: 22rpx; }
.history-entry { color: #7b899c; font-size: 21rpx; }
.chat-content { padding: 14rpx 30rpx 36rpx; } .chat-content { padding: 14rpx 30rpx 36rpx; }
.context-card { padding: 20rpx 22rpx; margin-bottom: 30rpx; border-radius: 14rpx; background-color: #f0f6ff; border: 1rpx solid #e0ebfb; } .context-card { padding: 20rpx 22rpx; margin-bottom: 30rpx; border-radius: 14rpx; background-color: #f0f6ff; border: 1rpx solid #e0ebfb; }
.context-heading, .context-product, .history-title { flex-direction: row; justify-content: space-between; align-items: center; } .context-heading, .context-product, .history-title { flex-direction: row; justify-content: space-between; align-items: center; }
+49 -3
View File
@@ -5,13 +5,32 @@
<view class="content"> <view class="content">
<text class="eyebrow">评估 #{{ assessmentId }} · 历史产品快照</text> <text class="eyebrow">评估 #{{ assessmentId }} · 历史产品快照</text>
<text class="headline">并排看清,选择更有依据</text> <text class="headline">并排看清,选择更有依据</text>
<text class="intro">对照同一次评估的产品条件,不代表授信或获批。金额单位为万元,展示顺序不代表优劣。</text> <text class="intro">同次评估 · 金额单位为万元 · 条件判断不代表获批</text>
<view v-if="routeError.length > 0" class="panel"><text class="warning">{{ routeError }}</text><button class="secondary" @click="toMatches">返回本次匹配</button></view> <view v-if="routeError.length > 0" class="panel"><text class="warning">{{ routeError }}</text><button class="secondary" @click="toMatches">返回本次匹配</button></view>
<template v-else> <template v-else>
<view class="toolbar"><view class="summary"><text class="summary-title">已选 {{ ids.length }} / 3 项</text><text class="hint">{{ complete ? '已读取同一版本的完整对比' : (ids.length < 2 ? '再选产品后开始对比' : '对比尚不完整,请查看各列状态') }}</text></view><button class="add" @click="toMatches">{{ ids.length < 3 ? '补选产品' : '调整选择' }}</button></view> <view class="toolbar"><view class="summary"><text class="summary-title">已选 {{ ids.length }} / 3 项</text><text class="hint">{{ complete ? '已读取同一版本的完整对比' : (ids.length < 2 ? '再选产品后开始对比' : '对比尚不完整,请查看各列状态') }}</text></view><button class="add" @click="toMatches">{{ ids.length < 3 ? '补选产品' : '调整选择' }}</button></view>
<text v-if="ids.length > 0" class="scroll-hint">左右滑动查看各产品 · 长内容可在单元格内上下滑动</text>
<view v-if="ids.length == 0" class="panel"><text class="summary-title">还没有选择产品</text><text class="intro">从本次匹配中选择2–3项,也可对照不符条件和待核实的产品。</text><button class="secondary" @click="toMatches">去选择产品</button></view> <view v-if="ids.length == 0" class="panel"><text class="summary-title">还没有选择产品</text><text class="intro">从本次匹配中选择2–3项,也可对照不符条件和待核实的产品。</text><button class="secondary" @click="toMatches">去选择产品</button></view>
<view v-else class="table"> <template v-else>
<view class="overview-table">
<view class="overview-row">
<view class="overview-label overview-head"><text class="row-label">核心参数</text></view>
<scroll-view v-for="column in columns" :key="column.id" scroll-y class="overview-value overview-head">
<view class="overview-product">
<template v-if="usable(column)"><text class="overview-name">{{ column.data!.item.product.name }}</text><text class="overview-bank">{{ column.data!.item.product.bank }}</text><text class="overview-link" @click="detail(column.id)">产品详情 ›</text></template>
<template v-else><text class="overview-name">产品 #{{ column.id }}</text><text class="overview-bank">{{ column.loading ? '读取中…' : problem(column) }}</text><text v-if="!column.loading" class="overview-link" @click="loadColumn(column.id)">重试此列</text></template>
<text class="overview-remove" @click="remove(column.id)">移除</text>
</view>
</scroll-view>
</view>
<view v-for="row in overviewRows" :key="row.id" class="overview-row">
<view class="overview-label"><text class="row-label">{{ row.label }}</text></view>
<view v-for="column in columns" :key="column.id" class="overview-value"><text :class="overviewClass(column, row.id)">{{ cell(column, row.id) }}</text></view>
</view>
</view>
<text class="overview-note">{{ hasDemo ? '含演示产品,仅供功能验证。' : '' }}展示顺序不代表优劣。</text>
<view class="details-toggle" @click="detailsOpen = !detailsOpen"><text class="summary-title">详细条件与匹配依据</text><text class="detail-toggle-label">{{ detailsOpen ? '收起' : '展开' }}</text></view>
<text v-if="detailsOpen" class="scroll-hint">左右滑动查看各产品 · 长内容可在单元格内上下滑动</text>
<view v-if="detailsOpen" class="table">
<view class="rail"><view class="rail-head"><text class="rail-title">对比指标</text><text class="hint">同次评估</text></view><view v-for="row in rows" :key="row.id" class="label-cell" :style="rowStyle(row)"><text class="row-label">{{ row.label }}</text></view></view> <view class="rail"><view class="rail-head"><text class="rail-title">对比指标</text><text class="hint">同次评估</text></view><view v-for="row in rows" :key="row.id" class="label-cell" :style="rowStyle(row)"><text class="row-label">{{ row.label }}</text></view></view>
<scroll-view scroll-x class="horizontal"> <scroll-view scroll-x class="horizontal">
<view class="columns" :style="columnsStyle()"> <view class="columns" :style="columnsStyle()">
@@ -34,6 +53,7 @@
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
</template>
<text class="footer-note">历史参数保留评估时版本;当前可用状态单独展示。缺失信息不会被推断为零,演示产品仅供功能验证。</text> <text class="footer-note">历史参数保留评估时版本;当前可用状态单独展示。缺失信息不会被推断为零,演示产品仅供功能验证。</text>
<button class="secondary" @click="consult">产品差异,咨询AI</button> <button class="secondary" @click="consult">产品差异,咨询AI</button>
<button class="secondary" @click="toMatches">返回本次匹配</button> <button class="secondary" @click="toMatches">返回本次匹配</button>
@@ -53,6 +73,9 @@
type CompareRow = { id: string, label: string, height: number } type CompareRow = { id: string, label: string, height: number }
const { pageStyle, contentStyle } = usePageLayout() const { pageStyle, contentStyle } = usePageLayout()
const assessmentId = ref(''); const ids = ref<string[]>([]); const columns = ref<CompareColumn[]>([]); const routeError = ref('') const assessmentId = ref(''); const ids = ref<string[]>([]); const columns = ref<CompareColumn[]>([]); const routeError = ref('')
const detailsOpen = ref(false)
// 首屏并排展示短参数;长证据保留原宽表,展开后读取完整内容,不截断判断依据。
const overviewRows: CompareRow[] = [{ id: 'state', label: '匹配状态', height: 0 }, { id: 'amount', label: '额度范围', height: 0 }, { id: 'term', label: '期限范围', height: 0 }, { id: 'rate', label: '年化利率', height: 0 }]
let visible = false; let generation = 0 let visible = false; let generation = 0
const rows: CompareRow[] = [ const rows: CompareRow[] = [
{ id: 'availability', label: '当前状态', height: 160 }, { id: 'coverage', label: '需求覆盖依据', height: 360 }, { id: 'availability', label: '当前状态', height: 160 }, { id: 'coverage', label: '需求覆盖依据', height: 360 },
@@ -73,9 +96,17 @@
} }
const usable = (column: CompareColumn): boolean => !column.loading && column.data != null && problem(column).length == 0 const usable = (column: CompareColumn): boolean => !column.loading && column.data != null && problem(column).length == 0
const complete = computed((): boolean => ids.value.length >= 2 && columns.value.length == ids.value.length && columns.value.every((c: CompareColumn): boolean => usable(c))) const complete = computed((): boolean => ids.value.length >= 2 && columns.value.length == ids.value.length && columns.value.every((c: CompareColumn): boolean => usable(c)))
const hasDemo = computed((): boolean => columns.value.some((c: CompareColumn): boolean => usable(c) && c.data!.item.product.demo))
const overviewClass = (column: CompareColumn, key: string): string => {
if (key == 'amount') return 'overview-text overview-amount'
if (key != 'state' || !usable(column)) return 'overview-text'
return 'overview-text ' + (column.data!.item.state == 'eligible' ? 'good' : (column.data!.item.state == 'ineligible' ? 'blocked' : 'pending'))
}
const cell = (column: CompareColumn, key: string): string => { const cell = (column: CompareColumn, key: string): string => {
if (!usable(column)) return column.loading ? '读取中…' : '本列未就绪' if (!usable(column)) return column.loading ? '读取中…' : '本列未就绪'
const m = column.data!.item; const p = m.product const m = column.data!.item; const p = m.product
// 核心表也保留当前不可用提示,不能让历史符合条件被误读为现在仍可办理。
if (key == 'state') return m.stateLabel + (m.availability != 'available' ? ' · ' + m.availabilityLabel : '')
if (key == 'availability') return m.availabilityLabel + ';不改变历史结论' if (key == 'availability') return m.availabilityLabel + ';不改变历史结论'
if (key == 'coverage') { if (key == 'coverage') {
const demand = m.positive.concat(m.blocking).concat(m.cautions).filter((r: MatchReason): boolean => r.fields.some((f: string): boolean => ['requestedAmountYuan','requestedTermMonths','purposeCode'].includes(f))) const demand = m.positive.concat(m.blocking).concat(m.cautions).filter((r: MatchReason): boolean => r.fields.some((f: string): boolean => ['requestedAmountYuan','requestedTermMonths','purposeCode'].includes(f)))
@@ -148,6 +179,21 @@
onUnload(() => { visible = false; generation++; columns.value = [] }) onUnload(() => { visible = false; generation++; columns.value = [] })
</script> </script>
<style> <style>
.overview-table { border: 1rpx solid #dfe8f3; border-radius: 14rpx; overflow: hidden; background-color: #fff; }
.overview-row { flex-direction: row; border-bottom: 1rpx solid #e7edf5; }
.overview-label { width: 120rpx; flex-shrink: 0; padding: 22rpx 12rpx; background-color: #eef4fb; justify-content: center; }
.overview-value { flex: 1; width: 0; padding: 22rpx 12rpx; border-left: 1rpx solid #e7edf5; justify-content: center; }
.overview-head { height: 270rpx; padding-top: 18rpx; padding-bottom: 18rpx; }
.overview-product { padding: 0; }
.overview-name { font-size: 24rpx; line-height: 35rpx; font-weight: 700; color: #243f60; }
.overview-bank { color: #788ba2; font-size: 20rpx; line-height: 30rpx; margin-top: 10rpx; }
.overview-link, .overview-remove { font-size: 22rpx; line-height: 34rpx; color: #0868f7; padding-top: 12rpx; }
.overview-remove { color: #788ba2; }
.overview-text { font-size: 23rpx; line-height: 36rpx; color: #455f7c; }
.overview-amount { font-weight: 700; color: #154779; }
.overview-note { color: #8494a7; font-size: 21rpx; line-height: 34rpx; margin-top: 14rpx; }
.details-toggle { flex-direction: row; align-items: center; justify-content: space-between; padding: 28rpx 0; }
.detail-toggle-label { color: #0868f7; font-size: 24rpx; }
.content { padding: 28rpx 22rpx 60rpx; } .content { padding: 28rpx 22rpx 60rpx; }
.eyebrow { color: #6d829c; font-size: 22rpx; } .eyebrow { color: #6d829c; font-size: 22rpx; }
.headline { color: #193653; font-size: 36rpx; font-weight: 700; margin-top: 14rpx; } .headline { color: #193653; font-size: 36rpx; font-weight: 700; margin-top: 14rpx; }
@@ -1,14 +1,15 @@
<template> <template>
<view class="export-area"> <view :class="compact ? 'export-area export-compact' : 'export-area'">
<view class="export-row"><text class="export-label">报告已保存至融资档案</text><button class="export-button" :disabled="busy" @click="download">{{ busy ? '正在生成…' : '下载PDF' }}</button></view> <view class="export-row"><text class="export-label">{{ compact ? '评估 #' + assessmentId + ' · 已保存' : '报告已保存至融资档案' }}</text><button class="export-button" :disabled="busy" @click="download">{{ busy ? '正在生成…' : '下载PDF' }}</button></view>
<text v-if="message.length > 0" :class="failed ? 'export-message export-error' : 'export-message'">{{ message }}</text> <text v-if="message.length > 0" :class="failed ? 'export-message export-error' : 'export-message'">{{ message }}</text>
<text v-if="message.length == 0" class="export-message">导出不含身份信息,请妥善保管下载文件。</text> <text v-if="message.length == 0 && !compact" class="export-message">导出不含身份信息,请妥善保管下载文件。</text>
</view> </view>
</template> </template>
<script setup lang="uts"> <script setup lang="uts">
import { getSession } from '@/services/api.uts' import { getSession } from '@/services/api.uts'
import { ReportDownload, downloadReportPdf } from '@/packages/finance/services/report-export.uts' import { ReportDownload, downloadReportPdf } from '@/packages/finance/services/report-export.uts'
const props = defineProps({ assessmentId: { type: String, required: true } }) // 报告页使用紧凑入口,下载进度和错误始终展示;其他页面保留原布局。
const props = defineProps({ assessmentId: { type: String, required: true }, compact: { type: Boolean, default: false } })
const emit = defineEmits(['authrequired', 'sessionchanged']) const emit = defineEmits(['authrequired', 'sessionchanged'])
const busy = ref(false); const message = ref(''); const failed = ref(false) const busy = ref(false); const message = ref(''); const failed = ref(false)
let operation: ReportDownload | null = null; let generation = 0 let operation: ReportDownload | null = null; let generation = 0
@@ -39,4 +40,5 @@
.export-button { font-size: 24rpx; color: #0868f7; background-color: #eaf3ff; border-radius: 10rpx; margin-left: 18rpx; padding-left: 24rpx; padding-right: 24rpx; } .export-button { font-size: 24rpx; color: #0868f7; background-color: #eaf3ff; border-radius: 10rpx; margin-left: 18rpx; padding-left: 24rpx; padding-right: 24rpx; }
.export-message { color: #8393a6; font-size: 22rpx; line-height: 36rpx; margin-top: 12rpx; } .export-message { color: #8393a6; font-size: 22rpx; line-height: 36rpx; margin-top: 12rpx; }
.export-error { color: #a05d37; } .export-error { color: #a05d37; }
.export-compact { margin-top: 0; margin-bottom: 0; }
</style> </style>
+21 -15
View File
@@ -4,12 +4,14 @@
<view class="tabs"><view v-for="item in tabs" :key="item.id" :class="tab == item.id ? 'tab active' : 'tab'" @click="switchTab(item.id)"><text :class="tab == item.id ? 'tab-label selected' : 'tab-label'">{{ item.label }}</text></view></view> <view class="tabs"><view v-for="item in tabs" :key="item.id" :class="tab == item.id ? 'tab active' : 'tab'" @click="switchTab(item.id)"><text :class="tab == item.id ? 'tab-label selected' : 'tab-label'">{{ item.label }}</text></view></view>
<scroll-view scroll-y :style="contentStyle(186, false)"> <scroll-view scroll-y :style="contentStyle(186, false)">
<view class="report-content"> <view class="report-content">
<text class="eyebrow">评估记录 #{{ assessmentId.length > 0 ? assessmentId : '待选择' }}</text> <!-- 离页清空报告时只切换可见性,避免在页面销毁过程中重新挂载文本节点。 -->
<text v-show="report == null" class="eyebrow">评估记录 #{{ assessmentId.length > 0 ? assessmentId : '待选择' }}</text>
<text v-if="loading" class="body-text">正在读取已保存报告…</text> <text v-if="loading" class="body-text">正在读取已保存报告…</text>
<view v-if="error.length > 0" class="panel"><text class="section-title">报告暂不可读取</text><text class="body-text">{{ error }}</text><button v-if="routeError.length == 0" class="secondary" @click="loadReport">重新加载报告</button><text class="action" @click="history">前往融资档案</text></view> <view v-if="error.length > 0" class="panel"><text class="section-title">报告暂不可读取</text><text class="body-text">{{ error }}</text><button v-if="routeError.length == 0" class="secondary" @click="loadReport">重新加载报告</button><text class="action" @click="history">前往融资档案</text></view>
<template v-if="report != null"> <template v-if="report != null">
<ReportExport :assessmentId="assessmentId" @authrequired="login" @sessionchanged="loadReport" /> <ReportExport :assessmentId="assessmentId" :compact="true" @authrequired="login" @sessionchanged="loadReport" />
<text class="report-note">生成于 {{ report!.generatedAt }} · 用户自填资料分析</text> <text class="metadata-toggle" @click="metadataOpen = !metadataOpen">用户自填资料分析 · 报告信息{{ metadataOpen ? '收起' : '展开' }}</text>
<view v-if="metadataOpen" class="metadata"><text class="report-note">生成于 {{ report!.generatedAt }}</text><text class="report-note">报告已保存至融资档案。导出不含身份信息,请妥善保管下载文件。</text></view>
<view v-if="report!.version == 1" class="legacy"><text class="report-note">这是一份旧版自填报告,当时未采集融资需求、未计算完整度与准备状态,也未执行产品匹配。历史内容保持原样。</text></view> <view v-if="report!.version == 1" class="legacy"><text class="report-note">这是一份旧版自填报告,当时未采集融资需求、未计算完整度与准备状态,也未执行产品匹配。历史内容保持原样。</text></view>
<template v-if="tab == 'overview'"> <template v-if="tab == 'overview'">
<view class="hero"> <view class="hero">
@@ -43,9 +45,9 @@
</template> </template>
</template> </template>
<template v-else> <template v-else>
<text class="headline">让下一次准备更充分</text><text class="intro">依据本次报告和产品条件整理。补充资料后需重新提交新评估,改善不保证获批。</text> <text class="headline">让下一次准备更充分</text><text class="intro">依据本次报告整理 · 改善不保证获批</text>
<view v-if="report!.version == 2" class="panel"><text class="section-title">优先补充与核实</text><text v-if="missingItems.length == 0" class="report-note">{{ report!.completenessPercent == null ? '本次未评定缺失字段,不能据此认为资料齐全。' : '本次字段清单没有待补充或格式待核实项,仍需准备核验材料。' }}</text><view class="fact-row" v-for="item in missingItems" :key="item.label"><text class="fact-label">{{ item.label }}</text><text class="fact-value">{{ item.value }}</text></view><text class="body-text" v-for="(item, index) in report!.readinessBasis" :key="index">{{ item }}</text></view> <view v-if="report!.version == 2" class="panel"><text class="section-title">优先补充与核实</text><text v-if="missingItems.length == 0" class="report-note">{{ report!.completenessPercent == null ? '本次未评定缺失字段,不能据此认为资料齐全。' : '本次字段清单没有待补充或格式待核实项,仍需准备核验材料。' }}</text><view class="fact-row" v-for="item in missingItems" :key="item.label"><text class="fact-label">{{ item.label }}</text><text class="fact-value">{{ item.value }}</text></view><text class="body-text" v-for="(item, index) in report!.readinessBasis" :key="index">{{ item }}</text></view>
<view class="panel"><text class="section-title">已保存的AI改善建议</text><text v-if="report!.suggestions.length == 0" class="report-note">暂无有依据的额外建议,不代表已经完成核验。</text><text class="body-text" v-for="(item, index) in report!.suggestions" :key="index">{{ item }}</text><text class="source-note">保留报告生成时的文本与依据,不重新调用模型。</text></view> <view class="panel"><text class="section-title">已保存的AI改善建议</text><text v-if="report!.suggestions.length == 0" class="report-note">暂无有依据的额外建议,不代表已经完成核验。</text><text class="body-text" v-for="(item, index) in report!.suggestions" :key="index">{{ item }}</text><text class="source-note">保留报告生成时的文本与依据;补充资料后需发起新评估。</text></view>
<view v-if="report!.matching.status != 'completed'" class="panel"><text class="section-title">产品条件提示</text><text class="report-note">本次未执行产品匹配,没有可关联的产品改善依据。</text></view> <view v-if="report!.matching.status != 'completed'" class="panel"><text class="section-title">产品条件提示</text><text class="report-note">本次未执行产品匹配,没有可关联的产品改善依据。</text></view>
<template v-else><view v-for="group in adviceGroups" :key="group.id" class="panel"><text class="section-title">{{ group.label }}</text><text class="report-note">仅展示本组前3项产品的已有依据,可进入完整列表查看更多。</text><text v-if="group.loading" class="body-text">正在读取产品依据…</text><template v-if="group.error.length > 0"><text class="body-text">{{ group.error }}</text><button class="secondary" @click="loadAdvice(group.id)">重试{{ group.label }}</button></template><text v-if="!group.loading && group.error.length == 0 && group.items.length == 0" class="report-note">本组暂无产品依据。</text><view v-for="item in group.items" :key="item.id" class="advice-product"><text class="product-name" @click="openProduct(item.id)">{{ item.product.name }} ›</text><text class="source-note">{{ item.stateLabel }} · 评估时版本 v{{ item.product.version }}</text><view v-for="reason in adviceReasons(item)" :key="reason.id"><text class="body-text">{{ reason.text }}</text><text v-for="line in reason.evidence" :key="line" class="evidence">{{ line }}</text></view><text v-if="item.availability != 'available'" class="availability">{{ item.availabilityLabel }}</text></view><text class="action" @click="allMatches(group.id)">查看全部{{ group.label }} ›</text></view></template> <template v-else><view v-for="group in adviceGroups" :key="group.id" class="panel"><text class="section-title">{{ group.label }}</text><text class="report-note">仅展示本组前3项产品的已有依据,可进入完整列表查看更多。</text><text v-if="group.loading" class="body-text">正在读取产品依据…</text><template v-if="group.error.length > 0"><text class="body-text">{{ group.error }}</text><button class="secondary" @click="loadAdvice(group.id)">重试{{ group.label }}</button></template><text v-if="!group.loading && group.error.length == 0 && group.items.length == 0" class="report-note">本组暂无产品依据。</text><view v-for="item in group.items" :key="item.id" class="advice-product"><text class="product-name" @click="openProduct(item.id)">{{ item.product.name }} ›</text><text class="source-note">{{ item.stateLabel }} · 评估时版本 v{{ item.product.version }}</text><view v-for="reason in adviceReasons(item)" :key="reason.id"><text class="body-text">{{ reason.text }}</text><text v-for="line in reason.evidence" :key="line" class="evidence">{{ line }}</text></view><text v-if="item.availability != 'available'" class="availability">{{ item.availabilityLabel }}</text></view><text class="action" @click="allMatches(group.id)">查看全部{{ group.label }} ›</text></view></template>
</template> </template>
@@ -73,13 +75,15 @@
const tabs: ReportTab[] = [{ id: 'overview', label: '评估概况' }, { id: 'matches', label: '匹配结果' }, { id: 'improvements', label: '改善建议' }] const tabs: ReportTab[] = [{ id: 'overview', label: '评估概况' }, { id: 'matches', label: '匹配结果' }, { id: 'improvements', label: '改善建议' }]
const assessmentId = ref(''); const tab = ref('overview'); const loading = ref(false); const error = ref(''); const routeError = ref('') const assessmentId = ref(''); const tab = ref('overview'); const loading = ref(false); const error = ref(''); const routeError = ref('')
const report = ref<AnalysisReport | null>(null); const factsOpen = ref(false); const completenessOpen = ref(false) const report = ref<AnalysisReport | null>(null); const factsOpen = ref(false); const completenessOpen = ref(false)
// 日期与导出说明按需展开,首屏优先呈现准备状态和当前栏目的内容。
const metadataOpen = ref(false)
const candidates = ref<MatchView[]>([]); const candidateLoading = ref(false); const candidateError = ref('') const candidates = ref<MatchView[]>([]); const candidateLoading = ref(false); const candidateError = ref('')
const adviceGroups = ref<AdviceGroup[]>([]) const adviceGroups = ref<AdviceGroup[]>([])
let generation = 0; let visible = false let generation = 0; let visible = false
const login = () => { uni.redirectTo({ url: '/packages/account/login/login?resumeReport=1&assessmentId=' + assessmentId.value + '&tab=' + tab.value }) } const login = () => { uni.redirectTo({ url: '/packages/account/login/login?resumeReport=1&assessmentId=' + assessmentId.value + '&tab=' + tab.value }) }
const missingItems = computed((): AssessmentField[] => report.value == null ? [] : report.value!.completenessItems.filter((item: AssessmentField): boolean => ['待补充','需核实'].includes(item.value))) const missingItems = computed((): AssessmentField[] => report.value == null ? [] : report.value!.completenessItems.filter((item: AssessmentField): boolean => ['待补充','需核实'].includes(item.value)))
const adviceReasons = (item: MatchView): MatchReason[] => item.state == 'ineligible' ? item.blocking.concat(item.cautions) : item.cautions const adviceReasons = (item: MatchView): MatchReason[] => item.state == 'ineligible' ? item.blocking.concat(item.cautions) : item.cautions
const reset = () => { report.value = null; candidates.value = []; adviceGroups.value = []; candidateLoading.value = false; candidateError.value = ''; factsOpen.value = false; completenessOpen.value = false } const reset = () => { report.value = null; candidates.value = []; adviceGroups.value = []; candidateLoading.value = false; candidateError.value = ''; factsOpen.value = false; completenessOpen.value = false; metadataOpen.value = false }
const current = (request: number, token: string): boolean => visible && request == generation && getSession()?.getString('token') == token const current = (request: number, token: string): boolean => visible && request == generation && getSession()?.getString('token') == token
const sessionChanged = (request: number, token: string): boolean => { const sessionChanged = (request: number, token: string): boolean => {
if (!visible || request != generation) return true if (!visible || request != generation) return true
@@ -168,16 +172,18 @@
.selected { color: #0868f7; font-weight: 700; } .selected { color: #0868f7; font-weight: 700; }
.report-content { padding: 28rpx 28rpx 65rpx; } .report-content { padding: 28rpx 28rpx 65rpx; }
.eyebrow { color: #778ba3; font-size: 22rpx; } .eyebrow { color: #778ba3; font-size: 22rpx; }
.headline { color: #1a3859; font-size: 36rpx; font-weight: 700; margin-top: 28rpx; } .headline { color: #1a3859; font-size: 34rpx; font-weight: 700; margin-top: 22rpx; }
.intro { color: #73869d; font-size: 24rpx; line-height: 40rpx; margin-top: 14rpx; } .intro { color: #73869d; font-size: 24rpx; line-height: 40rpx; margin-top: 14rpx; }
.hero { background-color: #155dd1; border-radius: 22rpx; padding: 32rpx; margin-top: 24rpx; } .metadata-toggle { color: #78889e; font-size: 21rpx; line-height: 36rpx; padding-top: 8rpx; padding-bottom: 4rpx; }
.hero-label { color: #d8e8ff; font-size: 24rpx; } .metadata { padding: 0 0 12rpx; }
.hero-title { color: #fff; font-size: 42rpx; font-weight: 700; line-height: 60rpx; margin-top: 14rpx; } .hero { background-color: #eef5ff; border: 1rpx solid #dfeafb; border-radius: 18rpx; padding: 26rpx; margin-top: 18rpx; }
.hero-note { color: #d8e8ff; font-size: 22rpx; line-height: 36rpx; margin-top: 12rpx; } .hero-label { color: #627c9c; font-size: 24rpx; }
.hero-divider { height: 1rpx; background-color: #4983dc; margin-top: 26rpx; margin-bottom: 24rpx; } .hero-title { color: #165cb9; font-size: 42rpx; font-weight: 700; line-height: 56rpx; margin-top: 8rpx; }
.percent { color: #fff; font-size: 38rpx; font-weight: 700; } .hero-note { color: #7086a1; font-size: 22rpx; line-height: 34rpx; margin-top: 8rpx; }
.progress-track { height: 12rpx; border-radius: 6rpx; background-color: #4983dc; margin-top: 18rpx; overflow: hidden; } .hero-divider { height: 1rpx; background-color: #d9e5f6; margin-top: 18rpx; margin-bottom: 16rpx; }
.progress-fill { height: 12rpx; background-color: #bde6ff; border-radius: 6rpx; } .percent { color: #285c9d; font-size: 30rpx; font-weight: 700; }
.progress-track { height: 8rpx; border-radius: 4rpx; background-color: #dbe7f8; margin-top: 12rpx; overflow: hidden; }
.progress-fill { height: 8rpx; background-color: #4c8beb; border-radius: 4rpx; }
.panel { margin-top: 24rpx; padding: 28rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e7edf6; } .panel { margin-top: 24rpx; padding: 28rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e7edf6; }
.panel-heading { flex-direction: row; justify-content: space-between; align-items: center; } .panel-heading { flex-direction: row; justify-content: space-between; align-items: center; }
.section-title { color: #253f5e; font-size: 29rpx; font-weight: 700; line-height: 42rpx; } .section-title { color: #253f5e; font-size: 29rpx; font-weight: 700; line-height: 42rpx; }
+3 -2
View File
@@ -1,7 +1,8 @@
// 发布前选择目标环境并配置 HTTPS 地址;未配置时请求失败,不回退模拟服务。 // 发布前选择目标环境并配置 HTTPS 地址;未配置时请求失败,不回退模拟服务。
export const APP_ENVIRONMENT: string = 'development' as string // 小程序体验版连接远程测试服;切回本机联调时改为 development。
export const APP_ENVIRONMENT: string = 'test' as string
const DEVELOPMENT_API_URL = 'http://127.0.0.1/api/v1' const DEVELOPMENT_API_URL = 'http://127.0.0.1/api/v1'
const TEST_API_URL = '' const TEST_API_URL = 'https://aitest.ychysg.cn/api/v1'
const PRODUCTION_API_URL = '' const PRODUCTION_API_URL = ''
export const API_BASE_URL = APP_ENVIRONMENT == 'production' ? PRODUCTION_API_URL : (APP_ENVIRONMENT == 'test' ? TEST_API_URL : DEVELOPMENT_API_URL) export const API_BASE_URL = APP_ENVIRONMENT == 'production' ? PRODUCTION_API_URL : (APP_ENVIRONMENT == 'test' ? TEST_API_URL : DEVELOPMENT_API_URL)
export const API_DEMO_ENABLED = false export const API_DEMO_ENABLED = false