# GeneralTemplate
**Repository Path**: midebug/general-template
## Basic Information
- **Project Name**: GeneralTemplate
- **Description**: GeneralTemplate 是一个面向多端业务项目的 uni-app 脚手架模板。当前模板基于 Vue3、Vite、uni-app、Pinia、TailwindCSS、weapp-tailwindcss 和 uni-ui 搭建,已封装页面路由生成、分包、主题换色、多语言、请求、上传、路由拦截、全局 hooks、基础业务组件和常用工具。
- **Primary Language**: Unknown
- **License**: Zlib
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2026-07-23
- **Last Updated**: 2026-07-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# GeneralTemplate
Author: Douze
## 项目简介
联系与责任边界说明见:[联系与说明](./docs/contact-disclaimer.md)。
GeneralTemplate 是一个面向多端业务项目的开箱即用 uni-app 脚手架模板。当前模板基于 Vue3、Vite、uni-app、Pinia、TailwindCSS、weapp-tailwindcss 和 uni-ui 搭建,已封装页面路由生成、分包、主题换色、多语言、请求、上传、路由拦截、全局 hooks、广告对接、AI 客服对话、基础业务组件和常用工具。
## 仓库地址
- GitHub:[https://github.com/nine195/GeneralTemplate](https://github.com/nine195/GeneralTemplate)
- Gitee:[https://gitee.com/Douze_git/general-template](https://gitee.com/Douze_git/general-template)
## 文档导航
- [仓库地址](#仓库地址)
- [项目 AI Skill](#项目-ai-skill)
- [专题文档](#专题文档)
- [AI 客服对话快速导航](#ai-客服对话快速导航)
- [广告对接流程快速导航](#广告对接流程快速导航)
- [功能专题导航](#功能专题导航)
- [1. 项目定位](#1-项目定位)
- [2. 支持端](#2-支持端)
- [3. 技术选型](#3-技术选型)
- [4. 快速开始](#4-快速开始)
- [5. 命令说明](#5-命令说明)
- [6. 环境变量](#6-环境变量)
- [7. 目录结构](#7-目录结构)
- [8. 命名规范](#8-命名规范)
- [9. 页面路由和 pages.json 生成](#9-页面路由和-pagesjson-生成)
- [10. 分包配置](#10-分包配置)
- [11. 组件规则](#11-组件规则)
- [12. UI 方案和适配层](#12-ui-方案和适配层)
- [13. TailwindCSS 使用说明](#13-tailwindcss-使用说明)
- [14. Pinia 状态管理](#14-pinia-状态管理)
- [15. 多语言配置](#15-多语言配置)
- [16. 动态主题换色](#16-动态主题换色)
- [17. 请求封装](#17-请求封装)
- [18. 图片上传封装](#18-图片上传封装)
- [19. 路由跳转和拦截](#19-路由跳转和拦截)
- [20. 全局 hooks](#20-全局-hooks)
- [21. 平台识别](#21-平台识别)
- [22. API 和工具目录](#22-api-和工具目录)
- [23. 内置 Demo 页面](#23-内置-demo-页面)
- [24. 新增业务模块流程](#24-新增业务模块流程)
- [25. 构建发布](#25-构建发布)
- [26. 常见问题](#26-常见问题)
- [27. 维护检查清单](#27-维护检查清单)
- [English Guide](#english-guide)
## 专题文档
README 是项目总入口。更详细的模块说明放在 `docs/` 目录:
- [文档中心](./docs/README.md)
- [联系与说明](./docs/contact-disclaimer.md)
- [架构说明](./docs/architecture.md)
- [页面路由和分包](./docs/pages-routing.md)
- [业务开发手册](./docs/development-guide.md)
- [请求和上传](./docs/http-upload.md)
- [AI 开发协作指南](./docs/ai-development.md):项目 Skill 地址、AI 开发规范、常用任务流程、验证命令
- [AI 对话组件](./docs/ai-chat.md)
- [广告接入流程](./docs/ad-integration.md):开箱即用流程、单页广告、全局注入、不使用广告时如何关闭、真实 provider 适配
- [主题和多语言](./docs/theme-i18n.md)
- [构建发布和排障](./docs/build-deploy-troubleshooting.md)
## AI 客服对话快速导航
| 入口 | 位置 |
| --- | --- |
| 使用说明 | [AI 对话组件](./docs/ai-chat.md) |
| Demo 页面 | `/pages/dz-ai-chat-demo/index` |
| 配置文件 | `src/config/dz-ai-chat-config.ts` |
| 组件文件 | `src/components/dz-ai-chat/dz-ai-chat.vue` |
| 类型文件 | `src/components/dz-ai-chat/dz-ai-chat-types.ts` |
## 广告对接流程快速导航
| 入口 | 位置 |
| --- | --- |
| 使用说明 | [广告接入流程](./docs/ad-integration.md) |
| Demo 页面 | `/pages/dz-ad-demo/index` |
| 配置文件 | `src/config/dz-ad-config.ts` |
| 广告服务 | `src/services/ad` |
| 页面广告位组件 | `src/components/dz-ad-slot/dz-ad-slot.vue` |
| 全局注入组件 | `src/components/dz-ad-global/dz-ad-global.vue` |
## 功能专题导航
| 功能 | 说明文档 | Demo 页面 | 核心配置 |
| --- | --- | --- | --- |
| AI 客服对话 | [AI 对话组件](./docs/ai-chat.md) | `/pages/dz-ai-chat-demo/index` | `src/config/dz-ai-chat-config.ts` |
| 广告接入 | [广告接入流程](./docs/ad-integration.md) | `/pages/dz-ad-demo/index` | `src/config/dz-ad-config.ts` |
## 项目 AI Skill
本项目已内置面向 AI 开发协作的项目 Skill,后续开发人员可以在使用 AI 修改本仓库时指定该文件,让 AI 先加载项目规范、项目类型、功能模块、配置入口和验证流程。
Skill 地址:
```text
.codex/skills/general-template/SKILL.md
```
详细使用说明见:[AI 开发协作指南](./docs/ai-development.md)。
## 1. 项目定位
这个模板的目标是给后续业务项目提供一套开箱即用的基础架构:
- 页面新增后可以通过配置自动进入 `pages.json`
- `components` 目录默认只作为组件目录,不注册路由
- 支持主包和分包配置,分包数组填好后自动生成平台对应配置
- H5、公众号、小程序、App、鸿蒙 App 使用同一套业务代码
- 基础 UI 通过 `dz-*` 组件隔离,后续更换 UI 库时减少业务改动
- 请求、上传、路由、主题、多语言、store 和 hooks 已经封装好
- README 作为团队交接文档,后续人员按文档即可新增页面、接口、主题和语言包
## 2. 支持端
| 目标端 | 命令 | 说明 |
| --- | --- | --- |
| H5 | `npm run dev:h5` / `npm run build:h5` | 普通 Web H5 |
| 微信公众号 H5 | `npm run dev:official-account` / `npm run build:official-account` | 本质是 H5,额外设置 `VITE_RUNTIME=wechat-official` |
| 微信小程序 | `npm run dev:mp-weixin` / `npm run build:mp-weixin` | 输出到 `dist/build/mp-weixin` |
| 支付宝小程序 | `npm run dev:mp-alipay` / `npm run build:mp-alipay` | 输出到对应小程序目录 |
| 抖音小程序 | `npm run dev:mp-toutiao` / `npm run build:mp-toutiao` | uni-app 平台名为 `mp-toutiao` |
| Android App | `npm run dev:app-android` / `npm run build:app-android` | 需要 DCloud App 相关环境 |
| iOS App | `npm run dev:app-ios` / `npm run build:app-ios` | 需要 iOS 打包环境 |
| 鸿蒙 App | `npm run dev:app-harmony` / `npm run build:app-harmony` | 需要鸿蒙相关工具链 |
不要并发运行多个端的构建命令。`uni-pages` 和本项目的 pages 插件都会写同一个 `src/pages.json`,多端并发构建可能互相覆盖生成结果。
## 3. 技术选型
| 类型 | 当前方案 | 文件位置 |
| --- | --- | --- |
| 应用框架 | uni-app Vue3 + Vite | `vite.config.ts`、`src/main.ts` |
| UI 基础能力 | 自定义 `dz-*` + uni-ui easycom | `src/components`、`src/ui/dz-ui-config.ts` |
| CSS | TailwindCSS v4 + weapp-tailwindcss | `src/app.css`、`vite.config.ts` |
| 状态管理 | Pinia | `src/store` |
| 页面生成 | `@uni-helper/vite-plugin-uni-pages` + 自定义 `dzPagesJsonPlugin` | `pages.config.ts`、`build/dz-pages-json-plugin.ts` |
| 请求 | 原生 `uni.request` 封装 | `src/services/http` |
| 上传 | 原生 `uni.uploadFile` 封装 | `src/services/upload` |
| AI 对话 | 通用 AI 对话组件,支持流式、附件和防代码模式 | `src/components/dz-ai-chat` |
| 路由 | 原生 `uni.navigateTo` 等 API 封装 | `src/utils/dz-route.ts` |
| 主题 | Pinia + CSS 变量 + 原生导航栏换色 | `src/theme`、`src/store/modules/dz-theme.ts` |
| 多语言 | 自研轻量 i18n store | `src/i18n`、`src/store/modules/dz-i18n.ts` |
| 自动导入 | Vue API、hooks、store 自动导入 | `vite.config.ts` |
| 组件自动注册 | `src/components` 和页面内 `components` | `vite.config.ts` |
UI 选型建议:业务页面优先使用 `dz-*` 适配层组件,不直接依赖某个第三方 UI 库。以后如果从 uni-ui 换到 uView、uv-ui 或 Wot Design Uni,优先改 `dz-*` 组件内部实现,而不是批量改业务页面。
## 4. 快速开始
安装依赖:
```bash
npm install
```
启动 H5:
```bash
npm run dev:h5
```
默认本地地址:
```text
http://127.0.0.1:5173/
```
类型检查:
```bash
npm run typecheck
```
构建 H5:
```bash
npm run build:h5
```
构建微信小程序:
```bash
npm run build:mp-weixin
```
## 5. 命令说明
| 命令 | 用途 |
| --- | --- |
| `npm run dev:h5` | 启动 H5 开发服务 |
| `npm run dev:official-account` | 启动公众号 H5 开发服务 |
| `npm run dev:mp-weixin` | 微信小程序开发构建 |
| `npm run dev:mp-alipay` | 支付宝小程序开发构建 |
| `npm run dev:mp-toutiao` | 抖音小程序开发构建 |
| `npm run dev:app` | App 开发构建 |
| `npm run dev:app-android` | Android App 开发构建 |
| `npm run dev:app-ios` | iOS App 开发构建 |
| `npm run dev:app-harmony` | 鸿蒙 App 开发构建 |
| `npm run build:h5` | H5 生产构建 |
| `npm run build:official-account` | 公众号 H5 生产构建 |
| `npm run build:mp-weixin` | 微信小程序生产构建 |
| `npm run build:mp-alipay` | 支付宝小程序生产构建 |
| `npm run build:mp-toutiao` | 抖音小程序生产构建 |
| `npm run build:app` | App 生产构建 |
| `npm run build:app-android` | Android App 生产构建 |
| `npm run build:app-ios` | iOS App 生产构建 |
| `npm run build:app-harmony` | 鸿蒙 App 生产构建 |
| `npm run typecheck` | TypeScript 和 Vue 类型检查 |
| `npm run postinstall` | 安装依赖后执行 `weapp-tw patch` |
构建建议顺序:
```bash
npm run typecheck
npm run build:h5
npm run build:mp-weixin
npm run build:mp-alipay
npm run build:mp-toutiao
```
App 和鸿蒙构建需要本机环境支持,按实际团队工具链补充执行。
## 6. 环境变量
环境文件:
- `.env`
- `.env.development`
- `.env.production`
当前变量:
| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `VITE_APP_TITLE` | `GeneralTemplate` | 应用标题 |
| `VITE_API_BASE_URL` | 空字符串或环境文件内地址 | 请求和上传的默认 baseURL |
| `VITE_REQUEST_TIMEOUT` | `15000` | 请求和上传超时时间,单位毫秒 |
| `VITE_RUNTIME` | `default` | 运行时标识,公众号 H5 使用 `wechat-official` |
读取位置:
- `src/config/dz-env.ts`
- `src/services/http/index.ts`
- `src/services/upload/index.ts`
- `src/hooks/dz-use-platform.ts`
使用示例:
```ts
import { appEnv } from '@/config/dz-env'
console.log(appEnv.apiBaseURL)
console.log(appEnv.runtime)
```
## 7. 目录结构
```text
GeneralTemplate
├─ build
│ └─ dz-pages-json-plugin.ts # 编译前写入 pages.json,保证 H5 多页能力识别
├─ src
│ ├─ apis # API 聚合目录
│ ├─ components # 全局 dz-* 基础组件
│ ├─ config # 环境配置
│ ├─ hooks # 全局 hooks 和组合式函数
│ ├─ i18n # 多语言文案配置
│ ├─ layouts # 预留布局目录
│ ├─ pages # 主包页面
│ ├─ pages-sub # 分包页面
│ ├─ router # 页面和分包配置
│ ├─ services # 请求、上传等服务
│ ├─ store # Pinia store
│ ├─ styles # 全局样式、主题样式
│ ├─ theme # 主题配置和 CSS 变量生成
│ ├─ ui # UI 适配层配置
│ ├─ utils # 工具函数、路由封装
│ ├─ App.vue # uni-app 应用入口
│ ├─ main.ts # Vue app 创建入口
│ ├─ manifest.json # uni-app manifest
│ └─ pages.json # 编译生成文件,不建议手写长期维护
├─ pages.config.ts # uni-pages 最终 pages 配置
├─ vite.config.ts # Vite 和 uni 插件配置
└─ package.json
```
核心文件说明:
| 文件 | 说明 |
| --- | --- |
| `src/router/dz-pagesjs.ts` | 主包页面、分包页面、预下载规则的源配置 |
| `build/dz-pages-json-plugin.ts` | Vite config 阶段先写完整 `pages.json`,避免 H5 router 被树摇 |
| `pages.config.ts` | `uni-pages` 读取这里生成最终 `src/pages.json` |
| `src/utils/dz-route.ts` | 路由跳转、拦截、自定义跳转封装 |
| `src/services/http` | 原生请求封装 |
| `src/services/upload` | 图片选择和上传封装 |
| `src/store/modules/dz-theme.ts` | 动态主题状态和更新方法 |
| `src/store/modules/dz-i18n.ts` | 多语言状态和动态文案合并 |
## 8. 命名规范
业务自有文件统一使用 `dz-` 前缀,例如:
- `dz-route.ts`
- `dz-theme-config.ts`
- `dz-demo.ts`
- `dz-button.vue`
uni-app 约定文件不强制加 `dz`,包括:
- `App.vue`
- `main.ts`
- `manifest.json`
- `pages.json`
- 页面目录内的 `index.vue`
页面目录建议:
```text
src/pages/dz-order/index.vue
src/pages/dz-order/components/dz-order-card.vue
src/pages-sub/dz-order/detail/index.vue
```
组件目录规则:
- 全局组件放 `src/components/dz-xxx/dz-xxx.vue`
- 页面私有组件放 `src/pages/xxx/components/dz-xxx.vue`
- 任意 `components` 目录都不注册为页面路由
## 9. 页面路由和 pages.json 生成
页面配置源文件是:
```text
src/router/dz-pagesjs.ts
```
生成链路:
```text
src/router/dz-pagesjs.ts
↓
build/dz-pages-json-plugin.ts
↓
pages.config.ts
↓
src/pages.json
↓
各端编译产物
```
为什么有 `build/dz-pages-json-plugin.ts`:
- uni-h5 会在 Vite config 阶段读取 `src/pages.json` 判断是否启用多页 router
- 如果这个阶段只有一个首页,H5 router 可能被树摇掉
- 本插件会提前按当前平台写入完整页面配置
- 后续 `uni-pages` 再根据 `pages.config.ts` 生成最终 `src/pages.json`
新增主包页面步骤:
1. 新建页面文件:
```text
src/pages/dz-order/index.vue
```
2. 在 `src/router/dz-pagesjs.ts` 的 `dzMainPages` 中加入:
```ts
export const dzMainPages: DzPageRoute[] = [
{
path: 'pages/index/index',
type: 'home',
style: {
navigationBarTitleText: '首页',
},
},
{
path: 'pages/dz-order/index',
style: {
navigationBarTitleText: '订单',
},
},
]
```
3. 使用路由跳转:
```ts
const router = useRouter()
router.navigateTo('/pages/dz-order/index')
```
页面路径规则:
- 路由配置中不写开头 `/`
- 跳转时可以写 `/pages/xxx/index` 或 `pages/xxx/index`
- 封装会自动把 `pages/*` 和 `pages-sub/*` 补成以 `/` 开头的路径
`src/pages.json` 是生成文件,尽量不要长期手写维护。需要改页面、分包、标题、预下载时优先改 `src/router/dz-pagesjs.ts` 和 `pages.config.ts`。
## 10. 分包配置
分包配置源文件:
```text
src/router/dz-pagesjs.ts
```
当前字段:
| 字段 | 说明 |
| --- | --- |
| `root` | 分包根目录,例如 `pages-sub/dz-demo` |
| `preload` | 是否生成首页预下载规则 |
| `pages` | 分包内页面数组 |
新增分包示例:
```ts
export const dzSubPackageModules: DzSubPackageModule[] = [
{
root: 'pages-sub/dz-order',
preload: true,
pages: [
{
path: 'list/index',
style: {
navigationBarTitleText: '订单列表',
},
},
{
path: 'detail/index',
style: {
navigationBarTitleText: '订单详情',
},
},
],
},
]
```
对应文件:
```text
src/pages-sub/dz-order/list/index.vue
src/pages-sub/dz-order/detail/index.vue
```
跳转:
```ts
const router = useRouter()
router.navigateTo('/pages-sub/dz-order/list/index')
router.navigateTo('/pages-sub/dz-order/detail/index', {
query: {
id: '1001',
},
})
```
H5 和小程序差异:
- H5 不支持真实 `subPackages`,构建时分包页面会展开成普通页面
- 小程序、App、鸿蒙端会生成真实 `subPackages`
- `preload: true` 会在非 H5 平台生成首页预下载规则
注意:多端构建不要并发执行,否则可能互相覆盖 `src/pages.json`。
## 11. 组件规则
组件自动注册配置在 `vite.config.ts`:
```ts
Components({
dirs: ['src/components', 'src/pages/**/components'],
extensions: ['vue'],
deep: true,
dts: 'src/components.d.ts',
directoryAsNamespace: false,
})
```
规则:
- `src/components` 下组件全局可用
- `src/pages/**/components` 下页面私有组件也会自动注册
- `components` 目录不会被注册为页面路由
- 组件名建议使用 `dz-` 前缀
内置组件:
| 组件 | 文件 | 说明 |
| --- | --- | --- |
| `dz-button` | `src/components/dz-button/dz-button.vue` | 按钮,支持类型、尺寸、加载、禁用、block |
| `dz-card` | `src/components/dz-card/dz-card.vue` | 卡片容器,支持标题、副标题、extra 插槽 |
| `dz-input` | `src/components/dz-input/dz-input.vue` | 输入框,支持 `v-model`、label、clearable |
| `dz-empty` | `src/components/dz-empty/dz-empty.vue` | 空状态 |
| `dz-base-status` | `src/components/dz-base-status/dz-base-status.vue` | 状态展示 |
使用示例:
```vue
保存
```
## 12. UI 方案和适配层
UI 配置:
```text
src/ui/dz-ui-config.ts
```
当前配置:
```ts
export const dzUiConfig = {
provider: 'dz-native',
prefix: 'dz',
fallbackProvider: 'uni-ui',
}
```
支持的 provider 类型:
```ts
type DzUiProvider = 'dz-native' | 'uni-ui' | 'uview-pro' | 'uv-ui' | 'wot-design-uni'
```
更换 UI 方案建议:
1. 不直接在业务页面大量使用第三方组件
2. 先在 `dz-*` 基础组件内完成 PoC
3. 保持业务页面只依赖 `dz-button`、`dz-card`、`dz-input` 等适配层
4. 若切到 `uv-ui` 或 `wot-design-uni`,优先改 `src/components/dz-*` 内部实现
5. 多端验证 H5、微信小程序、支付宝小程序、抖音小程序、App 和鸿蒙端
uni-ui easycom 配置在 `pages.config.ts` 和 `build/dz-pages-json-plugin.ts`:
```ts
easycom: {
autoscan: true,
custom: {
'^uni-(.*)': '@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue',
},
}
```
## 13. TailwindCSS 使用说明
入口文件:
```text
src/app.css
```
当前配置:
```css
@import "weapp-tailwindcss/index.css";
@source "./**/*.{vue,ts,js,uts}";
@source not "./uni_modules/**";
@source not "../dist/**";
@source not "../unpackage/**";
```
Vite 配置:
```ts
WeappTailwindcss({
cssEntries: [path.resolve(rootDir, 'src/app.css')],
rem2rpx: true,
})
```
使用建议:
- 页面和组件可以直接写 Tailwind class
- 多端单位换算由 weapp-tailwindcss 处理
- 项目主题颜色优先使用 CSS 变量类,例如 `dz-bg-primary`、`dz-text-primary`
- 不建议在业务页面写大量固定颜色,后续换主题会困难
示例:
```vue
标题
```
## 14. Pinia 状态管理
入口:
```text
src/store/index.ts
```
注册位置:
```text
src/main.ts
```
当前 store:
| Store | 文件 | 说明 |
| --- | --- | --- |
| `useAppStore` | `src/store/modules/dz-app.ts` | 应用标题、平台、运行时、前后台状态、启动次数 |
| `useUserStore` | `src/store/modules/dz-user.ts` | token、用户资料、登录状态 |
| `useDzThemeStore` | `src/store/modules/dz-theme.ts` | 当前主题、主题列表、动态更新主题 |
| `useDzI18nStore` | `src/store/modules/dz-i18n.ts` | 当前语言、语言包、动态合并文案 |
使用示例:
```ts
import { useUserStore } from '@/store/modules/dz-user'
const userStore = useUserStore()
userStore.setToken('token')
console.log(userStore.isLogin)
```
新增 store:
```text
src/store/modules/dz-order.ts
```
```ts
import { defineStore } from 'pinia'
export const useOrderStore = defineStore('order', {
state: () => ({
list: [] as Array<{ id: string; title: string }>,
}),
actions: {
setList(list: Array<{ id: string; title: string }>) {
this.list = list
},
},
})
```
## 15. 多语言配置
配置文件:
```text
src/i18n/dz-i18n-config.ts
src/store/modules/dz-i18n.ts
src/hooks/dz-use-i18n.ts
```
默认语言:
```ts
export const dzDefaultLocale: DzLocale = 'zh-CN'
```
页面使用:
```ts
const { locale, setLocale, t, mergeLocaleMessages } = useDzI18n()
const title = t('home.themeTitle')
setLocale('en-US')
```
动态追加或覆盖语言包:
```ts
mergeLocaleMessages('zh-CN', {
order: {
title: '订单',
detail: '订单详情',
},
})
```
带参数文案:
```ts
mergeLocaleMessages('zh-CN', {
order: {
count: '共 {count} 条订单',
},
})
t('order.count', { count: 10 })
```
新增语言步骤:
1. 扩展 `DzLocale`
2. 在 `dzMessages` 中新增语言对象
3. 页面语言切换按钮加入新语言
4. 用 `npm run typecheck` 检查
## 16. 动态主题换色
主题配置:
```text
src/theme/dz-theme-config.ts
src/theme/dz-theme-style.ts
src/styles/dz-theme.scss
src/store/modules/dz-theme.ts
src/hooks/dz-use-theme.ts
```
主题字段:
```ts
export interface DzThemePreset {
key: string
label: string
primary: `#${string}`
primaryText: `#${string}`
background: `#${string}`
surface: `#${string}`
text: `#${string}`
muted: `#${string}`
}
```
页面使用:
```ts
const {
themeStyle,
presets,
currentTheme,
setTheme,
mergeThemePresets,
updateCurrentTheme,
resetThemePresets,
} = useDzTheme()
```
```vue
页面内容
```
切换预设主题:
```ts
setTheme('green')
```
接口返回主题后动态合并:
```ts
mergeThemePresets([
{
key: 'brand',
label: '品牌色',
primary: '#0891b2',
primaryText: '#ffffff',
background: '#ecfeff',
surface: '#ffffff',
text: '#083344',
muted: '#0e7490',
},
], 'brand')
```
只更新当前主题部分字段:
```ts
updateCurrentTheme({
primary: '#7c3aed',
background: '#faf5ff',
})
```
重置主题:
```ts
resetThemePresets()
```
主题更新会做三件事:
- 更新 Pinia 状态
- 重新计算 CSS 变量
- 调用 `uni.setNavigationBarColor` 更新原生导航栏颜色
可用 CSS 变量:
```css
--dz-color-primary
--dz-color-primary-text
--dz-color-background
--dz-color-surface
--dz-color-text
--dz-color-muted
```
内置主题辅助类:
- `dz-theme-root`
- `dz-surface`
- `dz-bg-primary`
- `dz-text-primary`
- `dz-text-muted`
- `dz-border-primary`
## 17. 请求封装
请求封装位置:
```text
src/services/http/dz-request.ts
src/services/http/dz-types.ts
src/services/http/index.ts
src/hooks/dz-use-request.ts
```
特点:
- 基于原生 `uni.request`
- 不依赖 axios
- 支持 baseURL、timeout、header
- 支持 request interceptor 和 response interceptor
- 支持 `needAuth: false` 跳过 token
- 默认从 `useUserStore()` 读取 token 并注入 `Authorization`
- 支持 `showLoading`
基础使用:
```ts
import { request } from '@/services/http'
const res = await request.get<{ id: string; nickname: string }>('/profile')
console.log(res.data)
```
组合式使用:
```ts
const request = useRequest()
const res = await request.post('/login', {
username: 'douze',
password: '123456',
}, {
needAuth: false,
showLoading: '登录中',
})
```
完整配置:
```ts
await request.request({
url: '/orders',
method: 'GET',
params: {
page: 1,
pageSize: 20,
},
header: {
'x-request-id': 'demo',
},
timeout: 15000,
showLoading: true,
})
```
新增请求拦截器:
```ts
import { http } from '@/services/http'
http.useRequest((config) => {
return {
...config,
header: {
...config.header,
'x-platform': process.env.UNI_PLATFORM || 'h5',
},
}
})
```
新增响应拦截器:
```ts
http.useResponse((response) => {
return response
})
```
业务 API 文件建议放在 `src/apis`:
```ts
import { request } from '@/services/http'
export const dzOrderApi = {
getDetail(id: string) {
return request.get(`/orders/${id}`)
},
}
```
并在 `src/apis/dz-index.ts` 中导出。
## 18. 图片上传封装
上传封装位置:
```text
src/services/upload/dz-upload.ts
src/services/upload/dz-types.ts
src/services/upload/index.ts
src/hooks/dz-use-upload.ts
```
特点:
- 基于原生 `uni.uploadFile`
- 支持 `chooseImage`
- 支持选择后立即上传
- 支持批量上传
- 支持进度回调
- 支持请求拦截器和响应拦截器
- 默认读取 Pinia token 并注入 `Authorization`
选择并上传:
```ts
const uploader = useUpload()
const result = await uploader.chooseAndUploadImage({
url: '/upload/image',
name: 'file',
showLoading: '上传中',
formData: {
scene: 'avatar',
},
onProgress: (progress) => {
console.log(progress.progress)
},
})
console.log(result.data)
```
上传已知临时路径:
```ts
await uploader.uploadImage({
url: '/upload/image',
filePath: tempFilePath,
name: 'file',
formData: {
scene: 'order',
},
})
```
只选择图片:
```ts
const chooseResult = await uploader.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
})
```
批量上传:
```ts
await uploader.uploadImages([
{
url: '/upload/image',
filePath: path1,
},
{
url: '/upload/image',
filePath: path2,
},
])
```
跳过 token:
```ts
await uploader.uploadImage({
url: '/upload/public',
filePath,
needAuth: false,
})
```
## 19. 路由跳转和拦截
路由封装位置:
```text
src/utils/dz-route.ts
src/hooks/dz-use-router.ts
```
支持方法:
| 方法 | 对应 uni API |
| --- | --- |
| `navigateTo` / `to` | `uni.navigateTo` |
| `redirectTo` / `redirect` | `uni.redirectTo` |
| `switchTab` | `uni.switchTab` |
| `reLaunch` | `uni.reLaunch` |
| `back` | `uni.navigateBack` |
| `custom` | 自定义处理,仍可经过拦截器 |
基础使用:
```ts
const router = useRouter()
await router.navigateTo('/pages/profile/index')
await router.redirectTo('/pages/profile/index')
await router.reLaunch('/pages/index/index')
await router.back()
```
带参数:
```ts
await router.navigateTo('/pages/dz-query-demo/index', {
query: {
id: '1001',
source: 'home',
title: '中文参数',
note: '空格 & 符号 = 测试',
payload: {
name: '张三',
roles: ['管理员'],
},
},
})
```
页面接收参数时,建议统一使用路由工具解码,避免 H5、小程序、App 对中文、空格、`&`、`=` 等字符处理不一致:
```ts
import { onLoad } from '@dcloudio/uni-app'
import { dzDecodeRouteQuery, dzParseRouteQueryValue } from '@/utils/dz-route'
onLoad((options) => {
const query = dzDecodeRouteQuery(options)
const payload = dzParseRouteQueryValue<{ name: string; roles: string[] }>(query.payload, {
name: '',
roles: [],
})
console.log(query.title, query.note, payload)
})
```
路径可以写成:
```ts
router.navigateTo('/pages/profile/index')
router.navigateTo('pages/profile/index')
router.navigateTo('/pages-sub/dz-demo/index/index')
```
注册前置拦截:
```ts
const router = useRouter()
router.beforeEach((to) => {
if (to.meta?.needLogin) {
return {
...to,
url: '/pages/profile/index',
}
}
})
router.navigateTo('/pages/dz-order/index', {
meta: {
needLogin: true,
},
})
```
阻止跳转:
```ts
router.beforeEach((to) => {
if (to.meta?.disabled) {
return false
}
})
```
后置拦截:
```ts
router.afterEach((result) => {
console.log(result.blocked, result.config.url)
})
```
自定义跳转:
```ts
await router.custom({
type: 'navigateTo',
url: '/pages/profile/index',
meta: {
from: 'custom-entry',
},
customHandler: async (to) => {
console.log(to.url)
},
})
```
重复跳转保护:
```ts
const result = await router.navigateTo('/pages/profile/index')
if (result.blocked && result.reason === 'duplicated') {
console.log('重复跳转已被拦截')
}
```
H5 跳转注意事项:
- 当前封装统一走 `uni.navigateTo` 等官方 API
- H5 必须确保 uni-h5 router 已启用
- `build/dz-pages-json-plugin.ts` 会在 Vite config 阶段写入完整页面配置,避免 H5 router 被误判为单页后树摇
- 如果出现 `Cannot read properties of undefined (reading 'push')`,先确认没有并发构建,并重启 dev server
## 20. 全局 hooks
全局 hooks 位置:
```text
src/hooks/dz-use-app-lifecycle.ts
src/hooks/dz-setup-global-hooks.ts
src/App.vue
```
当前支持:
- `onAppLaunch`
- `onAppShow`
- `onAppHide`
App.vue 中把 uni-app 生命周期派发到 hooks:
```ts
onLaunch(options) {
emitAppLaunch(options)
}
onShow(options) {
emitAppShow(options)
}
onHide() {
emitAppHide()
}
```
使用方式:
```ts
import { onAppShow } from '@/hooks'
const stop = onAppShow((options) => {
console.log(options)
})
stop()
```
也可以通过全局属性使用:
```ts
const { proxy } = getCurrentInstance()!
proxy?.$hooks.onAppHide(() => {
console.log('hide')
})
```
内置行为:
- app launch/show 时更新 `useAppStore`
- app launch/show 时应用当前主题
- app hide 时设置 `visible=false`
## 21. 平台识别
平台 hook:
```text
src/hooks/dz-use-platform.ts
```
使用:
```ts
const platformInfo = usePlatform()
console.log(platformInfo.platform)
console.log(platformInfo.isH5)
console.log(platformInfo.isMiniProgram)
console.log(platformInfo.isApp)
console.log(platformInfo.isHarmony)
console.log(platformInfo.isWechatOfficialAccount)
```
返回字段:
| 字段 | 说明 |
| --- | --- |
| `platform` | `process.env.UNI_PLATFORM` |
| `runtime` | `VITE_RUNTIME` |
| `isH5` | 是否 H5 |
| `isMiniProgram` | 是否小程序 |
| `isApp` | 是否 App |
| `isHarmony` | 是否鸿蒙 App |
| `isWechatBrowser` | H5 下是否微信浏览器 |
| `isWechatOfficialAccount` | 是否公众号 H5 运行时 |
公众号 H5 判断条件:
```ts
isH5 && appEnv.runtime === 'wechat-official' && isWechatBrowser
```
## 22. API 和工具目录
API 文件放:
```text
src/apis
```
当前示例:
```text
src/apis/dz-demo.ts
src/apis/dz-index.ts
```
新增 API 示例:
```ts
import { request } from '@/services/http'
export interface DzOrder {
id: string
title: string
}
export const dzOrderApi = {
list() {
return request.get('/orders')
},
detail(id: string) {
return request.get(`/orders/${id}`)
},
}
```
工具函数放:
```text
src/utils
```
当前工具:
- `dz-format.ts`
- `dz-route.ts`
- `dz-index.ts`
日期格式化:
```ts
import { dzFormatDate } from '@/utils/dz-index'
dzFormatDate(new Date())
dzFormatDate('2026-04-28', '/')
```
## 23. 内置 Demo 页面
| 页面 | 路径 | 说明 |
| --- | --- | --- |
| 首页 | `/pages/index/index` | 主题、多语言、平台、请求入口、Demo 入口 |
| 用户配置 | `/pages/profile/index` | Pinia token 保存、页面内组件识别 |
| AI 对话 | `/pages/dz-ai-chat-demo/index` | 验证 token、请求地址、流式对话、图片/视频/语音附件、防代码模式 |
| 广告接入 | `/pages/dz-ad-demo/index` | 验证单页广告位、全局注入广告位、provider 流程 |
| 路由 Demo | `/pages/dz-route-demo/index` | 主包跳转、参数、重定向、分包跳转 |
| 参数 Demo | `/pages/dz-query-demo/index` | 验证 query 传参 |
| 拦截 Demo | `/pages/dz-guard-demo/index` | 验证 redirect 和拦截场景 |
| 分包首页 | `/pages-sub/dz-demo/index/index` | 验证分包配置 |
| 分包列表 | `/pages-sub/dz-demo/list/index` | 验证分包内列表和详情跳转 |
| 分包详情 | `/pages-sub/dz-demo/detail/index` | 验证分包详情和 query |
首页已有入口按钮:
- 打开用户配置
- 打开分包页面
- 打开路由 Demo
- 打开 AI 对话
- 打开广告接入 Demo
- 测试 Pinia Action
## 24. 新增业务模块流程
新增一个完整业务模块时,建议按以下顺序:
1. 新建页面目录
```text
src/pages/dz-order/index.vue
```
2. 新建页面私有组件
```text
src/pages/dz-order/components/dz-order-card.vue
```
3. 注册页面路由
```ts
// src/router/dz-pagesjs.ts
{
path: 'pages/dz-order/index',
style: {
navigationBarTitleText: '订单',
},
}
```
4. 新增 API
```text
src/apis/dz-order.ts
```
5. 新增 store,如果页面有共享状态
```text
src/store/modules/dz-order.ts
```
6. 新增语言包
```ts
mergeLocaleMessages('zh-CN', {
order: {
title: '订单',
},
})
```
或直接维护 `src/i18n/dz-i18n-config.ts`。
7. 页面使用主题容器
```vue
...
```
8. 路由跳转使用封装
```ts
const router = useRouter()
router.navigateTo('/pages/dz-order/index')
```
9. 校验
```bash
npm run typecheck
npm run build:h5
```
10. 如果涉及小程序或分包,再串行构建对应端
```bash
npm run build:mp-weixin
```
## 25. 构建发布
构建前检查:
```bash
npm run typecheck
```
H5:
```bash
npm run build:h5
```
微信公众号 H5:
```bash
npm run build:official-account
```
微信小程序:
```bash
npm run build:mp-weixin
```
支付宝小程序:
```bash
npm run build:mp-alipay
```
抖音小程序:
```bash
npm run build:mp-toutiao
```
Android App:
```bash
npm run build:app-android
```
iOS App:
```bash
npm run build:app-ios
```
鸿蒙 App:
```bash
npm run build:app-harmony
```
构建注意:
- 多端构建必须串行
- 构建后 `src/pages.json` 会停留在最后一次构建的平台结果
- 如果要继续 H5 开发,重新执行 `npm run dev:h5`
- 如果要提交代码,重点提交 `src/router/dz-pagesjs.ts`、`pages.config.ts`、业务页面,不要把手写改动放在生成文件里
## 26. 常见问题
### H5 点击跳转只刷新但页面不变
原因通常是 H5 router 没有启用。检查:
1. `build/dz-pages-json-plugin.ts` 是否仍在 `vite.config.ts` 的 `uni()` 之前执行
2. Vite config 阶段写入的 `src/pages.json` 是否包含多页
3. 是否并发执行了多个平台构建
4. 是否重启了 dev server
正确顺序:
```ts
plugins: [
dzPagesJsonPlugin(),
UniPages(...),
uni(),
]
```
### 报错 Cannot read properties of undefined (reading 'push')
这是 uni-h5 `$router` 没有挂载时的典型错误。处理:
```bash
npm run build:h5
npm run dev:h5
```
如果仍存在,删除旧 dev server 进程后重启,并确认 `src/pages.json` 不只有首页。
### 分包页面在 H5 不生成 subPackages
这是预期行为。H5 不支持真实 `subPackages`,模板会把分包页面展开成普通页面。小程序和 App 端会生成真实 `subPackages`。
### components 目录里的 vue 被注册成页面
正常情况下不会。`vite.config.ts` 里已经配置:
```ts
exclude: ['**/components/**']
```
如果仍被注册,检查目录名是否确实为 `components`。
### token 没有注入请求
检查:
1. 是否调用了 `useUserStore().setToken(token)`
2. 请求是否设置了 `needAuth: false`
3. 请求是否使用 `request` 或 `useRequest()`,不要绕过封装直接调用 `uni.request`
### 动态主题更新后页面没变色
检查页面根节点是否绑定:
```vue
```
并确认使用的是主题类:
- `dz-surface`
- `dz-bg-primary`
- `dz-text-primary`
- `dz-text-muted`
- `dz-border-primary`
### 小程序样式和 H5 不一致
检查:
1. 是否使用 Tailwind 支持的 class
2. 是否经过 `weapp-tailwindcss`
3. 是否写了 H5 专属 CSS
4. 是否在小程序端使用了不支持的选择器
## 27. 维护检查清单
新增页面时:
- [ ] 文件放在 `src/pages` 或 `src/pages-sub`
- [ ] 页面文件名按 uni-app 约定使用 `index.vue`
- [ ] 业务组件使用 `dz-` 前缀
- [ ] 页面注册到 `src/router/dz-pagesjs.ts`
- [ ] 页面标题写在 `style.navigationBarTitleText`
- [ ] 页面内组件放在 `components` 目录
- [ ] 跳转使用 `useRouter()`
- [ ] 页面根容器绑定 `themeStyle`
- [ ] 文案进入 i18n 配置
- [ ] 接口进入 `src/apis`
- [ ] 共享状态进入 Pinia store
- [ ] 跑 `npm run typecheck`
- [ ] 至少跑一次 H5 构建
- [ ] 涉及小程序或分包时串行跑对应平台构建
提交前:
- [ ] 没有并发构建残留
- [ ] `src/pages.json` 是可由配置再生成的结果
- [ ] README 如有架构变化同步更新
- [ ] 没有把 token、appid、密钥写死在代码里
- [ ] 小程序 appid 按项目需要填入 `src/manifest.json`
## English Guide
### Overview
GeneralTemplate is a multi-platform uni-app scaffold by Douze. It uses Vue3, Vite, Pinia, TailwindCSS, weapp-tailwindcss, uni-ui, native `uni.request`, native `uni.uploadFile`, generated pages, subpackages, interceptable navigation, dynamic themes, i18n, and global app hooks.
### Supported Targets
- H5
- WeChat Official Account H5
- WeChat Mini Program
- Alipay Mini Program
- Douyin Mini Program
- Android App
- iOS App
- HarmonyOS App
### Start
```bash
npm install
npm run dev:h5
```
Open:
```text
http://127.0.0.1:5173/
```
### Build
```bash
npm run typecheck
npm run build:h5
npm run build:mp-weixin
npm run build:mp-alipay
npm run build:mp-toutiao
npm run build:app-android
npm run build:app-ios
npm run build:app-harmony
```
Run platform builds sequentially, not in parallel. The page generation plugins write the same `src/pages.json`.
### Key Files
| File | Purpose |
| --- | --- |
| `src/router/dz-pagesjs.ts` | Main pages, subpackages, preload rules |
| `build/dz-pages-json-plugin.ts` | Writes complete platform pages before uni-h5 feature detection |
| `pages.config.ts` | Final uni-pages config |
| `src/utils/dz-route.ts` | Navigation wrapper and interceptors |
| `src/services/http` | Native request wrapper |
| `src/services/upload` | Image upload wrapper |
| `src/components/dz-ai-chat` | AI chat component with media attachments |
| `src/store/modules/dz-theme.ts` | Dynamic theme state |
| `src/store/modules/dz-i18n.ts` | Locale state and runtime message merge |
### Add A Page
Create:
```text
src/pages/dz-order/index.vue
```
Register:
```ts
// src/router/dz-pagesjs.ts
{
path: 'pages/dz-order/index',
style: {
navigationBarTitleText: 'Order',
},
}
```
Navigate:
```ts
const router = useRouter()
router.navigateTo('/pages/dz-order/index')
```
### Add A Subpackage
```ts
export const dzSubPackageModules = [
{
root: 'pages-sub/dz-order',
preload: true,
pages: [
{
path: 'detail/index',
style: {
navigationBarTitleText: 'Order Detail',
},
},
],
},
]
```
H5 flattens subpackage pages as normal pages. Mini Program, App, and HarmonyOS builds generate real `subPackages`.
### Request
```ts
import { request } from '@/services/http'
const res = await request.get('/profile')
```
Skip token:
```ts
await request.post('/login', data, {
needAuth: false,
})
```
### Upload
```ts
const uploader = useUpload()
await uploader.chooseAndUploadImage({
url: '/upload/image',
name: 'file',
showLoading: true,
})
```
### AI Chat
```ts
// src/config/dz-ai-chat-config.ts
export const dzAiChatConfig = {
enabled: true,
token: '',
requestUrl: '',
uploadUrl: '',
stream: true,
codeGuard: false,
height: '100vh',
}
```
```vue
```
The customer-facing page renders the full-page chat UI. Token, request URL, upload URL, stream mode, and code guard are configured in `src/config/dz-ai-chat-config.ts`. If `token` or `requestUrl` is missing, the component returns the fixed template message instead of calling the AI API. See [AI Chat Component](./docs/ai-chat.md).
### Theme
```ts
const { themeStyle, setTheme, mergeThemePresets, updateCurrentTheme } = useDzTheme()
setTheme('green')
updateCurrentTheme({
primary: '#7c3aed',
})
```
Bind theme style:
```vue
Page content
```
### I18n
```ts
const { t, setLocale, mergeLocaleMessages } = useDzI18n()
setLocale('en-US')
t('home.themeTitle')
mergeLocaleMessages('en-US', {
order: {
title: 'Order',
},
})
```
### Navigation
```ts
const router = useRouter()
router.beforeEach((to) => {
if (to.meta?.needLogin) {
return {
...to,
url: '/pages/profile/index',
}
}
})
await router.navigateTo('/pages/profile/index')
await router.reLaunch('/pages/index/index')
await router.back()
```
### Troubleshooting
If H5 navigation reports `Cannot read properties of undefined (reading 'push')`, check:
1. `dzPagesJsonPlugin()` runs before `uni()` in `vite.config.ts`
2. `src/pages.json` contains all pages, not only the home page
3. No platform builds are running in parallel
4. The H5 dev server has been restarted