You've already forked template-MP
Compare commits
2 Commits
1f3600103e
...
future
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8348d80e9 | ||
|
|
65656f1810 |
25
.env
25
.env
@@ -1,5 +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
|
||||
VITE_LIBVERSION=3.0.0 #微信小程序基础库
|
||||
# 接口地址
|
||||
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
|
||||
6
.eslintignore
Normal file
6
.eslintignore
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
dist
|
||||
.hbuilderx
|
||||
unpackage
|
||||
.env*
|
||||
!.env.example
|
||||
56
.eslintrc.js
Normal file
56
.eslintrc.js
Normal file
@@ -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',
|
||||
},
|
||||
}
|
||||
6
.prettierignore
Normal file
6
.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
dist
|
||||
.hbuilderx
|
||||
unpackage
|
||||
.env*
|
||||
!.env.example
|
||||
37
.prettierrc.js
Normal file
37
.prettierrc.js
Normal file
@@ -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',
|
||||
}
|
||||
1
App.vue
1
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';
|
||||
</style>
|
||||
|
||||
106
IFLOW.md
106
IFLOW.md
@@ -1,30 +1,41 @@
|
||||
# 项目概述 - IFLOW 上下文
|
||||
|
||||
这是一个基于 UniApp + Vue3 + uView-Plus 的微信小程序项目模板。它提供了一个基础的项目结构和一些常用的工具函数,方便快速开发微信小程序。
|
||||
这是一个基于 UniApp + Vue3 + uView-Plus 的微信小程序项目模板。它提供了一个基础的项目结构和一些常用的工具函数、自定义Hooks、组件等,方便快速开发微信小程序。
|
||||
|
||||
## 技术栈
|
||||
|
||||
* **UniApp**: 跨平台开发框架,用于构建微信小程序。
|
||||
* **Vue3**: 渐进式 JavaScript 框架,用于构建用户界面。
|
||||
* **Vue3**: 渐进式 JavaScript 框架,用于构建用户界面,采用 Composition API 和 setup 语法糖。
|
||||
* **uView-Plus**: 基于 UniApp 的 UI 组件库。
|
||||
* **z-paging**: 一个用于处理分页加载的组件库。
|
||||
* **Vuex**: 状态管理库,用于统一管理应用状态(登录状态、用户信息等)。
|
||||
* **luch-request**: 网络请求库,用于封装 HTTP 请求。
|
||||
* **ESLint & Prettier**: 代码规范和格式化工具。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
.
|
||||
├── api/ # 接口相关
|
||||
│ ├── modules/ # 业务接口
|
||||
│ ├── modules/ # 业务接口模块
|
||||
│ ├── index.js # API统一导出入口
|
||||
│ └── request.js # 请求封装
|
||||
├── common/ # 公共资源
|
||||
│ ├── constants/ # 常量定义
|
||||
│ ├── styles/ # 全局样式
|
||||
│ │ ├── common.css # codefun原子类样式
|
||||
│ │ └── base.scss # 全局样式变量
|
||||
│ │ ├── base.scss # 全局样式变量和工具类
|
||||
│ │ └── dark-mode.scss # 暗黑模式样式
|
||||
│ └── utils/ # 工具函数
|
||||
│ └── tool.js # 常用工具函数
|
||||
│ ├── tool.js # 常用工具函数
|
||||
│ └── env.js # 环境变量工具
|
||||
├── components/ # 公共组件
|
||||
├── wxcomponents/ # 微信原生组件
|
||||
│ └── common/ # 通用业务组件
|
||||
├── directives/ # 自定义指令
|
||||
├── hooks/ # Vue Composition API Hooks
|
||||
│ ├── index.js # Hooks统一导出入口
|
||||
│ ├── useRequest.js # 通用请求Hook
|
||||
│ ├── useApi.js # API相关Hook
|
||||
│ └── useState.js # 状态管理Hook
|
||||
├── uni_modules/ # uni-app 组件
|
||||
│ └── z-paging/ # 分页组件库
|
||||
├── lib/ # 第三方库
|
||||
@@ -37,9 +48,6 @@
|
||||
│ └── assets/ # 静态图片资源
|
||||
├── store/ # 状态管理
|
||||
├── subPages/ # 分包页面
|
||||
├── uni_modules/ # uni-app 组件
|
||||
│ └── z-paging/ # 分页组件库
|
||||
├── uview-plus/ # uView-Plus 组件库
|
||||
├── App.vue # 应用入口
|
||||
├── main.js # 主入口文件
|
||||
├── pages.json # 页面配置
|
||||
@@ -47,7 +55,11 @@
|
||||
├── uni.scss # 全局样式变量
|
||||
├── vite.config.js # Vite 编译配置
|
||||
├── .nvmdrc # Node.js 版本要求
|
||||
└── .env # 环境变量
|
||||
├── .env # 环境变量
|
||||
├── .eslintrc.js # ESLint 配置
|
||||
├── .prettierrc.js # Prettier 配置
|
||||
├── .eslintignore # ESLint 忽略文件
|
||||
└── .prettierignore # Prettier 忽略文件
|
||||
```
|
||||
|
||||
# 开发环境与运行
|
||||
@@ -65,11 +77,30 @@ npm install
|
||||
|
||||
## 运行项目
|
||||
|
||||
由于这是一个 UniApp 项目,通常需要使用 HBuilderX 或其他支持 UniApp 的 IDE 来运行和调试。具体的运行命令取决于你的开发环境。
|
||||
```bash
|
||||
# 开发环境运行
|
||||
npm run dev
|
||||
|
||||
# 构建生产环境
|
||||
npm run build
|
||||
|
||||
# 预览构建结果
|
||||
npm run preview
|
||||
|
||||
# 代码检查和修复
|
||||
npm run lint
|
||||
|
||||
# 代码格式化
|
||||
npm run format
|
||||
```
|
||||
|
||||
## 构建项目
|
||||
|
||||
同样,构建项目也需要使用 HBuilderX 或相应的 CLI 工具。
|
||||
构建项目使用 Vite 工具链:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
# 代码规范与开发约定
|
||||
|
||||
@@ -78,11 +109,12 @@ npm install
|
||||
* 全局样式文件位于 `common/styles/` 目录下,包括 `common.css` 和 `base.scss`。
|
||||
* `common.css` 提供了codefun原子类样式,用于快速布局。
|
||||
* `base.scss` 提供了SCSS变量和mixins,以及常用的样式类生成器。
|
||||
* 支持暗黑模式,通过 `dark-mode.scss` 实现。
|
||||
* 样式规范应遵循项目中已有的风格。
|
||||
|
||||
## JavaScript
|
||||
|
||||
* 严格遵循ES6规范。
|
||||
* 严格遵循ES6+规范。
|
||||
* 遵循JavaScript函数式编程范式。
|
||||
* 方法类函数应该使用 `function` 进行定义。
|
||||
* 避免出现超过4个以上的 `ref`,超过4个则使用 `reactive`。
|
||||
@@ -96,7 +128,7 @@ npm install
|
||||
* 所有 `Promise` 类方法使用 `async` `await` 写法,避免出现 `.then` 嵌套,并进行容错、错误抛出处理。
|
||||
* 在需要页面跳转、提示、加载、本地存储、或其他功能的时候,优先使用工具函数 `common/utils/tool.js` 中存在的函数。
|
||||
* 字符串拼接使用ES6的模板语法。
|
||||
* JavaScript规范应遵循项目中已有的风格。
|
||||
* 组件使用 Vue3 的 Composition API (setup语法糖) 编写。
|
||||
|
||||
## 静态资源
|
||||
|
||||
@@ -111,37 +143,57 @@ npm install
|
||||
* **页面跳转**: `navigateTo`, `redirectTo`, `reLaunch`, `switchTab`, `navigateBack`
|
||||
* **本地存储**: `storage`, `removeStorage`, `getStorageInfo`
|
||||
* **其他功能**: `copy` (复制文本), `saveImageToPhotos` (保存图片), `requestPayment` (微信支付), `upload` (文件上传), `loadFont` (加载字体)
|
||||
* **日期处理**: `formatDate` (日期格式化)
|
||||
* **工具函数**: `deepClone` (深拷贝), `debounce/throttle` (防抖节流)
|
||||
* **表单验证**: `getValidator` (表单验证工具)
|
||||
* **主题切换**: `toggleDarkMode`, `getCurrentTheme` (暗黑模式切换)
|
||||
* **权限管理**: `hasPermission`, `setPermissions`, `getUserPermissions` (权限检查和管理)
|
||||
|
||||
## 网络请求
|
||||
|
||||
* 网络请求使用 `lib/luch-request` 库进行封装。
|
||||
* 全局配置在 `api/request.js` 中定义,包括基础URL、请求头、SSL验证等。
|
||||
* 包含请求和响应拦截器,用于处理通用逻辑(如错误提示、鉴权等)。
|
||||
* 支持请求缓存机制和重试机制。
|
||||
* 各业务板块的接口都应存放在 `api/modules` 下,并将单个接口进行导出以便页面按需导入。
|
||||
* 提供 `useGet` 和 `usePost` 等 Hooks 用于在组件中便捷地发起请求。
|
||||
|
||||
## 状态管理 (Vuex)
|
||||
## 自定义Hooks
|
||||
|
||||
* 项目集成了Vuex进行全局状态管理。
|
||||
* 状态管理文件位于 `store/index.js`。
|
||||
* 使用 `useStore` 钩子在组件中访问状态。
|
||||
项目提供了多个可复用的 Vue Composition API Hooks,位于 `hooks/` 目录下:
|
||||
|
||||
* `useRequest`: 通用请求Hook,支持自动执行、手动执行、loading状态等
|
||||
* `useGet`: GET请求Hook
|
||||
* `usePost`: POST请求Hook
|
||||
* `useState`: 状态管理Hook
|
||||
* `useStorageState`: 本地存储状态Hook
|
||||
* `useFormState`: 表单状态Hook
|
||||
|
||||
## 组件
|
||||
|
||||
* 项目集成了 `uView-Plus` 和 `z-paging` 两个组件库。
|
||||
* 所有 `uni_modules` 目录中的组件无需导入直接可以进行使用。
|
||||
* `uView-Plus` 组件已通过 `easycom` 自动导入,可以直接使用,如:`<u-button>`、`<u-icon>`。
|
||||
* 全局组件放在 `components/` 目录下。
|
||||
* 自定义公共组件放在 `components/common/` 目录下,采用 Vue3 的 setup 语法糖编写。
|
||||
* 页面独立组件放在页面根目录下的 `components/`。
|
||||
* 微信的原生组件放在页面根目录下的 `wxcomponents/`,并在使用了组件的对应页面路由配置中添加组件的引用属性 `"usingComponents": { "components": "/wxcomponents/components/components" }`。
|
||||
* 组件编写应遵循项目中已有的风格。
|
||||
|
||||
## 分页功能
|
||||
## 自定义指令
|
||||
|
||||
* 项目使用 `z-paging` 组件实现分页功能。
|
||||
* 分页组件通过 `v-model` 绑定数据,并使用 `@query` 事件处理数据查询。
|
||||
* 在页面中直接使用 `v-for` 循环渲染数据项,如:`<view class="item" v-for="item in dataList" :key="item.id">`。
|
||||
* 通过 `paging.value?.reload()` 触发分页组件重新加载数据。
|
||||
* 通过 `paging.value?.complete()` 通知分页组件数据加载完成。
|
||||
* 项目支持自定义指令,位于 `directives/` 目录下
|
||||
* 包括防重复点击、长按、权限控制、拖拽等指令
|
||||
* 在 `main.js` 中已全局注册
|
||||
|
||||
## 常量管理
|
||||
|
||||
* 项目常量统一管理在 `common/constants/` 目录下
|
||||
* 包括应用信息、页面路径、存储键名、事件常量等
|
||||
|
||||
## 环境变量
|
||||
|
||||
* 环境变量通过 `.env` 文件配置
|
||||
* 提供 `common/utils/env.js` 工具进行环境变量验证和获取
|
||||
|
||||
## 页面
|
||||
|
||||
@@ -162,5 +214,3 @@ npm install
|
||||
## 其他
|
||||
|
||||
* 页面中的分享功能应该使用原生的微信分享功能,通过 `button` 或 `<u-button>` 组件的 `open-type="share"` 属性实现。
|
||||
* 项目使用环境变量管理不同环境的配置,通过 `.env` 文件配置。
|
||||
* Vite配置文件 `vite.config.js` 包含了自定义插件用于在编译时替换 `manifest.json` 中的 appid。
|
||||
207
README.md
207
README.md
@@ -1,80 +1,159 @@
|
||||
# <EFBFBD><EFBFBD>Ŀģ<EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD>˵<EFBFBD><EFBFBD>
|
||||
# UniApp 微信小程序快速开发模板
|
||||
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD><EFBFBD>ǻ<EFBFBD><EFBFBD><EFBFBD> UniApp + Vue3 + uView-Plus <EFBFBD><EFBFBD>С<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀģ<EFBFBD>壬<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һЩ<EFBFBD><EFBFBD><EFBFBD>õ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>á<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ߺ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
本模板是基于 UniApp + Vue3 + uView-Plus 的微信小程序项目模板,提供了一些常用的工具函数、组件和最佳实践。
|
||||
|
||||
## Ŀ¼<EFBFBD>ṹ
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> api/ # <EFBFBD>ӿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> modules/ # ҵ<EFBFBD><EFBFBD><EFBFBD>ӿ<EFBFBD>
|
||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> request.js # <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> common/ # <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ
|
||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> styles/ # ȫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ
|
||||
<EFBFBD><EFBFBD> <EFBFBD><EFBFBD> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> common.css # codefunԭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ
|
||||
<EFBFBD><EFBFBD> <EFBFBD><EFBFBD> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> base.scss # ȫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> utils/ # <EFBFBD><EFBFBD><EFBFBD>ߺ<EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> tool.ts # <EFBFBD><EFBFBD><EFBFBD>ù<EFBFBD><EFBFBD>ߺ<EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> components/ # <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> uni_modules/ # uni-app <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> z-paging/ # <EFBFBD><EFBFBD>ҳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> lib/ # <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> luch-request/ # luch-request <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> uview-plus/ # uView-Plus <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> mixins/ # Vue <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> global.ts # ȫ<EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> store/ # ״̬<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> index.ts # Vuex store
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> pages/ # <20><><EFBFBD><EFBFBD>ҳ<EFBFBD><D2B3>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> subPages/ # <EFBFBD>ְ<EFBFBD>ҳ<EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> App.vue # Ӧ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> main.js # <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> pages.json # ҳ<><D2B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> manifest.json # Ӧ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> uni.scss # ȫ<><C8AB><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> vite.config.js # Vite <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> .nvmdrc # Node.js <20>汾Ҫ<E6B1BE><D2AA>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> .env # <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
```
|
||||
├── 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 # 环境变量
|
||||
```
|
||||
|
||||
## ʹ<EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD>
|
||||
## 使用方法
|
||||
|
||||
1. <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD>Ŀ¼<EFBFBD><EFBFBD><EFBFBD>Ƶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĿĿ¼<EFBFBD><EFBFBD>
|
||||
2. <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD> package.json <EFBFBD>е<EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><EFBFBD><EFBFBD>ƺ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
3. ʹ<EFBFBD><EFBFBD> npm install <EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
4. <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD> pages.json <EFBFBD>е<EFBFBD>ҳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
5. <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¹<EFBFBD><EFBFBD><EFBFBD>
|
||||
1. 将模板目录复制到你的项目目录中
|
||||
2. 根据需要修改 package.json 中的项目名称和描述
|
||||
3. 使用 npm install 安装依赖
|
||||
4. 根据需要修改 pages.json 中的页面配置
|
||||
5. 开始构建你的新项目
|
||||
|
||||
## <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
## 开发环境与运行
|
||||
|
||||
### <EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- **dotenv** - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><D7A2>
|
||||
### 环境要求
|
||||
|
||||
### <20><>ʽ
|
||||
* Node.js (版本信息在 `.nvmdrc` 文件中指定,当前为 20.0.0)
|
||||
* npm 或 yarn
|
||||
|
||||
- common.css: ȫ<>ֻ<EFBFBD><D6BB><EFBFBD><EFBFBD><EFBFBD>ʽ
|
||||
- base.scss: <20><><EFBFBD>õ<EFBFBD> SCSS <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
### 安装依赖
|
||||
|
||||
### <20><><EFBFBD>ߺ<EFBFBD><DFBA><EFBFBD> (tool.js)
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
- alert: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ
|
||||
- loading/hideLoading: <20><>ʾ/<2F><><EFBFBD>ؼ<EFBFBD><D8BC><EFBFBD><EFBFBD><EFBFBD>ʾ
|
||||
- ҳ<><D2B3><EFBFBD><EFBFBD>ת<EFBFBD><D7AA><EFBFBD>ط<EFBFBD><D8B7><EFBFBD>: navigateTo, redirectTo, reLaunch, switchTab, navigateBack
|
||||
- <20><><EFBFBD>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: storage, removeStorage, getStorageInfo
|
||||
- copy: <20><><EFBFBD><EFBFBD><EFBFBD>ı<EFBFBD><C4B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- saveImageToPhotos: <20><><EFBFBD><EFBFBD>ͼƬ<CDBC><C6AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- requestPayment: <><CEA2>֧<EFBFBD><D6A7>
|
||||
- upload: <20>ļ<EFBFBD><C4BC>ϴ<EFBFBD>
|
||||
### 运行项目
|
||||
|
||||
### <20><><EFBFBD><EFBFBD>
|
||||
由于这是一个 UniApp 项目,通常需要使用 HBuilderX 或其他支持 UniApp 的 IDE 来运行和调试。具体的运行命令取决于你的开发环境。
|
||||
|
||||
- App.vue: ȫ<><C8AB><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD>ͻ<EFBFBD><CDBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- main.js: Vue Ӧ<>ó<EFBFBD>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>ȫ<EFBFBD>ֲ<EFBFBD><D6B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- pages.json: ҳ<><D2B3>·<EFBFBD>ɺʹ<C9BA><CDB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- uni.scss: ȫ<><C8AB><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD>
|
||||
### 构建项目
|
||||
|
||||
## ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
同样,构建项目也需要使用 HBuilderX 或相应的 CLI 工具。
|
||||
|
||||
1. <20><><EFBFBD><EFBFBD>ʵ<EFBFBD><CAB5><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
2. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD>Ļ<DEB8><C4BB><EFBFBD>չ<EFBFBD><D5B9><EFBFBD>ߺ<EFBFBD><DFBA><EFBFBD>
|
||||
3. <20><><EFBFBD><EFBFBD><EFBFBD>ɸ<EFBFBD><C9B8><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD><EFBFBD>Ļ<DEB8><C4BB>滻
|
||||
4. <20><>ʽ<EFBFBD>ļ<EFBFBD><C4BC>ɸ<EFBFBD><C9B8><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD>ƹ淶<C6B9><E6B7B6><EFBFBD>е<EFBFBD><D0B5><EFBFBD>
|
||||
## 项目特性
|
||||
|
||||
### 核心依赖
|
||||
- **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` 已进行全局混入,可以在 `<template></template>` 中直接使用
|
||||
|
||||
### 网络请求
|
||||
|
||||
- 网络请求使用 `lib/luch-request` 库进行封装
|
||||
- 包含请求和响应拦截器,用于处理通用逻辑
|
||||
- 各业务板块的接口都应存放在 `api/modules` 下
|
||||
|
||||
### 组件
|
||||
|
||||
- 所有 `uni_modules` 目录中的组件无需导入直接可以进行使用
|
||||
- `uView-Plus` 组件已通过 `easycom` 自动导入
|
||||
|
||||
### 页面
|
||||
|
||||
- 页面配置在 `pages.json` 中管理
|
||||
- 页面使用 Composition API (setup语法糖) 编写
|
||||
|
||||
## 代码提交规范
|
||||
|
||||
- 提交信息应清晰描述变更内容
|
||||
- 对于功能性的新增或修改,使用 `新增` 前缀
|
||||
- 对于错误修复,使用 `修复` 前缀
|
||||
- 对于性能优化、代码重构,使用 `优化` 前缀
|
||||
- 对于文档更新,使用 `文档` 前缀
|
||||
- 提交信息应使用中文
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 请根据实际项目需求进行修改和扩展功能
|
||||
2. 代码文件可按项目规范进行修改替换
|
||||
3. 样式文件可按项目规范进行修改
|
||||
15
api/index.js
Normal file
15
api/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* API统一导出入口
|
||||
*/
|
||||
// 导入网络请求实例
|
||||
import http from './request.js'
|
||||
// 导入各业务模块
|
||||
import user from './modules/user.js'
|
||||
// 导出网络请求实例
|
||||
export { http }
|
||||
// 导出各业务模块
|
||||
export { user }
|
||||
// 默认导出包含所有模块的对象
|
||||
export default {
|
||||
user,
|
||||
}
|
||||
85
api/modules/user.js
Normal file
85
api/modules/user.js
Normal file
@@ -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}`)
|
||||
},
|
||||
}
|
||||
149
api/request.js
149
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 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)
|
||||
@@ -11,31 +48,115 @@ http.setConfig(config => {
|
||||
}
|
||||
/* 设置全局配置 */
|
||||
http.setConfig(config => {
|
||||
|
||||
config.header = { ...config.header }
|
||||
config.sslVerify = false
|
||||
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
|
||||
|
||||
http.interceptors.response.use(response => {
|
||||
if (response.statusCode == 500 || response.statusCode == 404 || response.statusCode == 403) {
|
||||
console.error(response)
|
||||
return tool.alert('网络错误,请稍后重试')
|
||||
},
|
||||
config => {
|
||||
return Promise.reject(config)
|
||||
}
|
||||
)
|
||||
http.interceptors.response.use(
|
||||
response => {
|
||||
// 网络错误处理
|
||||
|
||||
if (response.statusCode == 401 || response.data.code == 401) {
|
||||
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) {
|
||||
|
||||
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)
|
||||
}
|
||||
)
|
||||
// 添加缓存功能到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)
|
||||
}
|
||||
}
|
||||
|
||||
30
common/constants/app.js
Normal file
30
common/constants/app.js
Normal file
@@ -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'
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// 定义内外边距,历遍1-100
|
||||
@for $i from 0 through 100 {
|
||||
// 只要双数和能被5除尽的数
|
||||
@if $i % 2==0 or $i % 5==0 {
|
||||
// 得出:u-margin-30或者u-m-30
|
||||
// 定义内外边距,只生成常用的数值
|
||||
$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;
|
||||
}
|
||||
@@ -10,76 +8,60 @@
|
||||
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;
|
||||
}
|
||||
@@ -89,30 +71,27 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
88
common/styles/dark-mode.scss
Normal file
88
common/styles/dark-mode.scss
Normal file
@@ -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);
|
||||
}
|
||||
80
common/utils/env.js
Normal file
80
common/utils/env.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 环境变量验证工具
|
||||
*/
|
||||
/**
|
||||
* 验证必需的环境变量
|
||||
* @param {Array<string>} 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
|
||||
@@ -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<Object>} 上传结果
|
||||
*/
|
||||
upload(filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
@@ -357,7 +548,120 @@ class Tool {
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 暗黑模式切换
|
||||
* @param {boolean} isDark 是否启用暗黑模式
|
||||
*/
|
||||
toggleDarkMode(isDark) {
|
||||
try {
|
||||
if (isDark) {
|
||||
// 启用暗黑模式
|
||||
document.documentElement.setAttribute('data-theme', 'dark')
|
||||
uni.setStorageSync('theme', 'dark')
|
||||
} else {
|
||||
// 启用亮色模式
|
||||
document.documentElement.setAttribute('data-theme', 'light')
|
||||
uni.setStorageSync('theme', 'light')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换主题失败:', error)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取当前主题模式
|
||||
* @returns {string} 当前主题 ('light' | 'dark')
|
||||
*/
|
||||
getCurrentTheme() {
|
||||
try {
|
||||
// 优先使用用户设置的主题
|
||||
const savedTheme = uni.getStorageSync('theme')
|
||||
if (savedTheme) {
|
||||
return savedTheme
|
||||
}
|
||||
// 检查系统主题
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
if (systemInfo.theme) {
|
||||
return systemInfo.theme
|
||||
}
|
||||
// 默认返回亮色主题
|
||||
return 'light'
|
||||
} catch (error) {
|
||||
console.error('获取主题失败:', error)
|
||||
return 'light'
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 权限检查
|
||||
* @param {string} permission 权限标识
|
||||
* @returns {boolean} 是否拥有权限
|
||||
*/
|
||||
hasPermission(permission) {
|
||||
try {
|
||||
// 获取用户权限列表
|
||||
const permissions = uni.getStorageSync('permissions') || []
|
||||
return permissions.includes(permission)
|
||||
} catch (error) {
|
||||
console.error('权限检查失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 设置用户权限
|
||||
* @param {Array<string>} permissions 权限列表
|
||||
*/
|
||||
setPermissions(permissions) {
|
||||
try {
|
||||
uni.setStorageSync('permissions', Array.isArray(permissions) ? permissions : [])
|
||||
} catch (error) {
|
||||
console.error('设置权限失败:', error)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取用户所有权限
|
||||
* @returns {Array<string>} 权限列表
|
||||
*/
|
||||
getUserPermissions() {
|
||||
try {
|
||||
return uni.getStorageSync('permissions') || []
|
||||
} catch (error) {
|
||||
console.error('获取权限失败:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查用户是否有任意权限
|
||||
* @param {Array<string>} permissions 权限列表
|
||||
* @returns {boolean} 是否有任意权限
|
||||
*/
|
||||
hasAnyPermission(permissions) {
|
||||
if (!Array.isArray(permissions)) {
|
||||
return false
|
||||
}
|
||||
const userPermissions = this.getUserPermissions()
|
||||
return permissions.some(permission => userPermissions.includes(permission))
|
||||
}
|
||||
/**
|
||||
* 检查用户是否拥有所有权限
|
||||
* @param {Array<string>} permissions 权限列表
|
||||
* @returns {boolean} 是否拥有所有权限
|
||||
*/
|
||||
hasAllPermissions(permissions) {
|
||||
if (!Array.isArray(permissions)) {
|
||||
return false
|
||||
}
|
||||
const userPermissions = this.getUserPermissions()
|
||||
return permissions.every(permission => userPermissions.includes(permission))
|
||||
}
|
||||
/**
|
||||
* 清除用户权限
|
||||
*/
|
||||
clearPermissions() {
|
||||
try {
|
||||
uni.removeStorageSync('permissions')
|
||||
} catch (error) {
|
||||
console.error('清除权限失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建单例并导出
|
||||
export default new Tool()
|
||||
|
||||
74
components/common/Card.vue
Normal file
74
components/common/Card.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<view class="card" :class="{ 'card--shadow': shadow, 'card--border': border }">
|
||||
<view class="card__header" v-if="title || $slots.header">
|
||||
<slot name="header">
|
||||
<view class="card__title">{{ title }}</view>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="card__body">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="card__footer" v-if="$slots.footer">
|
||||
<slot name="footer"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 定义props
|
||||
defineProps({
|
||||
// 卡片标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示阴影
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
|
||||
// 阴影样式
|
||||
&--shadow {
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
// 边框样式
|
||||
&--border {
|
||||
border: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
&__header {
|
||||
padding: 24rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
padding: 24rpx 32rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
195
components/common/CustomButton.vue
Normal file
195
components/common/CustomButton.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<view
|
||||
class="custom-button"
|
||||
:class="[`custom-button--${type}`, `custom-button--${size}`, { 'custom-button--disabled': disabled, 'custom-button--loading': loading }]"
|
||||
@click="handleClick"
|
||||
>
|
||||
<view class="custom-button__loading" v-if="loading">
|
||||
<u-loading-icon mode="circle" size="20" color="#fff" />
|
||||
</view>
|
||||
<view class="custom-button__content">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onBeforeUnmount } from 'vue'
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
// 按钮类型
|
||||
type: {
|
||||
type: String,
|
||||
default: 'primary', // primary, secondary, danger, ghost
|
||||
validator: (value) => ['primary', 'secondary', 'danger', 'ghost'].includes(value)
|
||||
},
|
||||
// 按钮大小
|
||||
size: {
|
||||
type: String,
|
||||
default: 'medium', // small, medium, large
|
||||
validator: (value) => ['small', 'medium', 'large'].includes(value)
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否加载中
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 防抖时间(毫秒)
|
||||
debounce: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
// 定义emits
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
// 防抖定时器
|
||||
const debounceTimer = ref(null)
|
||||
|
||||
/**
|
||||
* 处理点击事件
|
||||
* @param {Event} e 点击事件对象
|
||||
*/
|
||||
function handleClick(e) {
|
||||
// 如果禁用或加载中,不处理点击
|
||||
if (props.disabled || props.loading) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果设置了防抖
|
||||
if (props.debounce > 0) {
|
||||
if (debounceTimer.value) {
|
||||
clearTimeout(debounceTimer.value)
|
||||
}
|
||||
|
||||
debounceTimer.value = setTimeout(() => {
|
||||
emit('click', e)
|
||||
debounceTimer.value = null
|
||||
}, props.debounce)
|
||||
} else {
|
||||
emit('click', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 组件卸载前清除定时器
|
||||
onBeforeUnmount(() => {
|
||||
if (debounceTimer.value) {
|
||||
clearTimeout(debounceTimer.value)
|
||||
debounceTimer.value = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
border: 1rpx solid transparent;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// 主要按钮
|
||||
&--primary {
|
||||
color: #fff;
|
||||
background-color: #1890ff;
|
||||
border-color: #1890ff;
|
||||
|
||||
&:not(.custom-button--disabled):active {
|
||||
background-color: #40a9ff;
|
||||
border-color: #40a9ff;
|
||||
}
|
||||
}
|
||||
|
||||
// 次要按钮
|
||||
&--secondary {
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
border-color: #d9d9d9;
|
||||
|
||||
&:not(.custom-button--disabled):active {
|
||||
background-color: #e6e6e6;
|
||||
border-color: #bfbfbf;
|
||||
}
|
||||
}
|
||||
|
||||
// 危险按钮
|
||||
&--danger {
|
||||
color: #fff;
|
||||
background-color: #ff4d4f;
|
||||
border-color: #ff4d4f;
|
||||
|
||||
&:not(.custom-button--disabled):active {
|
||||
background-color: #ff7875;
|
||||
border-color: #ff7875;
|
||||
}
|
||||
}
|
||||
|
||||
// 幽灵按钮
|
||||
&--ghost {
|
||||
color: #1890ff;
|
||||
background-color: transparent;
|
||||
border-color: #1890ff;
|
||||
|
||||
&:not(.custom-button--disabled):active {
|
||||
color: #40a9ff;
|
||||
border-color: #40a9ff;
|
||||
}
|
||||
}
|
||||
|
||||
// 禁用状态
|
||||
&--disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
// 加载状态
|
||||
&--loading {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
// 小尺寸
|
||||
&--small {
|
||||
height: 56rpx;
|
||||
padding: 0 24rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
// 中等尺寸
|
||||
&--medium {
|
||||
height: 72rpx;
|
||||
padding: 0 32rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
// 大尺寸
|
||||
&--large {
|
||||
height: 88rpx;
|
||||
padding: 0 40rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
&__loading {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
141
components/common/FormInput.vue
Normal file
141
components/common/FormInput.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<view class="form-input">
|
||||
<view class="form-input__label" v-if="label">
|
||||
{{ label }}
|
||||
<text class="form-input__required" v-if="required">*</text>
|
||||
</view>
|
||||
<view class="form-input__wrapper">
|
||||
<input
|
||||
class="form-input__field"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:value="modelValue"
|
||||
:disabled="disabled"
|
||||
:maxlength="maxlength"
|
||||
@input="handleInput"
|
||||
@blur="handleBlur"
|
||||
@focus="handleFocus"
|
||||
/>
|
||||
<view class="form-input__suffix" v-if="$slots.suffix">
|
||||
<slot name="suffix"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-input__error" v-if="error">{{ error }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
// 输入框的值
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 标签
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 占位符
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 输入框类型
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
// 是否必填
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 最大长度
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 140
|
||||
},
|
||||
// 错误信息
|
||||
error: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
// 定义emits
|
||||
const emit = defineEmits(['update:modelValue', 'blur', 'focus'])
|
||||
|
||||
/**
|
||||
* 处理输入事件
|
||||
* @param {Event} e 输入事件对象
|
||||
*/
|
||||
function handleInput(e) {
|
||||
emit('update:modelValue', e.detail.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理失去焦点事件
|
||||
* @param {Event} e 失去焦点事件对象
|
||||
*/
|
||||
function handleBlur(e) {
|
||||
emit('blur', e.detail.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理获得焦点事件
|
||||
* @param {Event} e 获得焦点事件对象
|
||||
*/
|
||||
function handleFocus(e) {
|
||||
emit('focus', e.detail.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-input {
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
&__label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
&__required {
|
||||
color: #ff4d4f;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 24rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
&__field {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&__suffix {
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
&__error {
|
||||
font-size: 24rpx;
|
||||
color: #ff4d4f;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
141
directives/index.js
Normal file
141
directives/index.js
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 自定义指令入口文件
|
||||
*/
|
||||
|
||||
// 防止重复点击指令
|
||||
export const preventReClick = {
|
||||
mounted(el, binding) {
|
||||
el.addEventListener('click', () => {
|
||||
if (el.disabled) return
|
||||
el.disabled = true
|
||||
setTimeout(() => {
|
||||
el.disabled = false
|
||||
}, binding.value || 1000)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
// 长按指令
|
||||
export const longPress = {
|
||||
mounted(el, binding) {
|
||||
let timer = null
|
||||
let startTime = 0
|
||||
const handler = binding.value
|
||||
const duration = binding.arg || 1000
|
||||
|
||||
// 取消定时器
|
||||
const cancel = () => {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
// 开始计时
|
||||
const start = () => {
|
||||
startTime = Date.now()
|
||||
timer = setTimeout(() => {
|
||||
handler(el)
|
||||
}, duration)
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
el.addEventListener('touchstart', start)
|
||||
el.addEventListener('touchend', cancel)
|
||||
el.addEventListener('touchcancel', cancel)
|
||||
},
|
||||
}
|
||||
|
||||
// 权限控制指令
|
||||
export const permission = {
|
||||
mounted(el, binding) {
|
||||
const { value } = binding
|
||||
const permissions = uni.getStorageSync('permissions') || []
|
||||
|
||||
if (value && !permissions.includes(value)) {
|
||||
// 隐藏元素
|
||||
el.style.display = 'none'
|
||||
el.__vue__ && (el.__vue__.isDisplay = false)
|
||||
}
|
||||
},
|
||||
updated(el, binding) {
|
||||
const { value } = binding
|
||||
const permissions = uni.getStorageSync('permissions') || []
|
||||
|
||||
if (value && !permissions.includes(value)) {
|
||||
// 隐藏元素
|
||||
el.style.display = 'none'
|
||||
el.__vue__ && (el.__vue__.isDisplay = false)
|
||||
} else {
|
||||
// 显示元素
|
||||
el.style.display = ''
|
||||
el.__vue__ && (el.__vue__.isDisplay = true)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// 拖拽指令
|
||||
export const drag = {
|
||||
mounted(el) {
|
||||
let startX = 0
|
||||
let startY = 0
|
||||
let initialX = 0
|
||||
let initialY = 0
|
||||
let isDragging = false
|
||||
|
||||
// 获取元素初始位置
|
||||
const getInitialPosition = () => {
|
||||
const rect = el.getBoundingClientRect()
|
||||
initialX = rect.left
|
||||
initialY = rect.top
|
||||
}
|
||||
|
||||
// 鼠标按下事件
|
||||
const handleMouseDown = e => {
|
||||
isDragging = true
|
||||
startX = e.clientX
|
||||
startY = e.clientY
|
||||
getInitialPosition()
|
||||
el.style.position = 'absolute'
|
||||
el.style.left = initialX + 'px'
|
||||
el.style.top = initialY + 'px'
|
||||
el.style.zIndex = 9999
|
||||
}
|
||||
|
||||
// 鼠标移动事件
|
||||
const handleMouseMove = e => {
|
||||
if (!isDragging) return
|
||||
const dx = e.clientX - startX
|
||||
const dy = e.clientY - startY
|
||||
el.style.left = initialX + dx + 'px'
|
||||
el.style.top = initialY + dy + 'px'
|
||||
}
|
||||
|
||||
// 鼠标松开事件
|
||||
const handleMouseUp = () => {
|
||||
isDragging = false
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
el.addEventListener('mousedown', handleMouseDown)
|
||||
document.addEventListener('mousemove', handleMouseMove)
|
||||
document.addEventListener('mouseup', handleMouseUp)
|
||||
|
||||
// 在元素上存储事件处理函数,以便在指令解绑时移除
|
||||
el._dragHandlers = {
|
||||
handleMouseDown,
|
||||
handleMouseMove,
|
||||
handleMouseUp,
|
||||
}
|
||||
},
|
||||
unmounted(el) {
|
||||
// 移除事件监听器
|
||||
if (el._dragHandlers) {
|
||||
const { handleMouseDown, handleMouseMove, handleMouseUp } = el._dragHandlers
|
||||
el.removeEventListener('mousedown', handleMouseDown)
|
||||
document.removeEventListener('mousemove', handleMouseMove)
|
||||
document.removeEventListener('mouseup', handleMouseUp)
|
||||
delete el._dragHandlers
|
||||
}
|
||||
},
|
||||
}
|
||||
7
hooks/index.js
Normal file
7
hooks/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Hooks统一导出入口
|
||||
*/
|
||||
|
||||
export { useRequest } from './useRequest.js'
|
||||
export { useState, useStorageState, useFormState } from './useState.js'
|
||||
export { useGet, usePost } from './useApi.js'
|
||||
157
hooks/useApi.js
Normal file
157
hooks/useApi.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
import { http } from '@/api/index.js'
|
||||
import tool from '@/common/utils/tool.js'
|
||||
|
||||
/**
|
||||
* GET请求Hook
|
||||
* @param {string} url 请求URL
|
||||
* @param {Object} options 配置选项
|
||||
* @returns {Object} 请求相关状态和方法
|
||||
*/
|
||||
export function useGet(url, options = {}) {
|
||||
const {
|
||||
manual = false, // 是否手动触发
|
||||
params = {}, // 请求参数
|
||||
cache = false, // 是否使用缓存
|
||||
loadingDelay = 0, // loading延迟时间
|
||||
onSuccess = () => {}, // 成功回调
|
||||
onError = () => {}, // 失败回调
|
||||
} = options
|
||||
|
||||
// 状态
|
||||
const data = ref(null)
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
|
||||
// loading延迟定时器
|
||||
let loadingTimer = null
|
||||
|
||||
/**
|
||||
* 执行GET请求
|
||||
* @param {Object} requestParams 请求参数
|
||||
*/
|
||||
async function run(requestParams = {}) {
|
||||
// 清除之前的loading延迟定时器
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
|
||||
// 如果有loading延迟,先启动定时器
|
||||
if (loadingDelay > 0) {
|
||||
loadingTimer = setTimeout(() => {
|
||||
loading.value = true
|
||||
}, loadingDelay)
|
||||
} else {
|
||||
loading.value = true
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await http.get(url, {
|
||||
params: { ...params, ...requestParams },
|
||||
cache,
|
||||
})
|
||||
// 清除loading延迟定时器
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
data.value = response.data
|
||||
error.value = null
|
||||
onSuccess(response.data)
|
||||
return response.data
|
||||
} catch (err) {
|
||||
// 清除loading延迟定时器
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
error.value = err
|
||||
data.value = null
|
||||
console.error(`GET请求失败: ${url}`, err)
|
||||
onError(err)
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消请求
|
||||
*/
|
||||
function cancel() {
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 如果不是手动触发,立即执行
|
||||
if (!manual) {
|
||||
Promise.resolve().then(() => {
|
||||
run(params)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
run,
|
||||
cancel,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST请求Hook
|
||||
* @param {string} url 请求URL
|
||||
* @returns {Object} 请求相关状态和方法
|
||||
*/
|
||||
export function usePost(url) {
|
||||
/**
|
||||
* 执行POST请求
|
||||
* @param {Object} data 请求数据
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
async function run(data, options = {}) {
|
||||
const { loadingDelay = 0, onSuccess = () => {}, onError = () => {} } = options
|
||||
|
||||
let loadingTimer = null
|
||||
|
||||
// 如果有loading延迟,先启动定时器
|
||||
if (loadingDelay > 0) {
|
||||
loadingTimer = setTimeout(() => {
|
||||
tool.loading()
|
||||
}, loadingDelay)
|
||||
} else {
|
||||
tool.loading()
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await http.post(url, data)
|
||||
// 清除loading延迟定时器
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
tool.hideLoading()
|
||||
onSuccess(response.data)
|
||||
return response.data
|
||||
} catch (err) {
|
||||
// 清除loading延迟定时器
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
tool.hideLoading()
|
||||
console.error(`POST请求失败: ${url}`, err)
|
||||
onError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
run,
|
||||
}
|
||||
}
|
||||
113
hooks/useRequest.js
Normal file
113
hooks/useRequest.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
import tool from '@/common/utils/tool.js'
|
||||
|
||||
/**
|
||||
* 通用请求Hook
|
||||
* @param {Function} apiFunction API函数
|
||||
* @param {Object} options 配置选项
|
||||
* @returns {Object} 请求相关状态和方法
|
||||
*/
|
||||
export function useRequest(apiFunction, options = {}) {
|
||||
const {
|
||||
manual = false, // 是否手动触发
|
||||
defaultParams = [], // 默认参数
|
||||
onSuccess = () => {}, // 成功回调
|
||||
onError = () => {}, // 失败回调
|
||||
loadingDelay = 0, // loading延迟时间
|
||||
} = options
|
||||
|
||||
// 状态
|
||||
const data = ref(null)
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
const params = ref([])
|
||||
|
||||
// loading延迟定时器
|
||||
let loadingTimer = null
|
||||
|
||||
/**
|
||||
* 执行请求
|
||||
* @param {...any} args 请求参数
|
||||
*/
|
||||
async function run(...args) {
|
||||
// 清除之前的loading延迟定时器
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
|
||||
// 设置参数
|
||||
params.value = args
|
||||
|
||||
// 如果有loading延迟,先启动定时器
|
||||
if (loadingDelay > 0) {
|
||||
loadingTimer = setTimeout(() => {
|
||||
loading.value = true
|
||||
}, loadingDelay)
|
||||
} else {
|
||||
loading.value = true
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await apiFunction(...args)
|
||||
// 清除loading延迟定时器
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
data.value = result
|
||||
error.value = null
|
||||
onSuccess(result, args)
|
||||
return result
|
||||
} catch (err) {
|
||||
// 清除loading延迟定时器
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
error.value = err
|
||||
data.value = null
|
||||
console.error('请求失败:', err)
|
||||
onError(err, args)
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消请求
|
||||
*/
|
||||
function cancel() {
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新执行请求
|
||||
*/
|
||||
function refresh() {
|
||||
return run(...params.value)
|
||||
}
|
||||
|
||||
// 如果不是手动触发,立即执行
|
||||
if (!manual) {
|
||||
// 使用nextTick确保在组件挂载后执行
|
||||
Promise.resolve().then(() => {
|
||||
run(...defaultParams)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
params,
|
||||
run,
|
||||
cancel,
|
||||
refresh,
|
||||
}
|
||||
}
|
||||
140
hooks/useState.js
Normal file
140
hooks/useState.js
Normal file
@@ -0,0 +1,140 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import tool from '@/common/utils/tool.js'
|
||||
|
||||
/**
|
||||
* 状态管理Hook
|
||||
* @param {any} initialState 初始状态
|
||||
* @returns {Object} 状态和操作方法
|
||||
*/
|
||||
export function useState(initialState) {
|
||||
const state = ref(initialState)
|
||||
|
||||
/**
|
||||
* 设置状态
|
||||
* @param {any} newState 新状态
|
||||
*/
|
||||
function setState(newState) {
|
||||
if (typeof newState === 'function') {
|
||||
state.value = newState(state.value)
|
||||
} else {
|
||||
state.value = newState
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置状态到初始值
|
||||
*/
|
||||
function resetState() {
|
||||
state.value = typeof initialState === 'function' ? initialState() : initialState
|
||||
}
|
||||
|
||||
return {
|
||||
state: computed(() => state.value),
|
||||
setState,
|
||||
resetState,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地存储状态Hook
|
||||
* @param {string} key 存储键名
|
||||
* @param {any} defaultValue 默认值
|
||||
* @returns {Object} 状态和操作方法
|
||||
*/
|
||||
export function useStorageState(key, defaultValue = null) {
|
||||
const storageValue = tool.storage(key) ?? defaultValue
|
||||
const { state, setState, resetState } = useState(storageValue)
|
||||
|
||||
/**
|
||||
* 设置状态并同步到本地存储
|
||||
* @param {any} newState 新状态
|
||||
*/
|
||||
function setStorageState(newState) {
|
||||
if (typeof newState === 'function') {
|
||||
newState = newState(state.value)
|
||||
}
|
||||
|
||||
setState(newState)
|
||||
tool.storage(key, newState)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除本地存储中的值
|
||||
*/
|
||||
function clearStorage() {
|
||||
tool.removeStorage(key)
|
||||
resetState()
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
setState: setStorageState,
|
||||
resetState: () => {
|
||||
clearStorage()
|
||||
},
|
||||
clearStorage,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单状态Hook
|
||||
* @param {Object} initialFormData 初始表单数据
|
||||
* @returns {Object} 表单状态和操作方法
|
||||
*/
|
||||
export function useFormState(initialFormData = {}) {
|
||||
const { state, setState, resetState } = useState({ ...initialFormData })
|
||||
|
||||
/**
|
||||
* 设置表单项
|
||||
* @param {string} field 字段名
|
||||
* @param {any} value 字段值
|
||||
*/
|
||||
function setField(field, value) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置表单到初始状态
|
||||
*/
|
||||
function resetForm() {
|
||||
resetState()
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空表单
|
||||
*/
|
||||
function clearForm() {
|
||||
setState({})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置表单值
|
||||
* @param {Object} values 表单值对象
|
||||
*/
|
||||
function setFields(values) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
...values,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表单验证器
|
||||
* @returns {Object} 验证器实例
|
||||
*/
|
||||
function getValidator() {
|
||||
return tool.getValidator()
|
||||
}
|
||||
|
||||
return {
|
||||
form: state,
|
||||
setField,
|
||||
resetForm,
|
||||
clearForm,
|
||||
setFields,
|
||||
getValidator,
|
||||
}
|
||||
}
|
||||
10
main.js
10
main.js
@@ -1,21 +1,23 @@
|
||||
import App from './App'
|
||||
import uviewPlus from '/uview-plus'
|
||||
import globalMixin from './mixins/global'
|
||||
import store from './store'
|
||||
import { createSSRApp } from 'vue'
|
||||
import { preventReClick, longPress, permission, drag } from './directives/index'
|
||||
import './uni.promisify.adaptor'
|
||||
|
||||
uni.$zp = {
|
||||
config: {
|
||||
'empty-view-text': '空空如也~~',
|
||||
'refresher-enabled': true,
|
||||
},
|
||||
}
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
app.use(uviewPlus)
|
||||
app.use(globalMixin)
|
||||
app.use(store)
|
||||
// 注册自定义指令
|
||||
app.directive('prevent-re-click', preventReClick)
|
||||
app.directive('long-press', longPress)
|
||||
app.directive('permission', permission)
|
||||
app.directive('drag', drag)
|
||||
return { app }
|
||||
}
|
||||
|
||||
26
package.json
26
package.json
@@ -1,7 +1,33 @@
|
||||
{
|
||||
"name": "uniapp-template",
|
||||
"version": "1.0.0",
|
||||
"description": "基于 UniApp + Vue3 + uView-Plus 的微信小程序快速开发模板",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint src/**/*.{js,jsx,ts,tsx,vue} --fix",
|
||||
"format": "prettier --write src/**/*.{js,jsx,ts,tsx,vue,json,css,scss}",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^17.2.2",
|
||||
"dayjs": "*",
|
||||
"vue": "^3.5.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.0.0",
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"husky": "^8.0.0",
|
||||
"lint-staged": "^15.0.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.{js,jsx,ts,tsx,vue}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
99
uni.scss
99
uni.scss
@@ -21,32 +21,32 @@ $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: #333; //基本色
|
||||
$uni-text-color-inverse: #fff; //反色
|
||||
$uni-text-color-grey: #999; //辅助灰色,如加载更多的提示信息
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
$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-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;
|
||||
$uni-border-color: #c8c7cc;
|
||||
|
||||
/* 尺寸变量 */
|
||||
|
||||
/* 文字尺寸 */
|
||||
$uni-font-size-sm:12px;
|
||||
$uni-font-size-base:14px;
|
||||
$uni-font-size-lg:16px;
|
||||
$uni-font-size-sm: 12px;
|
||||
$uni-font-size-base: 14px;
|
||||
$uni-font-size-lg: 16px;
|
||||
|
||||
/* 图片尺寸 */
|
||||
$uni-img-size-sm:20px;
|
||||
$uni-img-size-base:26px;
|
||||
$uni-img-size-lg:40px;
|
||||
$uni-img-size-sm: 20px;
|
||||
$uni-img-size-base: 26px;
|
||||
$uni-img-size-lg: 40px;
|
||||
|
||||
/* Border Radius */
|
||||
$uni-border-radius-sm: 2px;
|
||||
@@ -68,12 +68,69 @@ $uni-spacing-col-lg: 12px;
|
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||
|
||||
/* 文章场景相关 */
|
||||
$uni-color-title: #2C405A; // 文章标题颜色
|
||||
$uni-font-size-title:20px;
|
||||
$uni-color-title: #2c405a; // 文章标题颜色
|
||||
$uni-font-size-title: 20px;
|
||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||
$uni-font-size-subtitle:26px;
|
||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||
$uni-font-size-paragraph:15px;
|
||||
$uni-font-size-subtitle: 26px;
|
||||
$uni-color-paragraph: #3f536e; // 文章段落颜色
|
||||
$uni-font-size-paragraph: 15px;
|
||||
|
||||
/* 全局资源URL变量 */
|
||||
$ASSETSURL:'https://cdn.vrupup.com/s/1732/assets/';
|
||||
$ASSETSURL: 'https://cdn.vrupup.com/s/1732/assets/';
|
||||
|
||||
/* 亮色模式颜色变量 */
|
||||
$uni-color-primary-light: #007aff;
|
||||
$uni-color-success-light: #4cd964;
|
||||
$uni-color-warning-light: #f0ad4e;
|
||||
$uni-color-error-light: #dd524d;
|
||||
|
||||
$uni-text-color-light: #333;
|
||||
$uni-text-color-inverse-light: #fff;
|
||||
$uni-text-color-grey-light: #999;
|
||||
$uni-text-color-placeholder-light: #808080;
|
||||
$uni-text-color-disable-light: #c0c0c0;
|
||||
|
||||
$uni-bg-color-light: #ffffff;
|
||||
$uni-bg-color-grey-light: #f8f8f8;
|
||||
$uni-bg-color-hover-light: #f1f1f1;
|
||||
$uni-bg-color-mask-light: rgba(0, 0, 0, 0.4);
|
||||
|
||||
$uni-border-color-light: #c8c7cc;
|
||||
|
||||
/* 暗色模式颜色变量 */
|
||||
$uni-color-primary-dark: #0a84ff;
|
||||
$uni-color-success-dark: #34c759;
|
||||
$uni-color-warning-dark: #ff9500;
|
||||
$uni-color-error-dark: #ff3b30;
|
||||
|
||||
$uni-text-color-dark: #f2f2f7;
|
||||
$uni-text-color-inverse-dark: #000000;
|
||||
$uni-text-color-grey-dark: #8e8e93;
|
||||
$uni-text-color-placeholder-dark: #4c4c4c;
|
||||
$uni-text-color-disable-dark: #636366;
|
||||
|
||||
$uni-bg-color-dark: #1c1c1e;
|
||||
$uni-bg-color-grey-dark: #2c2c2e;
|
||||
$uni-bg-color-hover-dark: #3a3a3c;
|
||||
$uni-bg-color-mask-dark: rgba(0, 0, 0, 0.6);
|
||||
|
||||
$uni-border-color-dark: #434346;
|
||||
|
||||
/* 默认使用亮色模式颜色 */
|
||||
$uni-color-primary: $uni-color-primary-light;
|
||||
$uni-color-success: $uni-color-success-light;
|
||||
$uni-color-warning: $uni-color-warning-light;
|
||||
$uni-color-error: $uni-color-error-light;
|
||||
|
||||
$uni-text-color: $uni-text-color-light;
|
||||
$uni-text-color-inverse: $uni-text-color-inverse-light;
|
||||
$uni-text-color-grey: $uni-text-color-grey-light;
|
||||
$uni-text-color-placeholder: $uni-text-color-placeholder-light;
|
||||
$uni-text-color-disable: $uni-text-color-disable-light;
|
||||
|
||||
$uni-bg-color: $uni-bg-color-light;
|
||||
$uni-bg-color-grey: $uni-bg-color-grey-light;
|
||||
$uni-bg-color-hover: $uni-bg-color-hover-light;
|
||||
$uni-bg-color-mask: $uni-bg-color-mask-light;
|
||||
|
||||
$uni-border-color: $uni-border-color-light;
|
||||
|
||||
@@ -3,7 +3,6 @@ import uni from '@dcloudio/vite-plugin-uni'
|
||||
import { resolve } from 'path'
|
||||
import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
// 自定义插件:替换 manifest.json 中的 appid
|
||||
// 仅在通过HBuilder首次编译时执行,避免热重载时重复执行导致内存占用高
|
||||
function replaceManifestAppid() {
|
||||
@@ -14,19 +13,15 @@ function replaceManifestAppid() {
|
||||
// 检查是否已经执行过插件
|
||||
const manifestUpdatedFlag = resolve(__dirname, '.manifest-updated')
|
||||
const isFirstCompile = !existsSync(manifestUpdatedFlag)
|
||||
|
||||
// 仅首次编译时执行
|
||||
if (!isFirstCompile) {
|
||||
console.log('跳过 manifest appid 更新(已执行过首次编译)')
|
||||
return
|
||||
}
|
||||
|
||||
// 获取环境变量,明确指定路径为项目根目录
|
||||
dotenv.config({ path: resolve(__dirname, '.env') })
|
||||
const appid = process.env.VITE_APPID
|
||||
const uni_appId = process.env.VITE_UNI_APPID
|
||||
const libVersion = process.env.VITE_LIBVERSION || '3.0.0'
|
||||
|
||||
if (appid && uni_appId) {
|
||||
// 读取 manifest.json 文件
|
||||
const manifestPath = resolve(__dirname, 'manifest.json')
|
||||
@@ -35,12 +30,9 @@ function replaceManifestAppid() {
|
||||
const manifest = JSON.parse(manifestContent)
|
||||
// 替换
|
||||
manifest.appid = uni_appId
|
||||
|
||||
if (manifest['mp-weixin']) {
|
||||
manifest['mp-weixin'].appid = appid
|
||||
manifest['mp-weixin'].libVersion = libVersion
|
||||
}
|
||||
|
||||
// 写回文件
|
||||
writeFileSync(manifestPath, JSON.stringify(manifest, null, 4))
|
||||
// 创建标记文件,表示已执行过插件
|
||||
@@ -53,21 +45,18 @@ function replaceManifestAppid() {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否通过HBuilder编译,决定是否启用插件
|
||||
const manifestUpdatedFlag = resolve(__dirname, '.manifest-updated')
|
||||
|
||||
if (existsSync(manifestUpdatedFlag)) {
|
||||
unlinkSync(manifestUpdatedFlag)
|
||||
console.log('已清理 manifest 更新标记文件')
|
||||
}
|
||||
|
||||
const plugins = manifestUpdatedFlag ? [replaceManifestAppid(), uni()] : [uni()]
|
||||
export default defineConfig({
|
||||
plugins,
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': '/src',
|
||||
'@': resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
@@ -75,6 +64,55 @@ export default defineConfig({
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
pure_funcs: ['console.log'], // 移除console.log
|
||||
},
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// 静态资源分类打包
|
||||
assetFileNames: (assetInfo) => {
|
||||
let extType = assetInfo.name.split('.').at(1)
|
||||
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
|
||||
extType = 'img'
|
||||
} else if (/woff|woff2|eot|ttf|otf/i.test(extType)) {
|
||||
extType = 'font'
|
||||
}
|
||||
return `static/${extType}/[name]-[hash].[ext]`
|
||||
},
|
||||
// 分包
|
||||
chunkFileNames: 'static/js/[name]-[hash].js',
|
||||
entryFileNames: 'static/js/[name]-[hash].js',
|
||||
manualChunks: {
|
||||
// 将第三方库单独打包
|
||||
vue: ['vue'],
|
||||
'uview-plus': ['uview-plus'],
|
||||
'luch-request': ['luch-request'],
|
||||
},
|
||||
},
|
||||
},
|
||||
// 启用 CSS 代码分割
|
||||
cssCodeSplit: true,
|
||||
// 小于此阈值的导入或引用资源将内联为 base64 编码
|
||||
assetsInlineLimit: 4096,
|
||||
// 启用 gzip 压缩
|
||||
brotliSize: true,
|
||||
},
|
||||
// 优化选项
|
||||
optimizeDeps: {
|
||||
include: ['vue', 'uview-plus', 'luch-request'],
|
||||
},
|
||||
// 开发服务器配置
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 3000,
|
||||
open: true,
|
||||
// 代理配置
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: process.env.VITE_BASE_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user