Browse Source
- Updated the layout of the admin configuration and user profile pages for better visual appeal and usability. - Introduced rounded borders and background enhancements for containers and cards. - Improved text visibility with highlighted styles and added informative descriptions. - Consolidated action buttons into sticky footers for easier access to save functionality. - Removed redundant elements and streamlined the form structure for a cleaner interface. This update significantly enhances the user experience by providing a more modern and intuitive design.main
4 changed files with 52 additions and 138 deletions
@ -1,106 +0,0 @@ |
|||||
# 设计:个人主页可选 EJS 整页渲染(多页可扩展) |
|
||||
|
|
||||
**日期**:2026-04-21 |
|
||||
**状态**:已定稿(修订:根路径解耦 + 多页自建) |
|
||||
|
|
||||
**修订说明** |
|
||||
|
|
||||
1. **`/@:publicSlug`** 不被 EJS 覆盖;**始终** Vue 默认主页。 |
|
||||
2. 个性化整页使用 **独立路由族**,**不**与 Nuxt 根路由抢请求。 |
|
||||
3. **不止一条**:支持 **多条** EJS 页面;主页所有者在控制台 **自行添加 / 编辑 / 禁用 / 删除**(具体交互由实现计划细化)。**不再**假设全局仅有一个 `custom` 字符串模板字段。 |
|
||||
|
|
||||
## 1. 背景与目标 |
|
||||
|
|
||||
当前公开个人根路径 `/@:publicSlug` 由 **Nuxt 页面** `app/pages/@[publicSlug]/index.vue` 与 **`layout: public`** 渲染,视觉与结构受站内组件体系约束。 |
|
||||
|
|
||||
**目标**: |
|
||||
|
|
||||
- **`/@:publicSlug`**:**不覆盖、不劫持**,**始终** Vue。 |
|
||||
- 在 **`@` 命名空间**下增加 **固定前缀 + 动态页键**,由服务端在用户启用时输出 **完整 HTML**(EJS),条数 **多条**,键由用户 **添加时指定**(在约束内)。 |
|
||||
- 模板内可链到 `/@slug/posts/...`、`timeline`、`about` 等现有 Vue 子路由。 |
|
||||
- **模板能力(已确认)**:**完整 EJS**(含任意 `<% %>`);等价于在服务器上以进程身份执行 **用户可控代码**,**信任模型与滥用责任由运营侧承担**;本版 **不做** 沙箱化必选。 |
|
||||
|
|
||||
## 2. 产品规则(已确认) |
|
||||
|
|
||||
| 维度 | 规则 | |
|
||||
|------|------| |
|
||||
| 根路径 | **`/@:publicSlug`** **仅** Vue。 | |
|
||||
| 个性化页 URL | **`/@:publicSlug/<PAGES_PREFIX>/<pageKey>`**(及同义尾部 `/`)。`<PAGES_PREFIX>` 为 **全站常量**(建议 `p`,短且易记;实现层 `PUBLIC_EJS_PAGES_PREFIX`),**不得**与用户内容的第一段自由混淆——访客能一眼看出是「自定义页族」。`<pageKey>` 为 **单段** 路径(见下条)。 | |
|
||||
| `pageKey` | 由用户在添加页面时设定;**约束**:长度、字符集(建议 `[a-z0-9-]` 小写 storage、展示可折叠)、**保留字** 拒绝(含与站内 **一级** 路径同名者,如 `posts`、`timeline`、`about`、`reading`、**以及前缀自身** `p` 若会产生歧义时由实现明确——至少禁止 `pageKey` 为空、`.`、`..`)。**同一 `publicSlug` 下 `pageKey` 唯一**。 | |
|
||||
| 条数上限 | **每用户可配置页面数上限**(具体数字实现定,如 20~50),写入控制台与错误提示;本 spec 不定死数字。 | |
|
||||
| 命中条件 | 用户 **`publicSlug` 有效**;存在一行 **`pageKey` 匹配** 且 **启用** 且 **`ejsSource` 非空**;否则对该 URL **404**。 | |
|
||||
| 子路由 | `/@:publicSlug/posts/...`、`timeline`、`about` 等 **始终** Vue;EJS 仅命中 **`/<PAGES_PREFIX>/:pageKey`** 双段结构,**不** 向下吞更深路径(若未来要「子路径」,另开设计)。 | |
|
||||
| 编辑权限 | **仅** 对应用户可在控制台 **增删改** 各页的 `pageKey`(或仅创建时可写、创建后只读——产品二选一,默认 **创建后可改 key** 易破坏外链,建议 **创建后 key 只读** 或 **改 key 视为新页 + 旧 URL 301**,实现计划定夺)。 | |
|
||||
| 数据真相 | 注入 EJS 的公开数据与 **`GET /api/public/profile/:publicSlug`** **语义一致**,通过 **共享 server 层** 组装;并额外注入 **当前页** 元信息(见 4.2)。 | |
|
||||
|
|
||||
## 3. 非目标(本版明确不做) |
|
||||
|
|
||||
- **不** 覆盖 **`/@:publicSlug`**。 |
|
||||
- **不** 用 EJS 接管除 **`/<PAGES_PREFIX>/:pageKey`** 以外的 `/@slug/**`。 |
|
||||
- **不** 在本版要求 **沙箱**;不将「受限模板」作为交付前提。 |
|
||||
- **不** 用仅客户端 `v-html` 冒充 **整页 SSR HTML** 主路径。 |
|
||||
|
|
||||
## 4. 架构(推荐实现) |
|
||||
|
|
||||
### 4.1 Nitro 短路:匹配 **`/@…/p/:pageKey`** |
|
||||
|
|
||||
在 **Nitro** 侧(`server/middleware` 或顺序明确的钩子 / 专用 route): |
|
||||
|
|
||||
1. 路径匹配 **`^\/@(?<publicSlug>[^/]+)\/<PAGES_PREFIX>\/(?<pageKey>[^/]+)\/?$`**(`<PAGES_PREFIX>` 与常量一致)。 |
|
||||
2. `publicSlug` 解析用户;按 **`(userId, pageKey)`** 查 **自定义页表**。 |
|
||||
3. 命中且启用:组装 `locals`,**`ejs.render`**,**`text/html`**,结束响应。 |
|
||||
4. **未命中**:**显式 404**(不要落到无关 Vue 页)。 |
|
||||
|
|
||||
**与根路径**:**`/@slug`** 永不进入本分支。 |
|
||||
|
|
||||
### 4.2 数据与注入 |
|
||||
|
|
||||
- **profile 单一来源**:**`getPublicProfilePayloadForHome(publicSlug)`**(名称可调整)与 **`GET /api/public/profile/:publicSlug`** 共用。 |
|
||||
- **`ejs.render(template, locals)`** 建议包含: |
|
||||
- **`profile`**:与公开 API **`data`** 同构; |
|
||||
- **`site`**:站名、公开根 URL; |
|
||||
- **`page`**:`{ key, path }`,`path` 为当前页规范路径(含前缀),便于模板内自链; |
|
||||
- **URL 辅助**:**`profileRootPath`**、`buildSitePath('posts')`** 等(与现有路由约定一致); |
|
||||
- **安全向 helper**:`escapeHtml`、日期格式化等(与选项 A 一致,不消灭任意 EJS 能力)。 |
|
||||
- **禁止项**:不向 `locals` 注入无界 **`require`**;**`ejs` 选项** 避免引入磁盘任意读;模板仅存 **DB**,文件名不传真实 FS **`root`**。 |
|
||||
|
|
||||
### 4.3 存储 |
|
||||
|
|
||||
- **独立表**(推荐):**`user_ejs_pages`**(名可微调):`user_id`、`page_key`、`ejs_source`、`enabled`、`created_at`、`updated_at`;**唯一约束 `(user_id, page_key)`**。 |
|
||||
- **不** 再依赖用户表上单字段 **`homeEjsSource`** 作为唯一真相(若历史上存在,迁移为表或弃用)。 |
|
||||
- **单页模板体积**、**全站每用户页数** 限流在服务层强制。 |
|
||||
|
|
||||
### 4.4 可选:站内发现 |
|
||||
|
|
||||
- Vue 根主页可列出「自定义页」入口(只展示 `enabled` 的页);**本 spec 不强制定 UI**。 |
|
||||
|
|
||||
## 5. 安全与滥用(选项 A 下的最低工程线) |
|
||||
|
|
||||
- **单请求** 渲染 **超时**、**单模板体积** 上限、**每用户页数** 上限。 |
|
||||
- 访客 **500/错误页** 不泄露栈;**预览/保存** 可对主人返回语法错误。 |
|
||||
- 运营可 **禁用某页** 或 **封禁用户** 使对应 URL **404**。 |
|
||||
|
|
||||
## 6. 错误与兼容性 |
|
||||
|
|
||||
- 运行时错误:**日志** + 访客安全页。 |
|
||||
- **不** 做「自动删除页」类降级,除非产品后续明确要求并文案提示。 |
|
||||
|
|
||||
## 7. 测试 |
|
||||
|
|
||||
- **单元测试**:`ejs.render` + 固定 `locals`;`pageKey` 与保留字校验。 |
|
||||
- **集成 / 手测**: |
|
||||
- 同一用户两条不同 `pageKey` 均可 200; |
|
||||
- 禁用或删除后 **404**; |
|
||||
- **`/@slug`**、**`/@slug/posts/...`** 仍为 Vue。 |
|
||||
|
|
||||
## 8. 依赖 |
|
||||
|
|
||||
- **`ejs`**;如有 **`@types/ejs`** 则 dev。 |
|
||||
|
|
||||
## 9. 与早期「仅 `/custom`」草案的关系 |
|
||||
|
|
||||
- 早期单一路径 **`/@slug/custom`** 视为 **仅 `pageKey = 'custom'` 的特殊情况**;现以 **通用多页模型** 为准。若产品仍需 **固定别名**(例如默认生成一页 `welcome`),可在实现时 **种子一行**,**不**再单独写死单一路由处理器(除非兼容迁移需要)。 |
|
||||
|
|
||||
--- |
|
||||
|
|
||||
**下一实现阶段**:见 `docs/superpowers/plans/` 下 **implementation plan**(`writing-plans` 产出)。 |
|
||||
Binary file not shown.
Loading…
Reference in new issue