# GeneralTemplate **Repository Path**: Douze_git/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**: 5 - **Forks**: 1 - **Created**: 2026-04-28 - **Last Updated**: 2026-07-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: 开箱即用, uni-app, 小程序, Vue, ai对话 ## 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