diff --git a/.env b/.env
index 6b96cba..b3c75e4 100644
--- a/.env
+++ b/.env
@@ -1,4 +1,20 @@
-VITE_BASE_URL= #接口地址
-VITE_ASSETSURL=https://cdn.vrupup.com/s/1598/assets/ #资源地址
-VITE_APPID=wx9cb717d8151d8486 #小程序APPID
-VITE_UNI_APPID=_UNI_8842336 #UNI-APPID
\ No newline at end of file
+# 接口地址
+VITE_BASE_URL=
+
+# 资源地址
+VITE_ASSETSURL=https://cdn.vrupup.com/s/1598/assets/
+
+# 小程序APPID
+VITE_APPID=wx9cb717d8151d8486
+
+# UNI-APPID
+VITE_UNI_APPID=_UNI_8842336
+
+# 调试模式
+VITE_DEBUG=false
+
+# API超时时间(毫秒)
+VITE_API_TIMEOUT=10000
+
+# 版本号
+VITE_APP_VERSION=1.0.0
\ No newline at end of file
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..f26eb7d
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,6 @@
+node_modules
+dist
+.hbuilderx
+unpackage
+.env*
+!.env.example
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..78fc279
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,56 @@
+module.exports = {
+ root: true,
+ env: {
+ browser: true,
+ node: true,
+ es6: true,
+ },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:vue/vue3-essential',
+ 'prettier',
+ 'plugin:prettier/recommended',
+ ],
+ parserOptions: {
+ parser: '@babel/eslint-parser',
+ ecmaVersion: 2020,
+ sourceType: 'module',
+ },
+ plugins: ['vue', 'prettier'],
+ rules: {
+ 'prettier/prettier': 'error',
+ 'vue/multi-word-component-names': 'off',
+ 'vue/no-unused-vars': 'error',
+ 'vue/html-self-closing': 'off',
+ 'vue/max-attributes-per-line': [
+ 'error',
+ {
+ singleline: {
+ max: 3,
+ },
+ multiline: {
+ max: 1,
+ },
+ },
+ ],
+ 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+ 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+ 'no-unused-vars': 'error',
+ 'no-undef': 'error',
+ 'no-var': 'error',
+ 'prefer-const': 'error',
+ 'object-shorthand': 'error',
+ 'quote-props': ['error', 'as-needed'],
+ 'array-callback-return': 'error',
+ 'prefer-arrow-callback': 'error',
+ 'arrow-parens': ['error', 'as-needed'],
+ 'arrow-spacing': 'error',
+ 'no-duplicate-imports': 'error',
+ },
+ globals: {
+ uni: 'readonly',
+ getApp: 'readonly',
+ getCurrentPages: 'readonly',
+ plus: 'readonly',
+ },
+}
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..f26eb7d
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+node_modules
+dist
+.hbuilderx
+unpackage
+.env*
+!.env.example
\ No newline at end of file
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000..9c77283
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,37 @@
+module.exports = {
+ // 一行最多 100 字符
+ printWidth: 100,
+ // 使用 2 个空格缩进
+ tabWidth: 2,
+ // 不使用缩进符,而使用空格
+ useTabs: false,
+ // 行尾需要有分号
+ semi: false,
+ // 使用单引号
+ singleQuote: true,
+ // 对象的 key 仅在必要时用引号
+ quoteProps: 'as-needed',
+ // jsx 不使用单引号,而使用双引号
+ jsxSingleQuote: false,
+ // 末尾不需要逗号
+ trailingComma: 'none',
+ // 大括号内的首尾需要空格
+ bracketSpacing: true,
+ // jsx 标签的反尖括号需要换行
+ bracketSameLine: false,
+ // 箭头函数,只有一个参数的时候,也需要括号
+ arrowParens: 'avoid',
+ // 每个文件格式化的范围是文件的全部内容
+ rangeStart: 0,
+ rangeEnd: Infinity,
+ // 不需要写文件开头的 @prettier
+ requirePragma: false,
+ // 不需要自动在文件开头插入 @prettier
+ insertPragma: false,
+ // 使用默认的折行标准
+ proseWrap: 'preserve',
+ // 根据显示样式决定 html 要不要折行
+ htmlWhitespaceSensitivity: 'css',
+ // 换行符使用 lf
+ endOfLine: 'lf',
+}
\ No newline at end of file
diff --git a/App.vue b/App.vue
index 55794e0..ad2cf28 100644
--- a/App.vue
+++ b/App.vue
@@ -12,5 +12,6 @@ export default {
@import "./uni.scss";
@import '@/common/styles/common.css';
@import '@/common/styles/base.scss';
+@import '@/common/styles/dark-mode.scss';
@import '@/uview-plus/index.scss';
diff --git a/README.md b/README.md
index 216e107..c581331 100644
--- a/README.md
+++ b/README.md
@@ -1,80 +1,159 @@
-# Ŀģʹ˵
-
-ģǻ UniApp + Vue3 + uView-Plus СĿģ壬һЩõáߺ
-
-## Ŀ¼ṹ
-
- ```
- api/ # ӿ
- modules/ # ҵӿ
- request.js # װ
- common/ # Դ
- styles/ # ȫʽ
- common.css # codefunԭʽ
- base.scss # ȫʽ
- utils/ # ߺ
- tool.ts # ùߺ
- components/ #
- uni_modules/ # uni-app
- z-paging/ # ҳ
- lib/ #
- luch-request/ # luch-request
- uview-plus/ # uView-Plus
- mixins/ # Vue
- global.ts # ȫֻ
- store/ # ״̬
- index.ts # Vuex store
- pages/ # ҳ
- subPages/ # ְҳ
- App.vue # Ӧ
- main.js # ļ
- pages.json # ҳ
- manifest.json # Ӧ
- uni.scss # ȫʽ
- vite.config.js # Vite
- .nvmdrc # Node.js 汾Ҫ
- .env #
-```
-
-## ʹ÷
-
-1. ģĿ¼ƵĿĿ¼
-2. Ҫ package.json еĿƺ
-3. ʹ npm install װ
-4. Ҫ pages.json еҳ
-5. ʼ¹
-
-## Ҫ
-
-### Ҫ
-- **dotenv** - ע
-
-### ʽ
-
-- common.css: ȫֻʽ
-- base.scss: õ SCSS
-
-### ߺ (tool.js)
-
-- alert: ʾ
-- loading/hideLoading: ʾ/ؼʾ
-- ҳתط: navigateTo, redirectTo, reLaunch, switchTab, navigateBack
-- ػ: storage, removeStorage, getStorageInfo
-- copy: ı
-- saveImageToPhotos: ͼƬ
-- requestPayment: ֧
-- upload: ļϴ
-
-###
-
-- App.vue: ȫʽͻ
-- main.js: Vue Ӧóʼȫֲ
-- pages.json: ҳ·ɺʹ
-- uni.scss: ȫʽ
-
-## ע
-
-1. ʵĿ
-2. ĿĻչߺ
-3. ɸҪĻ滻
-4. ʽļɸĿƹ淶е
+# UniApp 微信小程序快速开发模板
+
+本模板是基于 UniApp + Vue3 + uView-Plus 的微信小程序项目模板,提供了一些常用的工具函数、组件和最佳实践。
+
+## 目录结构
+
+```
+├── api/ # 接口相关
+│ ├── modules/ # 业务接口
+│ └── request.js # 请求封装
+├── common/ # 公共资源
+│ ├── styles/ # 全局样式
+│ │ ├── common.css # codefun原子类样式
+│ │ └── base.scss # 全局样式变量
+│ └── utils/ # 工具函数
+│ └── tool.js # 常用工具函数
+├── components/ # 公共组件
+├── uni_modules/ # uni-app 组件
+│ └── z-paging/ # 分页组件库
+├── lib/ # 第三方库
+│ └── luch-request/ # luch-request 网络请求库
+├── uview-plus/ # uView-Plus 组件库
+├── mixins/ # Vue 混入
+│ └── global.js # 全局混入
+├── store/ # 状态管理
+├── pages/ # 主包页面
+├── subPages/ # 分包页面
+├── App.vue # 应用入口
+├── main.js # 主入口文件
+├── pages.json # 页面配置
+├── manifest.json # 应用配置
+├── uni.scss # 全局样式变量
+├── vite.config.js # Vite 编译配置
+├── .nvmdrc # Node.js 版本要求
+└── .env # 环境变量
+```
+
+## 使用方法
+
+1. 将模板目录复制到你的项目目录中
+2. 根据需要修改 package.json 中的项目名称和描述
+3. 使用 npm install 安装依赖
+4. 根据需要修改 pages.json 中的页面配置
+5. 开始构建你的新项目
+
+## 开发环境与运行
+
+### 环境要求
+
+* Node.js (版本信息在 `.nvmdrc` 文件中指定,当前为 20.0.0)
+* npm 或 yarn
+
+### 安装依赖
+
+```bash
+npm install
+```
+
+### 运行项目
+
+由于这是一个 UniApp 项目,通常需要使用 HBuilderX 或其他支持 UniApp 的 IDE 来运行和调试。具体的运行命令取决于你的开发环境。
+
+### 构建项目
+
+同样,构建项目也需要使用 HBuilderX 或相应的 CLI 工具。
+
+## 项目特性
+
+### 核心依赖
+- **dotenv** - 环境变量注入
+
+### 样式系统
+
+- common.css: 全局原子类样式
+- base.scss: 实用的 SCSS 工具类
+
+### 工具函数 (tool.js)
+
+- alert: 文字轻提示
+- loading/hideLoading: 显示/隐藏加载提示
+- 页面跳转方法: navigateTo, redirectTo, reLaunch, switchTab, navigateBack
+- 本地存储: storage, removeStorage, getStorageInfo
+- copy: 复制文本到剪贴板
+- saveImageToPhotos: 保存图片到相册
+- requestPayment: 微信支付
+- upload: 文件上传
+- formatDate: 日期格式化
+- deepClone: 深拷贝
+- debounce/throttle: 防抖节流
+- getValidator: 表单验证工具
+- toggleDarkMode/getCurrentTheme: 暗黑模式切换
+
+### 网络请求
+
+- 基于 luch-request 的封装
+- 自动添加 token
+- 统一错误处理
+- 请求缓存机制
+- 请求重试机制
+
+### 组件
+
+- uView-Plus 组件库
+- z-paging 分页组件
+
+## 代码规范与开发约定
+
+### 样式
+
+- 全局样式文件位于 `common/styles/` 目录下
+- 遵循项目中已有的风格
+
+### JavaScript
+
+- 严格遵循ES6规范
+- 遵循JavaScript函数式编程范式
+- 方法类函数应该使用 `function` 进行定义
+- 页面的生命周期需要通过 `@dcloudio/uni-app` 依赖进行按需导入
+- 全局变量都集中放置于代码顶部
+- 变量名使用小驼峰命名法
+- 常量名使用全大写
+- 所有 `Promise` 类方法使用 `async` `await` 写法
+- 在需要页面跳转、提示、加载、本地存储等功能的时候,优先使用工具函数
+- 字符串拼接使用ES6的模板语法
+
+### 静态资源
+
+- 静态资源变量 `ASSETSURL` 已进行全局混入,可以在 `` 中直接使用
+
+### 网络请求
+
+- 网络请求使用 `lib/luch-request` 库进行封装
+- 包含请求和响应拦截器,用于处理通用逻辑
+- 各业务板块的接口都应存放在 `api/modules` 下
+
+### 组件
+
+- 所有 `uni_modules` 目录中的组件无需导入直接可以进行使用
+- `uView-Plus` 组件已通过 `easycom` 自动导入
+
+### 页面
+
+- 页面配置在 `pages.json` 中管理
+- 页面使用 Composition API (setup语法糖) 编写
+
+## 代码提交规范
+
+- 提交信息应清晰描述变更内容
+- 对于功能性的新增或修改,使用 `新增` 前缀
+- 对于错误修复,使用 `修复` 前缀
+- 对于性能优化、代码重构,使用 `优化` 前缀
+- 对于文档更新,使用 `文档` 前缀
+- 提交信息应使用中文
+
+## 注意事项
+
+1. 请根据实际项目需求进行修改和扩展功能
+2. 代码文件可按项目规范进行修改替换
+3. 样式文件可按项目规范进行修改
\ No newline at end of file
diff --git a/api/index.js b/api/index.js
new file mode 100644
index 0000000..77ba1bc
--- /dev/null
+++ b/api/index.js
@@ -0,0 +1,15 @@
+/**
+ * API统一导出入口
+ */
+// 导入网络请求实例
+import http from './request.js'
+// 导入各业务模块
+import user from './modules/user.js'
+// 导出网络请求实例
+export { http }
+// 导出各业务模块
+export { user }
+// 默认导出包含所有模块的对象
+export default {
+ user,
+}
diff --git a/api/modules/user.js b/api/modules/user.js
new file mode 100644
index 0000000..60739f9
--- /dev/null
+++ b/api/modules/user.js
@@ -0,0 +1,85 @@
+import http from '@/api/request.js'
+
+/**
+ * 用户相关API模块
+ */
+export default {
+ /**
+ * 用户登录
+ * @param {Object} data 登录数据
+ * @returns {Promise}
+ */
+ login(data) {
+ return http.post('/user/login', data)
+ },
+
+ /**
+ * 用户注册
+ * @param {Object} data 注册数据
+ * @returns {Promise}
+ */
+ register(data) {
+ return http.post('/user/register', data)
+ },
+
+ /**
+ * 获取用户信息
+ * @returns {Promise}
+ */
+ getUserInfo() {
+ return http.get('/user/info')
+ },
+
+ /**
+ * 更新用户信息
+ * @param {Object} data 用户信息数据
+ * @returns {Promise}
+ */
+ updateUserInfo(data) {
+ return http.put('/user/info', data)
+ },
+
+ /**
+ * 修改密码
+ * @param {Object} data 密码数据
+ * @returns {Promise}
+ */
+ changePassword(data) {
+ return http.post('/user/change-password', data)
+ },
+
+ /**
+ * 退出登录
+ * @returns {Promise}
+ */
+ logout() {
+ return http.post('/user/logout')
+ },
+
+ /**
+ * 获取用户列表
+ * @param {Object} params 查询参数
+ * @returns {Promise}
+ */
+ getUserList(params) {
+ return http.get('/user/list', { params })
+ },
+
+ /**
+ * 获取用户详情
+ * @param {number} userId 用户ID
+ * @returns {Promise}
+ */
+ getUserDetail(userId) {
+ return http.get(`/user/${userId}`)
+ },
+
+ /**
+ * 删除用户
+ * @param {number} userId 用户ID
+ * @returns {Promise}
+ */
+ deleteUser(userId) {
+ return http.delete(`/user/${userId}`)
+ },
+}
diff --git a/api/request.js b/api/request.js
index 3174fdc..250c6e0 100644
--- a/api/request.js
+++ b/api/request.js
@@ -1,9 +1,46 @@
-import Request from '@/lib/luch-request/index.js'
-import tool from '@/common/utils/tool.js'
-
-const baseUrl = import.meta.env.VITE_BASE_URL
+import Request from '@/lib/luch-request/index.js'
+import tool from '@/common/utils/tool.js'
+import EnvConfig from '@/common/utils/env.js'
+
+const baseUrl = EnvConfig.BASE_URL
const http = new Request()
-
+// 缓存存储
+const cache = new Map()
+// 缓存过期时间(毫秒),默认5分钟
+const CACHE_EXPIRATION = 5 * 60 * 1000
+// 缓存工具方法
+const cacheUtils = {
+ // 生成缓存key
+ generateKey(config) {
+ return `${config.method}:${config.url}:${JSON.stringify(config.data || {})}:${JSON.stringify(config.params || {})}`
+ },
+ // 获取缓存
+ get(key) {
+ const cached = cache.get(key)
+ if (!cached) return null
+ // 检查是否过期
+ if (Date.now() - cached.timestamp > CACHE_EXPIRATION) {
+ cache.delete(key)
+ return null
+ }
+ return cached.data
+ },
+ // 设置缓存
+ set(key, data) {
+ cache.set(key, {
+ data,
+ timestamp: Date.now(),
+ })
+ },
+ // 清除缓存
+ clear() {
+ cache.clear()
+ },
+ // 清除指定缓存
+ delete(key) {
+ cache.delete(key)
+ },
+}
/* 设置全局配置 */
http.setConfig(config => {
config.header = { ...config.header }
@@ -11,31 +48,115 @@ http.setConfig(config => {
config.baseURL = baseUrl
return config
})
-
http.interceptors.request.use(
async config => {
config.header = { ...config.header }
+ // 自动添加token
+ const token = uni.getStorageSync('token')
+ if (token) {
+ config.header.Authorization = `Bearer ${token}`
+ }
+ // 添加通用请求头
+ config.header['Content-Type'] = 'application/json'
+ // 检查缓存
+ if (config.method === 'GET' && config.cache !== false) {
+ const cacheKey = cacheUtils.generateKey(config)
+ const cachedData = cacheUtils.get(cacheKey)
+ if (cachedData) {
+ // 如果有缓存,直接返回缓存数据
+ return Promise.resolve({
+ data: cachedData,
+ statusCode: 200,
+ })
+ }
+ }
return config
},
config => {
return Promise.reject(config)
}
)
-
-http.interceptors.response.use(response => {
- if (response.statusCode == 500 || response.statusCode == 404 || response.statusCode == 403) {
- console.error(response)
- return tool.alert('网络错误,请稍后重试')
+http.interceptors.response.use(
+ response => {
+ // 网络错误处理
+ if (response.statusCode >= 500) {
+ console.error('服务器错误:', response)
+ tool.alert('服务器错误,请稍后重试')
+ return Promise.reject(response)
+ }
+ if (response.statusCode === 404) {
+ console.error('接口不存在:', response)
+ tool.alert('接口不存在')
+ return Promise.reject(response)
+ }
+ if (response.statusCode === 403) {
+ console.error('无权限访问:', response)
+ tool.alert('无权限访问')
+ return Promise.reject(response)
+ }
+ // 未授权处理
+ if (response.statusCode === 401 || response.data?.code === 401) {
+ console.error('未授权或登录过期:', response)
+ tool.alert('登录已过期,请重新登录')
+ // 清除本地token
+ uni.removeStorageSync('token')
+ // 跳转到登录页
+ setTimeout(() => {
+ uni.reLaunch({ url: '/pages/login/login' }) // 根据实际登录页路径调整
+ }, 1500)
+ return Promise.reject(response)
+ }
+ // 业务错误处理
+ if (response.data && response.data.code !== undefined && response.data.code !== 200) {
+ const message = response.data.message || response.data.msg || '业务错误'
+ console.error('业务错误:', message)
+ tool.alert(message)
+ return Promise.reject(response)
+ }
+ // 成功响应
+ if (response.statusCode === 200) {
+ // 缓存GET请求的响应数据
+ if (response.config && response.config.method === 'GET' && response.config.cache !== false) {
+ const cacheKey = cacheUtils.generateKey(response.config)
+ cacheUtils.set(cacheKey, response.data)
+ }
+ return Promise.resolve(response)
+ } else {
+ return Promise.reject(response)
+ }
+ },
+ error => {
+ console.error('请求失败:', error)
+ if (error.errMsg) {
+ // 网络错误
+ tool.alert('网络连接失败,请检查网络')
+ } else {
+ tool.alert('请求失败,请稍后重试')
+ }
+ return Promise.reject(error)
}
-
- if (response.statusCode == 401 || response.data.code == 401) {
- }
-
- if (response.statusCode == 200) {
- return Promise.resolve(response)
- } else {
- return Promise.reject(response)
- }
-})
-
+)
+// 添加缓存功能到http实例
+http.cache = cacheUtils
+// 添加重试方法
+http.retry = async function (config, retries = 3, delay = 1000) {
+ return new Promise((resolve, reject) => {
+ const attempt = async retryCount => {
+ try {
+ const response = await this.request(config)
+ resolve(response)
+ } catch (error) {
+ if (retryCount <= 0) {
+ reject(error)
+ } else {
+ // 延迟后重试
+ setTimeout(() => {
+ attempt(retryCount - 1)
+ }, delay)
+ }
+ }
+ }
+ attempt(retries)
+ })
+}
export default http
diff --git a/common/constants/app.js b/common/constants/app.js
new file mode 100644
index 0000000..df078ea
--- /dev/null
+++ b/common/constants/app.js
@@ -0,0 +1,30 @@
+/**
+ * 应用相关常量
+ */
+
+// 应用信息
+export const APP_NAME = '小程序模板'
+export const APP_VERSION = '1.0.0'
+
+// 页面路径常量
+export const PAGE_PATHS = {
+ INDEX: '/pages/index/index',
+ WELCOME: '/subPages/welcome/index',
+ LOGIN: '/pages/login/login', // 示例路径
+ REGISTER: '/pages/register/register' // 示例路径
+}
+
+// 存储键名常量
+export const STORAGE_KEYS = {
+ TOKEN: 'token',
+ USER_INFO: 'userInfo',
+ THEME: 'theme',
+ LANG: 'language'
+}
+
+// 事件常量
+export const EVENTS = {
+ USER_LOGIN: 'userLogin',
+ USER_LOGOUT: 'userLogout',
+ THEME_CHANGE: 'themeChange'
+}
\ No newline at end of file
diff --git a/common/styles/base.scss b/common/styles/base.scss
index f605867..a2f6da4 100644
--- a/common/styles/base.scss
+++ b/common/styles/base.scss
@@ -1,118 +1,97 @@
-// 定义内外边距,历遍1-100
-@for $i from 0 through 100 {
- // 只要双数和能被5除尽的数
- @if $i % 2==0 or $i % 5==0 {
- // 得出:u-margin-30或者u-m-30
- .w-#{$i} {
- width: calc($i * 1%) !important;
- }
- .p-x-#{$i} {
- padding-left: $i + rpx !important;
- padding-right: $i + rpx !important;
- }
-
- .p-y-#{$i} {
- padding-top: $i + rpx !important;
- padding-bottom: $i + rpx !important;
- }
-
- .m-x-#{$i} {
- margin-left: $i + rpx !important;
- margin-right: $i + rpx !important;
- }
-
- .m-y-#{$i} {
- margin-top: $i + rpx !important;
- margin-bottom: $i + rpx !important;
- }
-
- .m-#{$i} {
- margin-left: $i + rpx !important;
- margin-right: $i + rpx !important;
- margin-top: $i + rpx !important;
- margin-bottom: $i + rpx !important;
- }
-
- .p-#{$i} {
- padding-left: $i + rpx !important;
- padding-right: $i + rpx !important;
- padding-top: $i + rpx !important;
- padding-bottom: $i + rpx !important;
- }
-
- .m-l-#{$i} {
- margin-left: $i + rpx !important;
- }
-
- .m-t-#{$i} {
- margin-top: $i + rpx !important;
- }
-
- .m-r-#{$i} {
- margin-right: $i + rpx !important;
- }
-
- .m-b-#{$i} {
- margin-bottom: $i + rpx !important;
- }
-
- .p-l-#{$i} {
- padding-left: $i + rpx !important;
- }
-
- .p-t-#{$i} {
- padding-top: $i + rpx !important;
- }
-
- .p-r-#{$i} {
- padding-right: $i + rpx !important;
- }
-
- .p-b-#{$i} {
- padding-bottom: $i + rpx !important;
- }
-
- .l-p-#{$i} {
- letter-spacing: $i + rpx !important;
- }
-
- .z-i-#{$i} {
- z-index: $i;
- }
-
- .l-h-#{$i} {
- line-height: $i + rpx !important;
- }
- .r-#{$i} {
- right: $i + rpx !important;
- }
- .l-#{$i} {
- left: $i + rpx !important;
- }
-
- .t-#{$i} {
- top: $i + rpx !important;
- }
- .b-#{$i} {
- bottom: $i + rpx !important;
- }
+// 定义内外边距,只生成常用的数值
+$spacing-sizes: (0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100);
+@each $i in $spacing-sizes {
+ .w-#{$i} {
+ width: calc($i * 1%) !important;
+ }
+ .p-x-#{$i} {
+ padding-left: $i + rpx !important;
+ padding-right: $i + rpx !important;
+ }
+ .p-y-#{$i} {
+ padding-top: $i + rpx !important;
+ padding-bottom: $i + rpx !important;
+ }
+ .m-x-#{$i} {
+ margin-left: $i + rpx !important;
+ margin-right: $i + rpx !important;
+ }
+ .m-y-#{$i} {
+ margin-top: $i + rpx !important;
+ margin-bottom: $i + rpx !important;
+ }
+ .m-#{$i} {
+ margin-left: $i + rpx !important;
+ margin-right: $i + rpx !important;
+ margin-top: $i + rpx !important;
+ margin-bottom: $i + rpx !important;
+ }
+ .p-#{$i} {
+ padding-left: $i + rpx !important;
+ padding-right: $i + rpx !important;
+ padding-top: $i + rpx !important;
+ padding-bottom: $i + rpx !important;
+ }
+ .m-l-#{$i} {
+ margin-left: $i + rpx !important;
+ }
+ .m-t-#{$i} {
+ margin-top: $i + rpx !important;
+ }
+ .m-r-#{$i} {
+ margin-right: $i + rpx !important;
+ }
+ .m-b-#{$i} {
+ margin-bottom: $i + rpx !important;
+ }
+ .p-l-#{$i} {
+ padding-left: $i + rpx !important;
+ }
+ .p-t-#{$i} {
+ padding-top: $i + rpx !important;
+ }
+ .p-r-#{$i} {
+ padding-right: $i + rpx !important;
+ }
+ .p-b-#{$i} {
+ padding-bottom: $i + rpx !important;
+ }
+ .l-p-#{$i} {
+ letter-spacing: $i + rpx !important;
+ }
+ .z-i-#{$i} {
+ z-index: $i;
+ }
+ .l-h-#{$i} {
+ line-height: $i + rpx !important;
+ }
+ .r-#{$i} {
+ right: $i + rpx !important;
+ }
+ .l-#{$i} {
+ left: $i + rpx !important;
+ }
+ .t-#{$i} {
+ top: $i + rpx !important;
+ }
+ .b-#{$i} {
+ bottom: $i + rpx !important;
}
}
-
-// 定义字体(rpx)单位,大于或等于20的都为rpx单位字体
-@for $i from 9 through 60 {
+// 定义字体(rpx)单位,只生成常用的字体大小
+$font-sizes: (9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 40, 44, 48, 52, 56, 60);
+@each $i in $font-sizes {
.font-#{$i} {
font-size: $i + rpx !important;
}
}
-
// 圆角
-@for $i from 4 through 60 {
+$rounded-sizes: (4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60);
+@each $i in $rounded-sizes {
.rounded-#{$i} {
border-radius: $i + rpx;
}
}
-
// 多行文本溢出
@for $i from 1 through 5 {
.over-line-#{$i} {
@@ -125,3 +104,120 @@
-webkit-box-orient: vertical;
}
}
+// 显示相关工具类
+.display-none {
+ display: none !important;
+}
+.display-block {
+ display: block !important;
+}
+.display-inline {
+ display: inline !important;
+}
+.display-inline-block {
+ display: inline-block !important;
+}
+.display-flex {
+ display: flex !important;
+}
+// 浮动相关
+.float-left {
+ float: left !important;
+}
+.float-right {
+ float: right !important;
+}
+.float-none {
+ float: none !important;
+}
+.clearfix::after {
+ content: '';
+ display: table;
+ clear: both;
+}
+// 文本对齐
+.text-left {
+ text-align: left !important;
+}
+.text-center {
+ text-align: center !important;
+}
+.text-right {
+ text-align: right !important;
+}
+.text-justify {
+ text-align: justify !important;
+}
+// 文本装饰
+.text-underline {
+ text-decoration: underline !important;
+}
+.text-line-through {
+ text-decoration: line-through !important;
+}
+.text-no-underline {
+ text-decoration: none !important;
+}
+// 字体粗细
+.font-normal {
+ font-weight: normal !important;
+}
+.font-bold {
+ font-weight: bold !important;
+}
+// 文本大小写
+.text-uppercase {
+ text-transform: uppercase !important;
+}
+.text-lowercase {
+ text-transform: lowercase !important;
+}
+.text-capitalize {
+ text-transform: capitalize !important;
+}
+// 垂直对齐
+.align-baseline {
+ vertical-align: baseline !important;
+}
+.align-top {
+ vertical-align: top !important;
+}
+.align-middle {
+ vertical-align: middle !important;
+}
+.align-bottom {
+ vertical-align: bottom !important;
+}
+// 位置相关
+.position-relative {
+ position: relative !important;
+}
+.position-absolute {
+ position: absolute !important;
+}
+.position-fixed {
+ position: fixed !important;
+}
+.position-static {
+ position: static !important;
+}
+// 溢出处理
+.overflow-auto {
+ overflow: auto !important;
+}
+.overflow-hidden {
+ overflow: hidden !important;
+}
+.overflow-visible {
+ overflow: visible !important;
+}
+.overflow-scroll {
+ overflow: scroll !important;
+}
+// 可见性
+.visible {
+ visibility: visible !important;
+}
+.invisible {
+ visibility: hidden !important;
+}
diff --git a/common/styles/dark-mode.scss b/common/styles/dark-mode.scss
new file mode 100644
index 0000000..dadf739
--- /dev/null
+++ b/common/styles/dark-mode.scss
@@ -0,0 +1,88 @@
+/* 暗黑模式样式 */
+:root {
+ // 默认使用亮色模式变量
+ --uni-color-primary: #007aff;
+ --uni-color-success: #4cd964;
+ --uni-color-warning: #f0ad4e;
+ --uni-color-error: #dd524d;
+
+ --uni-text-color: #333;
+ --uni-text-color-inverse: #fff;
+ --uni-text-color-grey: #999;
+ --uni-text-color-placeholder: #808080;
+ --uni-text-color-disable: #c0c0c0;
+
+ --uni-bg-color: #ffffff;
+ --uni-bg-color-grey: #f8f8f8;
+ --uni-bg-color-hover: #f1f1f1;
+ --uni-bg-color-mask: rgba(0, 0, 0, 0.4);
+
+ --uni-border-color: #c8c7cc;
+}
+
+/* 暗黑模式 */
+[data-theme='dark'] {
+ --uni-color-primary: #0a84ff;
+ --uni-color-success: #34c759;
+ --uni-color-warning: #ff9500;
+ --uni-color-error: #ff3b30;
+
+ --uni-text-color: #f2f2f7;
+ --uni-text-color-inverse: #000000;
+ --uni-text-color-grey: #8e8e93;
+ --uni-text-color-placeholder: #4c4c4c;
+ --uni-text-color-disable: #636366;
+
+ --uni-bg-color: #1c1c1e;
+ --uni-bg-color-grey: #2c2c2e;
+ --uni-bg-color-hover: #3a3a3c;
+ --uni-bg-color-mask: rgba(0, 0, 0, 0.6);
+
+ --uni-border-color: #434346;
+}
+
+/* 系统级暗黑模式 */
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme='light']) {
+ --uni-color-primary: #0a84ff;
+ --uni-color-success: #34c759;
+ --uni-color-warning: #ff9500;
+ --uni-color-error: #ff3b30;
+
+ --uni-text-color: #f2f2f7;
+ --uni-text-color-inverse: #000000;
+ --uni-text-color-grey: #8e8e93;
+ --uni-text-color-placeholder: #4c4c4c;
+ --uni-text-color-disable: #636366;
+
+ --uni-bg-color: #1c1c1e;
+ --uni-bg-color-grey: #2c2c2e;
+ --uni-bg-color-hover: #3a3a3c;
+ --uni-bg-color-mask: rgba(0, 0, 0, 0.6);
+
+ --uni-border-color: #434346;
+ }
+}
+
+/* 应用暗黑模式的通用类 */
+.dark-mode {
+ background-color: var(--uni-bg-color);
+ color: var(--uni-text-color);
+}
+
+.dark-mode .dark-invert {
+ background-color: var(--uni-bg-color-inverse);
+ color: var(--uni-text-color-inverse);
+}
+
+/* 通用暗黑模式切换类 */
+.light-mode {
+ background-color: var(--uni-bg-color);
+ color: var(--uni-text-color);
+}
+
+/* 通用暗黑模式切换类 */
+.dark-mode {
+ background-color: var(--uni-bg-color);
+ color: var(--uni-text-color);
+}
diff --git a/common/utils/env.js b/common/utils/env.js
new file mode 100644
index 0000000..5e83101
--- /dev/null
+++ b/common/utils/env.js
@@ -0,0 +1,80 @@
+/**
+ * 环境变量验证工具
+ */
+/**
+ * 验证必需的环境变量
+ * @param {Array} requiredVars 必需的环境变量键名数组
+ * @throws {Error} 如果有任何必需的环境变量缺失则抛出错误
+ */
+export function validateEnv(requiredVars = []) {
+ const missingVars = []
+ for (const key of requiredVars) {
+ if (!import.meta.env[key]) {
+ missingVars.push(key)
+ }
+ }
+ if (missingVars.length > 0) {
+ throw new Error(`缺少必需的环境变量: ${missingVars.join(', ')}`)
+ }
+}
+/**
+ * 获取环境变量,带默认值和类型转换
+ * @param {string} key 环境变量键名
+ * @param {any} defaultValue 默认值
+ * @param {string} type 类型 ('string' | 'number' | 'boolean' | 'array')
+ * @returns {any} 环境变量值
+ */
+export function getEnv(key, defaultValue = null, type = 'string') {
+ const value = import.meta.env[key]
+ if (value === undefined || value === null || value === '') {
+ return defaultValue
+ }
+ switch (type) {
+ case 'number':
+ const numValue = Number(value)
+ return isNaN(numValue) ? defaultValue : numValue
+ case 'boolean':
+ return value === 'true' || value === '1'
+ case 'array':
+ try {
+ return JSON.parse(value)
+ } catch (e) {
+ return Array.isArray(defaultValue) ? defaultValue : []
+ }
+ case 'string':
+ default:
+ return value
+ }
+}
+/**
+ * 环境变量配置
+ */
+export const EnvConfig = {
+ // API基础URL
+ BASE_URL: getEnv('VITE_BASE_URL', '', 'string'),
+ // 静态资源URL
+ ASSETS_URL: getEnv('VITE_ASSETSURL', '', 'string'),
+ // 小程序APPID
+ APP_ID: getEnv('VITE_APPID', '', 'string'),
+ // UNI-APPID
+ UNI_APP_ID: getEnv('VITE_UNI_APPID', '', 'string'),
+ // 是否为开发环境
+ IS_DEV: getEnv('NODE_ENV', 'development', 'string') === 'development',
+ // 是否为生产环境
+ IS_PROD: getEnv('NODE_ENV', 'development', 'string') === 'production',
+ // 调试模式
+ DEBUG: getEnv('VITE_DEBUG', false, 'boolean'),
+ // API超时时间(毫秒)
+ API_TIMEOUT: getEnv('VITE_API_TIMEOUT', 10000, 'number'),
+}
+// 验证必需的环境变量
+try {
+ validateEnv(['VITE_BASE_URL', 'VITE_APPID'])
+} catch (error) {
+ console.error('环境变量验证失败:', error.message)
+ // 在开发环境中抛出错误
+ if (EnvConfig.IS_DEV) {
+ throw error
+ }
+}
+export default EnvConfig
\ No newline at end of file
diff --git a/common/utils/tool.js b/common/utils/tool.js
index 0f0395b..bcc650e 100644
--- a/common/utils/tool.js
+++ b/common/utils/tool.js
@@ -1,6 +1,5 @@
const baseUrl = import.meta.env.VITE_BASE_URL
const assetsUrl = import.meta.env.VITE_ASSETSURL
-
/**
* 工具类 - 提供常用的工具方法
* @class Tool
@@ -13,11 +12,9 @@ class Tool {
SUCCESS: 1,
LOADING: 2,
}
-
// 字体加载状态缓存
this.loadedFonts = new Set()
}
-
/**
* 文字轻提示
* @param {string} str 提示文字
@@ -30,13 +27,11 @@ class Tool {
console.warn('alert方法需要提供提示文字')
return
}
-
const iconMap = {
[this.ICON_TYPES.NONE]: 'none',
[this.ICON_TYPES.SUCCESS]: 'success',
[this.ICON_TYPES.LOADING]: 'loading',
}
-
uni.showToast({
title: String(str),
icon: iconMap[icon] || 'none',
@@ -49,7 +44,6 @@ class Tool {
})
})
}
-
/**
* 显示loading加载
* @param {string} [title=' '] 加载文案
@@ -58,14 +52,12 @@ class Tool {
loading(title = ' ', mask = true) {
uni.showLoading({ title, mask })
}
-
/**
* 关闭loading提示框
*/
hideLoading() {
uni.hideLoading()
}
-
/**
* 统一处理URL格式,确保以/开头
* @param {string} url 页面地址
@@ -76,17 +68,14 @@ class Tool {
if (!url || typeof url !== 'string') {
throw new Error('URL必须是字符串')
}
-
return url.startsWith('/') ? url : `/${url}`
}
-
/**
* 可返回跳转(导航到新页面)
* @param {string} url 页面地址
*/
navigateTo(url) {
const formattedUrl = this._formatUrl(url)
-
uni.navigateTo({
url: formattedUrl,
fail: err => {
@@ -95,7 +84,6 @@ class Tool {
},
})
}
-
/**
* 不可返回跳转(重定向到新页面)
* @param {string} url 页面地址
@@ -103,7 +91,6 @@ class Tool {
redirectTo(url) {
uni.redirectTo({ url: this._formatUrl(url) })
}
-
/**
* 清除页面栈跳转(重新启动到新页面)
* @param {string} url 页面地址
@@ -111,7 +98,6 @@ class Tool {
reLaunch(url) {
uni.reLaunch({ url: this._formatUrl(url) })
}
-
/**
* 跳转tabBar页
* @param {string} url 页面地址
@@ -119,7 +105,6 @@ class Tool {
switchTab(url) {
uni.switchTab({ url: this._formatUrl(url) })
}
-
/**
* 返回上一页面或指定页面
* @param {number} [delta=1] 返回的页面数
@@ -127,7 +112,6 @@ class Tool {
*/
navigateBack(delta = 1, fallbackUrl = '/pages/index/index') {
const pages = getCurrentPages()
-
if (pages.length <= 1) {
console.warn('无上一页,使用回退地址')
uni.reLaunch({ url: fallbackUrl })
@@ -135,7 +119,6 @@ class Tool {
uni.navigateBack({ delta })
}
}
-
/**
* 操作本地缓存
* @param {string} key 缓存键值
@@ -146,24 +129,20 @@ class Tool {
if (typeof key !== 'string') {
throw new Error('key必须是字符串')
}
-
// 设置操作
if (value !== undefined && value !== null) {
uni.setStorageSync(key, value)
return
}
-
// 读取操作
if (key !== '#') {
return uni.getStorageSync(key)
}
-
// 特殊操作
if (key === '#') {
uni.clearStorageSync()
}
}
-
/**
* 删除指定缓存
* @param {string} key 要删除的缓存键
@@ -172,10 +151,8 @@ class Tool {
if (typeof key !== 'string') {
throw new Error('key必须是字符串')
}
-
uni.removeStorageSync(key)
}
-
/**
* 获取缓存信息
* @returns {Object} 缓存信息
@@ -183,7 +160,6 @@ class Tool {
getStorageInfo() {
return uni.getStorageInfoSync()
}
-
/**
* 复制文本到剪贴板
* @param {string} data 要复制的文本
@@ -194,7 +170,6 @@ class Tool {
this.alert('暂无内容')
return false
}
-
try {
await new Promise((resolve, reject) => {
uni.setClipboardData({
@@ -203,7 +178,6 @@ class Tool {
fail: reject,
})
})
-
this.alert('复制成功')
return true
} catch (error) {
@@ -212,7 +186,6 @@ class Tool {
return false
}
}
-
/**
* 导入外部字体
* @param {string} fontName 字体文件名(不含路径)
@@ -222,15 +195,12 @@ class Tool {
if (!fontName || typeof fontName !== 'string') {
throw new Error('字体名称必须是字符串')
}
-
// 检查是否已加载过
if (this.loadedFonts.has(fontName)) {
return true
}
-
try {
const fontFamily = fontName.replace(/\.[^/.]+$/, '') // 移除文件扩展名
-
await new Promise((resolve, reject) => {
uni.loadFontFace({
family: fontFamily,
@@ -240,7 +210,6 @@ class Tool {
fail: reject,
})
})
-
this.loadedFonts.add(fontName)
return true
} catch (error) {
@@ -248,7 +217,6 @@ class Tool {
return false
}
}
-
/**
* 保存图片到相册
* @param {string} url 图片URL
@@ -259,7 +227,6 @@ class Tool {
this.alert('图片地址不能为空')
return false
}
-
try {
// 检查权限
const { authSetting } = await new Promise((resolve, reject) => {
@@ -268,7 +235,6 @@ class Tool {
fail: reject,
})
})
-
if (!authSetting['scope.writePhotosAlbum']) {
// 请求权限
await new Promise((resolve, reject) => {
@@ -279,7 +245,6 @@ class Tool {
})
})
}
-
// 获取图片信息
const { path } = await new Promise((resolve, reject) => {
uni.getImageInfo({
@@ -288,7 +253,6 @@ class Tool {
fail: reject,
})
})
-
// 保存到相册
await new Promise((resolve, reject) => {
uni.saveImageToPhotosAlbum({
@@ -297,12 +261,10 @@ class Tool {
fail: reject,
})
})
-
this.alert('已保存到相册')
return true
} catch (error) {
console.error('保存图片失败:', error)
-
if (error.errMsg && error.errMsg.includes('auth')) {
// 权限相关错误
await new Promise(resolve => {
@@ -313,16 +275,240 @@ class Tool {
success: resolve,
})
})
-
uni.openSetting()
} else {
this.alert('保存失败,请重试')
}
-
return false
}
}
-
+ /**
+ * 防抖函数
+ * @param {Function} func 要防抖的函数
+ * @param {number} wait 延迟时间(ms)
+ * @param {boolean} immediate 是否立即执行
+ * @returns {Function} 防抖后的函数
+ */
+ debounce(func, wait, immediate = false) {
+ let timeout
+ return function (...args) {
+ const later = () => {
+ timeout = null
+ if (!immediate) func.apply(this, args)
+ }
+ const callNow = immediate && !timeout
+ clearTimeout(timeout)
+ timeout = setTimeout(later, wait)
+ if (callNow) func.apply(this, args)
+ }
+ }
+ /**
+ * 节流函数
+ * @param {Function} func 要节流的函数
+ * @param {number} wait 延迟时间(ms)
+ * @returns {Function} 节流后的函数
+ */
+ throttle(func, wait) {
+ let timeout
+ let previous = 0
+ return function (...args) {
+ const now = Date.now()
+ const remaining = wait - (now - previous)
+ const context = this
+ if (remaining <= 0 || remaining > wait) {
+ if (timeout) {
+ clearTimeout(timeout)
+ timeout = null
+ }
+ previous = now
+ func.apply(context, args)
+ } else if (!timeout) {
+ timeout = setTimeout(() => {
+ previous = Date.now()
+ timeout = null
+ func.apply(context, args)
+ }, remaining)
+ }
+ }
+ }
+ /**
+ * 日期格式化
+ * @param {Date|string|number} date 日期对象、字符串或时间戳
+ * @param {string} fmt 格式字符串, 默认为 'YYYY-MM-DD HH:mm:ss'
+ * @returns {string} 格式化后的日期字符串
+ */
+ formatDate(date, fmt = 'YYYY-MM-DD HH:mm:ss') {
+ // 如果传入的是时间戳,转换为Date对象
+ if (typeof date === 'number') {
+ date = new Date(date)
+ } else if (typeof date === 'string') {
+ // 如果传入的是字符串,尝试转换为Date对象
+ date = new Date(date)
+ }
+ if (!(date instanceof Date) || isNaN(date.getTime())) {
+ throw new Error('Invalid date')
+ }
+ const o = {
+ 'Y+': date.getFullYear(),
+ 'M+': date.getMonth() + 1,
+ 'D+': date.getDate(),
+ 'H+': date.getHours(),
+ 'm+': date.getMinutes(),
+ 's+': date.getSeconds(),
+ 'q+': Math.floor((date.getMonth() + 3) / 3),
+ S: date.getMilliseconds(),
+ }
+ for (let k in o) {
+ if (new RegExp('(' + k + ')').test(fmt)) {
+ let str = o[k] + ''
+ if (k === 'S') {
+ fmt = fmt.replace(RegExp.$1, str.padStart(3, '0'))
+ } else {
+ fmt = fmt.replace(RegExp.$1, str.length === 1 ? '0' + str : str)
+ }
+ }
+ }
+ return fmt
+ }
+ /**
+ * 深拷贝函数
+ * @param {*} obj 要深拷贝的对象
+ * @returns {*} 拷贝后的对象
+ */
+ deepClone(obj) {
+ // 处理null、undefined和原始类型
+ if (obj === null || typeof obj !== 'object') {
+ return obj
+ }
+ // 处理日期对象
+ if (obj instanceof Date) {
+ return new Date(obj.getTime())
+ }
+ // 处理数组和对象
+ if (obj instanceof Array) {
+ return obj.map(item => this.deepClone(item))
+ }
+ // 处理普通对象
+ const clonedObj = {}
+ for (let key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ clonedObj[key] = this.deepClone(obj[key])
+ }
+ }
+ return clonedObj
+ }
+ /**
+ * 表单验证工具
+ * @class Validator
+ */
+ getValidator() {
+ const rules = {
+ /**
+ * 必填验证
+ * @param {*} value 值
+ * @param {boolean} required 是否必填
+ * @returns {boolean} 验证结果
+ */
+ required(value, required = true) {
+ if (!required) return true
+ return value !== undefined && value !== null && value !== ''
+ },
+ /**
+ * 邮箱验证
+ * @param {string} value 值
+ * @returns {boolean} 验证结果
+ */
+ email(value) {
+ if (!value) return true
+ const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
+ return reg.test(value)
+ },
+ /**
+ * 手机号验证
+ * @param {string} value 值
+ * @returns {boolean} 验证结果
+ */
+ mobile(value) {
+ if (!value) return true
+ const reg = /^1[3-9]\d{9}$/
+ return reg.test(value)
+ },
+ /**
+ * 最小长度验证
+ * @param {string} value 值
+ * @param {number} length 最小长度
+ * @returns {boolean} 验证结果
+ */
+ minLength(value, length) {
+ if (!value) return true
+ return value.length >= length
+ },
+ /**
+ * 最大长度验证
+ * @param {string} value 值
+ * @param {number} length 最大长度
+ * @returns {boolean} 验证结果
+ */
+ maxLength(value, length) {
+ if (!value) return true
+ return value.length <= length
+ },
+ /**
+ * 数值范围验证
+ * @param {number} value 值
+ * @param {number} min 最小值
+ * @param {number} max 最大值
+ * @returns {boolean} 验证结果
+ */
+ range(value, min, max) {
+ if (value === undefined || value === null) return true
+ return value >= min && value <= max
+ },
+ }
+ return {
+ /**
+ * 验证单个字段
+ * @param {*} value 值
+ * @param {Object} rule 规则
+ * @returns {string|null} 错误信息
+ */
+ validateField(value, rule) {
+ // 必填验证
+ if (rule.required !== undefined && !rules.required(value, rule.required)) {
+ return rule.message || '此字段为必填项'
+ }
+ // 如果值为空且非必填,跳过其他验证
+ if (value === undefined || value === null || value === '') {
+ return null
+ }
+ // 其他验证规则
+ if (rule.type && rules[rule.type]) {
+ if (!rules[rule.type](value, rule.param1, rule.param2)) {
+ return rule.message || '字段格式不正确'
+ }
+ }
+ return null
+ },
+ /**
+ * 验证整个表单
+ * @param {Object} data 表单数据
+ * @param {Object} rules 验证规则
+ * @returns {Object} 验证结果 { isValid: boolean, errors: Object }
+ */
+ validate(data, rules) {
+ const errors = {}
+ let isValid = true
+ for (let field in rules) {
+ const error = this.validateField(data[field], rules[field])
+ if (error) {
+ errors[field] = error
+ isValid = false
+ }
+ }
+ return { isValid, errors }
+ },
+ }
+ }
/**
* 微信支付
* @param {Object} paymentData 支付参数
@@ -338,6 +524,11 @@ class Tool {
})
})
}
+ /**
+ * 文件上传
+ * @param {string} filePath 文件路径
+ * @returns {Promise