Browse Source

docs: add discover page and discover visibility spec

Document /discover directory for logged-in users, profile settings for
opt-in and location display, schema/API/UI boundaries, and defaults for
empty publicSlug and avatarVisibility alignment.

Made-with: Cursor
main
npmrun 9 hours ago
parent
commit
08e5f63d6c
  1. 3
      .gitignore
  2. 109
      docs/superpowers/specs/2026-04-18-discover-page-design.md

3
.gitignore

@ -13,6 +13,9 @@ node_modules
logs logs
*.log *.log
# Brainstorming visual companion sessions (local only)
.superpowers/
# Misc # Misc
.DS_Store .DS_Store
.fleet .fleet

109
docs/superpowers/specs/2026-04-18-discover-page-design.md

@ -0,0 +1,109 @@
# 发现页与「出现在发现中」设计
## 背景与目标
在站内提供 **「发现」** 入口:登录用户可浏览 **自愿出现在发现列表中的其他用户**,卡片展示有限公开信息(含用户配置的地址展示文案),点击进入其公开主页 `/@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.ts``users` 表新增字段(命名实现时可与下列语义对齐):
| 字段(语义) | 类型建议 | 默认值 | 说明 |
| --- | --- | --- | --- |
| 出现在发现中 | `integer`(0/1)或等价 boolean | **0(false)** | 默认不曝光,需用户主动打开 |
| 发现卡片地址文案 | `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` 或复用现有列表常量)。
- **响应字段**(仅列展示所需,**禁止**返回 `email`、`password` 等敏感列):
- `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/users``401`;登录且库中无候选人 → 空列表;多名用户仅部分满足条件 → 仅返回满足者。
- **资料**:切换 `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** 再改代码,或在本文件追加修订段落并标注日期。
Loading…
Cancel
Save