## Context

`xyw_data_test.as_phone_asset`、`PhoneAssetEntity` 与 `PhoneAssetMapper` 已存在，但没有 Controller、Service、DTO、分页配置或可操作的前端页面。当前前端只在 `/reference/phone` 保留静态参考；根导航中的“手机号卡（参考）”不连接任何新表接口。

本变更是前后端联动：以已有 `as_phone_asset` 为唯一持久化来源，补齐手机号资产独立维护闭环。它不假设其他资产表的业务 Service 已完成，因此不能把手机号的关联快照当成用户可编辑的关系来源。

### 范围文件用途注释表

| 文件 | 文件用途 | 核心职责 | 关联文件与数据流 |
|---|---|---|---|
| `backend/.../config/MybatisPlusConfig.java`（新增） | MyBatis-Plus 配置 | 启用分页查询插件 | Controller → Service → Page 查询 → `PhoneAssetMapper` |
| `backend/.../asset/controller/PhoneAssetController.java`（新增） | HTTP 入口 | 接收 `/api/phone-assets` 请求并返回 `ApiResponse` | 前端 API 客户端 → Controller → Service |
| `backend/.../asset/service/PhoneAssetService.java`（新增） | 业务规则 | 过滤有效记录、校验唯一性、审计时间和软删除 | Controller → Service → Entity/Mapper → `as_phone_asset` |
| `backend/.../asset/dto/PhoneAssetSaveRequest.java`（新增） | 写入 DTO | 限定可由用户写入的字段并做字段校验 | 前端表单 → Controller 校验 → Service |
| `backend/.../asset/dto/PhoneAssetPageQuery.java`（新增） | 查询 DTO | 约束页码、页大小和筛选项 | 列表筛选 → Controller → Service |
| `backend/.../asset/dto/PhoneAssetResponse.java`（新增） | 读取 DTO | 向前端提供资产字段和只读关联快照 | Entity → Service 映射 → 前端表格/详情 |
| `backend/.../asset/dto/PhoneAssetPageResponse.java`（新增） | 分页返回 DTO | 统一返回 records、total、page、size | Service 分页结果 → API 客户端 |
| `backend/.../asset/exception/*.java`（新增） | 业务异常与统一返回 | 将不存在、重复、请求校验失败稳定映射为 404/409/400 | Service/Validator → Exception handler → `ApiResponse.error` |
| `backend/src/test/.../asset/**/*.java`（新增） | 后端自动化测试 | 覆盖分页、重复、软删除、输入校验和错误返回 | Service/Controller → MockMapper/MockMvc |
| `frontend/src/modules/phone/phone-api-client.js`（新增） | 浏览器 API 客户端 | 组装请求、解析统一返回体、抛出可显示错误 | 视图事件 → fetch → 后端 API |
| `frontend/src/modules/phone/PhoneAssetView.js`（新增） | 手机号资产工作页面 | 筛选、表格、分页、编辑弹窗、删除确认和详情 | 路由 → View → API client → 页面状态 |
| `frontend/src/router/index.js`（修改） | 前端路由 | 注册 `/phone-assets`；保留 `/reference/phone` 静态回退 | URL → PhoneAssetView |
| `frontend/src/App.js`（修改） | 导航壳 | 将主导航指向可操作手机号资产页 | 导航点击 → RouterLink → 路由 |
| `frontend/src/styles/app.css`（修改） | 共享样式 | 为工作页筛选区、表格、详情和窄屏布局提供样式 | View DOM → CSS |
| `frontend/tests/phone-asset.spec.js`（新增） | 前端端到端测试 | 以 mock API 验证页面加载、保存、删除和错误展示 | Playwright → View → mock `/api/phone-assets` |

## Goals / Non-Goals

**Goals:**

- 实现手机号资产的分页查询、单条查询、新增、编辑与软删除，所有有效记录读取均固定 `delete_time = 0`。
- 保存前去除手机号的首尾空格和 `+86` 前缀，并校验最终值为 11 位数字；手机号重复必须反馈为冲突，不覆盖原记录。
- 使用固定下拉选项：卡类型为中国移动/中国电信/中国联通/中国广电/虚拟号码/空串，管理类型为自有/租用/代运营/空值，处置状态为正常使用/闲置/停机/已注销且必填、默认正常使用。
- 新建记录将五个 JSON 关联快照显式初始化为 `[]`，并只读返回数组形式给前端。
- 提供与现有 Vue 3 + Element Plus 代码风格一致的操作页面，保留旧参考路由，避免旧链接失效。
- 为关键服务规则、HTTP 错误和前端主流程提供自动化回归测试。

**Non-Goals:**

- 不改 `as_phone_asset` 表、Entity、Mapper、已有索引、数据库配置或任何其他资产表。
- 不新增登录、权限、导入导出、批量操作、关联数据同步、关联字段编辑、设备存在性校验或外键。

- 不删除旧参考页面、旧路由或其他模块占位页面。

## Decisions

### 1. API 采用单资源 REST 路径与稳定返回壳

使用 `GET/POST /api/phone-assets` 与 `GET/PUT/DELETE /api/phone-assets/{id}`；成功响应沿用 `ApiResponse<T>` 的 `code/message/data` 结构。列表 `data` 固定为 `{ records, total, page, size }`，默认第 1 页、每页 20 条，页大小上限 100。

