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

21 KiB
Raw Blame History

隐蔽未实现接口与逻辑清单

本报告聚焦于"看起来已实现、实际未接通或存在断层"的功能点。这些问题在 mock 演示阶段不易察觉,切换真实后端后会表现为数据丢失、页面空态、交互无效等事故。

与第一份报告(《静态模拟数据残留审计报告》)互补,本报告不再重复 mock 数据源、store 初始化、伪 API 层等已知问题。


一、登录态与持久化缺失

1.1 记住登录状态 — 纯展示控件

登录页渲染了"记住登录状态"复选框,但该控件未被表单注册,无任何读取或写入逻辑。

位置 内容
app/(auth)/login/page.tsx:86 <Checkbox id="remember" /> — 未绑定 register("remember")useState

1.2 登录态无持久化 — 刷新即丢

全仓库未发现 localStoragesessionStoragecookie 写入,也未使用 zustand/middlewarepersiststore/auth.tslogin() 仅写内存态,页面刷新后回到未登录状态。

影响:切真实后端后,如果前端仍依赖 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 为 u10u11u12
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.idu1mockPlayers 中没有 id 为 u1 的打手,会触发 notFound()app/(main)/player/[id]/page.tsx:25)。


三、用户动作无持久化 — 刷新即回退

以下交互在前端有即时反馈,但数据仅存在于 Zustand 内存态,刷新页面或换设备后全部丢失。

3.1 点赞

位置 行为
components/post-like-button.tsx:21-32 调用 togglePostLikestore/posts.ts:50-61 本地翻转 liked 并 ±1

3.2 评论

位置 行为
components/post-comments.tsx:29-52 调用 addCommentstore/comments.ts 本地追加
components/post-comment-count.tsx:3 useCommentStore 本地计数

3.3 收藏

位置 行为
components/favorite-button.tsx:29-33 调用 toggleFavoritestore/favorites.ts 本地增删

3.4 通知已读

位置 行为
app/(account)/notifications/page.tsx 调用 markAsRead / markAllAsReadstore/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:79store/posts.ts:44 (client) app/(main)/post/[id]/page.tsx:17lib/api/posts.ts:10usePostStore.getState() (server?)

5.2 店铺模板保存 → 店铺主页

写入 读取
app/(dashboard)/dashboard/shop/templates/page.tsx:120store/shops.ts (client) app/(main)/shop/[id]/page.tsx:29shop.templateConfig.sections (server)

5.3 服务发布 → 打手详情页

写入 读取
app/(dashboard)/dashboard/services/new/page.tsx:132store/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:50updateShop 无 — 未发现任何校验逻辑
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.tsxcomponents/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.requestPermissionserviceWorkerPushSubscriptionpushManager 等 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,含 statusessetStatus
全仓库 rg "usePlayerStatusStore" 仅命中定义处

PLAN.md:107 规划了"打手有并发接单上限,搜索结果和打手详情页展示'可接单/忙碌'状态",该 store 疑似为此功能的未完成基础设施。


迁移优先级建议

P0 — 上线前必须解决(数据安全/业务正确性)

  1. 登录态持久化(刷新丢失 = 用户无法正常使用)
  2. 身份与实体 ID 对齐(否则后台全部空态)
  3. 所有用户写入动作接入后端持久化(点赞/评论/收藏/设置/通知已读)
  4. 上传功能替换为真实文件上传(头像/聊天图片/争议证据/认证材料/发帖图片)
  5. 移除 UI 中的"模拟"字样(dispute/[id]/page.tsx:377

P1 — 切后端时必须改造(数据一致性)

  1. 消息列表按当前用户过滤会话
  2. 筛选/排序/统计改为后端分页查询(社区/订单/首页推荐/收入统计)
  3. 打手详情页移除 player.services 优先读取逻辑,统一从服务列表查询
  4. 店铺规则执行逻辑落地(allowMultiShop/allowIndependentOrders 校验)
  5. 员工邀请增加校验流程
  6. 收入统计改用结构化字段关联订单

P2 — 功能补齐或明确下线

  1. 记住登录状态功能实现或移除控件
  2. 置顶/精选操作入口
  3. 关注功能
  4. 浏览器推送
  5. 打手在线状态(usePlayerStatusStore 接入或移除)
  6. 公告编辑替换 window.prompt 为正式表单