You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

6.3 KiB

发现页与「出现在发现中」设计

背景与目标

在站内提供 「发现」 入口:登录用户可浏览 自愿出现在发现列表中的其他用户,卡片展示有限公开信息(含用户配置的地址展示文案),点击进入其公开主页 /@publicSlug

用户是否在发现中曝光、以及地址是否在卡片上展示,均在 控制台 → 个人资料 中配置,不在发现页内做复杂编辑。

需求结论(已确认)

  • 发现页含义:全站用户展示名录(仅包含满足条件的用户),不是「仅看自己」的预览页。
  • 设置位置:「是否出现在发现中」及地址相关展示设置在 个人资料 中维护。
  • 访问控制仅登录用户可打开发现页及列表接口;未登录访问 /discover 由现有全局路由中间件重定向至 /login?redirect=...;列表 API 走非公开 /api/**,由服务端 10.auth-guard 保证未登录返回 401
  • 路由:发现页路径 /discover;主导航(AppShell)在已登录时增加 「发现」 按钮/链接。

产品默认(评审未单列回复时采用的明确约定)

以下在讨论中作为 推荐默认 写入 spec,避免实现阶段歧义:

  1. publicSlug 为空:用户 不出现在 发现列表中(避免卡片无法链到合法公开主页)。若用户打开「出现在发现中」但尚未设置公开标识,在个人资料页 内联提示 并引导填写 publicSlug
  2. 发现列表中的头像:遵守现有 avatarVisibility。当头像对公开策略为不可展示时,发现卡片使用占位(或不展示头像 URL),避免「发现」成为绕过资料隐私的渠道。

数据模型

packages/drizzle-pkg/database/sqlite/schema/auth.tsusers 表新增字段(命名实现时可与下列语义对齐):

字段(语义) 类型建议 默认值 说明
出现在发现中 integer(0/1)或等价 boolean 1(true) 默认出现在发现中,可在资料中关闭
发现卡片地址文案 text,可空 NULL 用户自填展示用字符串(首版不做地图/结构化省市区)
在发现卡片上显示地址 integer(0/1) 0(false) 仅当「出现在发现中」为真时有意义;避免无文案仍出现「地址」标签

列表查询 必要条件(AND):

  • status = 'active'
  • 「出现在发现中」为真
  • publicSlug IS NOT NULL(且非空字符串)

可选:若未来需要排序(如按更新时间),可依赖 updatedAt,本 spec 不强制。

服务端 API

  • 列表GET /api/discover/users(具体路径以实现为准,保持 REST 风格即可)。
    • 鉴权:必须登录;未登录 401
    • 查询参数:分页参数与项目内公开列表风格一致(如 page + 固定 pageSize 常量,可新建 DISCOVER_LIST_PAGE_SIZE 或复用现有列表常量)。
    • 响应字段(仅列展示所需,禁止返回 emailpassword 等敏感列):
      • publicSlug
      • nickname(或展示名回退规则与公开页一致)
      • avatar:仅当 avatarVisibility 允许对外展示时返回 URL,否则 null 或省略(与前端占位约定一致)
      • discoverLocation:仅当「出现在发现中」且「在卡片上显示地址」为真且文案非空时返回;否则不返回或返回 null

实现上可抽取「根据 avatarVisibility 判断是否返回头像 URL」的辅助函数,与公开资料 API 逻辑对齐,避免分叉规则。

个人资料(PATCH/PUT)

在现有 server/service/profile/updateProfile(及 server/api/me/profile.put.ts 请求体)中扩展:

  • discoverVisible(布尔)
  • discoverLocation(可选字符串,长度上限与项目内其他 text 字段一致,如合理 max
  • discoverShowLocation(布尔)

校验:

  • 使用 zod 或现有校验风格与白名单更新字段。
  • discoverVisible === true 且当前用户 publicSlug 为空:允许保存其他字段,但 发现列表仍不会出现该用户;前端应在个人资料页提示「需设置公开标识才会出现在发现中」。

前端

导航

  • app/components/AppShell.vue:已登录主导航中增加「发现」,指向 /discover,高亮规则与「首页」等一致(navActive('/discover'))。

页面

  • 新建 app/pages/discover/index.vue(或等价路径):
    • 调用列表 API,网格展示卡片(头像占位、昵称、@publicSlug、可选地址行)。
    • 卡片点击进入 /@publicSlug
    • 空状态:无任何符合条件的用户时的说明。
    • 分页或「加载更多」,与项目现有列表 UX 一致。

个人资料

  • app/pages/me/profile/index.vue(或当前资料编辑所在文件):新增分组 「发现与展示」
    • 开关:出现在发现中
    • 输入:地址/地区(展示文案)
    • 开关:在发现卡片上显示上述地址
    • publicSlug 的联动提示(见上文)

错误处理与边界

  • 停用/非 active 用户不出现在列表。
  • 列表接口错误:与项目统一 API 错误展示(如 toast)一致。
  • 首版 不强制 单独限流;若公开后需要,可后续对 /api/discover/users 增加与公开只读接口类似的保护。

测试建议

  • 中间件:未登录访问 /discover → 重定向登录且带 redirect
  • API:未登录 GET /api/discover/users401;登录且库中无候选人 → 空列表;多名用户仅部分满足条件 → 仅返回满足者。
  • 资料:切换 discoverVisible、地址开关与文案后列表字段变化符合 spec;avatarVisibility 为 private 时响应中头像为空/占位。
  • publicSlug 清空:该用户从发现列表消失。

与现有设计的关系

  • 页面访问模型见 docs/superpowers/specs/2026-04-16-auth-access-control-design.md(默认受保护、/discover 不在公开白名单)。
  • 公开主页路径仍为 /@publicSlug,与 docs/superpowers/specs/2026-04-18-person-panel-multitenant-hub-design.md 一致。

实现后文档

若实现与 spec 不一致(例如产品改为「无 publicSlug 也展示但 CTA 禁用」),应 先改本 spec 再改代码,或在本文件追加修订段落并标注日期。