# TaxerInfo 模块改写为 Tauri v1(单窗口 + Vue)设计文档 ## 1. 文档目标 将 `CallClient/WPF/TaxerInfo.xaml.cs` + `CallClient/WPF/TaxerInfo.xaml` 对应功能迁移到 **Tauri v1 + Vue(单窗口)** 架构,确保: - 业务行为与现有 WPF 一致(取号、开始办理、办结、实名信息、企业列表、网页加载、复制、查询)。 - 桌面能力由 Tauri(Rust)提供,界面由 Vue 负责。 - 后续可以逐步替换原 `WebBrowser` 内嵌业务页,最终统一为前端组件化实现。 --- ## 2. 现有模块功能盘点(WPF) ## 2.1 核心职责 `TaxerInfo` 窗口当前承担: 1. 展示办税员实名信息(身份证图、头像、姓名、手机号、采集状态)。 2. 展示当前取号下的企业/票据列表,支持“开始”“办结”“选择企业”。 3. 根据当前票据拼接 URL 并加载纳税人页面(`WebBrowser`)。 4. 提供操作按钮:复制税号、复制号码、刷新、一户式查询。 5. 展示“代理人办理企业清单”(通过 HTTP 接口获取)。 ## 2.2 主要数据模型 - `ticket`:票据/办理对象(状态、企业信息、号票信息等)。 - `Enterprise`:代理人办理企业清单项。 - `SerialNumber` - `EnterpriseName` - `TaxpayerId` - `ApiResponse / DataItem`:`today-enterprises` 接口返回结构。 - 外部响应: - `SmzJhxtGetBsyxxByqhhmResponse` - `SmzJhxtGetSmzcjxxResponse` - `SmzJhxtGetBdNsrxxResponse` ## 2.3 关键流程 1. `LoadInfo(comparecode)` 触发加载: - 调实名接口、绑定纳税人接口、绑定企业接口; - 查询当日关联票据列表; - 填充 UI。 2. 票据切换(`SelectedSubTicket`): - 更新“当前办理文案”; - 刷新纳税人页面 URL。 3. 开始办理(`StartCommand`): - 更新票据状态为办理中; - 记录窗口/员工信息; - 刷新右侧页面。 4. 办结(`EndCommand`): - 结束当前票据; - 浏览器跳转 `about:blank`。 --- ## 3. 迁移目标架构(Tauri v1) ## 3.1 技术分层 - **前端(Vue)** - 单窗口页面:`TaxerInfoView.vue` - 状态管理:Pinia(推荐)或 Vue reactive store - UI 组件:Element Plus / Ant Design Vue(任选) - **Tauri 后端(Rust)** - `#[tauri::command]` 提供桌面能力与系统访问 - 封装对旧服务接口调用(可直接 HTTP 调用) - 封装票据数据库/SDK 调用(通过 Rust 侧 adapter) - **桥接** - `invoke()`:前端调用命令 - `event`:推送异步状态(可选) ## 3.2 单窗口布局建议(与 WPF 对齐) - 左侧(固定宽度) - 办税员实名信息卡片(身份证图 + 头像 + 姓名 + 手机 + 采集状态) - 企业/票据列表(开始、办结按钮) - “选择企业”按钮 - 右侧(自适应) - 顶部操作栏(当前办理文案 + 四个按钮) - Tabs: - `纳税人信息`:`iframe/webview` 或前端重构页 - `代理人办理企业清单`:表格展示 `EnterpriseList` --- ## 4. 接口设计(Tauri Command + 业务 HTTP) 以下为建议接口(前端调用 Rust command): ## 4.1 初始化与加载 ### `taxer_load_info(compareCode: String, currentTicketId: String) -> TaxerLoadInfoDto` 聚合返回: - 办税员实名信息(姓名、性别、身份证、地址、头像、身份证底图) - 实名采集结果(手机号、备注、是否已采集) - 绑定纳税人列表(用于税号映射) - 当日票据列表(`TicketDto[]`) - 默认选中票据 - 代理人办理企业清单(`EnterpriseDto[]`) > 说明:WPF 中由 `BackgroundWorker + 多次接口请求 + 本地票据查询` 完成,迁移后建议统一为一个“聚合命令”,减少前端编排复杂度。 ## 4.2 票据动作 ### `taxer_start_ticket(ticketId: String, windowUid: i64, employeeUid: i64) -> TicketDto` - 更新开始时间、状态(dealing)、窗口/员工绑定。 - 返回更新后的票据数据。 ### `taxer_end_ticket(ticketId: String) -> TicketDto` - 标记办结(对应原 `t.Stop(DateTime.Now)`)。 - 返回更新后的票据数据。 ### `taxer_batch_end_or_abandon(ticketIds: Vec) -> ()` - 对应原 `End()` 的兜底逻辑(办理中停止、待办废弃)。 ## 4.3 URL 与外部查询 ### `taxer_build_nsr_url(ticketId: String, phoneNo: String, compareCode: String) -> String` 根据模板参数替换生成 URL(等价 `Analize_URL`): - `{djxh}` `ENTERPRISE_ID` - `{sfzhm}` 身份证号 - `{sflb}` 企业/个人标识 - `{swjgDm}` 税务机关代码 - `{qhhm}` 取号号码 - `{sjhm}` 手机号 ### `taxer_build_yhscx_url(ticketId: String) -> String` 生成“一户式查询”URL。 ## 4.4 代理人企业清单 ### `taxer_get_today_enterprises(ticketId: String) -> Vec` 调用: - `GET {ZIYUN_SERVICE_URL}/agentInfo/today-enterprises/{ticketId}` 返回字段映射: - `serialNumber -> SerialNumber` - `serviceTarget -> EnterpriseName` - `serviceTargetCode -> TaxpayerId` ## 4.5 系统能力 ### `system_copy_text(text: String) -> ()` - 复制号码、复制税号统一复用。 ### `system_open_external(url: String) -> ()` - 一户式查询可在系统默认浏览器打开(可选)。 --- ## 5. 前端 Vue 数据结构建议 ```ts export interface TaxerState { compareCode: string currentTicketId: string currentTicket?: TicketDto selectedSubTicket?: TicketDto ticketList: TicketDto[] enterpriseList: EnterpriseDto[] name: string phoneNo: string memo: string idCardImageBase64?: string headImageBase64?: string caijiRegistered: boolean isLoading: boolean isStarted: boolean currentCompanyText: string nsrUrl: string } ``` --- ## 6. 功能映射表(WPF -> Tauri + Vue) - `Caiji_Click`:打开实名采集弹窗 -> Vue Dialog + `taxer_submit_realname`(若后续迁移)。 - `Copy_Click`:复制 `TKT_ID` -> `system_copy_text(currentTicket.tktId)`。 - `Copy_Tax_Click`:复制税号 -> 先映射税号,再 `system_copy_text`。 - `Reflesh_Click`:浏览器刷新 -> `iframe` 重新赋值 `src` 或 key 强制重渲染。 - `YHSCX_Click`:一户式查询 -> `taxer_build_yhscx_url` + 新标签页/外部浏览器。 - `StartCommand`:开始办理 -> `taxer_start_ticket` 后更新列表和当前文案。 - `EndCommand`:办结 -> `taxer_end_ticket` 后更新状态并清空 `nsrUrl`。 - `SelectedSubTicket` setter:切换企业 -> 重算 `currentCompanyText` 与 `nsrUrl`。 - `LoadInfo`:初始化 -> `taxer_load_info` 一次加载。 --- ## 7. 页面交互细节(Vue) 1. 页面 `onMounted` 调 `taxer_load_info`。 2. 左侧列表点击行 => 更新 `selectedSubTicket`,调用 `taxer_build_nsr_url`。 3. 列表项按钮显示规则(与现有一致): - 显示“开始”:`isStarted && (status == 0 || status == 2)` - 显示“办结”:`isStarted && status == 4` 4. 采集状态图标: - 已采集:绿色(对应 `Registerd.png`) - 未采集:灰/红(对应 `UnRegisterd.png`) --- ## 8. 迁移实施步骤(建议) ## 第 1 阶段:壳迁移(低风险) - 建立 Tauri v1 + Vue 单窗口工程。 - 先做 UI 结构 1:1 迁移。 - 右侧“纳税人信息”先保留 `iframe/webview` 加载原 URL(不改业务页)。 ## 第 2 阶段:命令层落地 - 实现 `taxer_load_info / start / end / build_url / get_today_enterprises`。 - 前端改为只依赖 command,不再直接拼接/请求。 ## 第 3 阶段:体验与稳定性 - 增加错误提示、重试、超时处理。 - 增加日志链路(前端行为日志 + Rust 命令日志)。 - 补充 E2E 场景测试(见第 10 节)。 --- ## 9. 关键风险与处理 - **旧 SDK 依赖迁移风险**:`ticket/business` 等可能强依赖 .NET。 - 方案:先通过 HTTP/本地服务桥接,逐步替换为 Rust 实现。 - **WebBrowser 行为差异**:WPF WebBrowser 与 Tauri webview 对 cookie/session 行为不同。 - 方案:先验证登录态与跨域;必要时改外部浏览器打开。 - **线程模型差异**:WPF Dispatcher/BackgroundWorker 到 Vue 异步模型要重构。 - 方案:统一在 command 层串行/并行编排,前端只维护状态。 --- ## 10. 测试清单(迁移验收) - 初始化后实名信息完整展示(姓名、头像、身份证图、手机号)。 - 票据列表状态渲染正确(待办/办理中/已完成)。 - 开始办理后状态切换为办理中,当前办理文案正确。 - 办结后状态切换为已完成,页面清空/切换逻辑正确。 - 复制号码、复制税号可在系统剪贴板拿到正确值。 - 一户式查询 URL 参数替换正确(`djxh/sfzhm/sflb/swjgDm/qhhm/sjhm`)。 - 代理人办理企业清单可正确加载并展示。 - 网络异常时有可感知报错,不出现页面卡死。 --- ## 11. 建议目录结构(Tauri) ```text src-tauri/ src/ commands/ taxer.rs system.rs services/ baishui_service.rs ziyun_service.rs ticket_service.rs models/ taxer.rs ticket.rs src/ views/ TaxerInfoView.vue stores/ taxer.ts services/ tauri-taxer.ts components/ taxer/ TaxerProfileCard.vue TicketList.vue TaxerToolbar.vue EnterpriseTable.vue ``` --- ## 12. 结论 `TaxerInfo` 模块适合按“**UI 先迁移、命令聚合、再逐步替换内嵌网页**”的路径落地。 在 Tauri v1 中建议将现有 ViewModel 的业务入口收敛为 5~7 个 command,由 Vue 负责渲染与交互,可在保持业务一致的前提下提升后续可维护性。 --- ## 13. 深挖:`BaiShuiSDK` / `QueuingSystemSDK` 真实依赖行为 本节用于回答“改写后是否还能调用相同后端接口”的关键问题。 ## 13.1 `TaxerInfo` 实际依赖清单(代码级) - `BaiShuiSDK.Model.BaishuiModel` - `SmzJhxtGetBsyxxByqhhm`(取号号 -> 办税员信息) - `SmzGetSmzcjxx`(身份证号 -> 采集信息) - `SmzJhxtGetBdNsrxx`(身份证号 -> 绑定纳税人) - `QueuingSystemSDK.ticket` - `Update()`、`Stop()`、`Abandon()`、`GetList(...)` - `QueuingSystemSDK.business` - `new business((int)t.BIZ_UID)` 用于补全业务名称 - `QueuingSystemSDK.SystemControl` - `Get("REALNAME_CHECK_API")`、`Get("BS_NSR_URL")`、`Get("BS_YHSCX_URL")`、`Get("BS_TAX_AUTHORITY_NUM")`、`Get("ZIYUN_SERVICE_URL")` - `QueuingSystemSDK.HttpHelper` - `httpGet("{ZIYUN_SERVICE_URL}/agentInfo/today-enterprises/{tktId}")` - `DbHelperSQL.GetServerTime()`(开始办理时落库时间) ## 13.2 百税接口协议(必须保持兼容) `TaxerInfo` 间接通过 `BaishuiModel -> BsDefaultClient.execute` 调后端。真实请求格式不是纯 JSON,而是: - `POST {REALNAME_CHECK_API}` - `Content-Type: application/x-www-form-urlencoded` - body 参数: - `domain.ywId={bid}` - `domain.parmJson={UrlEncode(Json(request))}` 其中 `UrlEncode` 使用 SDK 自定义逻辑:逐字符编码,并将 `%xx` 转为大写(`%2f -> %2F`)。 这在某些网关上会影响签名或解析结果,建议 Rust 侧保持一致实现。 ## 13.3 `TaxerInfo` 使用的 3 个百税 `bid` - `smz.jhxtGetBsyxxByqhhm` - 请求字段:`qhhm` - 来源:`compareCode`(取号号码) - `smz.getSmzcjxx` - 请求字段:`sfzhm` - 来源:上一步返回 `result.sfzhm` - `smz.jhxtGetBdNsrxx` - 请求字段:`sfzhm` - 来源:同上 返回字段依赖(前端展示必须保留): - 办税员:`xm/xb/mz/sfzhm/zz/csrq/sfzzmPic/headPic` - 采集信息:`sjhm/bz/code` - 绑定纳税人:`dataList[].djxh/nsrsbh/nsrmc` ## 13.4 队列票据本地数据行为(非 HTTP) `ticket` 属于本地数据库实体(SQL Server),不是远端 REST: - `Update()` - 更新 `WIN_ID/BIZ_UID/EMP_UID/START_TIME/END_TIME/RANK/STATUS` - `Stop(DateTime)` - 更新 `END_TIME=GETDATE()`、`STATUS=complete(5)` - `Abandon()` - 更新 `END_TIME=GETDATE()`、`STATUS=abandoned(6)` - `GetList(where, ..., hideLinkTicket)` - 直接拼 SQL 查 `[ticket]` 表并映射对象 这意味着 Tauri 改写时有两个实现路径: 1. **兼容优先**:继续复用现有 .NET 服务层(本地中间服务)操作数据库;Tauri 仅调用中间层 API。 2. **重写优先**:Rust 直接连库并重建 `ticket` SQL 逻辑(工作量和风险更高)。 ## 13.5 配置来源(`SystemControl.Get`) `SystemControl` 在静态构造时会把 `system` 表配置加载进内存缓存,然后 `Get(key)` 直接读缓存。 对 `TaxerInfo` 迁移最关键的配置项如下: - `REALNAME_CHECK_API`:百税 API 入口地址 - `BS_NSR_URL`:纳税人信息页模板(含 `{djxh}` 等占位符) - `BS_YHSCX_URL`:一户式查询 URL 模板 - `BS_TAX_AUTHORITY_NUM`:`{swjgDm}` 参数 - `ZIYUN_SERVICE_URL`:紫云服务入口 默认值(由 `DBUpdateManager` 初始化)显示 `BS_NSR_URL/BS_YHSCX_URL` 为同一模板,迁移后可沿用同策略。 ## 13.6 直接 HTTP 接口(紫云) `GetDelegaterInfo()` 使用: - `GET {ZIYUN_SERVICE_URL}/agentInfo/today-enterprises/{CurrentTicket.TKT_ID}` 解析规则: - 响应 `code == 200 && data != null` 才展示 - 字段映射: - `serialNumber -> SerialNumber` - `serviceTarget -> EnterpriseName` - `serviceTargetCode -> TaxpayerId` - 否则清空列表 ## 13.7 Tauri v1 落地时的“接口保持一致”建议 ### A. 百税接口适配器(Rust)必须做到 - 保持相同 `bid` 与请求字段名 - 保持 form 参数名:`domain.ywId`、`domain.parmJson` - 保持 UTF-8 + URL 编码行为(百分号大写) - 保持错误语义: - `SmzJhxtGetBsyxxByqhhm`:`code != "00"` 视为失败 - `SmzGetSmzcjxx`:允许 `"00"` 和 `"01"` - `SmzJhxtGetBdNsrxx`:当前 SDK 对非 00 未强拦截(按原行为兼容) ### B. 队列票据能力建议 - 第一阶段不要在 Rust 侧直连数据库重写 `ticket` 全量逻辑; - 建议先将 `Start/Stop/GetList` 封成可调用服务(HTTP/IPC),Tauri 调该服务; - 等功能回归稳定后再考虑 Rust 直连库替换。 ### C. URL 模板能力 - `Analize_URL` 的 6 个占位符替换规则必须逐字兼容: - `{djxh}`、`{sfzhm}`、`{sflb}`、`{swjgDm}`、`{qhhm}`、`{sjhm}` ## 13.8 额外注意点(代码库现状) - `SmzJhxtGetBdNsrxxRequest` 类定义在 `SmzJhxtGetBsyGlqyxxRequest.cs` 文件中(文件名与类名不一致),迁移时建议统一命名避免维护歧义。 - `HttpHelper.httpGet` 未设置超时与异常细化;Tauri 迁移时建议新增超时、重试与可观测日志。 --- ## 14. 基于现有 Tauri 项目(`F:\workspace\zyclient_linux\TauriClient\call-client`)的落位分析 本节基于当前 Tauri v1 代码现状补充,目的是将 `TaxerInfo` 改写方案落到“现项目可实施”的路径,而非抽象设计。 ## 14.1 当前 Tauri 主结构(已实现) - 前端路由: - `/main` -> `MainView.vue` - `/ticketList` -> `TicketListView.vue` - `/login` -> `LoginView.vue` - `/setup` -> `ServerSetupView.vue` - Rust 窗口命令(已实现): - `open_ticket_window` - `close_ticket_window` - `focus_window` - `open_main_window` - `open_login_window` - 当前窗口形态: - `main` 窗口:`500x100`、`always_on_top`、无边框(条形呼叫台) - `ticketList` 窗口:`1024x720`、无边框、独立路由页 ## 14.2 “办税员窗口”当前行为(关键结论) `MainView.vue` 的“更多菜单”里确有 `办税员窗口` 按钮,但目前逻辑是: - `handleMoreCommand("main")` 仅提示“当前已在办税员窗口” - 并不会打开新的 TaxerInfo 页面/窗口 这说明:**现项目尚未承载 C# `TaxerInfo` 的完整 UI 与业务**,仅有一个呼叫控制条主窗。 ## 14.3 与 `TaxerInfo` 的差距(按能力分层) ### A. 窗口与页面承载差距 - 现 `main` 尺寸为 `500x100`,不具备 `TaxerInfo`(约 `1200x600`)承载条件。 - 当前仅有一个次窗口 `ticketList`,尚无 `taxerInfo` 路由/窗口定义。 ### B. 接口协议差距 - 现有 `src/api/index.ts` 全部走 `API_QUEUE_CALLER_PATH=/api/queue/caller`(JSON REST)。 - `TaxerInfo` 依赖的百税接口协议是 form-url-encoded + `domain.ywId/domain.parmJson`,目前项目中尚未实现该适配器。 ### C. 数据源差距 - 现 `MainView/TicketListView` 主要消费 `call-terminal/*` 接口(呼叫、票池、评价)。 - `TaxerInfo` 需要额外数据源: - 百税 9.2.2 / 9.2.12 / 9.2.3 - 紫云 `today-enterprises` - 本地票据扩展信息(企业名、税号映射等) ## 14.4 在现项目中建议的落位方案(不改代码阶段的设计结论) ### 方案选型 推荐新增独立窗口 `taxerInfo`(与 `ticketList` 同级),而不是直接塞入当前 `500x100` 主窗: 1. 保留 `main` 作为呼叫条形控制台(最小侵入)。 2. 在“更多菜单 -> 办税员窗口”中打开/聚焦 `taxerInfo`。 3. `taxerInfo` 使用新路由页面(建议 `/taxerInfo`)承载原 WPF 界面。 ### 这样做的原因 - 与现有多窗口架构一致(已有 `ticketList` 模式可复用)。 - 不影响当前呼叫链路稳定性。 - 便于逐步迁移 `TaxerInfo` 而非一次性替换 `MainView`。 ## 14.5 与现项目文件的映射建议 - 前端: - 新增 `src/views/TaxerInfoView.vue` - `src/router/index.ts` 增加 `/taxerInfo` - `src/host/window.ts` 增加 `openTaxerInfoWindow()` - `src/views/MainView.vue` 的 `handleMoreCommand("main")` 改为打开/聚焦 `taxerInfo` - Rust: - `src-tauri/src/commands/window.rs` 增加 - `open_taxer_info_window` - `close_taxer_info_window`(可选) - `src-tauri/src/lib.rs` 注册上述命令 ## 14.6 `TaxerInfo` 接口在现项目中的接入位置建议 ### 前端 API 层 在 `src/api` 下新增 `taxer.ts`,避免和 `call-terminal` 混杂: - `loadTaxerInfo(compareCode, currentTicketId)` - `startTaxerTicket(...)` - `endTaxerTicket(...)` - `getTodayEnterprises(ticketId)` - `buildNsrUrl(...)` / `buildYhscxUrl(...)`(可前后端任选) ### HTTP 传输层 沿用 `src/utils/service.ts` 的 axios + Tauri HTTP adapter 机制,但需要区分两类 baseURL: 1. 现有 `API_QUEUE_CALLER_PATH` 2. 百税 API 专用 baseURL(`REALNAME_CHECK_API`)+ form 编码适配 ## 14.7 文档结论更新(针对你的目标) 结合当前 Tauri 项目,`TaxerInfo` 改写最可行路径是: - **先新增 `taxerInfo` 独立窗口**(由 `MainView` 的“办税员窗口”按钮打开) - **再迁移 C# `TaxerInfo` 的 UI 与接口编排** - **最后再评估是否把条形主窗与办税员窗合并** 这样既满足“在 Tauri 主流程中打开办税员窗口”,又不会破坏现有呼叫台功能。