Files
juwan-frontend/隐蔽未实现接口与逻辑清单.md
T
zetaloop e3a392ae0d docs(audit): add mock-data and unimplemented APIs
Add two audit reports covering static mock data residue and hidden/unimplemented interfaces and logic.
2026-02-26 05:00:20 +08:00

345 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 隐蔽未实现接口与逻辑清单
本报告聚焦于"看起来已实现、实际未接通或存在断层"的功能点。这些问题在 mock 演示阶段不易察觉,切换真实后端后会表现为数据丢失、页面空态、交互无效等事故。
与第一份报告(《静态模拟数据残留审计报告》)互补,本报告不再重复 mock 数据源、store 初始化、伪 API 层等已知问题。
---
## 一、登录态与持久化缺失
### 1.1 记住登录状态 — 纯展示控件
登录页渲染了"记住登录状态"复选框,但该控件未被表单注册,无任何读取或写入逻辑。
| 位置 | 内容 |
| ------------------------------ | -------------------------------------------------------------------------- |
| `app/(auth)/login/page.tsx:86` | `<Checkbox id="remember" />` — 未绑定 `register("remember")``useState` |
### 1.2 登录态无持久化 — 刷新即丢
全仓库未发现 `localStorage``sessionStorage``cookie` 写入,也未使用 `zustand/middleware``persist``store/auth.ts``login()` 仅写内存态,页面刷新后回到未登录状态。
**影响**:切真实后端后,如果前端仍依赖 Zustand 内存态判断登录,刷新会导致用户被踢出。
---
## 二、身份与实体模型错位
### 2.1 当前用户与店铺/打手 ID 不匹配 — 后台大面积空态
登录固定为 `mockUsers[0]`id=`u1`),但 mock 数据中的店铺 owner 是 `u10`/`u11`/`u12`,打手是 `u5`/`u6` 起。
| 位置 | 内容 |
| -------------------------------------- | ---------------------------------------------- |
| `lib/mock/users.ts:119` | `currentUser = mockUsers[0]` — 固定 `u1` |
| `lib/mock/shops.ts:7` 起 | 店铺 owner 为 `u10``u11``u12` |
| `lib/mock/players.ts:7` 起 | 打手 user 从 `u5` 开始 |
| `lib/domain/resolve-current-shop.ts:5` | `shops.find(shop => shop.owner.id === userId)` |
**结果**:用户切换到店主身份后,`resolveOwnerShop` 始终返回 `null`,以下页面全部显示"当前账号没有可管理的店铺":
- `app/(dashboard)/dashboard/shop/employees/page.tsx:67`
- `app/(dashboard)/dashboard/shop/rules/page.tsx:29`
- `app/(dashboard)/dashboard/shop/income/page.tsx:28`
- `app/(dashboard)/dashboard/shop/page.tsx` 同理
- `app/(dashboard)/dashboard/shop/templates/page.tsx` 同理
- `app/(dashboard)/dashboard/shop/orders/page.tsx` 同理
### 2.2 打手主页链接指向不存在的打手
导航栏在 player 身份下跳转 `/player/${user.id}``components/header.tsx:88`),但 `user.id``u1``mockPlayers` 中没有 id 为 `u1` 的打手,会触发 `notFound()``app/(main)/player/[id]/page.tsx:25`)。
---
## 三、用户动作无持久化 — 刷新即回退
以下交互在前端有即时反馈,但数据仅存在于 Zustand 内存态,刷新页面或换设备后全部丢失。
### 3.1 点赞
| 位置 | 行为 |
| --------------------------------------- | --------------------------------------------------------------------- |
| `components/post-like-button.tsx:21-32` | 调用 `togglePostLike``store/posts.ts:50-61` 本地翻转 `liked` 并 ±1 |
### 3.2 评论
| 位置 | 行为 |
| ------------------------------------- | ------------------------------------------------ |
| `components/post-comments.tsx:29-52` | 调用 `addComment``store/comments.ts` 本地追加 |
| `components/post-comment-count.tsx:3` | 读 `useCommentStore` 本地计数 |
### 3.3 收藏
| 位置 | 行为 |
| -------------------------------------- | ----------------------------------------------------- |
| `components/favorite-button.tsx:29-33` | 调用 `toggleFavorite``store/favorites.ts` 本地增删 |
### 3.4 通知已读
| 位置 | 行为 |
| -------------------------------------- | ----------------------------------------------------------------------- |
| `app/(account)/notifications/page.tsx` | 调用 `markAsRead` / `markAllAsRead``store/notifications.ts` 本地标记 |
| `components/header.tsx:81-83` | 未读数通过 `.filter(n => !n.read).length` 本地计算 |
### 3.5 设置保存
| 位置 | 行为 |
| --------------------------------- | ---------------------------------------------------- |
| `app/(account)/settings/page.tsx` | 昵称、简介、通知偏好等修改仅写 `useAuthStore` 内存态 |
---
## 四、上传功能 — 占位或 Object URL
所有"上传"操作要么是纯占位 UI,要么使用 `URL.createObjectURL` 生成本地临时链接,刷新后失效。
### 4.1 头像上传
| 位置 | 行为 |
| ------------------------------------ | ------------------------------------------------------------- |
| `app/(account)/settings/page.tsx:75` | `setAvatar(URL.createObjectURL(file))` — 本地预览,无上传请求 |
### 4.2 聊天图片发送
| 位置 | 行为 |
| ------------------------------------ | --------------------------------------------------------------------------------- |
| `app/(order)/chat/[id]/page.tsx:152` | `sendImageMessage(session.id, URL.createObjectURL(file))` — blob URL 作为消息内容 |
### 4.3 争议证据上传
| 位置 | 行为 |
| --------------------------------------- | ------------------------------------------------------------------------------- |
| `app/(order)/dispute/[id]/page.tsx:88` | `URL.createObjectURL(file)` 生成预览,提交时传入 store |
| `app/(order)/dispute/[id]/page.tsx:102` | `URL.revokeObjectURL` 移除时释放 — 说明开发者意识到了临时性,但未替换为真实上传 |
### 4.4 身份认证证明材料 — 纯占位
| 位置 | 行为 |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `app/(account)/verify/page.tsx:170-181` | 三个 `<div>` 占位块(身份证正面/反面/游戏截图),有 `cursor-pointer` 样式但无 `<input type="file">`、无 `onClick`、无状态绑定 |
### 4.5 发帖图片 — 假计数 + 固定路径
| 位置 | 行为 |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `app/(main)/post/new/page.tsx:46` | `imageCount` 状态仅做数字加减 |
| `app/(main)/post/new/page.tsx:84` | 提交时 `images: Array.from({ length: imageCount }).map(() => "/posts/p1-1.jpg")` — 无论"上传"几张,全部指向同一张固定图片 |
---
## 五、客户端与服务端数据隔离
部分页面是 Server Component(或在服务端执行的函数),通过 `lib/api/*` 读取数据;而写入操作发生在客户端 Zustand store。在 Next.js 的 SSR/RSC 模式下,服务端无法读取客户端 store 的最新状态。
### 5.1 发帖 → 帖子详情
| 写入 | 读取 |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `app/(main)/post/new/page.tsx:79``store/posts.ts:44` (client) | `app/(main)/post/[id]/page.tsx:17``lib/api/posts.ts:10``usePostStore.getState()` (server?) |
### 5.2 店铺模板保存 → 店铺主页
| 写入 | 读取 |
| ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `app/(dashboard)/dashboard/shop/templates/page.tsx:120``store/shops.ts` (client) | `app/(main)/shop/[id]/page.tsx:29``shop.templateConfig.sections` (server) |
### 5.3 服务发布 → 打手详情页
| 写入 | 读取 |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `app/(dashboard)/dashboard/services/new/page.tsx:132``store/services.ts` (client) | `app/(main)/player/[id]/page.tsx:29-32` — 优先读 `player.services`(mock 内嵌数据),仅当为空时才 fallback 到 `listServicesByPlayer` |
**特别注意**:打手详情页的 `player.services && player.services.length > 0` 判断(`app/(main)/player/[id]/page.tsx:30`)意味着只要 mock 数据中打手自带了 services,新发布的服务就永远不会显示。这是一个数据遮蔽问题,不仅仅是隔离问题。
---
## 六、纯前端筛选/排序/统计
以下逻辑在前端内存中对全量数据做 filter/sort/slicemock 阶段数据量小时体验正常,接后端引入分页、权限、跨端一致性后会出现偏差。
### 6.1 社区列表 — 内存排序与筛选
| 位置 | 行为 |
| ------------------------------------- | ---------------------------------------------------------- |
| `app/(main)/community/page.tsx:22-34` | 全量 `posts.filter().sort()` 实现"最新/最热"和游戏标签筛选 |
### 6.2 订单列表 — 内存角色过滤 + Tab 过滤
| 位置 | 行为 |
| ------------------------------------ | ----------------------------------------------------------------- |
| `app/(order)/orders/page.tsx:91-103` | 先按 `consumerId/playerId/shopId` 过滤角色视角,再按 tab 过滤状态 |
### 6.3 管理后台概览 — 硬取首项 + 截断
| 位置 | 行为 |
| --------------------------------------- | --------------------------------------------------------- |
| `app/(dashboard)/dashboard/page.tsx:17` | `listPlayers()[0]` — 始终取第一个打手的数据作为"我的"数据 |
| `app/(dashboard)/dashboard/page.tsx:18` | `listShops()[0]` — 始终取第一个店铺 |
| `app/(dashboard)/dashboard/page.tsx:19` | `listOrders().slice(0, 3)` — 最近订单截前 3 条 |
### 6.4 首页推荐 — 全量渲染
| 位置 | 行为 |
| --------------------------- | ---------------------------------------------------------------------- |
| `app/(main)/page.tsx:12-14` | `listPlayers()` / `listShops()` 全量获取后直接渲染,无推荐算法、无分页 |
### 6.5 收入统计 — 正则匹配交易描述关联订单
| 位置 | 行为 |
| ------------------------------------------------------ | -------------------------------------------------------------------- |
| `app/(dashboard)/dashboard/shop/income/page.tsx:51-53` | `transaction.description.match(/ord[-\d]+/)` 从描述文本中提取订单 ID |
**风险**:后端描述格式变化时,统计数据会静默丢失,不报错。应由后端提供结构化的 `orderId` 字段。
---
## 七、消息列表未按用户过滤
| 位置 | 行为 |
| -------------------------------------- | ----------------------------------------------------------- |
| `app/(order)/chat/page.tsx:12` | `sessions` 直接全量渲染,未过滤当前用户是否为参与者 |
| `app/(order)/chat/page.tsx:21-23` | 仅用 `participant.id !== userId` 找"对方",但不排除无关会话 |
| `app/(order)/chat/[id]/page.tsx:52-59` | 会话详情页才做参与者校验 |
**结果**:列表页会展示当前用户不参与的会话,点进去才提示无权查看。
---
## 八、店铺规则 — 可保存但不执行
### 8.1 规则字段仅做展示
| 字段 | 保存位置 | 执行位置 |
| ------------------------------------ | ----------------------------------------------------------------- | ----------------------------------------------------- |
| `allowMultiShop` | `app/(dashboard)/dashboard/shop/rules/page.tsx:50``updateShop` | 无 — 未发现任何校验逻辑 |
| `allowIndependentOrders` | 同上 | 无 — 未发现任何校验逻辑 |
| `dispatchMode` | 同上 | `store/orders.ts:407` — 仅影响前端自动派单模拟 |
| `commissionType` / `commissionValue` | 同上 | `lib/domain/income.ts:22-31` — 仅影响前端收入计算展示 |
### 8.2 员工邀请 — 无校验直接重绑定
| 位置 | 行为 |
| --------------------------------------------------------- | --------------------------------------------------------- |
| `app/(dashboard)/dashboard/shop/employees/page.tsx:74-79` | 点击"邀请打手"直接调用 `assignToShop` + `playerCount + 1` |
| `store/players.ts:13-17` | `assignToShop` 仅修改 `shopId`/`shopName` 字段,无校验 |
**缺失**:未检查打手是否已属于其他店铺、是否符合 `allowMultiShop` 规则、是否需要打手同意。
### 8.3 公告编辑 — `window.prompt` 无审计
| 位置 | 行为 |
| --------------------------------------------- | ------------------------------------------ |
| `app/(dashboard)/dashboard/shop/page.tsx:157` | `window.prompt("", announcement)` 编辑公告 |
| `app/(dashboard)/dashboard/shop/page.tsx:175` | `window.prompt("", "")` 新增公告 |
---
## 九、社区列表的点赞/评论入口缺失
| 位置 | 行为 |
| --------------------------------------- | -------------------------------------------------------------- |
| `app/(main)/community/page.tsx:145-154` | 点赞和评论图标是 `<span>` 内的纯展示元素,无 `onClick`,无链接 |
帖子详情页(`components/post-like-button.tsx``components/post-comments.tsx`)有完整的点赞和评论交互,但社区列表页的卡片上这些图标仅做数字展示,用户无法在列表页直接操作。
---
## 十、置顶/精选 — 有字段和展示,无操作入口
| 位置 | 内容 |
| ----------------------------------- | ------------------------------------------------------------------------------------------- |
| `lib/types.ts:177` | `Post` 类型定义 `pinned: boolean` |
| `lib/mock/posts.ts:17` 等 | mock 帖子中有 `pinned: true` |
| `app/(main)/community/page.tsx:104` | 展示 `<Pin>` 图标 |
| `store/posts.ts:40` | `createPost` 强制 `pinned: false` |
| `store/posts.ts:17-22` | store 接口仅有 `createPost` / `togglePostLike` / `incrementCommentCount`,无 pin/unpin 方法 |
`PLAN.md:141` 规划了"用户自己置顶,最多 N 条",但目前无任何操作路径可以改变 `pinned` 状态。
---
## 十一、关注与推送 — 文案存在,实现缺席
### 11.1 关注
| 位置 | 内容 |
| ------------------------------------- | ------------------------------- |
| `PLAN.md:9` | "消费者可以收藏/关注打手或店铺" |
| `app/(account)/settings/page.tsx:234` | 通知偏好文案"点赞、评论、关注" |
全仓库未发现 `follow`/`关注` 相关的 store、api、页面动作。
### 11.2 浏览器推送
| 位置 | 内容 |
| ------------- | ------------------------------- |
| `PLAN.md:216` | "站内通知 + 用户可选浏览器推送" |
全仓库未发现 `Notification.requestPermission``serviceWorker``PushSubscription``pushManager` 等 Web Push 相关代码。现有通知体系仅为本地生成 + 本地已读。
---
## 十二、硬编码展示值
这些数值直接写在 JSX 中,不来自任何 store 或 API 计算。
| 位置 | 内容 |
| --------------------------------------- | -------------------------------------------------------- |
| `app/(account)/wallet/page.tsx:154` | `¥1,280.00`(本月收入) |
| `app/(account)/wallet/page.tsx:158` | `¥320.00`(待结算) |
| `app/(account)/wallet/page.tsx:162` | `¥5,400.00`(已提现) |
| `app/(dashboard)/dashboard/page.tsx:79` | `¥12,800`(店主本月收入) |
| `app/(auth)/layout.tsx:4-8` | `12,000+` 认证打手 / `98.6%` 好评率 / `50,000+` 完成订单 |
| `app/(order)/dispute/[id]/page.tsx:377` | UI 文案含"模拟处理结果"字样 |
---
## 十三、未使用的基础设施
### 13.1 `requestWithAuth` — 定义未调用
| 位置 | 内容 |
| --------------------- | ----------------------------------------------- |
| `lib/api/client.ts:9` | 定义了 `requestWithAuth<T>(executor, options?)` |
| 全仓库 | `rg "requestWithAuth("` 命中 0 处调用 |
### 13.2 `usePlayerStatusStore` — 定义未调用
| 位置 | 内容 |
| --------------------------- | ----------------------------------------------------------- |
| `store/player-status.ts:11` | 定义了 `usePlayerStatusStore`,含 `statuses``setStatus` |
| 全仓库 | `rg "usePlayerStatusStore"` 仅命中定义处 |
`PLAN.md:107` 规划了"打手有并发接单上限,搜索结果和打手详情页展示'可接单/忙碌'状态",该 store 疑似为此功能的未完成基础设施。
---
## 迁移优先级建议
### P0 — 上线前必须解决(数据安全/业务正确性)
1. 登录态持久化(刷新丢失 = 用户无法正常使用)
2. 身份与实体 ID 对齐(否则后台全部空态)
3. 所有用户写入动作接入后端持久化(点赞/评论/收藏/设置/通知已读)
4. 上传功能替换为真实文件上传(头像/聊天图片/争议证据/认证材料/发帖图片)
5. 移除 UI 中的"模拟"字样(`dispute/[id]/page.tsx:377`
### P1 — 切后端时必须改造(数据一致性)
6. 消息列表按当前用户过滤会话
7. 筛选/排序/统计改为后端分页查询(社区/订单/首页推荐/收入统计)
8. 打手详情页移除 `player.services` 优先读取逻辑,统一从服务列表查询
9. 店铺规则执行逻辑落地(`allowMultiShop`/`allowIndependentOrders` 校验)
10. 员工邀请增加校验流程
11. 收入统计改用结构化字段关联订单
### P2 — 功能补齐或明确下线
12. 记住登录状态功能实现或移除控件
13. 置顶/精选操作入口
14. 关注功能
15. 浏览器推送
16. 打手在线状态(`usePlayerStatusStore` 接入或移除)
17. 公告编辑替换 `window.prompt` 为正式表单