feat(deploy): production deployment setup and fixes

- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma
  binaryTargets for openssl 3.0
- nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal
  cron, http->https redirect
- prisma: add origin_goods.delisted migration, sync missing schema
  (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz
- api: CORS wildcard reflection, helmet CORP cross-origin, price
  backfill in persistProductDetail, categoryIcon ancestor fallback,
  mediaByColor per-color gallery in public goods detail
- admin: /admin base path (vite + router)
- import-data.mjs: udt_name casting, serial sequence advance fix
This commit is contained in:
yeuimu
2026-08-26 14:23:09 +08:00
parent be0b90e68f
commit 6c61a4e871
982 changed files with 74156 additions and 179393 deletions
+193 -193
View File
@@ -1,193 +1,193 @@
# AGENTS.md - AI Agent 开发规范
## 项目概述
InkReach 官网项目,基于 Nuxt 4 + Vue 3 + Tailwind CSS v4 的静态展示站点。
## 开发流程
- 包管理器:`npm`
- TypeScript strict 模式,所有代码必须通过类型检查
- 页面与组件代码放在 `app/` 目录下,符合 Nuxt 约定
### 1. 梳理需求
开发前确定好需求类型,在对应的 `plans/` 子目录下创建计划文档:
| 需求类型 | 计划目录 | 命名规则 | 示例 |
| -------- | ----------------- | --------------------- | ----------------------- |
| 新功能 | `plans/feature/` | `xxx-xxx-feature.md` | `hero-banner-feature.md` |
| 修 Bug | `plans/fix/` | `xxx-xxx-fix.md` | `layout-overflow-fix.md` |
| 重构 | `plans/refactor/` | `xxx-xxx-refactor.md` | `component-split-refactor.md` |
计划文档最末尾是开发任务表,每个任务项都有一个对应的二级标题的任务详情描述;当需求完成时任务项也标记勾选了。
### 2. 建立开发分支
使用 `enterprise-git-spec` 技能来建立分支。分支命名:
- 新功能:`feature/xxx-xxx`
- 修 Bug`bugfix/xxx-xxx`
- 重构:`refactor/xxx-xxx`
### 3. 按照需求开发
按照计划文档的任务项逐步实现功能。
**重要原则:所有测试必须通过**
- 开发过程中,确保所有测试用例通过
- 如果发现现有测试失败,必须立即修复
- 只有在所有测试都通过的情况下,才能认为开发完成
### 4. 合并到开发分支
所有测试通过后,使用 `enterprise-git-spec` 技能提交分支,然后合并回 `develop` 分支。
### 5. 沉淀开发经验
实现功能过程中,将通用的编码好想法、好思想、注意点等及时追加到本文件中。
### 6. 更新项目结构文档
每次功能开发完成后,更新 `docs/references/structs.md` 文件,记录项目目录结构和模块功能描述。
### 7. 更新使用文档
1. `README.md` - 快速上手,新功能开发完成后更新
2. `docs/references/*` - 详细使用指南,每次功能变更后更新
3. `skills/inkreach-official-website/SKILL.md` - AI Agent 使用文档
## 技术栈
| 技术 | 版本 | 用途 |
| --------------- | ----- | -------------- |
| Nuxt | 4.x | SSR/SSG 框架 |
| Vue | 3.x | UI 框架 |
| Tailwind CSS | 4.x | 原子化 CSS |
| @nuxtjs/seo | 5.x | SEO 模块 |
| FontAwesome | 7.x | 图标库 |
## 开发规范
### Vue / Nuxt 规范
- 使用 `<script setup>` + Composition API + TypeScript
- 组件文件命名:PascalCase(如 `ProductCard.vue`
- 页面放在 `app/pages/`,组件放在 `app/components/`
- 布局放在 `app/layouts/`
- 使用 Nuxt 自动导入,无需手动 import 组件和 composables
### 代码风格
- 缩进:2 空格
- 分号:必须
- 引号:单引号
- 行宽:100 字符
- 末尾逗号:es5
### TypeScript 规范
- 所有函数必须声明参数和返回值类型
- 优先使用 `interface`,交叉类型时用 `type`
- 避免使用 `any`,必须使用时用 `unknown` 替代
### Tailwind CSS 规范
- 自定义主题颜色在 `app/assets/css/tailwind.css``@theme` 中定义
- 自定义动画也在 `@theme` 中通过 `--animate-*``@keyframes` 注册
- 优先使用 Tailwind 工具类,必要时使用 `@apply``[任意值]` 语法
### 现有主题色
| 变量名 | 色值 | 用途 |
| --------------------------- | -------- | ------------ |
| `--color-primary` | `#fd6a03` | 主色调 |
| `--color-inkreach-homepage-1` | `#f3f4f5` | 浅灰背景 |
| `--color-inkreach-homepage-2` | `#f8f9fb` | 次浅灰背景 |
| `--color-inkreach-homepage-3` | `#fff6f0` | 暖色浅背景 |
| `--color-inkreach-homepage-4` | `#FF6800` | 橙色强调 |
| `--color-inkreach-homepage-5` | `#0C0501` | 深色/页脚 |
### 常用命令
```bash
npm run dev # 开发模式(nuxt dev --host
npm run build # 构建(nuxt build
npm run generate # 静态站点生成(nuxt generate
npm run preview # 预览构建产物
```
## 经验沉淀
### 启用 Nuxt 文件式路由
项目原先用 `app/app.vue` 作为整页入口。要新增独立路由页(如 `/product-center`):
1.`app/app.vue` 现有内容迁移到 `app/pages/index.vue`(保持 `/` 渲染原首页)。
2.`app/app.vue` 替换为最小容器:
```vue
<template>
<NuxtPage />
</template>
```
3. 新建 `app/pages/<name>.vue` 即可通过 `/<name>` 访问。
### Nitro 后端代理模式
若要把外部 HTTP API(如 NestJS 后端)包装为同源 `/api/*` 路由:
```typescript
// server/api/backend/categories.get.ts
export default defineCachedEventHandler(
async (event) => {
const config = useRuntimeConfig();
return $fetch(`${config.public.backendUrl}/public/categories`);
},
{
maxAge: 60,
swr: true,
name: 'backend-categories',
getKey: () => 'tree',
},
);
```
`runtimeConfig.public.backendUrl` 通过 `.env` 的 `NUXT_PUBLIC_BACKEND_URL` 注入,可被前端直接 `$fetch('/api/backend/...')` 调用且保持 SSR 工作。
注意:带 query 的列表接口(如 goods)需在 `getKey` 中将 `getQuery(event)` JSON 序列化作为缓存键,避免不同筛选条件互相污染。
### 后端响应包络
NestJS 全局 `TransformInterceptor` 把所有响应包成 `{ data: T, success: true }`。前端 `$fetch` 必须读取 `response.data`,类型定义也要带 `BackendEnvelope<T>` 包装层。
### `defineModel` 用于组件 v-model
Nuxt 4 内置 `defineModel` 支持双向绑定,比手写 `modelValue`/`update:modelValue` props+emit 更简洁:
```typescript
const keyword = defineModel<string>('keyword', { default: '' });
const emit = defineEmits<{ search: [] }>();
```
父组件用法:`<ProductFilterBar v-model:keyword="searchKeyword" @search="onSearch" />`
### SSR 期间 watch query 触发请求
在 `<script setup>` 中用 `await loadFilters()` 等顶层 await 时,Nuxt 会等待 Promise 解析才渲染首屏 HTML;之后用 `watch(() => [query.countryId, ...], fetchProducts)` 监听筛选条件变化,重新拉取数据。Top-level await 在 Nuxt 中是合法的(会变为异步 setup)。
### `npm install` 与 `node_modules`
首次 clone 项目后必须 `npm install` 安装依赖(项目使用 `package-lock.json`),否则 `node_modules` 为空。`npm install` 会自动触发 `postinstall: nuxt prepare` 生成 `.nuxt/` 类型与组件清单。
### 类型检查
项目使用 Nuxt 4 默认的 TS 配置(`.nuxt/tsconfig.app.json` 等)。运行类型检查:
```bash
npx vue-tsc --noEmit -p ./.nuxt/tsconfig.app.json
```
退出码 0 表示无类型错误,可作为合并前的本地验证步骤。
# AGENTS.md - AI Agent 开发规范
## 项目概述
InkReach 官网项目,基于 Nuxt 4 + Vue 3 + Tailwind CSS v4 的静态展示站点。
## 开发流程
- 包管理器:`npm`
- TypeScript strict 模式,所有代码必须通过类型检查
- 页面与组件代码放在 `app/` 目录下,符合 Nuxt 约定
### 1. 梳理需求
开发前确定好需求类型,在对应的 `plans/` 子目录下创建计划文档:
| 需求类型 | 计划目录 | 命名规则 | 示例 |
| -------- | ----------------- | --------------------- | ----------------------- |
| 新功能 | `plans/feature/` | `xxx-xxx-feature.md` | `hero-banner-feature.md` |
| 修 Bug | `plans/fix/` | `xxx-xxx-fix.md` | `layout-overflow-fix.md` |
| 重构 | `plans/refactor/` | `xxx-xxx-refactor.md` | `component-split-refactor.md` |
计划文档最末尾是开发任务表,每个任务项都有一个对应的二级标题的任务详情描述;当需求完成时任务项也标记勾选了。
### 2. 建立开发分支
使用 `enterprise-git-spec` 技能来建立分支。分支命名:
- 新功能:`feature/xxx-xxx`
- 修 Bug`bugfix/xxx-xxx`
- 重构:`refactor/xxx-xxx`
### 3. 按照需求开发
按照计划文档的任务项逐步实现功能。
**重要原则:所有测试必须通过**
- 开发过程中,确保所有测试用例通过
- 如果发现现有测试失败,必须立即修复
- 只有在所有测试都通过的情况下,才能认为开发完成
### 4. 合并到开发分支
所有测试通过后,使用 `enterprise-git-spec` 技能提交分支,然后合并回 `develop` 分支。
### 5. 沉淀开发经验
实现功能过程中,将通用的编码好想法、好思想、注意点等及时追加到本文件中。
### 6. 更新项目结构文档
每次功能开发完成后,更新 `docs/references/structs.md` 文件,记录项目目录结构和模块功能描述。
### 7. 更新使用文档
1. `README.md` - 快速上手,新功能开发完成后更新
2. `docs/references/*` - 详细使用指南,每次功能变更后更新
3. `skills/inkreach-official-website/SKILL.md` - AI Agent 使用文档
## 技术栈
| 技术 | 版本 | 用途 |
| --------------- | ----- | -------------- |
| Nuxt | 4.x | SSR/SSG 框架 |
| Vue | 3.x | UI 框架 |
| Tailwind CSS | 4.x | 原子化 CSS |
| @nuxtjs/seo | 5.x | SEO 模块 |
| FontAwesome | 7.x | 图标库 |
## 开发规范
### Vue / Nuxt 规范
- 使用 `<script setup>` + Composition API + TypeScript
- 组件文件命名:PascalCase(如 `ProductCard.vue`
- 页面放在 `app/pages/`,组件放在 `app/components/`
- 布局放在 `app/layouts/`
- 使用 Nuxt 自动导入,无需手动 import 组件和 composables
### 代码风格
- 缩进:2 空格
- 分号:必须
- 引号:单引号
- 行宽:100 字符
- 末尾逗号:es5
### TypeScript 规范
- 所有函数必须声明参数和返回值类型
- 优先使用 `interface`,交叉类型时用 `type`
- 避免使用 `any`,必须使用时用 `unknown` 替代
### Tailwind CSS 规范
- 自定义主题颜色在 `app/assets/css/tailwind.css``@theme` 中定义
- 自定义动画也在 `@theme` 中通过 `--animate-*``@keyframes` 注册
- 优先使用 Tailwind 工具类,必要时使用 `@apply``[任意值]` 语法
### 现有主题色
| 变量名 | 色值 | 用途 |
| --------------------------- | -------- | ------------ |
| `--color-primary` | `#fd6a03` | 主色调 |
| `--color-inkreach-homepage-1` | `#f3f4f5` | 浅灰背景 |
| `--color-inkreach-homepage-2` | `#f8f9fb` | 次浅灰背景 |
| `--color-inkreach-homepage-3` | `#fff6f0` | 暖色浅背景 |
| `--color-inkreach-homepage-4` | `#FF6800` | 橙色强调 |
| `--color-inkreach-homepage-5` | `#0C0501` | 深色/页脚 |
### 常用命令
```bash
npm run dev # 开发模式(nuxt dev --host
npm run build # 构建(nuxt build
npm run generate # 静态站点生成(nuxt generate
npm run preview # 预览构建产物
```
## 经验沉淀
### 启用 Nuxt 文件式路由
项目原先用 `app/app.vue` 作为整页入口。要新增独立路由页(如 `/product-center`):
1.`app/app.vue` 现有内容迁移到 `app/pages/index.vue`(保持 `/` 渲染原首页)。
2.`app/app.vue` 替换为最小容器:
```vue
<template>
<NuxtPage />
</template>
```
3. 新建 `app/pages/<name>.vue` 即可通过 `/<name>` 访问。
### Nitro 后端代理模式
若要把外部 HTTP API(如 NestJS 后端)包装为同源 `/api/*` 路由:
```typescript
// server/api/backend/categories.get.ts
export default defineCachedEventHandler(
async (event) => {
const config = useRuntimeConfig();
return $fetch(`${config.public.backendUrl}/public/categories`);
},
{
maxAge: 60,
swr: true,
name: 'backend-categories',
getKey: () => 'tree',
},
);
```
`runtimeConfig.public.backendUrl` 通过 `.env` 的 `NUXT_PUBLIC_BACKEND_URL` 注入,可被前端直接 `$fetch('/api/backend/...')` 调用且保持 SSR 工作。
注意:带 query 的列表接口(如 goods)需在 `getKey` 中将 `getQuery(event)` JSON 序列化作为缓存键,避免不同筛选条件互相污染。
### 后端响应包络
NestJS 全局 `TransformInterceptor` 把所有响应包成 `{ data: T, success: true }`。前端 `$fetch` 必须读取 `response.data`,类型定义也要带 `BackendEnvelope<T>` 包装层。
### `defineModel` 用于组件 v-model
Nuxt 4 内置 `defineModel` 支持双向绑定,比手写 `modelValue`/`update:modelValue` props+emit 更简洁:
```typescript
const keyword = defineModel<string>('keyword', { default: '' });
const emit = defineEmits<{ search: [] }>();
```
父组件用法:`<ProductFilterBar v-model:keyword="searchKeyword" @search="onSearch" />`
### SSR 期间 watch query 触发请求
在 `<script setup>` 中用 `await loadFilters()` 等顶层 await 时,Nuxt 会等待 Promise 解析才渲染首屏 HTML;之后用 `watch(() => [query.countryId, ...], fetchProducts)` 监听筛选条件变化,重新拉取数据。Top-level await 在 Nuxt 中是合法的(会变为异步 setup)。
### `npm install` 与 `node_modules`
首次 clone 项目后必须 `npm install` 安装依赖(项目使用 `package-lock.json`),否则 `node_modules` 为空。`npm install` 会自动触发 `postinstall: nuxt prepare` 生成 `.nuxt/` 类型与组件清单。
### 类型检查
项目使用 Nuxt 4 默认的 TS 配置(`.nuxt/tsconfig.app.json` 等)。运行类型检查:
```bash
npx vue-tsc --noEmit -p ./.nuxt/tsconfig.app.json
```
退出码 0 表示无类型错误,可作为合并前的本地验证步骤。