查询条件采用 `phoneNumber`（完整 11 位精确匹配）、`iccid`、`cardType`、`managementType`、`disposalStatus`、`deviceId`，并以 `id DESC` 稳定排序。精确手机号查询可利用已有 `(phone_number, delete_time)` 唯一索引，避免无索引模糊搜索成为首期性能隐患。

备选：沿用旧模块接口或先只做前端本地数据。前者耦合已淘汰表，后者不能验证新表；均不采用。

### 2. Service 显式负责软删除、审计列与并发结果

本期不在 Entity 上使用 `@TableLogic`：`delete_time` 需记录删除时刻而非布尔值。Service 的每个查询条件都加入 `delete_time = 0`；创建和更新显式写入 `create_time`/`update_time`；删除只更新 `delete_time`（Unix 毫秒）和 `update_time`。更新或删除影响行数为零时返回“记录不存在或已删除”，不恢复记录。

创建与改手机号均先检查有效手机号是否存在，并仍捕获数据库唯一索引冲突作为并发保护；冲突一律映射 HTTP 409。备选为仅依赖预查询或仅依赖数据库异常；两者单独使用都会在并发或错误提示方面不足。

### 3. 关联快照由服务端托管且本期只读

写入 DTO 仅包含 `phoneNumber`、`cardType`、`iccid`、`realNameOwner`、`managementType`、`disposalStatus` 与 `deviceId`。五个 `linked*` 字段不接受前端输入：创建时写入字符串 `[]`，编辑保持数据库现值；响应 DTO 解析为 `List<Long>`，前端仅在详情区以数量和 ID 列表展示。`relationSyncedAt` 也只读。

这样不会把尚未建成的 `wecom_account` 等关系同步逻辑伪装成可用功能。备选为让用户手工填写 JSON；会绕过关联表校验并制造未来同步覆盖风险，因此不采用。

### 4. 前端采用模块化 JS 视图与 Element Plus 原生组件

新增 `PhoneAssetView.js` 和 `phone-api-client.js`，延续当前非 SFC 的 Vue JavaScript 写法。页面由筛选栏、分页表格、右侧详情抽屉、同一新增/编辑弹窗和删除二次确认组成；所有 API 错误用 `ElMessage` 展示，保存成功后刷新当前筛选条件。路由新增 `/phone-assets`，导航文案从“手机号卡（参考）”调整为“手机号资产”；`/reference/phone` 继续保留。

备选为直接替换 `LegacyReferenceView`。该组件同时承载静态回退语义，直接替换会损失安全参考入口，故不采用。

### 5. 错误协议与方法注释作为跨端契约

后端使用统一异常处理：校验失败 400、无有效记录 404、手机号重复 409；前端 API 客户端只读取 `ApiResponse` 后再把 message 交给页面。所有本变更新增或含业务逻辑的方法、Vue 回调和测试辅助方法必须添加中文注释，至少写明“代码作用（白话）/关联文件/关联逻辑（调用链、消息链或数据流）”。

这使前端不依赖 HTTP 文本或数据库异常细节，也满足项目注释门禁。备选为在各 Controller 分支手写错误 JSON；会造成状态码和文案漂移，不采用。

### 6. 已确认的输入与删除规则

手机号输入在前端限制为数字和最多 11 位；前端与后端保存前都删除首尾空格和一个 `+86` 前缀，结果必须恰好为 11 位数字。`deviceId` 可为空，且本期不查询设备表验证存在性。

卡类型固定为中国移动、中国电信、中国联通、中国广电、虚拟号码、空串；管理类型固定为自有、租用、代运营、空值，空值在页面显示为空、后端存 `null`；处置状态固定为正常使用、闲置、停机、已注销，必填且创建默认“正常使用”。

删除前，Service 必须查询有效企微、微信、抖音、域名账号和商户中是否存在指向该手机号的关联；任一存在即返回冲突与关联类型，不执行软删除。关联快照仍仅初始化为 `[]` 并只读展示，自动同步留给后续独立功能。
## Risks / Trade-offs

- 关联快照当前均为空 → 只读展示空数组，后续关联模块完成后再单独计划同步与回填，不能由本期伪造数据。
- 无固定状态枚举 → 保留兼容性，但会允许不同拼写；待业务确认字典后用独立提案统一收敛。
- 数据库无外键，且设备模块未有 Service → `deviceId` 只做非负数校验，不判断存在性；后续设备模块上线时补逻辑关联校验。
- 数据库唯一索引的重复异常驱动依赖 MySQL → 先做业务预检并将异常集中转换，测试覆盖两条路径。
- `relationSyncedAt` 由未来同步维护 → 本期不在创建/编辑时伪造同步时间，避免“显示已同步”但实际没有同步。

## Migration Plan

1. 部署前确认 `xyw_data_test.as_phone_asset` 已含已审批的列和索引；本变更不执行 DDL。
2. 先部署后端，再部署前端；旧 `/reference/phone` 路由继续可用，因此前端切换失败可暂时把导航链路恢复到参考页。
3. 上线后以一条新建记录验证创建、筛选、编辑、删除；删除后同手机号可重新创建且列表不显示旧记录。
4. 回滚仅回滚新增应用代码与导航链接，不删除或改写已创建的 `as_phone_asset` 数据；已软删除记录保持审计状态。

## Open Questions

- 无阻塞业务问题；后续自动同步关联快照、登录与权限属于独立功能范围。
