# cms

静态化 CMS：JSON 源数据 + 资源，构建后上传到 CloudBase 静态托管，CDN 对外访问。

## 开发

### 目录结构

```
cms/
├── data/<entry>/{zh,en}.json              # 源数据（path 模式）；inline 模式可用 zh.ts/en.ts
├── data/<entry>/schema.ts                 # 该条目的 zod schema（export default z.array(row)，自包含）
├── data/<entry>/index.ts                  # 条目入口：调用 defineEntry(import.meta.url, schema, locales, opts?)
├── data/index.ts                          # entry 注册表（手动注册，只有此处注册的 entry 才进 dist）
├── data/define-entry.ts                   # defineEntry 实现（读源数据/深拷贝、组装 EntryModule）
├── data/schema.test.ts                    # 遍历 registry 对 zh/en 逐行跑 zod safeParse
├── assets/                                # 静态资源（图片、zip 等）
├── lib/
│   ├── build.ts                           # 构建：校验 + 路径改写 + 写产物
│   ├── publish.ts                         # 发布：build + tcb hosting deploy
│   ├── validate.ts                        # 校验（zod 结构校验 + 资源路径穿越）
│   ├── rewrite.ts                         # 资源路径改写
│   └── manifest.ts                        # hash + manifest 生成
├── package.json                           # 依赖与脚本（zod / zod-to-json-schema / tsx）
├── tsconfig.json
├── .env.example
├── AGENTS.md                              # Agent 操作约束
├── IMPORT-SOP.md                          # 外部数据批量导入 SOP（可选辅助）
└── README.md
```

### 数据维护

- **加静态资源**：放进 `cms/assets/`（可建子目录），在 `zh.json`/`en.json` 中用 `@/assets/xxx.png` 引用（`@` = `cms/` 根）。
- **改已有条目**：直接编辑对应 `cms/data/<entry>/zh.json`、`en.json`。
- **新增条目**：照抄 `cms/data/_example/`（`schema.ts` + `index.ts` + `zh.json`/`en.json`），改好字段与数据后，在 `cms/data/index.ts` 的 `registry` 中注册（未注册不会被构建）。
- 改完跑 `npm run build` 校验字段类型、多余/缺失字段、资源路径等问题。
- 更细的字段契约与 entry 规范见 [`AGENTS.md`](./AGENTS.md)（写给 Agent，人也可直接照做）。

### 让 AI 协助转换 / 新增数据

第一次发起请求**不必写全所有信息**——AI 会按 [`AGENTS.md`](./AGENTS.md) §0「输入不足先问，禁止脑补」逐项追问补齐。只需说明意图 + 对应场景即可起步：

**场景 A · 迁移旧 cms 数据（有模型 + 数据）**

> 按 `cms/AGENTS.md` + `IMPORT-SOP.md`，把 `cms/temp/xxx.json`（数据模型）和 `cms/temp/yyy.xlsx`（数据）迁成新 entry `<名字>`，拿不准的决策点先问我，不要脑补。

**场景 B · 新建 cms 数据对象（暂无数据）**

> 按 `cms/AGENTS.md` 新建一个 entry `<名字>`，用途是 `<一句话说明>`，字段清单和数据形态我稍后给，拿不准先问我。

> 提示：模型 / 数据文件先放进 `cms/temp/`（已 gitignore，用完即弃），再在 prompt 里写文件名即可。

## 部署

### 本地部署

```bash
cd cms
npm install                # 首次需要安装依赖
cp .env.example .env       # 填入真实的 CMS_ENV / CDN_BASE / TCB_ENV_ID / API_KEYID / API_KEY（.env 不入 git）

# 构建：产物直接输出到 cms/dist/（内含的资源 URL 会带上 CMS_ENV 段，默认 pre）
npm run build

# 发布到 CMS_ENV（pre或者prod）
npm run publish

```

