From 9fba2794908ee4d3c27dc7ee65c704d5010dc52e Mon Sep 17 00:00:00 2001 From: "Ray.Hao" <1490493387@qq.com> Date: Wed, 28 May 2025 23:39:26 +0800 Subject: [PATCH] =?UTF-8?q?wip:=20=E4=B8=B4=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .husky/uniapp从0到1.md | 2064 ----------------- .npmrc | 1 + components.d.ts | 49 + docs/theme-guide.md | 361 --- docs/wechat-login-guide.md | 342 --- package.json | 8 +- pages.config.ts | 31 +- src/api/file.ts | 4 +- src/composables/theme/rootTheme.ts | 22 - src/composables/theme/theme.ts | 74 - src/composables/useStomp.ts | 8 +- src/composables/useTabbar.ts | 61 - src/composables/useTheme.ts | 76 + src/constants/index.ts | 8 +- .../{storage-keys.ts => storage.constant.ts} | 0 src/directive/index.ts | 10 - src/directive/permission/index.ts | 69 - src/enums/ResultCodeEnum.ts | 18 - src/enums/api-code.enum.ts | 49 + src/layouts/default.vue | 32 +- src/layouts/tabbar.vue | 73 +- src/main.ts | 2 - src/pages.json | 65 +- src/pages/index/index.vue | 79 +- src/pages/login/complete-profile.vue | 2 +- src/pages/login/index.vue | 2 +- src/pages/mine/index.vue | 4 +- src/pages/mine/settings/index.vue | 4 +- src/pages/mine/settings/theme/index.vue | 14 +- src/plugins/index.ts | 9 - src/store/index.ts | 4 +- .../modules/{theme.ts => theme.store.ts} | 0 src/store/modules/{user.ts => user.store.ts} | 0 src/utils/auth.ts | 100 +- src/utils/request.ts | 6 +- uni-pages.d.ts | 2 +- vite.config.ts | 11 +- 37 files changed, 536 insertions(+), 3128 deletions(-) delete mode 100644 .husky/uniapp从0到1.md create mode 100644 .npmrc create mode 100644 components.d.ts delete mode 100644 docs/theme-guide.md delete mode 100644 docs/wechat-login-guide.md delete mode 100644 src/composables/theme/rootTheme.ts delete mode 100644 src/composables/theme/theme.ts delete mode 100644 src/composables/useTabbar.ts create mode 100644 src/composables/useTheme.ts rename src/constants/{storage-keys.ts => storage.constant.ts} (100%) delete mode 100644 src/directive/index.ts delete mode 100644 src/directive/permission/index.ts delete mode 100644 src/enums/ResultCodeEnum.ts create mode 100644 src/enums/api-code.enum.ts delete mode 100644 src/plugins/index.ts rename src/store/modules/{theme.ts => theme.store.ts} (100%) rename src/store/modules/{user.ts => user.store.ts} (100%) diff --git a/.husky/uniapp从0到1.md b/.husky/uniapp从0到1.md deleted file mode 100644 index a9f14dc..0000000 --- a/.husky/uniapp从0到1.md +++ /dev/null @@ -1,2064 +0,0 @@ - - -## 环境准备 - -[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) ` 版本。 - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024220508312.png) - -下载之后,双击安装包根据提示安装,通过以下命令检查是否成功安装: - -```bash -node -v -``` - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024224347056.png) - - - -### 安装 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 -``` - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024213719105.png) - -## 创建项目 - -### 初始化项目 - -按照 [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 -``` - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20240917083025350.png) - -如果使用命令创建失败,可以通过 Gitee 下载 ZIP 包:[vite-ts 分支](https://gitee.com/dcloud/uni-preset-vue/tree/vite-ts)。 - -### 配置编译器 - -默认生成的 `TypeScript ` 编译器配置文件 `tsconfig.json` 中继承的 `@vue/tsconfig/tsconfig.json` 文件不存在。因此,你需要移除此继承配置并添加相应的编译设置。 - -![](https://raw.gitmirror.com/youlaitech/images/main/docs/image-20240923143412332.png) - -根据 [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 -``` - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20240917085244593.png) - -项目启动后,访问 http://localhost:5173 预览效果: - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20240917085841605.png) - - - -## 代码规范配置 - -为了保证项目代码的规范性和一致性,可以为项目配置 `ESLint`、`Stylelint`、`Prettier` 以及 `Husky`,从而确保代码质量和开发流程的一致性。 - - ### 集成 ESLint - -`ESLint` 是一款 JavaScript 和 TypeScript 的代码规范工具,能够帮助开发团队保持代码风格一致并减少常见错误。 - -**ESLint 中文网**:[https://eslint.nodejs.cn/](https://eslint.nodejs.cn/) - -#### 安装插件 - -VSCode 插件市场搜索 ESLint 插件并安装 - -![](https://s2.loli.net/2023/04/17/C3mV14KT2vHo6ZA.png) - -#### 配置 ESLint - -通过以下命令快速生成 ESLint 配置文件: - -```bash -npx eslint --init -``` - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20240917202516052.png) - -执行该命令后,ESLint 会通过交互式问题的方式,帮助生成配置文件。针对 9.x 版本,默认会生成基于 Flat Config 格式的 `eslint.config.mjs` 文件,与之前的 `.eslintrc` 格式有所不同。 - -默认生成的 `eslint.config.mjs` 文件如下所示: - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20240917221647303.png) - -在此基础上,可以根据项目的需求进行一些定制化配置,例如添加忽略规则或自定义的特殊规则。 - -```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 提示该变量未使用。如下图所示: - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20240917235501951.png) - -#### 推荐配置 - -安装 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` 插件安装 - -![](https://s2.loli.net/2023/02/12/cBHDxNGzCaoeqvW.png) - -#### 安装依赖 - -```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 为所有文件默认格式化器 -} -``` - -#### 测试 - -下图演示了保存时的自动格式化效果,展示了代码中引号和换行的自动调整: - -![](https://raw.gitmirror.com/youlaitech/images/main/docs/PixPin_2024-09-18_15-45-29.gif) - - - -### 集成 Stylelint - -Stylelint 一个强大的 CSS linter(检查器),可帮助您避免错误并强制执行约定。 - -**Stylelint 官网**:[https://stylelint.io/](https://stylelint.io/) - -#### 安装插件 - -VSCode 插件搜索 `Stylelint` 并安装 - -![](https://s2.loli.net/2023/04/08/azfOTp4K12c3PvQ.png) - - - -#### 安装依赖 - -```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 自动将属性重新排序,达到预期。 - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/PixPin_2024-09-18_20-39-04.gif) - -#### 测试 - -执行以下命令进行检测 - -```bash -npm run lint:stylelint -``` - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20240918203537608.png) - -## Git提交规范配置 - - - -配置 Husky 的 `pre-commit` 和 `commit-msg` 钩子,实现代码提交的自动化检查和规范化。 - -- **pre-commit**: 使用 Husky + Lint-staged,在提交前进行代码规范检测和格式化。确保项目已配置 ESLint、Prettier 和 Stylelint。 -- **commit-msg**: 结合 Husky、Commitlint、Commitizen 和 cz-git,生成规范化且自定义的 Git commit 信息。 - -![](https://raw.gitmirror.com/youlaitech/images/main/docs/image-20240919150854229.png) - - - -### 集成 Husky - -Husky 是 Git 钩子工具,可以设置在 git 各个阶段(`pre-commit`、`commit-msg` 等)触发。 - -**Husky官网**:[https://typicode.github.io](https://typicode.github.io/husky/zh/get-started.html) - -![](https://raw.gitmirror.com/youlaitech/images/main/docs/image-20240919094059492.png) - -#### 安装依赖 - -```bash -pnpm add -D husky -``` - -#### 初始化 - -`init` 命令简化了项目中的 husky 设置。它会在 `.husky/` 中创建 `pre-commit` 脚本,并更新 `package.json` 中的 `prepare` 脚本。 - -```bash -pnpm exec husky init -``` - -#### 测试 - -![image-20240919100633078](https://raw.gitmirror.com/youlaitech/images/main/docs/image-20240919100633078.png) - -通过 `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 任务。 - -![](https://raw.gitmirror.com/youlaitech/images/main/docs/image-20240919112617112.png) - -通过这种集成方式,确保代码在提交前经过自动格式化和校验,提高代码质量和一致性。 - -### 集成 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 字符以内。 - -当你尝试提交不符合规范的提交信息时,提交会被阻止,并显示相关错误提示。如下图所示: - -![](https://raw.gitmirror.com/youlaitech/images/main/docs/image-20240919134921181.png) - -### 集成 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` 命令后,按照提示输入相关信息,最终生成符合规范的提交信息。 - -![](https://raw.gitmirror.com/youlaitech/images/main/docs/image-20240919150854229.png) - -## 整合 Sass - -**Sass**是帮助开发者编写、管理和维护样式的强大工具,通过 ` -``` - -在 `pages.json` 文件中,声明登录页面的路由: - -```json -// src/pages.json -{ - "pages": [ - { - "path": "pages/login/index", - "style": { - "navigationBarTitleText": "登录" - } - } - ] -} -``` - - - -### 登录测试 - -访问登录页面:http://localhost:5173/#/pages/login/index,输入用户名和密码 (`admin`/`123456`) 测试登录接口,登录成功后可以看到返回的访问令牌。 - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/PixPin_2024-09-22_17-21-05.gif) - -### 整合源码 - -整合`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 - - ![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20240921102456261.png) - -### 安装依赖 - -首先,安装 `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 - - - -``` - -登录页通过 `Pinia` 实现用户信息的全局状态管理,并在登录成功后跳转到个人中心页面。 - -```vue - - - - - -``` - - - -### 测试效果 - -登录后,个人中心会显示用户的头像和昵称。通过 Pinia 实现了登录状态的共享和跨页面传递。 - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/PixPin_2024-09-22_17-21-011.gif) - - - -### 整合源码 - -整合`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 与应用的主机地址一致,因此不会阻止该请求,即便真实请求已通过代理转发到外部服务器。 - -![反向代理跨域](https://raw.gitmirror.com/youlaitech/images/main/docs/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E8%B7%A8%E5%9F%9F.gif) - -需要注意,反向代理的目标是伪装请求来源,虽然它绕过了浏览器的同源策略,但有时也会让开发者误以为请求地址错误。实际上,这是由于代理转发过程造成的表面请求地址与真实请求地址的差异。 - -### 整合源码 - -整合`反向代理`和`环境变量`代码版本:[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 - -``` - -你将看到如下按钮效果: - -![](https://raw.gitmirror.com/youlaitech/images/main/docs/image-20240925122808028.png) - -### 整合源码 - -整合`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://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024232438606.png) - - - -下载 [微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html) - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024232328575.png) - -### 获取小程序 AppID - -访问 [微信公众平台](https://mp.weixin.qq.com/)申请小程序,获取 `AppID`。如果已申请,可在 `首页` → `小程序信息` → `查看详情` 查看 AppID - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024231827075.png) - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024232218176.png) - -### 配置项目 - -使用 HBuilder X 打开项目,修改 `manifest.json` 文件中的小程序配置,并填写获取的 AppID。 - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024232920676.png) - -### 设置微信开发者工具 - -使用微信扫码登录微信开发者工具,开启服务端口:点击工具栏`设置`→`安全设置`→`安全`→`服务端口`,选择打开。 - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024233058539.png) - -### 运行项目 - -在 HBuilder X 中,点击 `运行`→`运行到小程序模拟器`→`微信开发者工具`。 - -![image-20241024233326086](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024233326086.png) - -项目编译完成后,微信开发者工具会自动启动并呈现页面。 - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024233741449.png) - -### 上传发布 - -在微信开发者工具中,点击 `上传` 将应用发布到小程序平台。 - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024234052335.png) - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/image-20241024234313807.png) - -### 查看效果 - -最后,使用手机打开小程序查看效果: - -![](https://raw.gitmirror.com/youlaitech/image/main/blog/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20241024234523.jpg) diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..c483022 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +shamefully-hoist=true \ No newline at end of file diff --git a/components.d.ts b/components.d.ts new file mode 100644 index 0000000..3f5fa27 --- /dev/null +++ b/components.d.ts @@ -0,0 +1,49 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by vite-plugin-uni-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +declare module 'vue' { + export interface GlobalComponents { + CuDateQuery: typeof import('./src/components/cu-date-query/index.vue')['default'] + CuDict: typeof import('./src/components/cu-dict/index.vue')['default'] + CuDictLabel: typeof import('./src/components/cu-dict-label/index.vue')['default'] + CuPicker: typeof import('./src/components/cu-picker/index.vue')['default'] + DaTree: typeof import('./src/components/da-tree/index.vue')['default'] + Loading1: typeof import('./src/components/qiun-loading/loading1.vue')['default'] + Loading2: typeof import('./src/components/qiun-loading/loading2.vue')['default'] + Loading3: typeof import('./src/components/qiun-loading/loading3.vue')['default'] + Loading4: typeof import('./src/components/qiun-loading/loading4.vue')['default'] + Loading5: typeof import('./src/components/qiun-loading/loading5.vue')['default'] + QiunDataCharts: typeof import('./src/components/qiun-data-charts/qiun-data-charts.vue')['default'] + QiunError: typeof import('./src/components/qiun-error/qiun-error.vue')['default'] + QiunLoading: typeof import('./src/components/qiun-loading/qiun-loading.vue')['default'] + TodoItem: typeof import('./src/components/todo/TodoItem.vue')['default'] + TodoList: typeof import('./src/components/todo/TodoList.vue')['default'] + WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default'] + WdCard: typeof import('wot-design-uni/components/wd-card/wd-card.vue')['default'] + WdCell: typeof import('wot-design-uni/components/wd-cell/wd-cell.vue')['default'] + WdCellGroup: typeof import('wot-design-uni/components/wd-cell-group/wd-cell-group.vue')['default'] + WdConfigProvider: typeof import('wot-design-uni/components/wd-config-provider/wd-config-provider.vue')['default'] + WdForm: typeof import('wot-design-uni/components/wd-form/wd-form.vue')['default'] + WdGrid: typeof import('wot-design-uni/components/wd-grid/wd-grid.vue')['default'] + WdGridItem: typeof import('wot-design-uni/components/wd-grid-item/wd-grid-item.vue')['default'] + WdIcon: typeof import('wot-design-uni/components/wd-icon/wd-icon.vue')['default'] + WdMessageBox: typeof import('wot-design-uni/components/wd-message-box/wd-message-box.vue')['default'] + WdNavbar: typeof import('wot-design-uni/components/wd-navbar/wd-navbar.vue')['default'] + WdNoticeBar: typeof import('wot-design-uni/components/wd-notice-bar/wd-notice-bar.vue')['default'] + WdNotify: typeof import('wot-design-uni/components/wd-notify/wd-notify.vue')['default'] + WdPopup: typeof import('wot-design-uni/components/wd-popup/wd-popup.vue')['default'] + WdRadio: typeof import('wot-design-uni/components/wd-radio/wd-radio.vue')['default'] + WdRadioGroup: typeof import('wot-design-uni/components/wd-radio-group/wd-radio-group.vue')['default'] + WdSwiper: typeof import('wot-design-uni/components/wd-swiper/wd-swiper.vue')['default'] + WdSwitch: typeof import('wot-design-uni/components/wd-switch/wd-switch.vue')['default'] + WdTabbar: typeof import('wot-design-uni/components/wd-tabbar/wd-tabbar.vue')['default'] + WdTabbarItem: typeof import('wot-design-uni/components/wd-tabbar-item/wd-tabbar-item.vue')['default'] + WdTag: typeof import('wot-design-uni/components/wd-tag/wd-tag.vue')['default'] + WdToast: typeof import('wot-design-uni/components/wd-toast/wd-toast.vue')['default'] + WechatProfile: typeof import('./src/components/WechatProfile.vue')['default'] + } +} diff --git a/docs/theme-guide.md b/docs/theme-guide.md deleted file mode 100644 index 652c502..0000000 --- a/docs/theme-guide.md +++ /dev/null @@ -1,361 +0,0 @@ -# 主题设置功能指南 - -## 功能概述 - -本项目提供了完整的主题设置功能,支持暗黑模式切换和主题色自定义,让用户可以个性化应用的外观。 - -## 功能特性 - -### 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 deleted file mode 100644 index 9823d4d..0000000 --- a/docs/wechat-login-guide.md +++ /dev/null @@ -1,342 +0,0 @@ -# 微信小程序手机授权登录功能指南 - -## 功能概述 - -本项目实现了完整的微信小程序手机授权登录功能,包括: - -- 微信登录授权 -- 手机号获取授权 -- 头像昵称填写(使用微信小程序新能力) -- 用户信息完善流程 -- 登录状态管理 - -## 功能特性 - -### 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 f9e5359..c6481e3 100644 --- a/package.json +++ b/package.json @@ -90,11 +90,12 @@ "@dcloudio/uni-mp-weixin": "3.0.0-4020420240722002", "@dcloudio/uni-mp-xhs": "3.0.0-4020420240722002", "@dcloudio/uni-quickapp-webview": "3.0.0-4020420240722002", + "@stomp/stompjs": "^7.1.1", "@uni-helper/uni-use": "^0.19.14", - "@vueuse/core": "10.11.1", + "@vueuse/core": "9.13.0", "pinia": "^2.2.2", "vue": "^3.5.13", - "wot-design-uni": "^1.4.0" + "wot-design-uni": "^1.9.1" }, "devDependencies": { "@commitlint/cli": "^19.5.0", @@ -105,6 +106,7 @@ "@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-components": "^0.2.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", @@ -135,7 +137,7 @@ "unocss": "^0.62.4", "unocss-preset-weapp": "^0.62.2", "unplugin-auto-import": "^0.18.3", - "vite": "5.2.8", + "vite": "6.3.2", "vue-eslint-parser": "^9.4.3", "vue-tsc": "^1.0.24" } diff --git a/pages.config.ts b/pages.config.ts index f6d4302..4d68a28 100644 --- a/pages.config.ts +++ b/pages.config.ts @@ -3,9 +3,36 @@ import { defineUniPages } from "@uni-helper/vite-plugin-uni-pages"; export default defineUniPages({ // 你也可以定义 pages 字段,它具有最高的优先级。 - pages: [], + pages: [ + { + path: "pages/index/index", + layout: "tabbar", + }, + { + path: "pages/mine/index", + layout: "tabbar", + }, + ], globalStyle: { navigationBarTextStyle: "black", - navigationBarTitleText: "@uni-helper", + navigationBarTitleText: "vue-uniapp-template", + }, + + tabBar: { + custom: true, + height: "0px", + color: "#00000000", + selectedColor: "#00000000", + backgroundColor: "#00000000", + list: [ + { + text: "首页", + pagePath: "pages/index/index", + }, + { + text: "我的", + pagePath: "pages/mine/index", + }, + ], }, }); diff --git a/src/api/file.ts b/src/api/file.ts index c472c5c..118880f 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -1,5 +1,5 @@ import { getToken } from "@/utils/storage"; -import { ResultCodeEnum } from "@/enums/ResultCodeEnum"; +import { ApiCode } from "@/enums/api-code.enum"; // H5 使用 VITE_APP_BASE_API 作为代理路径,其他平台使用 VITE_APP_API_URL 作为请求路径 let baseApi = import.meta.env.VITE_APP_API_URL; @@ -31,7 +31,7 @@ const FileAPI = { success: (response) => { const resData = JSON.parse(response.data) as ResponseData; // 业务状态码 00000 表示成功 - if (resData.code === ResultCodeEnum.SUCCESS) { + if (resData.code === ApiCode.SUCCESS) { resolve(resData.data); } else { // 其他业务处理失败 diff --git a/src/composables/theme/rootTheme.ts b/src/composables/theme/rootTheme.ts deleted file mode 100644 index 0d5bb3d..0000000 --- a/src/composables/theme/rootTheme.ts +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 983a135..0000000 --- a/src/composables/theme/theme.ts +++ /dev/null @@ -1,74 +0,0 @@ -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 index c2b8912..fb2777a 100644 --- a/src/composables/useStomp.ts +++ b/src/composables/useStomp.ts @@ -1,5 +1,5 @@ import { Client, type IMessage, type StompSubscription } from "@stomp/stompjs"; -import { Auth } from "@/utils/auth"; +import { getAccessToken } from "@/utils/auth"; export interface UseStompOptions { /** WebSocket 地址,不传时使用 VITE_APP_WS_ENDPOINT 环境变量 */ @@ -70,7 +70,7 @@ export function useStomp(options: UseStompOptions = {}) { } // 每次连接前重新获取最新令牌,不依赖之前的token值 - const currentToken = Auth.getAccessToken(); + const currentToken = getAccessToken(); // 检查令牌是否为空,如果为空则不进行连接 if (!currentToken) { @@ -123,7 +123,7 @@ export function useStomp(options: UseStompOptions = {}) { }; // 设置 Web Socket 关闭监听器 - client.value.onWebSocketClose = (event) => { + client.value.onWebSocketClose = (event: CloseEvent) => { isConnected.value = false; isConnecting = false; console.log(`WebSocket已关闭: ${event?.code} ${event?.reason}`); @@ -147,7 +147,7 @@ export function useStomp(options: UseStompOptions = {}) { }; // 设置错误监听器 - client.value.onStompError = (frame) => { + client.value.onStompError = (frame: any) => { console.error("STOMP错误:", frame.headers, frame.body); isConnecting = false; diff --git a/src/composables/useTabbar.ts b/src/composables/useTabbar.ts deleted file mode 100644 index 27179b9..0000000 --- a/src/composables/useTabbar.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * @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/composables/useTheme.ts b/src/composables/useTheme.ts new file mode 100644 index 0000000..b5ab3ee --- /dev/null +++ b/src/composables/useTheme.ts @@ -0,0 +1,76 @@ +import { ref, computed } from "vue"; +import type { ConfigProviderThemeVars } from "wot-design-uni"; + +/* 默认的主题list */ +export const colorColumns = [ + { + value: "#0055FE", + label: "蓝色", + }, + { + value: "#CD5C5C", + label: "红色", + }, + { + value: "#228B22", + label: "绿色", + }, +]; + +/* 默认的主题 */ +export const initThemState = "light"; + +/* 默认的主题变量 - 只使用wot-design-uni支持的变量 */ +export const initThemeVars: ConfigProviderThemeVars = { + colorTheme: colorColumns[0].value, +}; + +/* 暗黑模式主题变量 */ +export const darkThemeVars: ConfigProviderThemeVars = { + colorTheme: colorColumns[0].value, +}; + +/* 主题状态 */ +export const themeState = ref(initThemState); + +/* 主题变量 */ +export const themeVars = ref(initThemeVars); + +/* 计算属性:根据主题状态返回对应的主题变量 */ +export const computedThemeVars = computed(() => { + const baseVars = themeState.value === "dark" ? darkThemeVars : initThemeVars; + return { + ...baseVars, + ...themeVars.value, + }; +}); + +/* 切换主题 */ +export const toggleTheme = () => { + themeState.value = themeState.value === "light" ? "dark" : "light"; + // 更新主题变量 + const newBaseVars = themeState.value === "dark" ? darkThemeVars : initThemeVars; + themeVars.value = { + ...newBaseVars, + colorTheme: themeVars.value.colorTheme, // 保持用户选择的主题色 + }; +}; + +/* 设置主题色 */ +export const setThemeColor = (color: string) => { + themeVars.value = { + ...themeVars.value, + colorTheme: color, + }; +}; + +/* 导出主题相关的工具 */ +export const useTheme = () => { + return { + themeState, + themeVars: computedThemeVars, + toggleTheme, + setThemeColor, + colorColumns, + }; +}; diff --git a/src/constants/index.ts b/src/constants/index.ts index f8be9d9..1e8e3b6 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,4 +1,6 @@ -export const ROLE_ROOT = "ROOT"; +/** + * 常量统一导出 + */ -// 🔗 导出所有存储键常量 -export * from "./storage-keys"; +// 存储相关常量 +export * from "./storage.constant"; diff --git a/src/constants/storage-keys.ts b/src/constants/storage.constant.ts similarity index 100% rename from src/constants/storage-keys.ts rename to src/constants/storage.constant.ts diff --git a/src/directive/index.ts b/src/directive/index.ts deleted file mode 100644 index 72de857..0000000 --- a/src/directive/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { App } from "vue"; - -import { hasPerm, hasRole } from "./permission"; - -// 全局注册 directive -export function setupDirective(app: App) { - // 使 v-hasPerm 在所有组件中都可用 - app.directive("hasPerm", hasPerm); - app.directive("hasRole", hasRole); -} diff --git a/src/directive/permission/index.ts b/src/directive/permission/index.ts deleted file mode 100644 index cdeb15c..0000000 --- a/src/directive/permission/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { Directive, DirectiveBinding } from "vue"; - -import { useUserStore } from "@/store"; - -/** - * 按钮权限 - */ -export const hasPerm: Directive = { - mounted(el: HTMLElement, binding: DirectiveBinding) { - const requiredPerms = binding.value; - - console.log("requiredPerms", requiredPerms); - if (!requiredPerms) { - return; - } - - // 校验传入的权限值是否合法 - if (!requiredPerms || (typeof requiredPerms !== "string" && !Array.isArray(requiredPerms))) { - throw new Error( - "需要提供权限标识!例如:v-has-perm=\"'sys:user:add'\" 或 v-has-perm=\"['sys:user:add', 'sys:user:edit']\"" - ); - } - - const { roles = [], perms = [] } = useUserStore().userInfo || {}; - - // 超级管理员拥有所有权限 - if (roles?.includes("ROOT")) { - return; - } - - // 检查权限 - const hasAuth = Array.isArray(requiredPerms) - ? requiredPerms.some((perm) => perms.includes(perm)) - : perms.includes(requiredPerms); - - // 如果没有权限,移除该元素 - if (!hasAuth && el.parentNode) { - el.parentNode.removeChild(el); - } - }, -}; - -/** - * 角色权限指令 - */ -export const hasRole: Directive = { - mounted(el: HTMLElement, binding: DirectiveBinding) { - const requiredRoles = binding.value; - - // 校验传入的角色值是否合法 - if (!requiredRoles || (typeof requiredRoles !== "string" && !Array.isArray(requiredRoles))) { - throw new Error( - "需要提供角色标识!例如:v-has-role=\"'ADMIN'\" 或 v-has-role=\"['ADMIN', 'TEST']\"" - ); - } - - const { roles = [] } = useUserStore().userInfo || {}; - - // 检查是否有对应角色权限 - const hasAuth = Array.isArray(requiredRoles) - ? requiredRoles.some((role) => roles.includes(role)) - : roles.includes(requiredRoles); - - // 如果没有权限,移除元素 - if (!hasAuth && el.parentNode) { - el.parentNode.removeChild(el); - } - }, -}; diff --git a/src/enums/ResultCodeEnum.ts b/src/enums/ResultCodeEnum.ts deleted file mode 100644 index 7a71a04..0000000 --- a/src/enums/ResultCodeEnum.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * 响应码枚举 - */ -export const enum ResultCodeEnum { - /** - * 成功 - */ - SUCCESS = "00000", - /** - * 错误 - */ - ERROR = "B0001", - - /** - * 令牌无效或过期 - */ - TOKEN_INVALID = "A0230", -} diff --git a/src/enums/api-code.enum.ts b/src/enums/api-code.enum.ts new file mode 100644 index 0000000..3772a3b --- /dev/null +++ b/src/enums/api-code.enum.ts @@ -0,0 +1,49 @@ +/** + * API响应码枚举 + */ +export const enum ApiCode { + /** + * 成功 + */ + SUCCESS = "00000", + + /** + * 通用错误 + */ + ERROR = "B0001", + + /** + * 令牌无效或过期 + */ + TOKEN_INVALID = "A0230", + + /** + * 令牌已过期 + */ + TOKEN_EXPIRED = "A0231", + + /** + * 未授权访问 + */ + UNAUTHORIZED = "A0232", + + /** + * 禁止访问 + */ + FORBIDDEN = "A0233", + + /** + * 参数校验失败 + */ + PARAM_INVALID = "A0400", + + /** + * 资源不存在 + */ + NOT_FOUND = "A0404", + + /** + * 服务器内部错误 + */ + INTERNAL_ERROR = "B0500", +} diff --git a/src/layouts/default.vue b/src/layouts/default.vue index 2d5d262..54d7c3e 100644 --- a/src/layouts/default.vue +++ b/src/layouts/default.vue @@ -1,4 +1,32 @@ + + + + diff --git a/src/layouts/tabbar.vue b/src/layouts/tabbar.vue index b022b8a..b7a6af2 100644 --- a/src/layouts/tabbar.vue +++ b/src/layouts/tabbar.vue @@ -8,26 +8,79 @@ * 记得注释 -->