diff --git a/.husky/commit-msg b/.husky/commit-msg
index fd2bf70..23ce835 100644
--- a/.husky/commit-msg
+++ b/.husky/commit-msg
@@ -1 +1 @@
-npx --no-install commitlint --edit $1
+#npx --no-install commitlint --edit $1
diff --git a/.husky/pre-commit b/.husky/pre-commit
index a3ec6f2..ce4711a 100644
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,2 +1,2 @@
echo "Running pre-commit hook..."
-pnpm run lint:lint-staged
+#pnpm run lint:lint-staged
diff --git a/.husky/uniapp从0到1.md b/.husky/uniapp从0到1.md
new file mode 100644
index 0000000..a9f14dc
--- /dev/null
+++ b/.husky/uniapp从0到1.md
@@ -0,0 +1,2064 @@
+
+
+## 环境准备
+
+[vue-uniapp-template](https://gitee.com/youlaiorg/vue-uniapp-template) 是一个通过 `vue-cli ` 构建的跨移动端脚手架模板,结合了 `uniapp`、`vue3 `和 `typescript`。在开始之前,需要准备以下环境。如果环境准备OK,请忽略本节。
+
+### 安装 Node
+
+> `Node.js` 是运行 JavaScript 代码的环境,也是 `npm` 包管理器的依赖。
+
+打开 [Node.js 官方下载页面](https://nodejs.org/zh-cn/download/prebuilt-installer),根据你的操作系统选择合适的版本进行下载,**推荐安装 LTS 版本**,这是长期支持版本,适合开发环境,比如这里选择 `v20.18.0(LTS) ` 版本。
+
+
+
+下载之后,双击安装包根据提示安装,通过以下命令检查是否成功安装:
+
+```bash
+node -v
+```
+
+
+
+
+
+### 安装 VSCode
+
+>`VSCode` 是一款非常流行的代码编辑器,特别适合前端开发。
+
+访问 [Visual Studio Code 官方网站](https://code.visualstudio.com/) ,根据你的操作系统下载相应版本的 `VSCode` ,下载完成后,双击安装程序并按照提示完成安装。
+
+
+
+ ### 安装 vue-cli
+
+> `Vue CLI` 是 Vue.js 的命令行工具,能够快速创建、开发、构建 Vue.js 项目。
+
+打开终端或命令提示符, 使用 `npm` 全局安装 `Vue CLI`:
+
+```
+npm install -g @vue/cli
+```
+
+安装完成后,检查 `Vue CLI` 是否安装成功:
+
+```bash
+vue --version
+```
+
+
+
+## 创建项目
+
+### 初始化项目
+
+按照 [uni-app 官方文档](https://uniapp.dcloud.net.cn/quickstart-cli.html#创建uni-app) 的步骤,通过 `vue-cli` 创建 `uni-app` + `vue` + `typescript` 脚手架:
+
+```bash
+npx degit dcloudio/uni-preset-vue#vite-ts vue-uniapp-template
+```
+
+
+
+如果使用命令创建失败,可以通过 Gitee 下载 ZIP 包:[vite-ts 分支](https://gitee.com/dcloud/uni-preset-vue/tree/vite-ts)。
+
+### 配置编译器
+
+默认生成的 `TypeScript ` 编译器配置文件 `tsconfig.json` 中继承的 `@vue/tsconfig/tsconfig.json` 文件不存在。因此,你需要移除此继承配置并添加相应的编译设置。
+
+
+
+根据 [TypeScript 官方配置文档](https://www.typescriptlang.org/tsconfig/),调整后的完整配置如下:
+
+```json
+{
+ "compilerOptions": {
+ "module": "esnext",
+ "moduleResolution": "node",
+ "target": "esnext",
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+
+ "sourceMap": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ },
+ "lib": ["esnext", "dom"],
+ "types": ["@dcloudio/types"]
+ },
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
+ "exclude": ["node_modules", "dist"]
+}
+```
+
+- `"module": "esnext"`: 指定模块系统为 `ESNext`,即最新的 ECMAScript 模块系统,支持 `import.meta` 和其他最新的特性。
+- `"moduleResolution": "node"`: 模块解析策略,通常设置为 `Node` 以支持 Node.js 风格的模块解析。
+- `"target": "esnext"`: 将目标 JavaScript 版本设置为 `ESNext`,编译输出现代浏览器能够支持的最新特性代码。
+- `"allowJs": true`: 允许 TypeScript 编译器处理 `.js` 文件,混合使用 TypeScript 和 JavaScript 文件。
+- `"skipLibCheck": true`: 跳过库文件的类型检查,提升编译速度。
+- `"strict": true` : 启用所有严格类型检查选项。
+
+### 启动项目
+
+创建完成后,使用 `VSCode` 打开项目并启动:
+
+```bash
+# 安装依赖
+pnpm install
+# 启动项目
+pnpm run dev:h5
+```
+
+
+
+项目启动后,访问 http://localhost:5173 预览效果:
+
+
+
+
+
+## 代码规范配置
+
+为了保证项目代码的规范性和一致性,可以为项目配置 `ESLint`、`Stylelint`、`Prettier` 以及 `Husky`,从而确保代码质量和开发流程的一致性。
+
+ ### 集成 ESLint
+
+`ESLint` 是一款 JavaScript 和 TypeScript 的代码规范工具,能够帮助开发团队保持代码风格一致并减少常见错误。
+
+**ESLint 中文网**:[https://eslint.nodejs.cn/](https://eslint.nodejs.cn/)
+
+#### 安装插件
+
+VSCode 插件市场搜索 ESLint 插件并安装
+
+
+
+#### 配置 ESLint
+
+通过以下命令快速生成 ESLint 配置文件:
+
+```bash
+npx eslint --init
+```
+
+
+
+执行该命令后,ESLint 会通过交互式问题的方式,帮助生成配置文件。针对 9.x 版本,默认会生成基于 Flat Config 格式的 `eslint.config.mjs` 文件,与之前的 `.eslintrc` 格式有所不同。
+
+默认生成的 `eslint.config.mjs` 文件如下所示:
+
+
+
+在此基础上,可以根据项目的需求进行一些定制化配置,例如添加忽略规则或自定义的特殊规则。
+
+```js
+import globals from "globals"; // 全局变量配置
+import pluginJs from "@eslint/js"; // JavaScript 的推荐配置
+import tseslint from "typescript-eslint"; // TypeScript 配置
+import pluginVue from "eslint-plugin-vue"; // Vue 配置
+
+export default [
+ {files: ["**/*.{js,mjs,cjs,ts,vue}"]}, // 校验的文件类型
+ {languageOptions: { globals: {...globals.browser , ...globals.node} }}, // 浏览器/Node环境全局变量
+ pluginJs.configs.recommended, // JavaScript 推荐配置
+ ...tseslint.configs.recommended, // TypeScript 推荐配置
+ ...pluginVue.configs["flat/essential"], // Vue 推荐配置
+ { files: ["**/*.vue"], languageOptions: { parserOptions: { parser: tseslint.parser } } }, // 对 .vue 文件使用 TypeScript 解析器
+
+ // 添加忽略的文件或目录
+ {
+ ignores: [
+ "/dist",
+ "/public",
+ "/node_modules",
+ "**/*.min.js",
+ "**/*.config.mjs",
+ "**/*.tsbuildinfo",
+ "/src/manifest.json",
+ ]
+ },
+
+ // 自定义规则
+ {
+ rules: {
+ quotes: ["error", "double"], // 强制使用双引号
+ "quote-props": ["error", "always"], // 强制对象的属性名使用引号
+ semi: ["error", "always"], // 要求使用分号
+ indent: ["error", 2], // 使用两个空格进行缩进
+ "no-multiple-empty-lines": ["error", { max: 1 }], // 不允许多个空行
+ "no-trailing-spaces": "error", // 不允许行尾有空格
+
+ // TypeScript 规则
+ "@typescript-eslint/no-explicit-any": "off", // 禁用 no-explicit-any 规则,允许使用 any 类型
+ "@typescript-eslint/explicit-function-return-type": "off", // 不强制要求函数必须明确返回类型
+ "@typescript-eslint/no-empty-interface": "off", // 禁用 no-empty-interface 规则,允许空接口声明
+ "@typescript-eslint/no-empty-object-type": "off", // 允许空对象类型
+
+ // Vue 规则
+ "vue/multi-word-component-names": "off", // 关闭多单词组件名称的限制
+ "vue/html-indent": ["error", 2], // Vue 模板中的 HTML 缩进使用两个空格
+ "vue/no-v-html": "off", // 允许使用 v-html (根据实际项目需要)
+ },
+ },
+];
+```
+
+#### 添加 ESLint 脚本
+
+为了方便使用 ESLint,可以在 `package.json` 中添加 `lint` 脚本命令:
+
+```json
+{
+ "scripts": {
+ "lint:eslint": "eslint --fix ./src"
+ }
+}
+```
+
+此脚本会自动修复符合 ESLint 规则的代码问题,并输出检查结果。
+
+#### 测试效果
+
+在 `App.vue` 文件中声明一个未使用的变量,并运行 `pnpm run lint:eslint`,可以看到 ESLint 提示该变量未使用。如下图所示:
+
+
+
+#### 推荐配置
+
+安装 Vue 文件解析器 `vue-eslint-parser`:
+
+```bash
+pnpm add -D vue-eslint-parser
+```
+
+针对不同文件配置插件和解析器:
+
+```javascript
+// eslint.config.mjs
+import globals from "globals";
+import js from "@eslint/js";
+
+// ESLint 核心插件
+import pluginVue from "eslint-plugin-vue";
+import pluginTypeScript from "@typescript-eslint/eslint-plugin";
+
+// Prettier 插件及配置
+import configPrettier from "eslint-config-prettier";
+import pluginPrettier from "eslint-plugin-prettier";
+
+// 解析器
+import * as parserVue from "vue-eslint-parser";
+import * as parserTypeScript from "@typescript-eslint/parser";
+
+// 定义 ESLint 配置
+export default [
+ // 通用 JavaScript/TypeScript 配置
+ {
+ ...js.configs.recommended,
+ ignores: [
+ "/dist",
+ "/public",
+ "/node_modules",
+ "**/*.min.js",
+ "**/*.config.mjs",
+ "**/*.tsbuildinfo",
+ "/src/manifest.json",
+ ],
+ languageOptions: {
+ globals: {
+ ...globals.browser, // 浏览器变量 (window, document 等)
+ ...globals.node, // Node.js 变量 (process, require 等)
+ },
+ },
+ plugins: {
+ prettier: pluginPrettier,
+ },
+ rules: {
+ ...configPrettier.rules,
+ ...pluginPrettier.configs.recommended.rules,
+ "no-debug": "off", // 允许使用 debugger
+ "prettier/prettier": [
+ "error",
+ {
+ endOfLine: "auto", // 解决换行符冲突
+ },
+ ],
+ },
+ },
+
+ // TypeScript 配置
+ {
+ files: ["**/*.?([cm])ts"],
+ languageOptions: {
+ parser: parserTypeScript,
+ parserOptions: {
+ sourceType: "module",
+ },
+ },
+ plugins: {
+ "@typescript-eslint": pluginTypeScript,
+ },
+ rules: {
+ ...pluginTypeScript.configs.recommended.rules,
+ "@typescript-eslint/no-explicit-any": "off", // 允许使用 any
+ "@typescript-eslint/no-empty-function": "off", // 允许空函数
+ "@typescript-eslint/no-empty-object-type": "off", // 允许空对象类型
+ "@typescript-eslint/consistent-type-imports": [
+ "error",
+ { disallowTypeAnnotations: false, fixStyle: "inline-type-imports" },
+ ], // 统一类型导入风格
+ },
+ },
+
+ // TypeScript 声明文件的特殊配置
+ {
+ files: ["**/*.d.ts"],
+ rules: {
+ "eslint-comments/no-unlimited-disable": "off", // 关闭 eslint 注释相关规则
+ "unused-imports/no-unused-vars": "off", // 忽略未使用的导入
+ },
+ },
+
+ // JavaScript (commonjs) 配置
+ {
+ files: ["**/*.?([cm])js"],
+ rules: {
+ "@typescript-eslint/no-var-requires": "off", // 允许 require
+ },
+ },
+
+ // Vue 文件配置
+ {
+ files: ["**/*.vue"],
+ languageOptions: {
+ parser: parserVue,
+ parserOptions: {
+ parser: "@typescript-eslint/parser",
+ sourceType: "module",
+ },
+ },
+ plugins: {
+ vue: pluginVue,
+ },
+ processor: pluginVue.processors[".vue"],
+ rules: {
+ ...pluginVue.configs["vue3-recommended"].rules,
+ "vue/no-v-html": "off", // 允许 v-html
+ "vue/require-default-prop": "off", // 允许没有默认值的 prop
+ "vue/multi-word-component-names": "off", // 关闭组件名称多词要求
+ "vue/html-self-closing": [
+ "error",
+ {
+ html: { void: "always", normal: "always", component: "always" },
+ svg: "always",
+ math: "always",
+ },
+ ], // 自闭合标签
+ },
+ },
+];
+```
+
+
+
+### 集成 Prettier
+
+Prettier 是一个代码格式化工具,能够和 ESLint 配合使用,确保代码风格统一。
+
+**prettier 中文网**:[https://prettier.nodejs.cn/](https://prettier.nodejs.cn/)
+
+#### 安装插件
+
+VSCode 插件市场搜索 `Prettier - Code formatter` 插件安装
+
+
+
+#### 安装依赖
+
+```bash
+pnpm install -D prettier eslint-config-prettier eslint-plugin-prettier
+```
+
+- **prettier**:主要的 Prettier 格式化库。
+
+- **eslint-plugin-prettier**:将 Prettier 的规则作为 ESLint 的规则来运行。
+
+- **eslint-config-prettier**:禁用所有与格式相关的 ESLint 规则,以避免和 Prettier 的冲突。
+
+#### 配置 Prettier
+
+项目根目录下新建配置文件 `prettier.config.mjs`,添加常用规则:
+
+```js
+export default {
+ printWidth: 100, // 每行最多字符数量,超出换行(默认80)
+ tabWidth: 2, // 缩进空格数,默认2个空格
+ useTabs: false, // 指定缩进方式,空格或tab,默认false,即使用空格
+ semi: true, // 使用分号
+ singleQuote: false, // 使用单引号 (true:单引号;false:双引号)
+ trailingComma: 'all', // 末尾使用逗号
+};
+```
+
+#### 配置忽略文件
+
+项目根目录新建 `.prettierignore` 文件指定 Prettier 不需要格式化的文件和文件夹
+
+```bash
+# .prettierignore
+node_modules
+dist
+public
+*.min.js
+```
+
+#### 添加格式化脚本
+
+在 `package.json` 文件中添加:
+
+```json
+{
+ "scripts": {
+ "format": "prettier --write ./src"
+ }
+}
+```
+
+#### 保存自动格式化
+
+打开 VSCode 的 `File` → `Preferences` → `Settings`,然后选择 `Open Settings (JSON)`,添加以下配置
+
+```json
+{
+ "editor.formatOnSave": true, // 保存格式化文件
+ "editor.defaultFormatter": "esbenp.prettier-vscode" // 指定 prettier 为所有文件默认格式化器
+}
+```
+
+#### 测试
+
+下图演示了保存时的自动格式化效果,展示了代码中引号和换行的自动调整:
+
+
+
+
+
+### 集成 Stylelint
+
+Stylelint 一个强大的 CSS linter(检查器),可帮助您避免错误并强制执行约定。
+
+**Stylelint 官网**:[https://stylelint.io/](https://stylelint.io/)
+
+#### 安装插件
+
+VSCode 插件搜索 `Stylelint` 并安装
+
+
+
+
+
+#### 安装依赖
+
+```bash
+pnpm install -D postcss postcss-html postcss-scss stylelint stylelint-config-recommended stylelint-config-recommended-scss stylelint-config-recommended-vue stylelint-config-recess-order stylelint-config-html stylelint-prettier
+```
+
+| 依赖 | 说明 | 备注 |
+| --------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| postcss | CSS 解析工具,允许使用现代 CSS 语法并将其转换为兼容的旧语法 | |
+| postcss-html | 解析 HTML (类似 HTML) 的 PostCSS 语法 | [postcss-html 文档](https://github.com/gucong3000/postcss-html) |
+| postcss-scss | PostCSS 的 SCSS 解析器 | [postcss-scss 文档](https://github.com/postcss/postcss-scss),支持 CSS 行类注释 |
+| stylelint | stylelint 核心库 | [stylelint ](https://stylelint.io/) |
+| stylelint-config-standard | Stylelint 标准共享配置 | [stylelint-config-standard 文档](https://github.com/stylelint/stylelint-config-standard) |
+| stylelint-config-recommended | | |
+| stylelint-config-recommended-scss | 扩展 stylelint-config-recommended 共享配置并为 SCSS 配置其规则 | [stylelint-config-recommended-scss 文档](https://github.com/stylelint-scss/stylelint-config-recommended-scss) |
+| stylelint-config-recommended-vue | 扩展 stylelint-config-recommended 共享配置并为 Vue 配置其规则 | [stylelint-config-recommended-vue 文档](https://github.com/ota-meshi/stylelint-config-recommended-vue) |
+| stylelint-config-recess-order | 提供优化样式顺序的配置 | [CSS 书写顺序规范](https://jingyan.baidu.com/article/647f0115cf48957f2148a8a3.html) |
+| stylelint-config-html | 共享 HTML (类似 HTML) 配置,捆绑 postcss-html 并对其进行配置 | [stylelint-config-html 文档](https://github.com/ota-meshi/stylelint-config-html) |
+| stylelint-prettier | | |
+
+
+
+#### 配置 Stylelint
+
+根目录新建 `.stylelintrc.cjs` 文件,配置如下:
+
+```javascript
+{
+ "extends": [
+ "stylelint-config-recommended",
+ "stylelint-config-recommended-scss",
+ "stylelint-config-recommended-vue/scss",
+ "stylelint-config-html/vue",
+ "stylelint-config-recess-order"
+ ],
+ "plugins": ["stylelint-prettier"],
+ "overrides": [
+ {
+ "files": ["**/*.{vue,html}"],
+ "customSyntax": "postcss-html"
+ },
+ {
+ "files": ["**/*.{css,scss}"],
+ "customSyntax": "postcss-scss"
+ }
+ ],
+
+ "rules": {
+ "import-notation": "string",
+ "selector-class-pattern": null,
+ "custom-property-pattern": null,
+ "keyframes-name-pattern": null,
+ "no-descending-specificity": null,
+ "no-empty-source": null,
+ "selector-pseudo-class-no-unknown": [
+ true,
+ {
+ "ignorePseudoClasses": ["global", "export", "deep"]
+ }
+ ],
+ "unit-no-unknown": [true, {
+ "ignoreUnits": ["rpx"]
+ }]
+ "property-no-unknown": [
+ true,
+ {
+ "ignoreProperties": []
+ }
+ ],
+ "at-rule-no-unknown": [
+ true,
+ {
+ "ignoreAtRules": ["apply", "use", "forward"]
+ }
+ ]
+ }
+}
+```
+
+#### 配置忽略文件
+
+根目录创建 .stylelintignore 文件,配置忽略文件如下:
+
+```basic
+*.min.js
+dist
+public
+node_modules
+```
+
+#### 添加 Stylelint 脚本
+
+package.json 添加 Stylelint 检测指令:
+
+```json
+ "scripts": {
+ "lint:stylelint": "stylelint \"**/*.{css,scss,vue,html}\" --fix"
+ }
+```
+
+#### 保存自动修复
+
+项目根目录下`.vscode/settings.json` 文件添加配置:
+
+```json
+{
+ "editor.codeActionsOnSave": {
+ "source.fixAll.stylelint": true
+ },
+ "stylelint.validate": ["css", "scss", "vue", "html"]
+}
+```
+
+为了验证把尺寸属性 width 放置在定位属性 position 前面,根据 [CSS 书写顺序规范](https://jingyan.baidu.com/article/647f0115cf48957f2148a8a3.html) 推断是不符合规范的,在保存时 Stylelint 自动将属性重新排序,达到预期。
+
+
+
+#### 测试
+
+执行以下命令进行检测
+
+```bash
+npm run lint:stylelint
+```
+
+
+
+## Git提交规范配置
+
+
+
+配置 Husky 的 `pre-commit` 和 `commit-msg` 钩子,实现代码提交的自动化检查和规范化。
+
+- **pre-commit**: 使用 Husky + Lint-staged,在提交前进行代码规范检测和格式化。确保项目已配置 ESLint、Prettier 和 Stylelint。
+- **commit-msg**: 结合 Husky、Commitlint、Commitizen 和 cz-git,生成规范化且自定义的 Git commit 信息。
+
+
+
+
+
+### 集成 Husky
+
+Husky 是 Git 钩子工具,可以设置在 git 各个阶段(`pre-commit`、`commit-msg` 等)触发。
+
+**Husky官网**:[https://typicode.github.io](https://typicode.github.io/husky/zh/get-started.html)
+
+
+
+#### 安装依赖
+
+```bash
+pnpm add -D husky
+```
+
+#### 初始化
+
+`init` 命令简化了项目中的 husky 设置。它会在 `.husky/` 中创建 `pre-commit` 脚本,并更新 `package.json` 中的 `prepare` 脚本。
+
+```bash
+pnpm exec husky init
+```
+
+#### 测试
+
+
+
+通过 `pre-commit` 钩子,可以自动运行各种代码检查工具,在提交代码前强制执行代码质量和样式检查。常见的工具包括:
+
+- **`eslint`**:用于检查和修复 JavaScript/TypeScript 代码中的问题。
+- **`stylelint`**:用于检测和修复 CSS/SCSS 样式问题。
+
+接下来,集成 **`lint-staged`** 和 **`commitlint`** 来进一步完善开发体验。
+
+### 集成 lint-staged
+
+`lint-staged` 是一个工具,专门用于只对 Git 暂存区的文件运行 lint 或其他任务,确保只检查和修复被修改或新增的代码部分,而不会影响整个代码库。这样可以显著提升效率,尤其是对于大型项目。
+
+#### 安装依赖
+
+使用以下命令安装 `lint-staged`:
+
+```json
+pnpm add -D lint-staged
+```
+
+#### 配置 lint-staged
+
+在 `package.json` 中添加 `lint-staged` 配置,确保在 `pre-commit` 阶段自动检测暂存的文件:
+
+```json
+{
+ "name": "vue-uniapp-template",
+ "version": "0.0.0",
+ "lint-staged": {
+ "*.{js,ts}": [
+ "eslint --fix",
+ "prettier --write"
+ ],
+ "*.{cjs,json}": [
+ "prettier --write"
+ ],
+ "*.{vue,html}": [
+ "eslint --fix",
+ "prettier --write",
+ "stylelint --fix"
+ ],
+ "*.{scss,css}": [
+ "stylelint --fix",
+ "prettier --write"
+ ],
+ "*.md": [
+ "prettier --write"
+ ]
+ }
+}
+```
+
+在 `package.json` 的 `scripts` 部分中,添加用于运行 `lint-staged` 的命令:
+
+```json
+"scripts": {
+ "lint:lint-staged": "lint-staged"
+}
+```
+
+#### 添加 Husky 钩子
+
+在项目根目录的 `.husky/pre-commit` 中添加以下命令,确保在提交代码前执行 `lint-staged`:
+
+```bash
+pnpm run lint:lint-staged
+```
+
+#### 测试
+
+提交代码时,`lint-staged` 会自动对暂存的文件运行相应的 lint 任务。
+
+
+
+通过这种集成方式,确保代码在提交前经过自动格式化和校验,提高代码质量和一致性。
+
+### 集成 Commitlint
+
+`commitlint` 用于检查 Git 提交信息是否符合特定规范(如 Angular 提交规范),从而保证提交信息的一致性。
+
+**Commitlint官网**:[https://commitlint.js.org/](https://commitlint.js.org/)
+
+#### 安装依赖
+
+```bash
+pnpm add -D @commitlint/cli @commitlint/config-conventional
+```
+
+#### 配置 Commitlint
+
+在项目根目录下创建 `commitlint.config.cjs` 文件,添加以下内容来启用 Angular 规范:
+
+```json
+module.exports = {
+ // 继承的规则
+ extends: ["@commitlint/config-conventional"],
+ // 自定义规则
+ rules: {
+ // 提交类型枚举,git提交type必须是以下类型 @see https://commitlint.js.org/#/reference-rules
+ "type-enum": [
+ 2,
+ "always",
+ [
+ "feat", // 新增功能
+ "fix", // 修复缺陷
+ "docs", // 文档变更
+ "style", // 代码格式(不影响功能,例如空格、分号等格式修正)
+ "refactor", // 代码重构(不包括 bug 修复、功能新增)
+ "perf", // 性能优化
+ "test", // 添加疏漏测试或已有测试改动
+ "build", // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)
+ "ci", // 修改 CI 配置、脚本
+ "revert", // 回滚 commit
+ "chore", // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)
+ ],
+ ],
+ "subject-case": [0], // subject大小写不做校验
+ },
+};
+```
+
+#### 添加 Husky 钩子
+
+将 `commitlint` 与 Husky 集成,在 `.husky/commit-msg` 文件中添加以下内容,确保提交信息符合规范:
+
+```bash
+npx --no-install commitlint --edit $1
+```
+
+#### 测试
+
+根据 Angular 的提交规范,提交信息由以下部分组成:
+
+1. **类型**:表示本次提交的类型,例如 `feat` (新功能)、`fix` (修复 bug)、`docs` (文档更新)。
+2. **作用域**(可选):说明本次提交影响的模块,例如 `auth`、`ui`。
+3. **简短描述**:简洁明了的提交描述,限定在 50 字符以内。
+
+当你尝试提交不符合规范的提交信息时,提交会被阻止,并显示相关错误提示。如下图所示:
+
+
+
+### 集成 Commitizen 和 cz-git
+
+- **commitizen**: 是一个帮助开发者以标准化格式生成提交信息的工具。--[Commitizen 官方文档](https://commitizen.github.io/cz-cli/)
+
+- **cz-git**: `cz-git` 是 `Commitizen` 的适配器之一,它基于 `Commitizen`,提供了更多自定义功能和增强的交互体验。--[cz-git 官方文档](https://cz-git.qbb.sh/zh/)
+
+#### 安装依赖
+
+```bash
+pnpm add -D commitizen cz-git
+```
+
+#### 配置 cz-git
+
+在项目中初始化 `Commitizen`,并配置使用 `cz-git` 作为适配器。在 `package.json` 中添加以下配置:
+
+```json
+"config": {
+ "commitizen": {
+ "path": "node_modules/cz-git"
+ }
+}
+```
+
+在`commitlint` 的配置文件 `commitlint.config.cjs` 中添加配置,commitlint 配置模板:[https://cz-git.qbb.sh/zh/config/](https://cz-git.qbb.sh/zh/config/)
+
+```javascript
+module.exports = {
+ // 继承的规则
+ extends: ["@commitlint/config-conventional"],
+ // 自定义规则
+ rules: {
+ // ...
+ },
+ // cz-git 配置
+ prompt: {
+ messages: {
+ type: "选择你要提交的类型 :",
+ scope: "选择一个提交范围(可选):",
+ customScope: "请输入自定义的提交范围 :",
+ subject: "填写简短精炼的变更描述 :\n",
+ body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
+ breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
+ footerPrefixesSelect: "选择关联issue前缀(可选):",
+ customFooterPrefix: "输入自定义issue前缀 :",
+ footer: "列举关联issue (可选) 例如: #31, #I3244 :\n",
+ generatingByAI: "正在通过 AI 生成你的提交简短描述...",
+ generatedSelectByAI: "选择一个 AI 生成的简短描述:",
+ confirmCommit: "是否提交或修改commit ?",
+ },
+ // prettier-ignore
+ types: [
+ { value: "feat", name: "特性: ✨ 新增功能", emoji: ":sparkles:" },
+ { value: "fix", name: "修复: 🐛 修复缺陷", emoji: ":bug:" },
+ { value: "docs", name: "文档: 📝 文档变更", emoji: ":memo:" },
+ { value: "style", name: "格式: 💄 代码格式(不影响功能,例如空格、分号等格式修正)", emoji: ":lipstick:" },
+ { value: "refactor", name: "重构: ♻️ 代码重构(不包括 bug 修复、功能新增)", emoji: ":recycle:" },
+ { value: "perf", name: "性能: ⚡️ 性能优化", emoji: ":zap:" },
+ { value: "test", name: "测试: ✅ 添加疏漏测试或已有测试改动", emoji: ":white_check_mark:"},
+ { value: "build", name: "构建: 📦️ 构建流程、外部依赖变更(如升级 npm 包、修改 vite 配置等)", emoji: ":package:"},
+ { value: "ci", name: "集成: 🎡 修改 CI 配置、脚本", emoji: ":ferris_wheel:"},
+ { value: "revert", name: "回退: ⏪️ 回滚 commit",emoji: ":rewind:"},
+ { value: "chore", name: "其他: 🔨 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)", emoji: ":hammer:"},
+ ],
+ useEmoji: true,
+ emojiAlign: "center",
+ useAI: false,
+ aiNumber: 1,
+ themeColorCode: "",
+ scopes: [],
+ allowCustomScopes: true,
+ allowEmptyScopes: true,
+ customScopesAlign: "bottom",
+ customScopesAlias: "custom",
+ emptyScopesAlias: "empty",
+ upperCaseSubject: false,
+ markBreakingChangeMode: false,
+ allowBreakingChanges: ["feat", "fix"],
+ breaklineNumber: 100,
+ breaklineChar: "|",
+ skipQuestions: [],
+ issuePrefixes: [{ value: "closed", name: "closed: ISSUES has been processed" }],
+ customIssuePrefixAlign: "top",
+ emptyIssuePrefixAlias: "skip",
+ customIssuePrefixAlias: "custom",
+ allowCustomIssuePrefix: true,
+ allowEmptyIssuePrefix: true,
+ confirmColorize: true,
+ maxHeaderLength: Infinity,
+ maxSubjectLength: Infinity,
+ minSubjectLength: 0,
+ scopeOverrides: undefined,
+ defaultBody: "",
+ defaultIssues: "",
+ defaultScope: "",
+ defaultSubject: "",
+ },
+};
+
+```
+
+#### 添加 cz-git 脚本
+
+在`package.json` 文件中添加 `commit` 脚本命令
+
+```json
+ "scripts": {
+ "commit": "git-cz"
+ }
+```
+
+#### 测试
+
+执行 `pnpm run commit` 命令后,按照提示输入相关信息,最终生成符合规范的提交信息。
+
+
+
+## 整合 Sass
+
+**Sass**是帮助开发者编写、管理和维护样式的强大工具,通过 `
+```
+
+在 `pages.json` 文件中,声明登录页面的路由:
+
+```json
+// src/pages.json
+{
+ "pages": [
+ {
+ "path": "pages/login/index",
+ "style": {
+ "navigationBarTitleText": "登录"
+ }
+ }
+ ]
+}
+```
+
+
+
+### 登录测试
+
+访问登录页面:http://localhost:5173/#/pages/login/index,输入用户名和密码 (`admin`/`123456`) 测试登录接口,登录成功后可以看到返回的访问令牌。
+
+
+
+### 整合源码
+
+整合`HTTP请求`代码版本:[vue-uniapp-template#737f6a](hhttps://gitee.com/youlaiorg/vue-uniapp-template/commit/737f6a3ee5217dcad006fa06b6fae63374d9826b)。
+
+
+
+## 整合 Pinia
+
+> Pinia 是 Vue 的状态管理库,专为跨组件或页面共享状态设计。
+
+- **Pinia 官方文档**: https://pinia.vuejs.org/zh/getting-started.html
+
+ 
+
+### 安装依赖
+
+首先,安装 `pinia` 依赖:
+
+```bash
+pnpm add pinia
+```
+
+### 全局注册
+
+在项目的 `src` 目录下创建 `store` 文件夹,并新建 `index.ts` 文件,初始化并注册 Pinia 实例。
+
+```typescript
+// src/store/index.ts
+import type { App } from "vue";
+import { createPinia } from "pinia";
+
+const store = createPinia();
+// 注册 Pinia
+export function setupStore(app: App) {
+ app.use(store); // 全局注册 Pinia
+}
+```
+
+接着,将 `store` 在项目入口文件 `main.ts` 中引入,并将其作为全局插件传递给应用:
+
+```typescript
+// src/main.ts
+import { createSSRApp } from "vue";
+import App from "./App.vue";
+
+import { setupStore } from "@/store";
+
+export function createApp() {
+ const app = createSSRApp(App);
+ // 全局注册 store
+ setupStore(app);
+
+ return {
+ app,
+ };
+}
+
+```
+
+接下来,我们通过 Pinia 管理登录状态和用户信息,并在多个页面共享状态。
+
+### 用户信息接口
+
+编写一个 API 来获取当前登录用户的信息:
+
+```typescript
+import request from "@/utils/request";
+
+const USER_BASE_URL = "/api/v1/users";
+
+const UserAPI = {
+ /**
+ * 获取当前登录用户信息
+ *
+ * @returns 登录用户昵称、头像信息,包括角色和权限
+ */
+ getUserInfo(): Promise {
+ return request({
+ url: `${USER_BASE_URL}/me`,
+ method: "GET",
+ });
+ },
+};
+export default UserAPI;
+
+/** 登录用户信息 */
+export interface UserInfo {
+ /** 用户ID */
+ userId?: number;
+
+ /** 用户名 */
+ username?: string;
+
+ /** 昵称 */
+ nickname?: string;
+
+ /** 头像URL */
+ avatar?: string;
+
+ /** 角色 */
+ roles: string[];
+
+ /** 权限 */
+ perms: string[];
+}
+```
+
+
+
+### 用户状态管理
+
+通过 Pinia 定义 `user` 模块,管理登录状态、用户信息等。
+
+```typescript
+// src/store/module/user.ts
+import { defineStore } from "pinia";
+import AuthAPI from "@/api/auth";
+import UserAPI, { UserInfo } from "@/api/user";
+
+export const useUserStore = defineStore("user", () => {
+ // 确保 token 是响应式的
+ const token = ref(uni.getStorageSync("token") || "");
+ const userInfo = ref(null);
+
+ // 登录
+ const login = async (username: string, password: string) => {
+ const { tokenType, accessToken } = await AuthAPI.login(username, password);
+ token.value = `${tokenType} ${accessToken}`; // Bearer token
+ uni.setStorageSync("token", token.value);
+ };
+
+ // 获取用户信息
+ const getUserInfo = async () => {
+ const info = await UserAPI.getUserInfo();
+ userInfo.value = info;
+ };
+
+ // 登出
+ const logout = async () => {
+ await AuthAPI.logout();
+ userInfo.value = null;
+ token.value = ""; // 清空 token
+ uni.removeStorageSync("token"); // 从本地缓存移除 token
+ };
+
+ return {
+ token,
+ userInfo,
+ login,
+ logout,
+ getUserInfo,
+ };
+});
+```
+
+### 个人中心页面
+
+个人中心页面展示用户的头像和昵称,未登录时引导用户去登录。
+
+```vue
+
+
+ 我的
+
+
+
+
+ {{ userInfo?.nickname }}
+
+
+
+
+
+ 您还未登录,请先登录
+
+
+
+
+
+
+```
+
+登录页通过 `Pinia` 实现用户信息的全局状态管理,并在登录成功后跳转到个人中心页面。
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### 测试效果
+
+登录后,个人中心会显示用户的头像和昵称。通过 Pinia 实现了登录状态的共享和跨页面传递。
+
+
+
+
+
+### 整合源码
+
+整合`Pinia`代码版本:[vue-uniapp-template#737f6a3](https://gitee.com/youlaiorg/vue-uniapp-template/commit/737f6a3ee5217dcad006fa06b6fae63374d9826b)。
+
+## 反向代理
+
+在开发中,若服务端没有启用 CORS(跨域资源共享),浏览器会基于安全策略拦截跨域请求,导致无法访问接口。为了绕过这个问题,我们可以通过 Vite 的反向代理功能,将开发阶段的请求代理到真实的 API 服务器上,伪装成同源请求。
+
+本节将介绍如何配置 Vite 的反向代理来处理跨域请求。
+
+---
+
+### 环境变量配置
+
+我们将通过环境变量来管理项目端口和 API 请求地址,以下是 `.env.development` 中的相关配置:
+
+```bash
+# .env.development
+
+# 项目运行的端口号
+VITE_APP_PORT=5173
+
+# API 请求的基础路径(开发环境)
+VITE_APP_BASE_API=/dev-api
+
+# 真实 API 服务器的 URL
+VITE_APP_API_URL=https://api.youlai.tech
+```
+
+### 请求工具的调整
+
+为了让请求走代理,我们需要在请求工具中将 `VITE_APP_API_URL` 替换为 `VITE_APP_BASE_API`。这样,所有对 `API` 的请求都会通过代理标识 `/dev-api` 进行转发。
+
+```typescript
+export default function request(options: UniApp.RequestOptions): Promise {
+ return new Promise((resolve, reject) => {
+ uni.request({
+ ...options,
+ // 原请求方式: 使用真实 API URL
+ // url: `${import.meta.env.VITE_APP_API_URL}${options.url}`, // 示例: https://api.youlai.tech/login
+
+ // 修改后:使用代理标识,实际转发到真实 API
+ url: `${import.meta.env.VITE_APP_BASE_API}${options.url}`, // 示例: http://localhost:5173/dev-api/login
+ });
+ });
+}
+```
+
+### Vite 反向代理配置
+
+接下来,在 `vite.config.ts` 中添加反向代理配置,将 `/dev-api` 的请求代理到 `VITE_APP_API_URL`,通过 `http-proxy` 实现请求的转发。
+
+```typescript
+// vite.config.ts
+import { defineConfig, UserConfig, ConfigEnv, loadEnv } from "vite";
+
+export default defineConfig(async ({ mode }: ConfigEnv): Promise => {
+ const env = loadEnv(mode, process.cwd());
+
+ return {
+ server: {
+ host: "0.0.0.0",
+ port: +env.VITE_APP_PORT,
+ open: true,
+ // 反向代理配置
+ proxy: {
+ [env.VITE_APP_BASE_API]: {
+ target: env.VITE_APP_API_URL, // 目标服务器
+ changeOrigin: true, // 支持跨域
+ rewrite: (path) => path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""), // 去掉前缀
+ },
+ },
+ },
+ plugins: [
+ // 插件配置...
+ ],
+ };
+});
+```
+
+### 测试与验证
+
+在配置好反向代理后,浏览器发出的请求将被 Vite 的代理服务器拦截并转发至真实的 API 地址。例如,浏览器请求 `http://localhost:5173/dev-api/api/v1/auth/login` 时,Vite 会将该请求代理到 `https://api.youlai.tech/api/v1/auth/login`。
+
+下图展示了这一过程,浏览器认为请求的 URL 与应用的主机地址一致,因此不会阻止该请求,即便真实请求已通过代理转发到外部服务器。
+
+
+
+需要注意,反向代理的目标是伪装请求来源,虽然它绕过了浏览器的同源策略,但有时也会让开发者误以为请求地址错误。实际上,这是由于代理转发过程造成的表面请求地址与真实请求地址的差异。
+
+### 整合源码
+
+整合`反向代理`和`环境变量`代码版本:[vue-uniapp-template#272d643](https://gitee.com/youlaiorg/vue-uniapp-template/tree/272d643744b3b009a90a54e9344a0fc827f8b9fc)。
+
+## 整合 `wot-design-uni`
+
+`wot-design-uni` 是基于 `Vue 3` 和 `TypeScript` 构建的高质量组件库。组件库遵循 `Wot Design` 的设计规范,提供 70 多个组件,支持暗黑模式、国际化和自定义主题,旨在为开发者提供一致的 UI 交互,同时提高开发效率。
+
+> **说明:** 本文档整合步骤基于 `wot-design-uni` 官方文档编写,建议开发者参考 [官方文档](https://wot-design-uni.pages.dev/guide/quick-use.html#npm-%E5%AE%89%E8%A3%85) 进行安装和配置,以确保组件库的正确使用。
+
+---
+
+### 安装依赖
+
+根据官方文档,使用 `pnpm` 安装组件库的依赖:
+
+```bash
+pnpm add wot-design-uni
+```
+
+### 配置自动引入组件
+
+在传统的 `Vue` 项目中,使用组件需要手动安装、引用、注册。而使用 `easycom` 可以简化这些操作。只要组件路径符合规范,就可以直接在页面中使用,无需手动导入和注册。
+
+在 `pages.json` 文件中配置 `easycom` 自动引入:
+
+```json
+// pages.json
+{
+ "easycom": {
+ "autoscan": true,
+ "custom": {
+ "^wd-(.*)": "wot-design-uni/components/wd-$1/wd-$1.vue"
+ }
+ },
+ "pages": [
+ // 这里是项目已有的内容
+ ]
+}
+```
+
+**关于 `easycom`:**
+`easycom` 是 `uni-app` 提供的自动化引入功能,更多细节请参考 [easycom 官方文档](https://uniapp.dcloud.net.cn/collocation/pages.html#easycom)。
+
+### Volar 支持
+
+为了让 `Volar` 正确识别和提示全局组件,你需要在项目的 `tsconfig.json` 文件中配置全局组件类型支持:
+
+```json
+// tsconfig.json
+{
+ "compilerOptions": {
+ "types": ["wot-design-uni/global"]
+ }
+}
+```
+
+这将确保你在 `TypeScript` 项目中编写代码时,Volar 能提供完整的类型支持和代码提示。
+
+### 测试组件
+
+安装和配置完成后,你可以开始使用 `wot-design-uni` 的组件。在页面中,直接写组件标签即可,无需手动导入和注册:
+
+```vue
+
+
+ 主要按钮
+ 成功按钮
+ 信息按钮
+ 警告按钮
+ 危险按钮
+
+
+```
+
+你将看到如下按钮效果:
+
+
+
+### 整合源码
+
+整合`wot-design-uni` 代码版本:[vue-uniapp-template#a775721](https://gitee.com/youlaiorg/vue-uniapp-template/tree/a7757213f282a6414a3cf2cc13aa2c54a9479ae4)。
+
+
+
+## 项目部署
+
+
+
+### H5 部署
+
+执行 `pnpm run build:h5` 命令来完成项目的打包:
+
+```bash
+pnpm run build:h5
+```
+
+打包后生成的静态文件位于 `dist/build/h5` 目录下。将该目录下的文件复制到服务器的 `/usr/share/nginx/html/vue-uniapp-template` 目录。
+
+接下来,配置 nginx:
+
+```nginx
+# nginx.conf
+server {
+ listen 80;
+ server_name localhost;
+ location / {
+ root /usr/share/nginx/html/vue-uniapp-template;
+ index index.html index.htm;
+ }
+ # 反向代理配置
+ location /prod-api/ {
+ # 将 api.youlai.tech 替换为后端 API 地址,注意保留后面的斜杠 /
+ proxy_pass http://api.youlai.tech/;
+ }
+}
+```
+
+这样配置完成后,就可以通过 `nginx `服务器来访问你的项目了。
+
+### 小程序发布
+
+### 下载工具
+
+下载 [ HBuilder X ](https://www.dcloud.io/hbuilderx.html) 编辑器
+
+
+
+
+
+下载 [微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html)
+
+
+
+### 获取小程序 AppID
+
+访问 [微信公众平台](https://mp.weixin.qq.com/)申请小程序,获取 `AppID`。如果已申请,可在 `首页` → `小程序信息` → `查看详情` 查看 AppID
+
+
+
+
+
+### 配置项目
+
+使用 HBuilder X 打开项目,修改 `manifest.json` 文件中的小程序配置,并填写获取的 AppID。
+
+
+
+### 设置微信开发者工具
+
+使用微信扫码登录微信开发者工具,开启服务端口:点击工具栏`设置`→`安全设置`→`安全`→`服务端口`,选择打开。
+
+
+
+### 运行项目
+
+在 HBuilder X 中,点击 `运行`→`运行到小程序模拟器`→`微信开发者工具`。
+
+
+
+项目编译完成后,微信开发者工具会自动启动并呈现页面。
+
+
+
+### 上传发布
+
+在微信开发者工具中,点击 `上传` 将应用发布到小程序平台。
+
+
+
+
+
+### 查看效果
+
+最后,使用手机打开小程序查看效果:
+
+
diff --git a/docs/theme-guide.md b/docs/theme-guide.md
new file mode 100644
index 0000000..652c502
--- /dev/null
+++ b/docs/theme-guide.md
@@ -0,0 +1,361 @@
+# 主题设置功能指南
+
+## 功能概述
+
+本项目提供了完整的主题设置功能,支持暗黑模式切换和主题色自定义,让用户可以个性化应用的外观。
+
+## 功能特性
+
+### 1. 暗黑模式
+
+- 支持浅色/暗黑模式切换
+- 自动保存用户偏好设置
+- 平滑的过渡动画效果
+- 导航栏颜色自动适配
+
+### 2. 主题色设置
+
+- 12种预设主题色可选
+- 支持自定义十六进制颜色值
+- 实时预览效果
+- 主题色持久化存储
+
+### 3. 效果预览
+
+- 按钮样式预览
+- 文本颜色预览
+- 标签组件预览
+- 实时更新显示
+
+## 文件结构
+
+```
+src/
+├── composables/
+│ └── theme/
+│ ├── theme.ts # 主题管理核心逻辑
+│ └── rootTheme.ts # 主题配置和默认值
+├── store/
+│ └── modules/
+│ └── theme.ts # 主题状态管理
+├── pages/
+│ └── setting.vue # 主题设置页面
+├── utils/
+│ ├── theme.ts # 主题工具函数
+│ └── colorUtils.ts # 颜色处理工具
+└── styles/
+ └── global.scss # 全局主题样式
+```
+
+## 核心组件说明
+
+### 1. useTheme Composable
+
+位置:`src/composables/theme/theme.ts`
+
+主要功能:
+
+- 主题模式切换(浅色/暗黑)
+- 主题色设置和保存
+- 导航栏颜色适配
+- 本地存储管理
+
+```typescript
+const {
+ theme, // 当前主题模式
+ themeVars, // 主题变量
+ toggleTheme, // 切换主题模式
+ setThemeColor, // 设置主题色
+ initTheme, // 初始化主题
+} = useTheme();
+```
+
+### 2. useThemeStore
+
+位置:`src/store/modules/theme.ts`
+
+主要功能:
+
+- 主题色状态管理
+- CSS变量动态设置
+- 多平台兼容处理
+
+```typescript
+const themeStore = useThemeStore();
+
+// 设置主题色
+themeStore.setPrimaryColor("#165DFF");
+
+// 初始化主题
+themeStore.initTheme();
+```
+
+### 3. 主题设置页面
+
+位置:`src/pages/setting.vue`
+
+功能模块:
+
+- 暗黑模式开关
+- 预设主题色选择
+- 自定义颜色输入
+- 效果实时预览
+- 重置默认主题
+
+## 使用方法
+
+### 1. 基础使用
+
+在页面中引入主题功能:
+
+```vue
+
+
+
+
+
+
+
+```
+
+### 2. 在布局中使用
+
+在 `src/layouts/tabbar.vue` 中已经集成了主题功能:
+
+```vue
+
+
+
+
+
+```
+
+### 3. 自定义主题色
+
+支持的颜色格式:
+
+- 6位十六进制:`#165DFF`
+- 3位十六进制:`#16F`(会自动转换为6位)
+
+```javascript
+// 设置自定义主题色
+const customColor = "#FF6B6B";
+themeStore.setPrimaryColor(customColor);
+```
+
+## 预设主题色
+
+项目提供了12种预设主题色:
+
+| 颜色名称 | 颜色值 | 说明 |
+| -------- | ------- | ---------- |
+| 蓝色 | #0055FE | 默认主题色 |
+| 红色 | #CD5C5C | 经典红色 |
+| 绿色 | #228B22 | 自然绿色 |
+| 紫色 | #722ED1 | 优雅紫色 |
+| 橙色 | #FA8C16 | 活力橙色 |
+| 黄色 | #FADB14 | 明亮黄色 |
+| 青色 | #13C2C2 | 清新青色 |
+| 粉色 | #EB2F96 | 温馨粉色 |
+| 天蓝色 | #1890FF | 科技蓝色 |
+| 深红色 | #F5222D | 警示红色 |
+
+## 样式变量
+
+主题系统使用CSS变量来实现动态主题切换:
+
+```scss
+:root {
+ /* 主色调 */
+ --primary-color: #165dff;
+ --primary-color-light: #94bfff;
+ --primary-color-dark: #0e3c9b;
+
+ /* 功能色 */
+ --success-color: #0fc6c2;
+ --warning-color: #ff7d00;
+ --danger-color: #f5222d;
+ --info-color: #86909c;
+}
+```
+
+在组件中使用:
+
+```scss
+.my-button {
+ background-color: var(--primary-color);
+ color: #fff;
+
+ &:hover {
+ background-color: var(--primary-color-dark);
+ }
+}
+```
+
+## 平台兼容性
+
+### H5平台
+
+- 使用CSS变量动态设置主题
+- 支持所有现代浏览器
+- 平滑的过渡动画
+
+### 小程序平台
+
+- 使用原生API设置TabBar样式
+- 通过工具函数处理样式应用
+- 兼容微信、支付宝等小程序
+
+### APP平台
+
+- 支持原生导航栏颜色设置
+- 状态栏颜色自动适配
+- 性能优化处理
+
+## 最佳实践
+
+### 1. 主题初始化
+
+在应用启动时初始化主题:
+
+```typescript
+// src/App.vue
+import { useThemeStore } from "@/store/modules/theme";
+
+const themeStore = useThemeStore();
+
+onLaunch(() => {
+ // 初始化主题
+ themeStore.initTheme();
+});
+```
+
+### 2. 组件中使用主题
+
+```vue
+
+
+ 标题
+
+
+
+
+
+```
+
+### 3. 暗黑模式适配
+
+```scss
+// 暗黑模式样式
+:deep(.wd-config-provider[data-theme="dark"]) {
+ .my-component {
+ background: #2a2a2a;
+ color: #fff;
+
+ .title {
+ color: var(--primary-color);
+ }
+ }
+}
+```
+
+## 注意事项
+
+1. **性能优化**:主题切换时避免频繁的DOM操作
+2. **兼容性**:小程序环境下某些CSS特性可能不支持
+3. **用户体验**:提供平滑的过渡动画效果
+4. **持久化**:确保用户设置能够正确保存和恢复
+5. **响应式**:在不同屏幕尺寸下保持良好的显示效果
+
+## 故障排除
+
+### 1. 主题色不生效
+
+- 检查CSS变量是否正确设置
+- 确认组件是否在 `wd-config-provider` 包裹内
+- 验证颜色值格式是否正确
+
+### 2. 暗黑模式切换异常
+
+- 检查 `theme` 状态是否正确更新
+- 确认暗黑模式样式是否正确编写
+- 验证导航栏颜色设置是否生效
+
+### 3. 设置不持久化
+
+- 检查本地存储权限
+- 确认存储key是否正确
+- 验证初始化逻辑是否执行
+
+## 扩展开发
+
+### 1. 添加新的预设主题色
+
+在 `src/composables/theme/rootTheme.ts` 中添加:
+
+```typescript
+export const colorColumns = [
+ // 现有颜色...
+ {
+ value: "#YOUR_COLOR",
+ label: "你的颜色名称",
+ },
+];
+```
+
+### 2. 自定义主题变量
+
+在 `src/composables/theme/rootTheme.ts` 中扩展:
+
+```typescript
+export const initThemeVars: ConfigProviderThemeVars = {
+ colorTheme: colorColumns[0].value,
+ // 添加更多主题变量
+ colorSuccess: "#52c41a",
+ colorWarning: "#faad14",
+ colorDanger: "#ff4d4f",
+};
+```
+
+### 3. 添加主题切换动画
+
+```scss
+.theme-transition {
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+```
+
+通过以上指南,你可以充分利用项目的主题设置功能,为用户提供个性化的应用体验。
diff --git a/docs/wechat-login-guide.md b/docs/wechat-login-guide.md
new file mode 100644
index 0000000..9823d4d
--- /dev/null
+++ b/docs/wechat-login-guide.md
@@ -0,0 +1,342 @@
+# 微信小程序手机授权登录功能指南
+
+## 功能概述
+
+本项目实现了完整的微信小程序手机授权登录功能,包括:
+
+- 微信登录授权
+- 手机号获取授权
+- 头像昵称填写(使用微信小程序新能力)
+- 用户信息完善流程
+- 登录状态管理
+
+## 功能特性
+
+### 1. 微信登录流程
+
+- **基础微信登录**:使用 `uni.login()` 获取 code,调用后端接口完成登录
+- **增强微信登录**:支持更多用户信息和手机号一次性授权
+- **登录状态检查**:自动检查用户信息完整性,引导用户完善信息
+
+### 2. 手机号授权
+
+- **一键授权**:使用微信小程序 `getPhoneNumber` 能力
+- **安全获取**:通过后端接口解密获取真实手机号
+- **状态显示**:显示脱敏手机号,支持重新授权
+
+### 3. 头像昵称填写
+
+- **微信新能力**:使用 `chooseAvatar` 和 `type="nickname"` 输入框
+- **自动上传**:头像选择后自动上传到服务器
+- **实时预览**:支持头像实时预览和昵称输入
+
+### 4. 用户信息完善
+
+- **智能引导**:登录后自动检查信息完整性
+- **分步填写**:头像、昵称、性别、手机号分步骤完善
+- **跳过机制**:允许用户暂时跳过,但会提示影响功能使用
+
+## 文件结构
+
+```
+src/
+├── pages/
+│ └── login/
+│ ├── index.vue # 登录页面
+│ └── complete-profile.vue # 完善信息页面
+├── components/
+│ └── WechatProfile.vue # 微信头像昵称组件
+├── api/
+│ ├── auth.ts # 认证API
+│ ├── user.ts # 用户API
+│ └── file.ts # 文件上传API
+├── store/
+│ └── modules/
+│ └── user.ts # 用户状态管理
+└── utils/
+ ├── auth.ts # 认证工具函数
+ └── storage.ts # 存储工具函数
+```
+
+## 核心组件说明
+
+### 1. 登录页面 (`pages/login/index.vue`)
+
+**主要功能:**
+
+- 用户名密码登录
+- 微信一键登录
+- 登录状态检查和跳转
+
+**关键代码:**
+
+```typescript
+// 微信登录处理
+const handleWechatLogin = async () => {
+ const { code } = await uni.login({ provider: "weixin" });
+
+ // 尝试增强登录
+ try {
+ const result = await userStore.loginByWechatMini({ code });
+ // 检查信息完整性
+ if (result.isNewUser || !result.isProfileComplete) {
+ // 跳转到完善信息页面
+ uni.navigateTo({
+ url: `/pages/login/complete-profile?redirect=${redirect}`,
+ });
+ }
+ } catch (error) {
+ // 回退到基础登录
+ await userStore.loginByWechat(code);
+ }
+};
+```
+
+### 2. 完善信息页面 (`pages/login/complete-profile.vue`)
+
+**主要功能:**
+
+- 头像上传(支持微信新能力)
+- 昵称输入
+- 性别选择
+- 手机号授权
+- 信息提交和验证
+
+**关键代码:**
+
+```typescript
+// 手机号授权
+const onGetPhoneNumber = async (e: any) => {
+ if (e.detail.errMsg === "getPhoneNumber:ok") {
+ const phoneData = await UserAPI.getPhoneNumber({
+ code: e.detail.code,
+ encryptedData: e.detail.encryptedData,
+ iv: e.detail.iv,
+ });
+ profileForm.mobile = phoneData.phoneNumber;
+ }
+};
+```
+
+### 3. 微信头像昵称组件 (`components/WechatProfile.vue`)
+
+**主要功能:**
+
+- 使用微信小程序头像选择能力
+- 昵称输入框(type="nickname")
+- 性别选择
+- 数据双向绑定
+
+**关键代码:**
+
+```vue
+
+
+
+
+
+```
+
+## API 接口说明
+
+### 1. 认证相关接口
+
+```typescript
+// 基础微信登录
+AuthAPI.wechatLogin(code: string): Promise
+
+// 增强微信登录
+AuthAPI.wechatMiniLogin(data: WechatMiniLoginData): Promise
+```
+
+### 2. 用户相关接口
+
+```typescript
+// 获取微信手机号
+UserAPI.getPhoneNumber(data: WechatPhoneData): Promise
+
+// 绑定手机号
+UserAPI.bindMobile(data: MobileBindingForm): Promise
+
+// 更新用户信息
+UserAPI.updateProfile(data: UserProfileForm): Promise
+```
+
+### 3. 文件上传接口
+
+```typescript
+// 上传文件
+FileAPI.upload(filePath: string): Promise
+```
+
+## 类型定义
+
+### 微信登录相关
+
+```typescript
+interface WechatMiniLoginData {
+ code: string;
+ userInfo?: {
+ nickName?: string;
+ avatarUrl?: string;
+ gender?: number;
+ };
+ phoneData?: {
+ code: string;
+ encryptedData?: string;
+ iv?: string;
+ };
+}
+
+interface WechatLoginResult extends LoginResult {
+ isNewUser?: boolean;
+ isProfileComplete?: boolean;
+ userInfo?: {
+ userId?: number;
+ username?: string;
+ nickname?: string;
+ avatar?: string;
+ mobile?: string;
+ };
+}
+```
+
+### 手机号授权相关
+
+```typescript
+interface WechatPhoneData {
+ code: string;
+ encryptedData?: string;
+ iv?: string;
+}
+
+interface PhoneNumberResult {
+ phoneNumber: string;
+ purePhoneNumber?: string;
+ countryCode?: string;
+}
+```
+
+## 使用流程
+
+### 1. 用户首次登录
+
+1. 用户点击微信登录按钮
+2. 调用 `uni.login()` 获取微信 code
+3. 调用后端登录接口,获取 token
+4. 检查用户信息完整性
+5. 如果信息不完整,跳转到完善信息页面
+
+### 2. 完善用户信息
+
+1. 用户进入完善信息页面
+2. 选择头像(使用微信新能力或传统上传)
+3. 输入昵称(使用 type="nickname" 输入框)
+4. 选择性别
+5. 授权获取手机号
+6. 提交信息,更新用户资料
+
+### 3. 后续登录
+
+1. 用户再次登录时,检查信息完整性
+2. 如果信息完整,直接跳转到主页
+3. 如果信息不完整,引导用户完善
+
+## 配置要求
+
+### 1. 微信小程序配置
+
+在 `manifest.json` 中配置:
+
+```json
+{
+ "mp-weixin": {
+ "appid": "your-appid",
+ "setting": {
+ "urlCheck": false
+ },
+ "permission": {
+ "scope.userInfo": {
+ "desc": "用于完善用户资料"
+ }
+ }
+ }
+}
+```
+
+### 2. 后端接口要求
+
+- 支持微信登录 code 解析
+- 支持微信手机号解密
+- 支持文件上传
+- 支持用户信息更新
+
+## 注意事项
+
+### 1. 微信小程序新能力
+
+- `chooseAvatar` 和 `type="nickname"` 需要微信基础库 2.21.2+
+- 需要在微信开发者工具中测试
+- 真机调试时需要注意兼容性
+
+### 2. 手机号授权
+
+- 需要微信小程序认证
+- 需要在微信公众平台配置服务器域名
+- 手机号解密需要在后端完成
+
+### 3. 用户体验
+
+- 提供跳过机制,避免强制完善信息
+- 显示脱敏手机号,保护用户隐私
+- 支持重新授权和修改信息
+
+## 扩展功能
+
+### 1. 社交登录
+
+可以扩展支持其他社交平台登录:
+
+- QQ 登录
+- 支付宝登录
+- 苹果登录
+
+### 2. 实名认证
+
+可以添加实名认证功能:
+
+- 身份证验证
+- 人脸识别
+- 银行卡验证
+
+### 3. 多端同步
+
+可以实现多端登录状态同步:
+
+- H5 端登录
+- APP 端登录
+- 小程序端登录
+
+## 故障排除
+
+### 1. 微信登录失败
+
+- 检查 appid 配置
+- 检查服务器域名配置
+- 检查网络连接
+
+### 2. 手机号授权失败
+
+- 检查小程序是否已认证
+- 检查后端解密接口
+- 检查用户授权状态
+
+### 3. 头像上传失败
+
+- 检查文件上传接口
+- 检查文件大小限制
+- 检查网络状态
+
+## 总结
diff --git a/package.json b/package.json
index 2104be3..f9e5359 100644
--- a/package.json
+++ b/package.json
@@ -90,6 +90,8 @@
"@dcloudio/uni-mp-weixin": "3.0.0-4020420240722002",
"@dcloudio/uni-mp-xhs": "3.0.0-4020420240722002",
"@dcloudio/uni-quickapp-webview": "3.0.0-4020420240722002",
+ "@uni-helper/uni-use": "^0.19.14",
+ "@vueuse/core": "10.11.1",
"pinia": "^2.2.2",
"vue": "^3.5.13",
"wot-design-uni": "^1.4.0"
@@ -103,6 +105,8 @@
"@dcloudio/uni-stacktracey": "3.0.0-4020420240722002",
"@dcloudio/vite-plugin-uni": "3.0.0-4020420240722002",
"@eslint/js": "^9.10.0",
+ "@uni-helper/vite-plugin-uni-layouts": "^0.1.10",
+ "@uni-helper/vite-plugin-uni-pages": "^0.2.28",
"@vue/runtime-core": "^3.4.21",
"commitizen": "^4.3.0",
"cz-git": "^1.9.4",
diff --git a/pages.config.ts b/pages.config.ts
new file mode 100644
index 0000000..f6d4302
--- /dev/null
+++ b/pages.config.ts
@@ -0,0 +1,11 @@
+// pages.config.ts
+import { defineUniPages } from "@uni-helper/vite-plugin-uni-pages";
+
+export default defineUniPages({
+ // 你也可以定义 pages 字段,它具有最高的优先级。
+ pages: [],
+ globalStyle: {
+ navigationBarTextStyle: "black",
+ navigationBarTitleText: "@uni-helper",
+ },
+});
diff --git a/src/api/auth.ts b/src/api/auth.ts
new file mode 100644
index 0000000..13adfc6
--- /dev/null
+++ b/src/api/auth.ts
@@ -0,0 +1,126 @@
+import request from "@/utils/request";
+
+const AuthAPI = {
+ /**
+ * 登录接口
+ *
+ * @param username 用户名
+ * @param password 密码
+ * @returns 返回 token
+ */
+ login(data: LoginFormData): Promise {
+ return request({
+ url: "/api/v1/auth/login",
+ method: "POST",
+ data: data,
+ header: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ });
+ },
+
+ /**
+ * 微信登录接口
+ *
+ * @param code 微信登录code
+ * @returns 返回 token
+ */
+ wechatLogin(code: string): Promise {
+ return request({
+ url: "/api/v1/auth/wechat-login",
+ method: "POST",
+ data: { code },
+ header: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ });
+ },
+
+ /**
+ * 微信小程序登录接口(增强版)
+ *
+ * @param data 微信登录数据
+ * @returns 返回 token 和用户信息
+ */
+ wechatMiniLogin(data: WechatMiniLoginData): Promise {
+ return request({
+ url: "/api/v1/auth/wechat-mini-login",
+ method: "POST",
+ data: data,
+ header: {
+ "Content-Type": "application/json",
+ },
+ });
+ },
+
+ /**
+ * 登出接口
+ */
+ logout(): Promise {
+ return request({
+ url: "/api/v1/auth/logout",
+ method: "DELETE",
+ });
+ },
+};
+
+export default AuthAPI;
+
+/** 登录响应 */
+export interface LoginResult {
+ /** 访问token */
+ accessToken: string;
+ /** token 类型 */
+ tokenType?: string;
+}
+
+export interface LoginFormData {
+ username: string;
+ password: string;
+}
+
+/** 微信小程序登录数据 */
+export interface WechatMiniLoginData {
+ /** 微信登录code */
+ code: string;
+ /** 用户信息(可选) */
+ userInfo?: {
+ /** 昵称 */
+ nickName?: string;
+ /** 头像URL */
+ avatarUrl?: string;
+ /** 性别 */
+ gender?: number;
+ /** 国家 */
+ country?: string;
+ /** 省份 */
+ province?: string;
+ /** 城市 */
+ city?: string;
+ };
+ /** 手机号授权数据(可选) */
+ phoneData?: {
+ /** 手机号授权code */
+ code: string;
+ /** 加密数据 */
+ encryptedData?: string;
+ /** 初始向量 */
+ iv?: string;
+ };
+}
+
+/** 微信登录结果 */
+export interface WechatLoginResult extends LoginResult {
+ /** 是否为新用户 */
+ isNewUser?: boolean;
+ /** 用户信息是否完整 */
+ isProfileComplete?: boolean;
+ /** 用户基本信息 */
+ userInfo?: {
+ userId?: number;
+ username?: string;
+ nickname?: string;
+ avatar?: string;
+ mobile?: string;
+ };
+}
diff --git a/src/api/auth/index.ts b/src/api/auth/index.ts
deleted file mode 100644
index 7f8ae3a..0000000
--- a/src/api/auth/index.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import request from "@/utils/request";
-
-const AuthAPI = {
- /**
- * 登录接口
- *
- * @param username 用户名
- * @param password 密码
- * @returns 返回 token
- */
- login(data: LoginFormData): Promise {
- console.log("data", data);
- return request({
- url: "/api/v1/auth/login",
- method: "POST",
- data: data,
- header: {
- "Content-Type": "application/x-www-form-urlencoded",
- },
- });
- },
-
- /**
- * 微信登录接口
- *
- * @param code 微信登录code
- * @returns 返回 token
- */
- wechatLogin(code: string): Promise {
- return request({
- url: "/api/v1/auth/wechat-login",
- method: "POST",
- data: { code },
- header: {
- "Content-Type": "application/x-www-form-urlencoded",
- },
- });
- },
-
- /**
- * 登出接口
- */
- logout(): Promise {
- return request({
- url: "/api/v1/auth/logout",
- method: "DELETE",
- });
- },
-};
-
-export default AuthAPI;
-
-/** 登录响应 */
-export interface LoginResult {
- /** 访问token */
- accessToken: string;
- /** token 类型 */
- tokenType?: string;
-}
-
-export interface LoginFormData {
- username: string;
- password: string;
-}
diff --git a/src/api/file/index.ts b/src/api/file.ts
similarity index 97%
rename from src/api/file/index.ts
rename to src/api/file.ts
index 1e19bf9..c472c5c 100644
--- a/src/api/file/index.ts
+++ b/src/api/file.ts
@@ -1,4 +1,4 @@
-import { getToken } from "@/utils/cache";
+import { getToken } from "@/utils/storage";
import { ResultCodeEnum } from "@/enums/ResultCodeEnum";
// H5 使用 VITE_APP_BASE_API 作为代理路径,其他平台使用 VITE_APP_API_URL 作为请求路径
diff --git a/src/api/system/config.ts b/src/api/system/config.ts
deleted file mode 100644
index fb42cde..0000000
--- a/src/api/system/config.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import request from "@/utils/request";
-
-const CONFIG_BASE_URL = "/api/v1/config";
-
-const ConfigAPI = {
- /** 获取系统配置分页数据 */
- getPage(queryParams: ConfigPageQuery) {
- return request>({
- url: `${CONFIG_BASE_URL}/page`,
- method: "GET",
- data: queryParams,
- });
- },
- /**
- * 获取系统配置表单数据
- *
- * @param id ConfigID
- * @returns Config表单数据
- */
- getFormData(id: number) {
- return request({
- url: `${CONFIG_BASE_URL}/${id}/form`,
- method: "GET",
- });
- },
-
- /** 添加系统配置*/
- add(data: ConfigForm) {
- return request({
- url: `${CONFIG_BASE_URL}`,
- method: "POST",
- data: data,
- });
- },
-
- /**
- * 更新系统配置
- *
- * @param id ConfigID
- * @param data Config表单数据
- */
- update(id: number, data: ConfigForm) {
- return request({
- url: `${CONFIG_BASE_URL}/${id}`,
- method: "PUT",
- data: data,
- });
- },
-
- /**
- * 删除系统配置
- *
- * @param ids 系统配置ID
- */
- deleteById(id: number) {
- return request({
- url: `${CONFIG_BASE_URL}/${id}`,
- method: "DELETE",
- });
- },
-
- refreshCache() {
- return request({
- url: `${CONFIG_BASE_URL}/refresh`,
- method: "PUT",
- });
- },
-};
-
-export default ConfigAPI;
-
-/** $系统配置分页查询参数 */
-export interface ConfigPageQuery extends PageQuery {
- /** 搜索关键字 */
- keywords?: string;
-}
-
-/** 系统配置表单对象 */
-export interface ConfigForm {
- /** 主键 */
- id?: number;
- /** 配置名称 */
- configName?: string;
- /** 配置键 */
- configKey?: string;
- /** 配置值 */
- configValue?: string;
- /** 描述、备注 */
- remark?: string;
-}
-
-/** 系统配置分页对象 */
-export interface ConfigPageVO {
- /** 主键 */
- id?: number;
- /** 配置名称 */
- configName?: string;
- /** 配置键 */
- configKey?: string;
- /** 配置值 */
- configValue?: string;
- /** 描述、备注 */
- remark?: string;
-}
diff --git a/src/api/system/dept.ts b/src/api/system/dept.ts
deleted file mode 100644
index cdbd165..0000000
--- a/src/api/system/dept.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import request from "@/utils/request";
-
-const DEPT_BASE_URL = "/api/v1/dept";
-
-const DeptAPI = {
- /**
- * 获取部门列表
- *
- * @param queryParams 查询参数(可选)
- * @returns 部门树形表格数据
- */
- getList(queryParams?: DeptQuery) {
- return request({
- url: `${DEPT_BASE_URL}`,
- method: "GET",
- data: queryParams,
- });
- },
-
- /** 获取部门下拉列表 */
- getOptions() {
- return request({
- url: `${DEPT_BASE_URL}/options`,
- method: "GET",
- });
- },
-
- /**
- * 获取部门表单数据
- *
- * @param id 部门ID
- * @returns 部门表单数据
- */
- getFormData(id: number) {
- return request({
- url: `${DEPT_BASE_URL}/${id}/form`,
- method: "GET",
- });
- },
-
- /**
- * 新增部门
- *
- * @param data 部门表单数据
- * @returns 请求结果
- */
- add(data: DeptForm) {
- return request({
- url: `${DEPT_BASE_URL}`,
- method: "POST",
- data: data,
- });
- },
-
- /**
- * 修改部门
- *
- * @param id 部门ID
- * @param data 部门表单数据
- * @returns 请求结果
- */
- update(id: number, data: DeptForm) {
- return request({
- url: `${DEPT_BASE_URL}/${id}`,
- method: "PUT",
- data: data,
- });
- },
-
- /**
- * 删除部门
- *
- * @param ids 部门ID,多个以英文逗号(,)分隔
- * @returns 请求结果
- */
- deleteByIds(ids: string) {
- return request({
- url: `${DEPT_BASE_URL}/${ids}`,
- method: "DELETE",
- });
- },
-};
-
-export default DeptAPI;
-
-/** 部门查询参数 */
-export interface DeptQuery {
- /** 搜索关键字 */
- keywords?: string;
- /** 状态 */
- status?: number;
-}
-
-/** 部门类型 */
-export interface DeptVO {
- /** 子部门 */
- children?: DeptVO[];
- /** 创建时间 */
- createTime?: Date;
- /** 部门ID */
- id?: number;
- /** 部门名称 */
- name?: string;
- /** 部门编号 */
- code?: string;
- /** 父部门ID */
- parentId?: number;
- /** 排序 */
- sort?: number;
- /** 状态(1:启用;0:禁用) */
- status?: number;
- /** 修改时间 */
- updateTime?: Date;
-}
-
-/** 部门表单类型 */
-export interface DeptForm {
- /** 部门ID(新增不填) */
- id?: number;
- /** 部门名称 */
- name?: string;
- /** 部门编号 */
- code?: string;
- /** 父部门ID */
- parentId: number;
- /** 排序 */
- sort?: number;
- /** 状态(1:启用;0:禁用) */
- status?: number;
-}
diff --git a/src/api/system/dict.ts b/src/api/system/dict.ts
deleted file mode 100644
index 39f08d3..0000000
--- a/src/api/system/dict.ts
+++ /dev/null
@@ -1,305 +0,0 @@
-import request from "@/utils/request";
-
-const DICT_BASE_URL = "/api/v1/dicts";
-
-const DictAPI = {
- //---------------------------------------------------
- // 字典相关接口
- //---------------------------------------------------
-
- /**
- * 字典分页列表
- *
- * @param queryParams 查询参数
- * @returns 字典分页结果
- */
- getPage(queryParams: DictPageQuery) {
- return request>({
- url: `${DICT_BASE_URL}/page`,
- method: "GET",
- data: queryParams,
- });
- },
-
- /**
- * 字典表单数据
- *
- * @param id 字典ID
- * @returns 字典表单数据
- */
- getFormData(id: number) {
- return request({
- url: `${DICT_BASE_URL}/${id}/form`,
- method: "GET",
- });
- },
-
- /**
- * 新增字典
- *
- * @param data 字典表单数据
- */
- create(data: DictForm) {
- return request({
- url: `${DICT_BASE_URL}`,
- method: "POST",
- data: data,
- });
- },
-
- /**
- * 修改字典
- *
- * @param id 字典ID
- * @param data 字典表单数据
- */
- update(id: number, data: DictForm) {
- return request({
- url: `${DICT_BASE_URL}/${id}`,
- method: "PUT",
- data: data,
- });
- },
-
- /**
- * 删除字典
- *
- * @param ids 字典ID,多个以英文逗号(,)分隔
- */
- deleteByIds(ids: string) {
- return request({
- url: `${DICT_BASE_URL}/${ids}`,
- method: "DELETE",
- });
- },
-
- //---------------------------------------------------
- // 字典项相关接口
- //---------------------------------------------------
- /**
- * 获取字典分页列表
- *
- * @param queryParams 查询参数
- * @returns 字典分页结果
- */
- getDictItemPage(dictCode: string, queryParams: DictItemPageQuery) {
- return request>({
- url: `${DICT_BASE_URL}/${dictCode}/items/page`,
- method: "GET",
- data: queryParams,
- });
- },
-
- /**
- * 获取字典项列表
- */
- getDictItems(dictCode: string) {
- return request({
- url: `${DICT_BASE_URL}/${dictCode}/items`,
- method: "GET",
- });
- },
-
- /**
- * 新增字典项
- */
- createDictItem(dictCode: string, data: DictItemForm) {
- return request({
- url: `${DICT_BASE_URL}/${dictCode}/items`,
- method: "POST",
- data: data,
- });
- },
-
- /**
- * 获取字典项表单数据
- *
- * @param id 字典项ID
- * @returns 字典项表单数据
- */
- getDictItemFormData(dictCode: string, id: number) {
- return request({
- url: `${DICT_BASE_URL}/${dictCode}/items/${id}/form`,
- method: "GET",
- });
- },
-
- /**
- * 修改字典项
- */
- updateDictItem(dictCode: string, id: number, data: DictItemForm) {
- return request({
- url: `${DICT_BASE_URL}/${dictCode}/items/${id}`,
- method: "PUT",
- data: data,
- });
- },
-
- /**
- * 删除字典项
- */
- deleteDictItems(dictCode: string, ids: string) {
- return request({
- url: `${DICT_BASE_URL}/${dictCode}/items/${ids}`,
- method: "DELETE",
- });
- },
-};
-
-export default DictAPI;
-
-/**
- * 字典查询参数
- */
-export interface DictPageQuery extends PageQuery {
- /**
- * 关键字(字典名称/编码)
- */
- keywords?: string;
-
- /**
- * 字典状态(1:启用,0:禁用)
- */
- status?: number;
-}
-
-/**
- * 字典分页对象
- */
-export interface DictPageVO {
- /**
- * 字典ID
- */
- id: number;
- /**
- * 字典名称
- */
- name: string;
- /**
- * 字典编码
- */
- dictCode: string;
- /**
- * 字典状态(1:启用,0:禁用)
- */
- status: number;
-}
-
-/**
- * 字典
- */
-export interface DictForm {
- /**
- * 字典ID
- */
- id?: number;
- /**
- * 字典名称
- */
- name?: string;
- /**
- * 字典编码
- */
- dictCode?: string;
- /**
- * 字典状态(1-启用,0-禁用)
- */
- status?: number;
- /**
- * 备注
- */
- remark?: string;
-}
-
-/**
- * 字典查询参数
- */
-export interface DictItemPageQuery extends PageQuery {
- /** 关键字(字典数据值/标签) */
- keywords?: string;
-
- /** 字典编码 */
- dictCode?: string;
-}
-
-/**
- * 字典分页对象
- */
-export interface DictItemPageVO {
- /**
- * 字典ID
- */
- id: number;
- /**
- * 字典编码
- */
- dictCode: string;
- /**
- * 字典数据值
- */
- value: string;
- /**
- * 字典数据标签
- */
- label: string;
- /**
- * 状态(1:启用,0:禁用)
- */
- status: number;
- /**
- * 字典排序
- */
- sort?: number;
-}
-
-/**
- * 字典
- */
-export interface DictItemForm {
- /**
- * 字典ID
- */
- id?: number;
- /**
- * 字典编码
- */
- dictCode?: string;
- /**
- * 字典数据值
- */
- value?: string;
- /**
- * 字典数据标签
- */
- label?: string;
- /**
- * 状态(1:启用,0:禁用)
- */
- status?: number;
- /**
- * 字典排序
- */
- sort?: number;
-
- /**
- * 标签类型
- */
- tagType?: "success" | "warning" | "info" | "primary" | "danger" | undefined;
-}
-
-/**
- * 字典项下拉选项
- */
-export interface DictItemOption {
- /** 字典数据值 */
- value: string | number;
-
- /** 字典数据标签 */
- label: string;
-
- /** 标签类型 */
- tagType?: "" | "success" | "info" | "warning" | "danger" | "primary";
-
- /** 允许其他属性 */
- [key: string]: any;
-}
diff --git a/src/api/system/log.ts b/src/api/system/log.ts
deleted file mode 100644
index 76f87f8..0000000
--- a/src/api/system/log.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import request from "@/utils/request";
-
-const LOG_BASE_URL = "/api/v1/logs";
-
-const LogAPI = {
- /**
- * 获取日志分页列表
- *
- * @param queryParams 查询参数
- */
- getPage(queryParams: LogPageQuery) {
- return request>({
- url: `${LOG_BASE_URL}/page`,
- method: "GET",
- data: queryParams,
- });
- },
-
- /**
- * 获取访问趋势
- *
- * @param queryParams
- * @returns
- */
- getVisitTrend(queryParams: VisitTrendQuery) {
- return request({
- url: `${LOG_BASE_URL}/visit-trend`,
- method: "GET",
- data: queryParams,
- });
- },
-
- /**
- * 获取访问趋势
- *
- * @param queryParams
- * @returns
- */
- getVisitStats() {
- return request({
- url: `${LOG_BASE_URL}/visit-stats`,
- method: "GET",
- });
- },
-};
-
-export default LogAPI;
-
-/**
- * 日志分页查询对象
- */
-export interface LogPageQuery extends PageQuery {
- /** 搜索关键字 */
- keywords?: string;
- /** 操作时间 */
- createTime?: [string, string] | string;
-}
-
-/**
- * 系统日志分页VO
- */
-export interface LogVO {
- /** 主键 */
- id?: number;
- /** 日志模块 */
- module?: string;
- /** 日志内容 */
- content?: string;
- /** 请求路径 */
- requestUri?: string;
- /** 请求方法 */
- method?: string;
- /** IP 地址 */
- ip?: string;
- /** 地区 */
- region?: string;
- /** 浏览器 */
- browser?: string;
- /** 终端系统 */
- os?: string;
- /** 执行时间(毫秒) */
- executionTime?: number;
- /** 操作人 */
- operator?: string;
- /** 操作时间 */
- createTime?: string;
-}
-
-/** 访问趋势视图对象 */
-export interface VisitTrendVO {
- /** 日期列表 */
- dates: string[];
- /** 浏览量(PV) */
- pvList: number[];
- /** 访客数(UV) */
- uvList: number[];
- /** IP数 */
- ipList: number[];
-}
-
-/** 访问趋势查询参数 */
-export interface VisitTrendQuery {
- /** 开始日期 */
- startDate: string;
- /** 结束日期 */
- endDate: string;
-}
-
-/** 访问统计 */
-export interface VisitStatsVO {
- /** 今日访客数(UV) */
- todayUvCount: number;
- /** 总访客数 */
- totalUvCount: number;
- /** 访客数同比增长率(相对于昨天同一时间段的增长率) */
- uvGrowthRate: number;
- /** 今日浏览量(PV) */
- todayPvCount: number;
- /** 总浏览量 */
- totalPvCount: number;
- /** 同比增长率(相对于昨天同一时间段的增长率) */
- pvGrowthRate: number;
-}
diff --git a/src/api/system/menu.ts b/src/api/system/menu.ts
deleted file mode 100644
index 54424c2..0000000
--- a/src/api/system/menu.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import request from "@/utils/request";
-// 菜单基础URL
-const MENU_BASE_URL = "/api/v1/menus";
-
-const MenuAPI = {
- /**
- * 获取当前用户的路由列表
- *
- * 无需传入角色,后端解析token获取角色自行判断是否拥有路由的权限
- *
- * @returns 路由列表
- */
- getRoutes() {
- return request({
- url: `${MENU_BASE_URL}/routes`,
- method: "GET",
- });
- },
-
- /**
- * 获取菜单下拉数据源
- *
- * @returns 菜单下拉数据源
- */
- getOptions(onlyParent?: boolean) {
- return request({
- url: `${MENU_BASE_URL}/options`,
- method: "GET",
- data: { onlyParent: onlyParent },
- });
- },
-};
-
-export default MenuAPI;
-
-/** RouteVO,路由对象 */
-export interface RouteVO {
- /** 子路由列表 */
- children: RouteVO[];
- /** 组件路径 */
- component?: string;
- /** 路由属性 */
- meta?: Meta;
- /** 路由名称 */
- name?: string;
- /** 路由路径 */
- path?: string;
- /** 跳转链接 */
- redirect?: string;
-}
-
-/** Meta,路由属性 */
-export interface Meta {
- /** 【目录】只有一个子路由是否始终显示 */
- alwaysShow?: boolean;
- /** 是否隐藏(true-是 false-否) */
- hidden?: boolean;
- /** ICON */
- icon?: string;
- /** 【菜单】是否开启页面缓存 */
- keepAlive?: boolean;
- /** 路由title */
- title?: string;
-}
-
-/**
- * 组件数据源
- */
-interface OptionType {
- /** 值 */
- value: string | number;
- /** 文本 */
- label: string;
- /** 子列表 */
- children?: OptionType[];
-}
diff --git a/src/api/system/notice.ts b/src/api/system/notice.ts
deleted file mode 100644
index b876cc2..0000000
--- a/src/api/system/notice.ts
+++ /dev/null
@@ -1,201 +0,0 @@
-import request from "@/utils/request";
-
-const NOTICE_BASE_URL = "/api/v1/notices";
-
-const NoticeAPI = {
- /** 获取通知公告分页数据 */
- getPage(queryParams?: NoticePageQuery) {
- return request>({
- url: `${NOTICE_BASE_URL}/page`,
- method: "GET",
- data: queryParams,
- });
- },
-
- /**
- * 获取通知公告表单数据
- *
- * @param id NoticeID
- * @returns Notice表单数据
- */
- getFormData(id: number) {
- return request({
- url: `${NOTICE_BASE_URL}/${id}/form`,
- method: "GET",
- });
- },
-
- /**
- * 添加通知公告
- *
- * @param data Notice表单数据
- * @returns
- */
- add(data: NoticeForm) {
- return request({
- url: `${NOTICE_BASE_URL}`,
- method: "POST",
- data: data,
- });
- },
-
- /**
- * 更新通知公告
- *
- * @param id NoticeID
- * @param data Notice表单数据
- */
- update(id: number, data: NoticeForm) {
- return request({
- url: `${NOTICE_BASE_URL}/${id}`,
- method: "PUT",
- data: data,
- });
- },
-
- /**
- * 批量删除通知公告,多个以英文逗号(,)分割
- *
- * @param ids 通知公告ID字符串,多个以英文逗号(,)分割
- */
- deleteByIds(ids: string) {
- return request({
- url: `${NOTICE_BASE_URL}/${ids}`,
- method: "DELETE",
- });
- },
-
- /**
- * 发布通知
- *
- * @param id 被发布的通知公告id
- * @returns
- */
- publish(id: number) {
- return request({
- url: `${NOTICE_BASE_URL}/${id}/publish`,
- method: "PUT",
- });
- },
-
- /**
- * 撤回通知
- *
- * @param id 撤回的通知id
- * @returns
- */
- revoke(id: number) {
- return request({
- url: `${NOTICE_BASE_URL}/${id}/revoke`,
- method: "PUT",
- });
- },
- /**
- * 查看通知
- *
- * @param id
- */
- getDetail(id: string) {
- return request({
- url: `${NOTICE_BASE_URL}/${id}/detail`,
- method: "GET",
- });
- },
-
- /* 全部已读 */
- readAll() {
- return request({
- url: `${NOTICE_BASE_URL}/read-all`,
- method: "PUT",
- });
- },
-
- /** 获取我的通知分页列表 */
- getMyNoticePage(queryParams?: NoticePageQuery) {
- return request>({
- url: `${NOTICE_BASE_URL}/my-page`,
- method: "GET",
- data: queryParams,
- });
- },
-};
-
-export default NoticeAPI;
-
-/** 通知公告分页查询参数 */
-export interface NoticePageQuery extends PageQuery {
- /** 标题 */
- title?: string;
- /** 发布状态(0:未发布,1:已发布,-1:已撤回) */
- publishStatus?: number;
-
- isRead?: number;
-}
-
-/** 通知公告表单对象 */
-export interface NoticeForm {
- id?: number;
- /** 通知标题 */
- title?: string;
- /** 通知内容 */
- content?: string;
- /** 通知类型 */
- type?: number;
- /** 优先级(L:低,M:中,H:高) */
- level?: string;
- /** 目标类型(1-全体 2-指定) */
- targetType?: number;
- /** 目标ID合集,以,分割 */
- targetUserIds?: string;
-}
-
-/** 通知公告分页对象 */
-export interface NoticePageVO {
- id: string;
- /** 通知标题 */
- title?: string;
- /** 通知内容 */
- content?: string;
- /** 通知类型 */
- type?: number;
- /** 发布人 */
- publisherName?: string;
- /** 优先级(0-低 1-中 2-高) */
- priority?: number;
- /** 目标类型(0-全体 1-指定) */
- targetType?: number;
- /** 发布状态(0-未发布 1已发布 2已撤回) */
- publishStatus?: number;
- /** 发布时间 */
- publishTime?: Date;
- /** 撤回时间 */
- revokeTime?: Date;
- /** 优先级(L-低 M-中 H-高) */
- level?: string | number;
-}
-
-export interface NoticeDetailVO {
- /** 通知ID */
- id?: string;
-
- /** 通知标题 */
- title?: string;
-
- /** 通知内容 */
- content?: string;
-
- /** 通知类型 */
- type?: number;
-
- /** 发布人 */
- publisherName?: string;
-
- /** 优先级(L-低 M-中 H-高) */
- level?: string;
-
- /** 发布时间 */
- publishTime?: Date;
-
- /** 发布状态 */
- publishStatus?: number;
-}
diff --git a/src/api/system/role.ts b/src/api/system/role.ts
deleted file mode 100644
index 61868b0..0000000
--- a/src/api/system/role.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-import request from "@/utils/request";
-
-const ROLE_BASE_URL = "/api/v1/roles";
-
-const RoleAPI = {
- /** 获取角色分页数据 */
- getPage(queryParams?: RolePageQuery) {
- return request>({
- url: `${ROLE_BASE_URL}/page`,
- method: "GET",
- data: queryParams,
- });
- },
-
- /** 获取角色下拉数据源 */
- getOptions() {
- return request({
- url: `${ROLE_BASE_URL}/options`,
- method: "GET",
- });
- },
- /**
- * 获取角色的菜单ID集合
- *
- * @param roleId 角色ID
- * @returns 角色的菜单ID集合
- */
- getRoleMenuIds(roleId: number) {
- return request({
- url: `${ROLE_BASE_URL}/${roleId}/menuIds`,
- method: "GET",
- });
- },
-
- /**
- * 分配菜单权限
- *
- * @param roleId 角色ID
- * @param data 菜单ID集合
- */
- updateRoleMenus(roleId: number, data: number[]) {
- return request({
- url: `${ROLE_BASE_URL}/${roleId}/menus`,
- method: "PUT",
- data: data,
- });
- },
-
- /**
- * 获取角色表单数据
- *
- * @param id 角色ID
- * @returns 角色表单数据
- */
- getFormData(id: number) {
- return request({
- url: `${ROLE_BASE_URL}/${id}/form`,
- method: "GET",
- });
- },
-
- /** 添加角色 */
- add(data: RoleForm) {
- return request({
- url: `${ROLE_BASE_URL}`,
- method: "POST",
- data: data,
- });
- },
-
- /**
- * 更新角色
- *
- * @param id 角色ID
- * @param data 角色表单数据
- */
- update(id: number, data: RoleForm) {
- return request({
- url: `${ROLE_BASE_URL}/${id}`,
- method: "PUT",
- data: data,
- });
- },
-
- /**
- * 批量删除角色,多个以英文逗号(,)分割
- *
- * @param ids 角色ID字符串,多个以英文逗号(,)分割
- */
- deleteByIds(ids: string) {
- return request({
- url: `${ROLE_BASE_URL}/${ids}`,
- method: "DELETE",
- });
- },
-};
-
-export default RoleAPI;
-
-/** 角色分页查询参数 */
-export interface RolePageQuery extends PageQuery {
- /** 搜索关键字 */
- keywords?: string;
-}
-
-/** 角色分页对象 */
-export interface RolePageVO {
- /** 角色编码 */
- code?: string;
- /** 角色ID */
- id: number;
- /** 角色名称 */
- name?: string;
- /** 排序 */
- sort?: number;
- /** 角色状态 */
- status?: number;
- /** 创建时间 */
- createTime?: string;
- /** 修改时间 */
- updateTime?: string;
-}
-
-/** 角色表单对象 */
-export interface RoleForm {
- /** 角色ID */
- id?: number;
- /** 角色编码 */
- code?: string;
- /** 数据权限 */
- dataScope: number;
- /** 角色名称 */
- name?: string;
- /** 排序 */
- sort: number;
- /** 角色状态(1-正常;0-停用) */
- status?: number;
-}
diff --git a/src/api/system/user.ts b/src/api/user.ts
similarity index 89%
rename from src/api/system/user.ts
rename to src/api/user.ts
index c7cf28d..5167973 100644
--- a/src/api/system/user.ts
+++ b/src/api/user.ts
@@ -135,6 +135,15 @@ const UserAPI = {
method: "DELETE",
});
},
+
+ /** 获取微信手机号 */
+ getPhoneNumber(data: WechatPhoneData): Promise {
+ return request({
+ url: `${USER_BASE_URL}/wechat-phone`,
+ method: "POST",
+ data: data,
+ });
+ },
};
export default UserAPI;
@@ -297,7 +306,7 @@ export interface UserForm {
avatar?: string;
/** 部门ID */
deptId?: number;
- /** 邮箱 */
+ /** 用户邮箱 */
email?: string;
/** 性别 */
gender?: number;
@@ -314,3 +323,23 @@ export interface UserForm {
/** 用户名 */
username?: string;
}
+
+/** 微信手机号授权数据 */
+export interface WechatPhoneData {
+ /** 微信授权码 */
+ code: string;
+ /** 加密数据 */
+ encryptedData?: string;
+ /** 初始向量 */
+ iv?: string;
+}
+
+/** 手机号获取结果 */
+export interface PhoneNumberResult {
+ /** 手机号 */
+ phoneNumber: string;
+ /** 纯手机号(去除+86) */
+ purePhoneNumber?: string;
+ /** 国家代码 */
+ countryCode?: string;
+}
diff --git a/src/components/WechatProfile.vue b/src/components/WechatProfile.vue
new file mode 100644
index 0000000..a4bbbb3
--- /dev/null
+++ b/src/components/WechatProfile.vue
@@ -0,0 +1,216 @@
+
+
+
+
+ 头像
+
+
+
+
+
+ 昵称
+
+
+
+
+
+ 性别
+
+ 男
+ 女
+
+
+
+
+
+
+
+
diff --git a/src/composables/theme/rootTheme.ts b/src/composables/theme/rootTheme.ts
new file mode 100644
index 0000000..0d5bb3d
--- /dev/null
+++ b/src/composables/theme/rootTheme.ts
@@ -0,0 +1,22 @@
+import type { ConfigProviderThemeVars } from "wot-design-uni";
+/* 默认的主题list */
+export const colorColumns = [
+ {
+ value: "#0055FE",
+ label: "蓝色",
+ },
+ {
+ value: "#CD5C5C",
+ label: "红色",
+ },
+ {
+ value: "#228B22",
+ label: "绿色",
+ },
+];
+/* 默认的主题 */
+export const initThemState = "light";
+/* 默认的主题 */
+export const initThemeVars: ConfigProviderThemeVars = {
+ colorTheme: colorColumns[0].value,
+};
diff --git a/src/composables/theme/theme.ts b/src/composables/theme/theme.ts
new file mode 100644
index 0000000..983a135
--- /dev/null
+++ b/src/composables/theme/theme.ts
@@ -0,0 +1,74 @@
+import { colorColumns, initThemState, initThemeVars } from "./rootTheme";
+/* 暗黑模式切换 */
+const theme = ref<"light" | "dark">(initThemState);
+/* 组件库的主题色 */
+const themeVars = ref({ ...initThemeVars });
+
+// 从本地存储恢复主题设置
+function loadThemeFromStorage() {
+ try {
+ const savedTheme = uni.getStorageSync("app_theme_mode");
+ const savedThemeColor = uni.getStorageSync("app_theme_color");
+
+ if (savedTheme && (savedTheme === "light" || savedTheme === "dark")) {
+ theme.value = savedTheme;
+ }
+
+ if (savedThemeColor) {
+ themeVars.value.colorTheme = savedThemeColor;
+ }
+ } catch (error) {
+ console.error("加载主题设置失败:", error);
+ }
+}
+
+// 保存主题设置到本地存储
+function saveThemeToStorage() {
+ try {
+ uni.setStorageSync("app_theme_mode", theme.value);
+ uni.setStorageSync("app_theme_color", themeVars.value.colorTheme);
+ } catch (error) {
+ console.error("保存主题设置失败:", error);
+ }
+}
+
+export function useTheme() {
+ /* 切换暗黑模式 */
+ function toggleTheme(mode?: "light" | "dark") {
+ theme.value = mode || (theme.value === "light" ? "dark" : "light");
+ setNavigationBarColor();
+ saveThemeToStorage();
+ }
+
+ /* 设置主题色 */
+ function setThemeColor(color: string) {
+ themeVars.value.colorTheme = color;
+ saveThemeToStorage();
+ }
+
+ /* 初始化theme */
+ function initTheme() {
+ loadThemeFromStorage();
+ setNavigationBarColor();
+ }
+
+ function setNavigationBarColor() {
+ uni.setNavigationBarColor({
+ frontColor: theme.value === "light" ? "#000000" : "#ffffff",
+ backgroundColor: theme.value === "light" ? "#ffffff" : "#000000",
+ animation: {
+ duration: 400,
+ timingFunc: "easeIn",
+ },
+ });
+ }
+
+ return {
+ theme,
+ themeVars,
+ initTheme,
+ colorColumns,
+ toggleTheme,
+ setThemeColor,
+ };
+}
diff --git a/src/composables/useStomp.ts b/src/composables/useStomp.ts
new file mode 100644
index 0000000..c2b8912
--- /dev/null
+++ b/src/composables/useStomp.ts
@@ -0,0 +1,368 @@
+import { Client, type IMessage, type StompSubscription } from "@stomp/stompjs";
+import { Auth } from "@/utils/auth";
+
+export interface UseStompOptions {
+ /** WebSocket 地址,不传时使用 VITE_APP_WS_ENDPOINT 环境变量 */
+ brokerURL?: string;
+ /** 用于鉴权的 token,不传时使用 getAccessToken() 的返回值 */
+ token?: string;
+ /** 重连延迟,单位毫秒,默认为 8000 */
+ reconnectDelay?: number;
+ /** 连接超时时间,单位毫秒,默认为 10000 */
+ connectionTimeout?: number;
+ /** 是否开启指数退避重连策略 */
+ useExponentialBackoff?: boolean;
+ /** 最大重连次数,默认为 5 */
+ maxReconnectAttempts?: number;
+ /** 最大重连延迟,单位毫秒,默认为 60000 */
+ maxReconnectDelay?: number;
+ /** 是否开启调试日志 */
+ debug?: boolean;
+}
+
+/**
+ * STOMP WebSocket连接组合式函数
+ * 用于管理WebSocket连接的建立、断开、重连和消息订阅
+ */
+export function useStomp(options: UseStompOptions = {}) {
+ // 默认值:brokerURL 从环境变量中获取,token 从 getAccessToken() 获取
+ const defaultBrokerURL = import.meta.env.VITE_APP_WS_ENDPOINT || "";
+
+ const brokerURL = ref(options.brokerURL ?? defaultBrokerURL);
+ // 默认配置参数
+ const reconnectDelay = options.reconnectDelay ?? 15000; // 默认15秒重连间隔
+ const connectionTimeout = options.connectionTimeout ?? 10000;
+ const useExponentialBackoff = options.useExponentialBackoff ?? false;
+ const maxReconnectAttempts = options.maxReconnectAttempts ?? 3; // 最多重连3次
+ const maxReconnectDelay = options.maxReconnectDelay ?? 60000;
+
+ // 连接状态标记
+ const isConnected = ref(false);
+ // 重连尝试次数
+ const reconnectCount = ref(0);
+ // 重连计时器
+ let reconnectTimer: any = null;
+ // 连接超时计时器
+ let connectionTimeoutTimer: any = null;
+ // 存储所有订阅
+ const subscriptions = new Map();
+
+ // 用于保存 STOMP 客户端的实例
+ const client = ref(null);
+ // 防止重复连接的标志
+ let isConnecting = false;
+ let isManualDisconnect = false;
+
+ /**
+ * 初始化 STOMP 客户端
+ */
+ const initializeClient = () => {
+ // 如果客户端已存在且正在连接或已连接,直接返回
+ if (client.value && (client.value.active || client.value.connected)) {
+ console.log("STOMP客户端已存在且处于活动状态,跳过初始化");
+ return;
+ }
+
+ // 检查WebSocket端点是否配置
+ if (!brokerURL.value) {
+ console.error("WebSocket连接失败: 未配置WebSocket端点URL");
+ return;
+ }
+
+ // 每次连接前重新获取最新令牌,不依赖之前的token值
+ const currentToken = Auth.getAccessToken();
+
+ // 检查令牌是否为空,如果为空则不进行连接
+ if (!currentToken) {
+ console.error("WebSocket连接失败:授权令牌为空,请先登录");
+ return;
+ }
+
+ // 如果有旧的客户端,先清理
+ if (client.value) {
+ try {
+ client.value.deactivate();
+ } catch (error) {
+ console.warn("清理旧客户端时出错:", error);
+ }
+ client.value = null;
+ }
+
+ // 创建 STOMP 客户端
+ client.value = new Client({
+ brokerURL: brokerURL.value,
+ connectHeaders: {
+ Authorization: `Bearer ${currentToken}`,
+ },
+ debug: options.debug ? console.log : () => {},
+ reconnectDelay: 0, // 禁用内置重连机制,使用自定义重连
+ heartbeatIncoming: 4000,
+ heartbeatOutgoing: 4000,
+ });
+
+ // 设置连接监听器
+ client.value.onConnect = () => {
+ isConnected.value = true;
+ isConnecting = false;
+ reconnectCount.value = 0;
+ clearTimeout(connectionTimeoutTimer);
+ clearTimeout(reconnectTimer);
+ console.log("WebSocket连接已建立");
+ };
+
+ // 设置断开连接监听器
+ client.value.onDisconnect = () => {
+ isConnected.value = false;
+ isConnecting = false;
+ console.log("WebSocket连接已断开");
+
+ // 如果不是手动断开且未达到最大重连次数,则尝试重连
+ if (!isManualDisconnect && reconnectCount.value < maxReconnectAttempts) {
+ handleReconnect();
+ }
+ };
+
+ // 设置 Web Socket 关闭监听器
+ client.value.onWebSocketClose = (event) => {
+ isConnected.value = false;
+ isConnecting = false;
+ console.log(`WebSocket已关闭: ${event?.code} ${event?.reason}`);
+
+ // 如果是手动断开,不要重连
+ if (isManualDisconnect) {
+ console.log("手动断开连接,不进行重连");
+ return;
+ }
+
+ // 如果是授权问题导致的关闭,尝试重连
+ if (
+ (event?.code === 1000 || event?.code === 1006 || event?.code === 1008) &&
+ reconnectCount.value < maxReconnectAttempts
+ ) {
+ console.log("检测到连接异常关闭,将尝试重连");
+
+ // 通过 handleReconnect 统一处理重连,避免重复计数
+ handleReconnect();
+ }
+ };
+
+ // 设置错误监听器
+ client.value.onStompError = (frame) => {
+ console.error("STOMP错误:", frame.headers, frame.body);
+ isConnecting = false;
+
+ // 检查是否是授权错误
+ if (
+ frame.headers?.message?.includes("Unauthorized") ||
+ frame.body?.includes("Unauthorized") ||
+ frame.body?.includes("Token")
+ ) {
+ console.warn("WebSocket授权错误,请检查登录状态");
+ // 授权错误不进行重连
+ isManualDisconnect = true;
+ }
+ };
+ };
+
+ /**
+ * 处理重连逻辑
+ */
+ const handleReconnect = () => {
+ // 如果已经在连接中或手动断开,不重连
+ if (isConnecting || isManualDisconnect) {
+ return;
+ }
+
+ if (reconnectCount.value >= maxReconnectAttempts) {
+ console.error(`已达到最大重连次数(${maxReconnectAttempts}),停止重连`);
+ return;
+ }
+
+ reconnectCount.value++;
+ console.log(`准备重连(${reconnectCount.value}/${maxReconnectAttempts})...`);
+
+ // 使用指数退避策略增加重连间隔
+ const delay = useExponentialBackoff
+ ? Math.min(reconnectDelay * Math.pow(2, reconnectCount.value - 1), maxReconnectDelay)
+ : reconnectDelay;
+
+ // 清除之前的计时器
+ if (reconnectTimer) {
+ clearTimeout(reconnectTimer);
+ }
+
+ // 设置重连计时器
+ reconnectTimer = setTimeout(() => {
+ if (!isConnected.value && !isManualDisconnect && !isConnecting) {
+ console.log(`开始重连...`);
+ connect();
+ }
+ }, delay);
+ };
+
+ // 监听 brokerURL 的变化,若地址改变则重新初始化
+ watch(brokerURL, (newURL, oldURL) => {
+ if (newURL !== oldURL) {
+ console.log(`brokerURL changed from ${oldURL} to ${newURL}`);
+ // 断开当前连接,重新激活客户端
+ if (client.value && client.value.connected) {
+ client.value.deactivate();
+ }
+ brokerURL.value = newURL;
+ initializeClient(); // 重新初始化客户端
+ }
+ });
+
+ // 初始化客户端
+ initializeClient();
+
+ /**
+ * 激活连接(如果已经连接或正在激活则直接返回)
+ */
+ const connect = () => {
+ // 重置手动断开标志
+ isManualDisconnect = false;
+
+ // 检查是否有配置WebSocket端点
+ if (!brokerURL.value) {
+ console.error("WebSocket连接失败: 未配置WebSocket端点URL");
+ return;
+ }
+
+ // 防止重复连接
+ if (isConnecting) {
+ console.log("WebSocket正在连接中,跳过重复连接请求");
+ return;
+ }
+
+ if (!client.value) {
+ initializeClient();
+ }
+
+ if (!client.value) {
+ console.error("STOMP客户端初始化失败");
+ return;
+ }
+
+ // 避免重复连接:检查是否已连接
+ if (client.value.connected) {
+ console.log("WebSocket已经连接,跳过重复连接");
+ isConnected.value = true;
+ return;
+ }
+
+ // 设置连接标志
+ isConnecting = true;
+
+ // 设置连接超时
+ clearTimeout(connectionTimeoutTimer);
+ connectionTimeoutTimer = setTimeout(() => {
+ if (!isConnected.value && isConnecting) {
+ console.warn("WebSocket连接超时");
+ isConnecting = false;
+ if (!isManualDisconnect && reconnectCount.value < maxReconnectAttempts) {
+ handleReconnect();
+ }
+ }
+ }, connectionTimeout);
+
+ try {
+ client.value.activate();
+ console.log("正在建立WebSocket连接...");
+ } catch (error) {
+ console.error("激活WebSocket连接失败:", error);
+ isConnecting = false;
+ }
+ };
+
+ /**
+ * 订阅指定主题
+ * @param destination 目标主题地址
+ * @param callback 接收到消息时的回调函数
+ * @returns 返回订阅 id,用于后续取消订阅
+ */
+ const subscribe = (destination: string, callback: (_message: IMessage) => void): string => {
+ if (!client.value || !client.value.connected) {
+ console.warn(`尝试订阅 ${destination} 失败: 客户端未连接`);
+ return "";
+ }
+
+ try {
+ const subscription = client.value.subscribe(destination, callback);
+ const subscriptionId = subscription.id;
+ subscriptions.set(subscriptionId, subscription);
+ console.log(`订阅成功: ${destination}, ID: ${subscriptionId}`);
+ return subscriptionId;
+ } catch (error) {
+ console.error(`订阅 ${destination} 失败:`, error);
+ return "";
+ }
+ };
+
+ /**
+ * 取消订阅
+ * @param subscriptionId 订阅 id
+ */
+ const unsubscribe = (subscriptionId: string) => {
+ const subscription = subscriptions.get(subscriptionId);
+ if (subscription) {
+ subscription.unsubscribe();
+ subscriptions.delete(subscriptionId);
+ console.log(`已取消订阅: ${subscriptionId}`);
+ }
+ };
+
+ /**
+ * 断开WebSocket连接
+ */
+ const disconnect = () => {
+ // 设置手动断开标志
+ isManualDisconnect = true;
+
+ // 清除所有计时器
+ if (reconnectTimer) {
+ clearTimeout(reconnectTimer);
+ reconnectTimer = null;
+ }
+
+ if (connectionTimeoutTimer) {
+ clearTimeout(connectionTimeoutTimer);
+ connectionTimeoutTimer = null;
+ }
+
+ // 清除所有订阅
+ for (const [id, subscription] of subscriptions.entries()) {
+ try {
+ subscription.unsubscribe();
+ } catch (error) {
+ console.warn(`取消订阅 ${id} 时出错:`, error);
+ }
+ }
+ subscriptions.clear();
+
+ // 断开连接
+ if (client.value) {
+ try {
+ if (client.value.connected || client.value.active) {
+ client.value.deactivate();
+ console.log("WebSocket连接已主动断开");
+ }
+ } catch (error) {
+ console.error("断开WebSocket连接时出错:", error);
+ }
+ client.value = null;
+ }
+
+ isConnected.value = false;
+ isConnecting = false;
+ reconnectCount.value = 0;
+ };
+
+ return {
+ isConnected,
+ connect,
+ subscribe,
+ unsubscribe,
+ disconnect,
+ };
+}
diff --git a/src/composables/useTabbar.ts b/src/composables/useTabbar.ts
new file mode 100644
index 0000000..27179b9
--- /dev/null
+++ b/src/composables/useTabbar.ts
@@ -0,0 +1,61 @@
+/*
+ * @Author: weisheng
+ * @Date: 2024-10-29 22:12:54
+ * @LastEditTime: 2025-01-13 16:45:28
+ * @LastEditors: 810505339
+ * @Description:
+ * @FilePath: \wot-demo\src\composables\useTabbar.ts
+ * 记得注释
+ */
+export interface TabbarItem {
+ name: string;
+ value: number | null;
+ active: boolean;
+ title: string;
+ icon: string;
+}
+
+const tabbarItems = ref([
+ { name: "home", value: null, active: true, title: "home", icon: "home" },
+ { name: "hi", value: null, active: false, title: "hi", icon: "app" },
+ { name: "setting", value: null, active: false, title: "setting", icon: "setting" },
+]);
+
+export function useTabbar() {
+ const tabbarList = computed(() => tabbarItems.value);
+
+ const activeTabbar = computed(() => {
+ const item = tabbarItems.value.find((item) => item.active);
+ return item || tabbarItems.value[0];
+ });
+
+ const getTabbarItemValue = (name: string) => {
+ const item = tabbarItems.value.find((item) => item.name === name);
+ return item && item.value ? item.value : null;
+ };
+
+ const setTabbarItem = (name: string, value: number) => {
+ const tabbarItem = tabbarItems.value.find((item) => item.name === name);
+ if (tabbarItem) {
+ tabbarItem.value = value;
+ }
+ };
+
+ const setTabbarItemActive = (name: string) => {
+ tabbarItems.value.forEach((item) => {
+ if (item.name === name) {
+ item.active = true;
+ } else {
+ item.active = false;
+ }
+ });
+ };
+
+ return {
+ tabbarList,
+ activeTabbar,
+ getTabbarItemValue,
+ setTabbarItem,
+ setTabbarItemActive,
+ };
+}
diff --git a/src/constants/index.ts b/src/constants/index.ts
new file mode 100644
index 0000000..f8be9d9
--- /dev/null
+++ b/src/constants/index.ts
@@ -0,0 +1,4 @@
+export const ROLE_ROOT = "ROOT";
+
+// 🔗 导出所有存储键常量
+export * from "./storage-keys";
diff --git a/src/constants/storage-keys.ts b/src/constants/storage-keys.ts
new file mode 100644
index 0000000..6567efa
--- /dev/null
+++ b/src/constants/storage-keys.ts
@@ -0,0 +1,11 @@
+/**
+ * 存储键常量统一管理
+ * 包括 localStorage、sessionStorage 等各种存储的键名
+ */
+
+// 🔐 用户认证相关
+export const ACCESS_TOKEN_KEY = "access_token";
+export const REFRESH_TOKEN_KEY = "refresh_token";
+
+// 📊 用户缓存相关
+export const USER_INFO_KEY = "user_info";
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
deleted file mode 100644
index abab537..0000000
--- a/src/hooks/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * 全局Hooks入口文件
- * 导出所有可用的Hooks
- */
-
-// 导出WebSocket相关Hook
-export * from "./websocket";
diff --git a/src/hooks/websocket/core/useStomp.ts b/src/hooks/websocket/core/useStomp.ts
deleted file mode 100644
index 76bcea2..0000000
--- a/src/hooks/websocket/core/useStomp.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-import { getToken as getAccessToken } from "@/utils/cache";
-
-export interface UseStompOptions {
- /** WebSocket 地址,不传时使用 VITE_APP_WS_ENDPOINT 环境变量 */
- brokerURL?: string;
- /** 重连延迟,单位毫秒,默认为 8000 */
- reconnectDelay?: number;
- /** 连接超时时间,单位毫秒,默认为 10000 */
- connectionTimeout?: number;
- /** 是否开启指数退避重连策略 */
- useExponentialBackoff?: boolean;
- /** 最大重连次数,默认为 5 */
- maxReconnectAttempts?: number;
- /** 最大重连延迟,单位毫秒,默认为 60000 */
- maxReconnectDelay?: number;
- /** 是否开启调试日志 */
- debug?: boolean;
-}
-
-/**
- * STOMP WebSocket连接Hook (UniApp版本)
- * 用于管理WebSocket连接的建立、断开、重连和消息订阅
- */
-export function useStomp(options: UseStompOptions = {}) {
- // 默认配置
- const defaultBrokerURL = import.meta.env.VITE_APP_WS_ENDPOINT || "";
- const brokerURL = ref(options.brokerURL ?? defaultBrokerURL);
- const reconnectDelay = options.reconnectDelay ?? 8000;
- const connectionTimeout = options.connectionTimeout ?? 10000;
- const useExponentialBackoff = options.useExponentialBackoff ?? false;
- const maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
- const maxReconnectDelay = options.maxReconnectDelay ?? 60000;
-
- // 连接状态和计数
- const isConnected = ref(false);
- const reconnectCount = ref(0);
- let reconnectTimer: number | null = null;
- let connectionTimeoutTimer: number | null = null;
-
- // 存储所有订阅
- const subscriptions = ref>({});
-
- // WebSocket实例
- let socketTask: any = null;
- const client = ref(null);
-
- /**
- * 创建WebSocket连接
- */
- const createSocketConnection = () => {
- const token = getAccessToken();
- if (!token) {
- console.error("WebSocket连接失败:未找到有效token");
- return null;
- }
-
- // 创建WebSocket连接
- try {
- // 构建带有token的URL
- let url = brokerURL.value;
- if (url.indexOf("?") > -1) {
- url += "&token=" + token;
- } else {
- url += "?token=" + token;
- }
-
- // 创建WebSocket连接
- socketTask = uni.connectSocket({
- url: url,
- complete: () => {},
- });
-
- if (!socketTask) {
- console.error("WebSocket连接创建失败");
- return null;
- }
-
- // 设置WebSocket事件处理函数
- socketTask.onOpen(() => {
- isConnected.value = true;
- reconnectCount.value = 0;
- if (connectionTimeoutTimer) clearTimeout(connectionTimeoutTimer);
- console.log("WebSocket连接已建立");
- });
-
- socketTask.onClose(() => {
- isConnected.value = false;
- console.log("WebSocket连接已关闭");
-
- // 如果使用指数退避重连策略,则处理重连
- if (useExponentialBackoff && reconnectCount.value < maxReconnectAttempts) {
- handleReconnect();
- }
- });
-
- socketTask.onError((error: any) => {
- console.error("WebSocket连接错误:", error);
- });
-
- socketTask.onMessage((res: any) => {
- const message = JSON.parse(res.data);
- // 处理订阅消息
- if (message.subscription && subscriptions.value[message.subscription]) {
- const subscription = subscriptions.value[message.subscription];
- if (subscription.callback) {
- subscription.callback(message);
- }
- }
- });
-
- return socketTask;
- } catch (error) {
- console.error("创建WebSocket连接时出错:", error);
- return null;
- }
- };
-
- /**
- * 处理重连逻辑
- */
- const handleReconnect = () => {
- if (reconnectCount.value >= maxReconnectAttempts) {
- console.error(`已达到最大重连次数(${maxReconnectAttempts}),停止重连`);
- return;
- }
-
- reconnectCount.value++;
- console.log(`尝试重连(${reconnectCount.value}/${maxReconnectAttempts})...`);
-
- // 使用指数退避策略
- const delay = useExponentialBackoff
- ? Math.min(reconnectDelay * Math.pow(2, reconnectCount.value - 1), maxReconnectDelay)
- : reconnectDelay;
-
- // 清除之前的计时器
- if (reconnectTimer) {
- clearTimeout(reconnectTimer);
- }
-
- // 设置重连计时器
- reconnectTimer = setTimeout(() => {
- if (!isConnected.value) {
- connect();
- }
- }, delay) as unknown as number;
- };
-
- /**
- * 建立WebSocket连接
- */
- const connect = () => {
- if (isConnected.value) {
- return;
- }
-
- // 创建WebSocket连接
- socketTask = createSocketConnection();
- client.value = socketTask;
-
- // 设置连接超时
- if (connectionTimeoutTimer) {
- clearTimeout(connectionTimeoutTimer);
- }
-
- connectionTimeoutTimer = setTimeout(() => {
- if (!isConnected.value) {
- console.warn("WebSocket连接超时");
- if (useExponentialBackoff) {
- handleReconnect();
- }
- }
- }, connectionTimeout) as unknown as number;
- };
-
- /**
- * 订阅主题
- * @param destination 主题地址
- * @param callback 回调函数
- * @returns 订阅ID
- */
- const subscribe = (destination: string, callback: (message: any) => void): string => {
- if (!socketTask || !isConnected.value) {
- console.warn("WebSocket未连接,无法订阅:", destination);
- return "";
- }
-
- // 生成唯一订阅ID
- const subscriptionId = "sub-" + Math.random().toString(36).substr(2, 9);
-
- // 发送订阅消息
- socketTask.send({
- data: JSON.stringify({
- command: "SUBSCRIBE",
- headers: {
- id: subscriptionId,
- destination: destination,
- },
- }),
- success: () => {
- console.log(`订阅成功: ${destination}, ID: ${subscriptionId}`);
- },
- fail: (err: any) => {
- console.error(`订阅失败(${destination}):`, err);
- },
- });
-
- // 保存订阅
- subscriptions.value[subscriptionId] = {
- destination,
- callback,
- };
-
- return subscriptionId;
- };
-
- /**
- * 取消订阅
- * @param subscriptionId 订阅ID
- */
- const unsubscribe = (subscriptionId: string) => {
- if (!socketTask || !isConnected.value || !subscriptions.value[subscriptionId]) {
- return;
- }
-
- try {
- // 发送取消订阅消息
- socketTask.send({
- data: JSON.stringify({
- command: "UNSUBSCRIBE",
- headers: {
- id: subscriptionId,
- },
- }),
- success: () => {
- console.log(`已取消订阅: ${subscriptionId}`);
- },
- fail: (err: any) => {
- console.error(`取消订阅失败(${subscriptionId}):`, err);
- },
- });
- } catch (error) {
- console.error(`取消订阅失败(${subscriptionId}):`, error);
- } finally {
- Reflect.deleteProperty(subscriptions.value, subscriptionId);
- }
- };
-
- /**
- * 断开WebSocket连接
- */
- const disconnect = () => {
- if (!socketTask) {
- return;
- }
-
- // 取消所有订阅
- Object.keys(subscriptions.value).forEach(unsubscribe);
-
- // 断开WebSocket连接
- try {
- socketTask.close({
- success: () => {
- console.log("WebSocket连接已断开");
- isConnected.value = false;
- socketTask = null;
- },
- fail: (err: any) => {
- console.error("断开WebSocket连接失败:", err);
- },
- });
- } catch (error) {
- console.error("断开WebSocket连接失败:", error);
- }
- };
-
- // 返回公开的API
- return {
- isConnected,
- client,
- connect,
- disconnect,
- subscribe,
- unsubscribe,
- };
-}
diff --git a/src/hooks/websocket/index.ts b/src/hooks/websocket/index.ts
deleted file mode 100644
index acc9aa0..0000000
--- a/src/hooks/websocket/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * WebSocket相关Hook入口文件
- * 统一导出所有WebSocket相关Hook
- */
-
-// 核心基础Hook
-export { useStomp } from "./core/useStomp";
-
-// 业务服务Hook
-export { useDictSync } from "./services/useDictSync";
diff --git a/src/hooks/websocket/services/useDictSync.ts b/src/hooks/websocket/services/useDictSync.ts
deleted file mode 100644
index e2e343b..0000000
--- a/src/hooks/websocket/services/useDictSync.ts
+++ /dev/null
@@ -1,188 +0,0 @@
-import { useDictStore } from "@/store/modules/dict";
-import { useStomp } from "../core/useStomp";
-import { ref } from "vue";
-
-// 字典消息类型
-export interface DictMessage {
- dictCode: string;
- timestamp: number;
-}
-
-// 字典事件回调类型
-export type DictMessageCallback = (message: DictMessage) => void;
-
-// 全局单例实例
-let instance: ReturnType | null = null;
-
-/**
- * 创建字典同步Hook
- * 负责监听后端字典变更并同步到前端
- */
-function createDictSyncHook() {
- const dictStore = useDictStore();
-
- // 使用现有的useStomp,配置适合字典场景的重连参数
- const { isConnected, connect, subscribe, unsubscribe, disconnect } = useStomp({
- reconnectDelay: 10000, // 使用更长的重连延迟 - 10秒
- connectionTimeout: 15000, // 更长的连接超时时间 - 15秒
- useExponentialBackoff: false, // 字典数据不需要指数退避策略
- });
-
- // 存储订阅ID
- const subscriptionIds = ref([]);
-
- // 已订阅的主题
- const subscribedTopics = ref>(new Set());
-
- // 消息回调函数列表
- const messageCallbacks = ref([]);
-
- /**
- * 注册字典消息回调
- * @param callback 回调函数
- */
- const onDictMessage = (callback: DictMessageCallback) => {
- messageCallbacks.value.push(callback);
- return () => {
- // 返回取消注册的函数
- const index = messageCallbacks.value.indexOf(callback);
- if (index !== -1) {
- messageCallbacks.value.splice(index, 1);
- }
- };
- };
-
- /**
- * 初始化WebSocket
- */
- const initWebSocket = async () => {
- try {
- // 连接WebSocket
- connect();
-
- // 设置字典订阅
- setupDictSubscription();
- } catch (error) {
- console.error("[WebSocket] 初始化失败:", error);
- }
- };
-
- /**
- * 关闭WebSocket
- */
- const closeWebSocket = () => {
- // 取消所有订阅
- subscriptionIds.value.forEach((id) => {
- unsubscribe(id);
- });
- subscriptionIds.value = [];
- subscribedTopics.value.clear();
-
- // 断开连接
- disconnect();
- };
-
- /**
- * 设置字典订阅
- */
- const setupDictSubscription = () => {
- const topic = "/topic/dict";
-
- // 防止重复订阅
- if (subscribedTopics.value.has(topic)) {
- console.log(`跳过重复订阅: ${topic}`);
- return;
- }
-
- console.log(`开始尝试订阅字典主题: ${topic}`);
-
- // 使用简化的重试逻辑,依赖useStomp的连接管理
- const attemptSubscribe = () => {
- if (!isConnected.value) {
- console.log("等待WebSocket连接建立...");
- // 3秒后再次尝试
- setTimeout(attemptSubscribe, 3000);
- return;
- }
-
- // 检查是否已订阅
- if (subscribedTopics.value.has(topic)) {
- return;
- }
-
- console.log(`连接已建立,开始订阅: ${topic}`);
-
- // 订阅字典更新
- const subId = subscribe(topic, (message: any) => {
- handleDictEvent(message);
- });
-
- if (subId) {
- subscriptionIds.value.push(subId);
- subscribedTopics.value.add(topic);
- console.log(`字典主题订阅成功: ${topic}`);
- } else {
- console.warn(`字典主题订阅失败: ${topic}`);
- }
- };
-
- // 开始尝试订阅
- attemptSubscribe();
- };
-
- /**
- * 处理字典事件
- * @param message STOMP消息
- */
- const handleDictEvent = (message: any) => {
- if (!message.body) return;
-
- try {
- // 记录接收到的消息
- console.log(`收到字典更新消息: ${message.body}`);
-
- // 尝试解析消息
- const parsedData = JSON.parse(message.body) as DictMessage;
- const dictCode = parsedData.dictCode;
-
- if (!dictCode) return;
-
- // 清除缓存,等待按需加载
- dictStore.removeDictItem(dictCode);
- console.log(`字典缓存已清除: ${dictCode}`);
-
- // 调用所有注册的回调函数
- messageCallbacks.value.forEach((callback) => {
- try {
- callback(parsedData);
- } catch (callbackError) {
- console.error("[WebSocket] 回调执行失败:", callbackError);
- }
- });
-
- // 显示提示消息
- console.info(`字典 ${dictCode} 已变更,将在下次使用时自动加载`);
- } catch (error) {
- console.error("[WebSocket] 解析消息失败:", error);
- }
- };
-
- return {
- isConnected,
- initWebSocket,
- closeWebSocket,
- handleDictEvent,
- onDictMessage,
- };
-}
-
-/**
- * 字典同步Hook
- * 用于监听后端字典变更并同步到前端
- */
-export function useDictSync() {
- if (!instance) {
- instance = createDictSyncHook();
- }
- return instance;
-}
diff --git a/src/layouts/default.vue b/src/layouts/default.vue
new file mode 100644
index 0000000..2d5d262
--- /dev/null
+++ b/src/layouts/default.vue
@@ -0,0 +1,4 @@
+
+ 默认布局
+
+
diff --git a/src/layouts/tabbar.vue b/src/layouts/tabbar.vue
new file mode 100644
index 0000000..b022b8a
--- /dev/null
+++ b/src/layouts/tabbar.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages.json b/src/pages.json
index 1abf054..eb3f26d 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -1,170 +1,65 @@
{
- "easycom": {
- "autoscan": true,
- "custom": {
- "^wd-(.*)": "wot-design-uni/components/wd-$1/wd-$1.vue",
- "^cu-(.*)": "@/components/cu-$1/index.vue"
- }
- },
-
"pages": [
{
"path": "pages/index/index",
- "style": {
- "navigationStyle": "custom"
- }
+ "type": "home"
},
{
- "path": "pages/work/index",
- "style": {
- "navigationBarTitleText": "工作台"
- }
- },
- {
- "path": "pages/mine/index",
- "style": {
- "navigationBarTitleText": "我的"
- }
- },
- {
- "path": "pages/mine/about/index",
- "style": {
- "navigationBarTitleText": "关于我们"
- }
- },
- {
- "path": "pages/mine/faq/index",
- "style": {
- "navigationBarTitleText": "常见问题"
- }
- },
- {
- "path": "pages/mine/feedback/index",
- "style": {
- "navigationBarTitleText": "问题反馈"
- }
- },
- {
- "path": "pages/mine/settings/index",
- "style": {
- "navigationBarTitleText": "设置"
- }
- },
- {
- "path": "pages/mine/settings/account/index",
- "style": {
- "navigationBarTitleText": "账号和安全"
- }
- },
- {
- "path": "pages/mine/settings/agreement/index",
- "style": {
- "navigationBarTitleText": "用户协议"
- }
- },
- {
- "path": "pages/mine/settings/privacy/index",
- "style": {
- "navigationBarTitleText": "隐私政策"
- }
- },
- {
- "path": "pages/mine/settings/theme/index",
- "style": {
- "navigationBarTitleText": "主题设置"
- }
- },
- {
- "path": "pages/mine/settings/network/index",
- "style": {
- "navigationBarTitleText": "网络检测"
- }
+ "path": "pages/login/complete-profile",
+ "type": "page"
},
{
"path": "pages/login/index",
- "style": {
- "navigationBarTitleText": "登录"
- }
+ "type": "page"
+ },
+ {
+ "path": "pages/mine/index",
+ "type": "page"
+ },
+ {
+ "path": "pages/mine/about/index",
+ "type": "page"
+ },
+ {
+ "path": "pages/mine/faq/index",
+ "type": "page"
+ },
+ {
+ "path": "pages/mine/feedback/index",
+ "type": "page"
},
{
"path": "pages/mine/profile/index",
- "style": {
- "navigationBarTitleText": "个人资料"
- }
- },
-
- {
- "path": "pages/work/user/index",
- "style": {
- "navigationStyle": "custom"
- }
+ "type": "page"
},
{
- "path": "pages/work/log/index",
- "style": {
- "navigationBarTitleText": "日志管理"
- }
+ "path": "pages/mine/settings/index",
+ "type": "page"
},
{
- "path": "pages/work/config/index",
- "style": {
- "navigationBarTitleText": "系统配置"
- }
+ "path": "pages/mine/settings/account/index",
+ "type": "page"
},
{
- "path": "pages/work/notice/index",
- "style": {
- "navigationBarTitleText": "通知公告"
- }
+ "path": "pages/mine/settings/agreement/index",
+ "type": "page"
},
{
- "path": "pages/work/notice/detail",
- "style": {
- "navigationBarTitleText": "通知详情"
- }
+ "path": "pages/mine/settings/network/index",
+ "type": "page"
},
{
- "path": "pages/work/role/index",
- "style": {
- "navigationBarTitleText": "角色管理"
- }
+ "path": "pages/mine/settings/privacy/index",
+ "type": "page"
},
{
- "path": "pages/work/role/assign-perm",
- "style": {
- "navigationBarTitleText": "角色分配权限"
- }
+ "path": "pages/mine/settings/theme/index",
+ "type": "page"
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
- "navigationBarTitleText": "vue-uniapp-template",
- "navigationBarBackgroundColor": "#F8F8F8",
- "backgroundColor": "#F8F8F8"
+ "navigationBarTitleText": "@uni-helper"
},
- "tabBar": {
- "color": "#474747",
- "selectedColor": "#3B8DFF",
- "backgroundColor": "#F8F8F8",
- "list": [
- {
- "pagePath": "pages/index/index",
- "text": "首页",
- "iconPath": "static/tabbar/home.png",
- "selectedIconPath": "static/tabbar/home-active.png"
- },
- {
- "pagePath": "pages/work/index",
- "text": "工作台",
- "iconPath": "static/tabbar/work.png",
- "selectedIconPath": "static/tabbar/work-active.png"
- },
- {
- "pagePath": "pages/mine/index",
- "text": "我的",
- "iconPath": "static/tabbar/mine.png",
- "selectedIconPath": "static/tabbar/mine-active.png"
- }
- ]
- }
-}
+ "subPackages": []
+}
\ No newline at end of file
diff --git a/src/pages/login/complete-profile.vue b/src/pages/login/complete-profile.vue
new file mode 100644
index 0000000..bcce91a
--- /dev/null
+++ b/src/pages/login/complete-profile.vue
@@ -0,0 +1,512 @@
+
+
+
+
+
+
+
+
+
+
+
+ 基本信息
+
+
+
+
+
+
+
+
+ 头像
+
+
+
+ 点击上传头像
+
+
+
+
+
+
+
+
+ 昵称
+ *
+
+
+
+
+
+
+ 性别
+
+ 男
+ 女
+
+
+
+
+
+
+
+ 手机号
+ *
+
+
+
+
+
+
+ {{ formatPhoneNumber(profileForm.mobile) }}
+ 更换
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/login/index.vue b/src/pages/login/index.vue
index 280d378..348af18 100644
--- a/src/pages/login/index.vue
+++ b/src/pages/login/index.vue
@@ -166,29 +166,66 @@ const handleWechatLogin = async () => {
provider: "weixin",
});
- // 调用后端接口进行登录认证
- const result = await userStore.loginByWechat(code);
+ // 尝试使用增强的微信登录接口
+ try {
+ const result = await userStore.loginByWechatMini({
+ code: code,
+ });
- if (result) {
- // 获取用户信息
- await userStore.getInfo();
- toast.success("登录成功");
+ if (result) {
+ // 获取用户信息
+ await userStore.getInfo();
+ toast.success("登录成功");
- // 检查用户信息是否完整
- if (!userStore.isUserInfoComplete()) {
- // 如果信息不完整,跳转到完善信息页面
- setTimeout(() => {
- uni.navigateTo({
- url: `/pages/login/complete-profile?redirect=${encodeURIComponent(redirect.value)}`,
- });
- }, 1000);
- } else {
- // 否则直接跳转到重定向页面
- setTimeout(() => {
- uni.reLaunch({
- url: redirect.value,
- });
- }, 1000);
+ // 检查是否为新用户或信息不完整
+ const wechatResult = result as any; // 类型断言
+ if (
+ wechatResult.isNewUser ||
+ !wechatResult.isProfileComplete ||
+ !userStore.isUserInfoComplete()
+ ) {
+ // 如果信息不完整,跳转到完善信息页面
+ setTimeout(() => {
+ uni.navigateTo({
+ url: `/pages/login/complete-profile?redirect=${encodeURIComponent(redirect.value)}`,
+ });
+ }, 1000);
+ } else {
+ // 否则直接跳转到重定向页面
+ setTimeout(() => {
+ uni.reLaunch({
+ url: redirect.value,
+ });
+ }, 1000);
+ }
+ }
+ } catch (enhancedError) {
+ // 如果增强接口失败,回退到原始接口
+ console.log("增强微信登录失败,回退到原始接口:", enhancedError);
+
+ const result = await userStore.loginByWechat(code);
+
+ if (result) {
+ // 获取用户信息
+ await userStore.getInfo();
+ toast.success("登录成功");
+
+ // 检查用户信息是否完整
+ if (!userStore.isUserInfoComplete()) {
+ // 如果信息不完整,跳转到完善信息页面
+ setTimeout(() => {
+ uni.navigateTo({
+ url: `/pages/login/complete-profile?redirect=${encodeURIComponent(redirect.value)}`,
+ });
+ }, 1000);
+ } else {
+ // 否则直接跳转到重定向页面
+ setTimeout(() => {
+ uni.reLaunch({
+ url: redirect.value,
+ });
+ }, 1000);
+ }
}
}
// #endif
diff --git a/src/pages/mine/profile/index.vue b/src/pages/mine/profile/index.vue
index a2be5a7..d70b5fe 100644
--- a/src/pages/mine/profile/index.vue
+++ b/src/pages/mine/profile/index.vue
@@ -63,8 +63,8 @@
-
-
diff --git a/src/pages/work/config/index.vue b/src/pages/work/config/index.vue
deleted file mode 100644
index aa8dd4b..0000000
--- a/src/pages/work/config/index.vue
+++ /dev/null
@@ -1,324 +0,0 @@
-
-
-
-
-
-
-
-
-
- 查询
-
- 重置
-
-
-
-
-
-
-
-
-
-
-
- {{ item.configName }}
-
-
-
-
-
-
-
-
-
-
-
- 操作
-
-
-
-
-
-
-
-
-
-
-
- 添加
- 刷新缓存
-
-
-
-
-
-
-
-
-
-
-
- 取消
-
- 确定
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/work/index.vue b/src/pages/work/index.vue
deleted file mode 100644
index 8197afa..0000000
--- a/src/pages/work/index.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
-
-
-
-
-
- {{ child.title }}
-
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/work/log/index.vue b/src/pages/work/log/index.vue
deleted file mode 100644
index 8f7fe92..0000000
--- a/src/pages/work/log/index.vue
+++ /dev/null
@@ -1,174 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- 重置
- 查询
-
-
-
-
-
-
-
-
- {{ item.operator }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 查看详情
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/work/notice/detail.vue b/src/pages/work/notice/detail.vue
deleted file mode 100644
index 60ed8b1..0000000
--- a/src/pages/work/notice/detail.vue
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/pages/work/notice/index.vue b/src/pages/work/notice/index.vue
deleted file mode 100644
index 19ccc61..0000000
--- a/src/pages/work/notice/index.vue
+++ /dev/null
@@ -1,284 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- 查询
-
- 重置
-
-
-
-
-
-
-
-
-
-
-
- {{ item.title }}
-
- {{ getStatusText(item.publishStatus) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 操作
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/work/role/assign-perm.vue b/src/pages/work/role/assign-perm.vue
deleted file mode 100644
index 53fb546..0000000
--- a/src/pages/work/role/assign-perm.vue
+++ /dev/null
@@ -1,109 +0,0 @@
-
-
-
-
-
-
-
- 全 选
-
-
- 全不选
-
-
- 全展开
-
-
- 全收起
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/work/role/index.vue b/src/pages/work/role/index.vue
deleted file mode 100644
index dc4b211..0000000
--- a/src/pages/work/role/index.vue
+++ /dev/null
@@ -1,356 +0,0 @@
-
-
-
-
-
-
-
-
- 重置
- 确定
-
-
-
-
-
-
-
-
-
-
-
-
- {{ item.name }}
-
-
-
-
- 正常
- 停用
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 操作
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/work/user/index.vue b/src/pages/work/user/index.vue
deleted file mode 100644
index b15526f..0000000
--- a/src/pages/work/user/index.vue
+++ /dev/null
@@ -1,426 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 重置
- 查询
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ item.nickname }}
-
-
-
-
-
-
-
- 正常
- 禁用
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 编辑
-
-
-
- 删除
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/store/index.ts b/src/store/index.ts
index 8f9d085..63acd80 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -9,6 +9,5 @@ export function setupStore(app: App) {
}
export * from "./modules/user";
-export * from "./modules/dict";
export * from "./modules/theme";
export { store };
diff --git a/src/store/modules/dict.ts b/src/store/modules/dict.ts
deleted file mode 100644
index c79d91b..0000000
--- a/src/store/modules/dict.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { defineStore } from "pinia";
-import DictAPI, { type DictItemOption } from "@/api/system/dict";
-
-const DICT_CACHE_KEY = "dict_cache";
-
-export const useDictStore = defineStore("dict", () => {
- // 字典数据缓存
- const dictCache = ref>(uni.getStorageSync(DICT_CACHE_KEY) || {});
-
- // 监听dictCache变化,同步到本地存储
- watch(
- dictCache,
- (newVal) => {
- uni.setStorageSync(DICT_CACHE_KEY, newVal);
- },
- { deep: true }
- );
-
- // 请求队列(防止重复请求)
- const requestQueue: Record> = {};
-
- /**
- * 缓存字典数据
- * @param dictCode 字典编码
- * @param data 字典项列表
- */
- const cacheDictItems = (dictCode: string, data: DictItemOption[]) => {
- dictCache.value[dictCode] = data;
- };
-
- /**
- * 加载字典数据(如果缓存中没有则请求)
- * @param dictCode 字典编码
- */
- const loadDictItems = async (dictCode: string) => {
- if (dictCache.value[dictCode]) return;
-
- // 防止重复请求
- if (!requestQueue[dictCode]) {
- requestQueue[dictCode] = DictAPI.getDictItems(dictCode).then((data) => {
- cacheDictItems(dictCode, data);
- Reflect.deleteProperty(requestQueue, dictCode);
- });
- }
- await requestQueue[dictCode];
- };
-
- /**
- * 获取字典项列表
- * @param dictCode 字典编码
- * @returns 字典项列表
- */
- const getDictItems = (dictCode: string): DictItemOption[] => {
- return dictCache.value[dictCode] || [];
- };
-
- /**
- * 移除指定字典项
- * @param dictCode 字典编码
- */
- const removeDictItem = (dictCode: string) => {
- if (dictCache.value[dictCode]) {
- Reflect.deleteProperty(dictCache.value, dictCode);
- }
- };
-
- /**
- * 清空字典缓存
- */
- const clearDictCache = () => {
- dictCache.value = {};
- };
-
- return {
- loadDictItems,
- getDictItems,
- removeDictItem,
- clearDictCache,
- };
-});
diff --git a/src/store/modules/theme.ts b/src/store/modules/theme.ts
index 51c1d34..7cda548 100644
--- a/src/store/modules/theme.ts
+++ b/src/store/modules/theme.ts
@@ -1,7 +1,6 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { applyThemeToMiniProgram } from "@/utils/theme";
-import { getLighterColor, getDarkerColor } from "@/utils/colorUtils";
// 从缓存获取主题色
const getThemeColor = (): string => {
@@ -28,11 +27,9 @@ export const useThemeStore = defineStore("theme", () => {
// H5环境
document.documentElement.style.setProperty("--primary-color", color);
- // 计算衍生色
- const lighterColor = getLighterColor(color, 0.8);
- const darkerColor = getDarkerColor(color, 0.8);
- document.documentElement.style.setProperty("--primary-color-light", lighterColor);
- document.documentElement.style.setProperty("--primary-color-dark", darkerColor);
+ // 设置简单的衍生色(不依赖外部工具函数)
+ document.documentElement.style.setProperty("--primary-color-light", color + "80"); // 添加透明度
+ document.documentElement.style.setProperty("--primary-color-dark", color);
} else {
// 小程序环境
applyThemeToMiniProgram(color);
diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts
index 0f364f9..03d4f5c 100644
--- a/src/store/modules/user.ts
+++ b/src/store/modules/user.ts
@@ -1,7 +1,10 @@
import { defineStore } from "pinia";
import AuthAPI, { type LoginFormData } from "@/api/auth";
-import UserAPI, { type UserInfo } from "@/api/system/user";
-import { setToken, getUserInfo, setUserInfo, clearAll } from "@/utils/cache";
+import UserAPI, { type UserInfo } from "@/api/user";
+import { setAccessToken, clearTokens } from "@/utils/auth";
+import { getUserInfo, setUserInfo } from "@/utils/storage";
+import { USER_INFO_KEY } from "@/constants";
+import { Storage } from "@/utils/storage";
export const useUserStore = defineStore("user", () => {
const userInfo = ref(getUserInfo());
@@ -11,7 +14,7 @@ export const useUserStore = defineStore("user", () => {
return new Promise((resolve, reject) => {
AuthAPI.login(data)
.then((data) => {
- setToken(data.accessToken);
+ setAccessToken(data.accessToken);
resolve(data);
})
.catch((error) => {
@@ -26,7 +29,7 @@ export const useUserStore = defineStore("user", () => {
return new Promise((resolve, reject) => {
AuthAPI.wechatLogin(code)
.then((data) => {
- setToken(data.accessToken);
+ setAccessToken(data.accessToken);
resolve(data);
})
.catch((error) => {
@@ -36,6 +39,21 @@ export const useUserStore = defineStore("user", () => {
});
};
+ // 微信小程序增强登录
+ const loginByWechatMini = (data: any): Promise => {
+ return new Promise((resolve, reject) => {
+ AuthAPI.wechatMiniLogin(data)
+ .then((result) => {
+ setAccessToken(result.accessToken);
+ resolve(result);
+ })
+ .catch((error) => {
+ console.error("微信小程序登录失败", error);
+ reject(error);
+ });
+ });
+ };
+
// 获取用户信息
const getInfo = () => {
return new Promise((resolve, reject) => {
@@ -59,7 +77,8 @@ export const useUserStore = defineStore("user", () => {
} catch (error) {
console.error("登出失败", error);
} finally {
- clearAll(); // 清除本地的 token 和用户信息缓存
+ clearTokens(); // 清除本地的 token
+ Storage.remove(USER_INFO_KEY); // 清除用户信息缓存
userInfo.value = undefined; // 清空用户信息
}
};
@@ -75,6 +94,7 @@ export const useUserStore = defineStore("user", () => {
userInfo,
login,
loginByWechat,
+ loginByWechatMini,
logout,
getInfo,
isUserInfoComplete,
diff --git a/src/types/auto-imports.d.ts b/src/types/auto-imports.d.ts
index 5fab0a3..3e3bdcc 100644
--- a/src/types/auto-imports.d.ts
+++ b/src/types/auto-imports.d.ts
@@ -15,6 +15,7 @@ declare global {
const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
+ const guessSerializerType: typeof import('@uni-helper/uni-use')['guessSerializerType']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
@@ -76,17 +77,53 @@ declare global {
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
+ const tryOnBackPress: typeof import('@uni-helper/uni-use')['tryOnBackPress']
+ const tryOnHide: typeof import('@uni-helper/uni-use')['tryOnHide']
+ const tryOnInit: typeof import('@uni-helper/uni-use')['tryOnInit']
+ const tryOnLoad: typeof import('@uni-helper/uni-use')['tryOnLoad']
+ const tryOnReady: typeof import('@uni-helper/uni-use')['tryOnReady']
+ const tryOnScopeDispose: typeof import('@uni-helper/uni-use')['tryOnScopeDispose']
+ const tryOnShow: typeof import('@uni-helper/uni-use')['tryOnShow']
+ const tryOnUnload: typeof import('@uni-helper/uni-use')['tryOnUnload']
const unref: typeof import('vue')['unref']
+ const useActionSheet: typeof import('@uni-helper/uni-use')['useActionSheet']
const useAttrs: typeof import('vue')['useAttrs']
+ const useClipboardData: typeof import('@uni-helper/uni-use')['useClipboardData']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
+ const useDownloadFile: typeof import('@uni-helper/uni-use')['useDownloadFile']
+ const useGlobalData: typeof import('@uni-helper/uni-use')['useGlobalData']
const useId: typeof import('vue')['useId']
+ const useInterceptor: typeof import('@uni-helper/uni-use')['useInterceptor']
const useLink: (typeof import("vue-router"))["useLink"]
+ const useLoading: typeof import('@uni-helper/uni-use')['useLoading']
+ const useModal: typeof import('@uni-helper/uni-use')['useModal']
const useModel: typeof import('vue')['useModel']
- const useRoute: (typeof import("vue-router"))["useRoute"]
- const useRouter: (typeof import("vue-router"))["useRouter"]
+ const useNetwork: typeof import('@uni-helper/uni-use')['useNetwork']
+ const useOnline: typeof import('@uni-helper/uni-use')['useOnline']
+ const usePage: typeof import('@uni-helper/uni-use')['usePage']
+ const usePageScroll: typeof import('@uni-helper/uni-use')['usePageScroll']
+ const usePages: typeof import('@uni-helper/uni-use')['usePages']
+ const usePreferredDark: typeof import('@uni-helper/uni-use')['usePreferredDark']
+ const usePreferredLanguage: typeof import('@uni-helper/uni-use')['usePreferredLanguage']
+ const usePrevPage: typeof import('@uni-helper/uni-use')['usePrevPage']
+ const usePrevRoute: typeof import('@uni-helper/uni-use')['usePrevRoute']
+ const useProvider: typeof import('@uni-helper/uni-use')['useProvider']
+ const useRequest: typeof import('@uni-helper/uni-use')['useRequest']
+ const useRoute: typeof import('@uni-helper/uni-use')['useRoute']
+ const useRouter: typeof import('@uni-helper/uni-use')['useRouter']
+ const useScanCode: typeof import('@uni-helper/uni-use')['useScanCode']
+ const useScreenBrightness: typeof import('@uni-helper/uni-use')['useScreenBrightness']
+ const useSelectorQuery: typeof import('@uni-helper/uni-use')['useSelectorQuery']
const useSlots: typeof import('vue')['useSlots']
+ const useSocket: typeof import('@uni-helper/uni-use')['useSocket']
+ const useStorage: typeof import('@uni-helper/uni-use')['useStorage']
+ const useStorageAsync: typeof import('@uni-helper/uni-use')['useStorageAsync']
+ const useStorageSync: typeof import('@uni-helper/uni-use')['useStorageSync']
const useTemplateRef: typeof import('vue')['useTemplateRef']
+ const useToast: typeof import('@uni-helper/uni-use')['useToast']
+ const useUploadFile: typeof import('@uni-helper/uni-use')['useUploadFile']
+ const useVisible: typeof import('@uni-helper/uni-use')['useVisible']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
diff --git a/src/utils/auth.ts b/src/utils/auth.ts
index dfbb479..f32aff6 100644
--- a/src/utils/auth.ts
+++ b/src/utils/auth.ts
@@ -1,4 +1,46 @@
import { useUserStore } from "@/store/modules/user";
+import { Storage } from "./storage";
+import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from "@/constants";
+
+/**
+ * 获取访问令牌
+ * @returns 返回访问令牌,如果不存在则返回null
+ */
+export function getAccessToken(): string | null {
+ return Storage.get(ACCESS_TOKEN_KEY) || null;
+}
+
+/**
+ * 设置访问令牌
+ * @param token 访问令牌
+ */
+export function setAccessToken(token: string): void {
+ Storage.set(ACCESS_TOKEN_KEY, token);
+}
+
+/**
+ * 获取刷新令牌
+ * @returns 返回刷新令牌,如果不存在则返回null
+ */
+export function getRefreshToken(): string | null {
+ return Storage.get(REFRESH_TOKEN_KEY) || null;
+}
+
+/**
+ * 设置刷新令牌
+ * @param token 刷新令牌
+ */
+export function setRefreshToken(token: string): void {
+ Storage.set(REFRESH_TOKEN_KEY, token);
+}
+
+/**
+ * 清除所有令牌
+ */
+export function clearTokens(): void {
+ Storage.remove(ACCESS_TOKEN_KEY);
+ Storage.remove(REFRESH_TOKEN_KEY);
+}
/**
* 检查用户登录状态,未登录则跳转到登录页面
diff --git a/src/utils/cache.ts b/src/utils/cache.ts
deleted file mode 100644
index 69865ef..0000000
--- a/src/utils/cache.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-const TOKEN_KEY = "app-token";
-const USER_INFO_KEY = "user-info";
-
-// 设置 token
-export function setToken(token: string) {
- uni.setStorageSync(TOKEN_KEY, token);
-}
-
-// 获取 token
-export function getToken(): string {
- return uni.getStorageSync(TOKEN_KEY) || "";
-}
-
-// 清除 token
-export function clearToken() {
- uni.removeStorageSync(TOKEN_KEY);
-}
-
-// 设置用户信息
-export function setUserInfo(userInfo: any) {
- uni.setStorageSync(USER_INFO_KEY, userInfo);
-}
-
-// 获取用户信息
-export function getUserInfo(): any {
- return uni.getStorageSync(USER_INFO_KEY) || null;
-}
-
-// 清除用户信息
-export function clearUserInfo() {
- uni.removeStorageSync(USER_INFO_KEY);
-}
-
-// 清除所有缓存信息
-export function clearAll() {
- clearToken();
- clearUserInfo();
- // 清除字典缓存
- uni.removeStorageSync("dict_cache");
-}
diff --git a/src/utils/colorUtils.ts b/src/utils/colorUtils.ts
deleted file mode 100644
index 96db635..0000000
--- a/src/utils/colorUtils.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * 颜色处理工具类
- */
-
-/**
- * 获取浅色版本的颜色
- * @param hexColor 十六进制颜色值
- * @param factor 调亮因子 (0-1)
- * @returns 调亮后的颜色值
- */
-export function getLighterColor(hexColor: string, factor: number): string {
- // 去掉#前缀
- const hex = hexColor.replace("#", "");
-
- // 解析RGB值
- let r = parseInt(hex.substring(0, 2), 16);
- let g = parseInt(hex.substring(2, 4), 16);
- let b = parseInt(hex.substring(4, 6), 16);
-
- // 调亮颜色
- r = Math.min(255, Math.floor(r + (255 - r) * factor));
- g = Math.min(255, Math.floor(g + (255 - g) * factor));
- b = Math.min(255, Math.floor(b + (255 - b) * factor));
-
- // 转回16进制
- return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
-}
-
-/**
- * 获取深色版本的颜色
- * @param hexColor 十六进制颜色值
- * @param factor 调暗因子 (0-1)
- * @returns 调暗后的颜色值
- */
-export function getDarkerColor(hexColor: string, factor: number): string {
- // 去掉#前缀
- const hex = hexColor.replace("#", "");
-
- // 解析RGB值
- let r = parseInt(hex.substring(0, 2), 16);
- let g = parseInt(hex.substring(2, 4), 16);
- let b = parseInt(hex.substring(4, 6), 16);
-
- // 调暗颜色
- r = Math.max(0, Math.floor(r * factor));
- g = Math.max(0, Math.floor(g * factor));
- b = Math.max(0, Math.floor(b * factor));
-
- // 转回16进制
- return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
-}
diff --git a/src/utils/request.ts b/src/utils/request.ts
index 5909a49..a1e1d19 100644
--- a/src/utils/request.ts
+++ b/src/utils/request.ts
@@ -1,4 +1,4 @@
-import { getToken, clearAll } from "@/utils/cache";
+import { getAccessToken, clearTokens } from "@/utils/auth";
import { ResultCodeEnum } from "@/enums/ResultCodeEnum";
export default function request(options: UniApp.RequestOptions): Promise {
@@ -14,7 +14,7 @@ export default function request(options: UniApp.RequestOptions): Promise {
url: `${baseApi}${options.url}`,
header: {
...options.header,
- Authorization: getToken() ? `Bearer ${getToken()}` : "",
+ Authorization: getAccessToken() ? `Bearer ${getAccessToken()}` : "",
},
success: (response) => {
console.log("success response", response);
@@ -27,7 +27,7 @@ export default function request(options: UniApp.RequestOptions): Promise {
// 令牌失效或过期处理
else if (resData.code === ResultCodeEnum.TOKEN_INVALID) {
console.log("令牌失效或过期处理");
- clearAll();
+ clearTokens();
// 跳转到登录页
uni.reLaunch({
url: "/pages/login/index",
diff --git a/src/utils/storage.ts b/src/utils/storage.ts
new file mode 100644
index 0000000..a39f65e
--- /dev/null
+++ b/src/utils/storage.ts
@@ -0,0 +1,72 @@
+/**
+ * 存储工具类
+ * 提供localStorage和sessionStorage操作方法
+ */
+
+/**
+ * localStorage 存储
+ */
+function set(key: string, value: any): void {
+ uni.setStorageSync(key, JSON.stringify(value));
+}
+
+function get(key: string, defaultValue?: T): T {
+ const value = uni.getStorageSync(key);
+ if (!value) return defaultValue as T;
+
+ try {
+ return JSON.parse(value);
+ } catch {
+ // 如果解析失败,返回原始字符串
+ return value as unknown as T;
+ }
+}
+
+function remove(key: string): void {
+ uni.removeStorageSync(key);
+}
+
+export const Storage = {
+ set,
+ get,
+ remove,
+};
+
+// 为了向后兼容,导出具体的函数
+import { ACCESS_TOKEN_KEY, USER_INFO_KEY } from "@/constants";
+
+/**
+ * 获取令牌
+ */
+export function getToken(): string | null {
+ return Storage.get(ACCESS_TOKEN_KEY) || null;
+}
+
+/**
+ * 设置令牌
+ */
+export function setToken(token: string): void {
+ Storage.set(ACCESS_TOKEN_KEY, token);
+}
+
+/**
+ * 获取用户信息
+ */
+export function getUserInfo(): T | undefined {
+ return Storage.get(USER_INFO_KEY);
+}
+
+/**
+ * 设置用户信息
+ */
+export function setUserInfo(userInfo: any): void {
+ Storage.set(USER_INFO_KEY, userInfo);
+}
+
+/**
+ * 清除所有数据
+ */
+export function clearAll(): void {
+ Storage.remove(ACCESS_TOKEN_KEY);
+ Storage.remove(USER_INFO_KEY);
+}
diff --git a/uni-pages.d.ts b/uni-pages.d.ts
new file mode 100644
index 0000000..03826df
--- /dev/null
+++ b/uni-pages.d.ts
@@ -0,0 +1,35 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by vite-plugin-uni-pages
+
+interface NavigateToOptions {
+ url: "/pages/index/index" |
+ "/pages/login/complete-profile" |
+ "/pages/login/index" |
+ "/pages/mine/index" |
+ "/pages/mine/about/index" |
+ "/pages/mine/faq/index" |
+ "/pages/mine/feedback/index" |
+ "/pages/mine/profile/index" |
+ "/pages/mine/settings/index" |
+ "/pages/mine/settings/account/index" |
+ "/pages/mine/settings/agreement/index" |
+ "/pages/mine/settings/network/index" |
+ "/pages/mine/settings/privacy/index" |
+ "/pages/mine/settings/theme/index";
+}
+interface RedirectToOptions extends NavigateToOptions {}
+
+interface SwitchTabOptions {
+
+}
+
+type ReLaunchOptions = NavigateToOptions | SwitchTabOptions;
+
+declare interface Uni {
+ navigateTo(options: UniNamespace.NavigateToOptions & NavigateToOptions): void;
+ redirectTo(options: UniNamespace.RedirectToOptions & RedirectToOptions): void;
+ switchTab(options: UniNamespace.SwitchTabOptions & SwitchTabOptions): void;
+ reLaunch(options: UniNamespace.ReLaunchOptions & ReLaunchOptions): void;
+}
diff --git a/vite.config.ts b/vite.config.ts
index 13591c1..dc0b2cc 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,6 +1,9 @@
import { defineConfig, type UserConfig, type ConfigEnv, loadEnv } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import AutoImport from "unplugin-auto-import/vite";
+import UniLayouts from "@uni-helper/vite-plugin-uni-layouts";
+import UniPages from "@uni-helper/vite-plugin-uni-pages";
+import { uniuseAutoImports } from "@uni-helper/uni-use";
export default defineConfig(async ({ mode }: ConfigEnv): Promise => {
const UnoCss = await import("unocss/vite").then((i) => i.default);
@@ -20,17 +23,26 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise =>
},
},
},
+ build: {
+ target: "es6",
+ cssTarget: "chrome61",
+ },
+ optimizeDeps: {
+ exclude: ["vue-demi"],
+ },
plugins: [
- // https://github.com/unocss/unocss
+ // 在 uni() 之前使用
UnoCss(),
-
+ UniLayouts(),
+ UniPages(),
AutoImport({
- imports: ["vue", "uni-app"],
+ imports: ["vue", "uni-app", uniuseAutoImports()],
dts: "src/types/auto-imports.d.ts", // 自动生成的类型声明文件
eslintrc: {
enabled: false,
},
}),
+
uni(),
],
};