> 说明：`build` 产物本身不按 env 分层，`dist/` 直接就是最终产物；但产物内 JSON 中的资源 URL 需要 `cms/<env>/` 前缀，因此构建期仍读 `CMS_ENV` 用于 URL 生成。`publish` 时再按 `CMS_ENV` 决定远端目录 `cms/pre` 或 `cms/prod`。`API_KEYID`/`API_KEY` 已在 `.env` 中配置好后无需在命令行重复传入；如需临时覆盖某个变量，也可以在命令前以 `KEY=value` 的形式内联传入。

### 流水线部署

也可以通过 CI 流水线触发构建与发布：

```
https://zhiyan.woa.com/qci/9988/pipeline/#/pipeline/detail/11333765/build/current
```

触发时 `PACKAGE_NAME` 选择 `cms`，`CMS_ENV` 选择 `prod` 表示发布到生产（对外正式）环境，选择 `pre` 表示发布到预览环境。

`【云开发平台登录页相关配置】部署后大概 cdn 刷新时间需要 1 分钟左右，可以过 1 分钟再查看。手动刷新方案：账号-100008215038、环境-tcbjssdk-cdn-5g9n6m9scceec659，刷新url: https://tcbjssdk-cdn-5g9n6m9scceec659-1258016615.tcloudbaseapp.com/cms/prod/manifest.json`

### 产物结构（不入 git）

```
cms/dist/
├── manifest.json
├── data/<entry>/{schema,zh,en}.json   # schema.json 由 zod 反向生成
└── assets/**
```

## 使用

### 访问 URL

```
https://tcbjssdk-cdn-5g9n6m9scceec659-1258016615.tcloudbaseapp.com/cms/<env>/manifest.json
https://tcbjssdk-cdn-5g9n6m9scceec659-1258016615.tcloudbaseapp.com/cms/<env>/data/<entry>/<lang>.json
https://tcbjssdk-cdn-5g9n6m9scceec659-1258016615.tcloudbaseapp.com/cms/<env>/data/<entry>/schema.json
https://tcbjssdk-cdn-5g9n6m9scceec659-1258016615.tcloudbaseapp.com/cms/<env>/assets/**
```

资源 URL 在 build 时改写为完整 CDN URL，并带 `?v=<hash>` 用于缓存失效。

### 前端运行时（示意）

```ts
const CDN_BASE = 'https://tcbjssdk-cdn-5g9n6m9scceec659-1258016615.tcloudbaseapp.com';
const CMS_ENV  = import.meta.env.VITE_CMS_ENV ?? 'prod'; // 'pre' | 'prod'
const LANG     = getLang();                              // 'zh' | 'en'

const manifest = await fetch(`${CDN_BASE}/cms/${CMS_ENV}/manifest.json`,
  { cache: 'no-cache' }).then(r => r.json());

const v = manifest.files[`data/_example/${LANG}.json`].slice(0, 8);
const list = await fetch(
  `${CDN_BASE}/cms/${CMS_ENV}/data/_example/${LANG}.json?v=${v}`,
).then(r => r.json());
```

### 云开发平台访问 pre

云开发控制台默认访问 prod 环境数据；若需要在控制台（如云函数、静态托管等页面）中查看/操作 **pre** 环境的 CMS 数据，可在访问链接的 hash 部分前加上 `!pre` 标记，例如：

```
https://tcb.cloud.tencent.com/dev?envId=<envId>&!pre#/cloud-function/overview
```

其中 `<envId>` 替换为实际环境 ID（如 `v1-api-comp-6gps446f3cb09e04`）。带 `!pre` 后，控制台在该次访问中会读取 pre 环境的 CMS 数据；不带则默认访问 prod。

### 云开发平台登录页访问 pre

```
https://tcb.cloud.tencent.com/login?env=pre
```

带 `env=pre` 后，访问中会读取 pre 环境的 CMS 数据；不带则默认访问 prod。