Merge pull request 'orgin/master' (#1) from orgin/master into master
Reviewed-on: http://localhost:3000/uniccoo/uniViteTemplate/pulls/1
13
.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# 忽略目录
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
.hbuilderx/*
|
||||||
|
.idea/*
|
||||||
|
|
||||||
|
# 忽略文件
|
||||||
|
*.log
|
||||||
|
**/*.tsbuildinfo
|
||||||
|
.eslintcache
|
||||||
|
pnpm-lock.yaml
|
||||||
|
/.eslintrc-auto-import.json
|
||||||
|
/typings/
|
||||||
6
.npmrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# 提示:如果你想自动安装对等依赖,在项目根目录下的.npmrc文件中添加"auto-install-peers=true"。
|
||||||
|
# 提示:如果你不希望pnpm在对等依赖问题上失败,在项目根目录下的.npmrc文件中添加"strict-peer-dependencies=false"。
|
||||||
|
# auto-install-peers=true
|
||||||
|
strict-peer-dependencies=false
|
||||||
|
# registry=https://registry.npmmirror.com
|
||||||
|
registry=https://registry.npmjs.org
|
||||||
12
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
//////////////////////////////////
|
||||||
|
// 向此工作区的用户推荐的扩展列表 //
|
||||||
|
/////////////////////////////////
|
||||||
|
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar", // Vue语言支持扩展
|
||||||
|
"Vue.vscode-typescript-vue-plugin", // 一个TS服务器插件,使TS服务器知道*.vue文件。
|
||||||
|
"esbenp.prettier-vscode", // 代码格式化
|
||||||
|
"dbaeumer.vscode-eslint" // 代码质量检查
|
||||||
|
]
|
||||||
|
}
|
||||||
73
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// vscode 编辑器和插件的配置 ////////////////////////////////////////////////
|
||||||
|
// 参考:https://blog.csdn.net/weixin_46238462/article/details/125867532 //
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
{
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.detectIndentation": false,
|
||||||
|
"editor.tabSize": 4,
|
||||||
|
"files.eol": "auto",
|
||||||
|
"eslint.enable": true,
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
// 官网参考:https://prettier.io/docs/en/options.html#tab-width //
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
"prettier.semi": true,
|
||||||
|
"prettier.tabWidth": 4,
|
||||||
|
"prettier.printWidth": 160,
|
||||||
|
"prettier.endOfLine": "auto",
|
||||||
|
"prettier.singleQuote": true,
|
||||||
|
"prettier.ignorePath": ".prettierignore",
|
||||||
|
// Enable the ESlint flat config support
|
||||||
|
// (remove this if your ESLint extension above v3.0.5)
|
||||||
|
"eslint.experimental.useFlatConfig": true,
|
||||||
|
|
||||||
|
// Disable the default formatter, use eslint instead
|
||||||
|
"prettier.enable": false,
|
||||||
|
"editor.formatOnSave": false,
|
||||||
|
|
||||||
|
// Auto fix
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit",
|
||||||
|
"source.organizeImports": "never"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||||
|
"eslint.rules.customizations": [
|
||||||
|
{ "rule": "style/*", "severity": "off" },
|
||||||
|
{ "rule": "format/*", "severity": "off" },
|
||||||
|
{ "rule": "*-indent", "severity": "off" },
|
||||||
|
{ "rule": "*-spacing", "severity": "off" },
|
||||||
|
{ "rule": "*-spaces", "severity": "off" },
|
||||||
|
{ "rule": "*-order", "severity": "off" },
|
||||||
|
{ "rule": "*-dangle", "severity": "off" },
|
||||||
|
{ "rule": "*-newline", "severity": "off" },
|
||||||
|
{ "rule": "*quotes", "severity": "off" },
|
||||||
|
{ "rule": "*semi", "severity": "off" }
|
||||||
|
],
|
||||||
|
|
||||||
|
// Enable eslint for all supported languages
|
||||||
|
"eslint.validate": [
|
||||||
|
"javascript",
|
||||||
|
"javascriptreact",
|
||||||
|
"typescript",
|
||||||
|
"typescriptreact",
|
||||||
|
"vue",
|
||||||
|
"html",
|
||||||
|
"markdown",
|
||||||
|
"json",
|
||||||
|
"jsonc",
|
||||||
|
"yaml",
|
||||||
|
"toml",
|
||||||
|
"xml",
|
||||||
|
"gql",
|
||||||
|
"graphql",
|
||||||
|
"astro",
|
||||||
|
"css",
|
||||||
|
"less",
|
||||||
|
"scss",
|
||||||
|
"pcss",
|
||||||
|
"postcss"
|
||||||
|
]
|
||||||
|
}
|
||||||
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 h_mo
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
235
README.md
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
# 🌈 uni-app Vue3 Vite4 pinia2 TypeScript 基础框架
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://cn.vuejs.org/" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/-Vue3-34495e?logo=vue.js" />
|
||||||
|
</a>
|
||||||
|
<a href="https://pinia.vuejs.org/zh/" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/-Vite4-646cff?logo=vite&logoColor=white" />
|
||||||
|
</a>
|
||||||
|
<a href="https://www.typescriptlang.org/zh/" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/-TypeScript5-blue?logo=typescript&logoColor=white" />
|
||||||
|
</a>
|
||||||
|
<a href="https://eslint.org/" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/-ESLint8-4b32c3?logo=eslint&logoColor=white" />
|
||||||
|
</a>
|
||||||
|
<a href="https://pnpm.io/" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/-pnpm8-F69220?logo=pnpm&logoColor=white" />
|
||||||
|
</a>
|
||||||
|
<a href="https://unocss.dev/" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/-UnoCss-4d4d4d?logo=unocss" />
|
||||||
|
</a>
|
||||||
|
<a href="https://iconify.design/" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/-Iconify-1769aa?logo=Iconify" />
|
||||||
|
</a>
|
||||||
|
<a href="https://gitee.com/h_mo/uniapp-vue3-vite-ts-template" target="_blank">
|
||||||
|
<img src="https://svg.hamm.cn/gitee.svg?type=star&user=h_mo&project=uniapp-vue3-vite-ts-template"/>
|
||||||
|
</a>
|
||||||
|
<a href="https://gitee.com/h_mo/uniapp-vue3-vite-ts-template" target="_blank">
|
||||||
|
<img src="https://svg.hamm.cn/gitee.svg?type=fork&user=h_mo&project=uniapp-vue3-vite-ts-template"/>
|
||||||
|
</a>
|
||||||
|
<a href="https://ext.dcloud.net.cn/plugin?id=8559" target="_blank">
|
||||||
|
<img src="https://svg.hamm.cn/badge.svg?key=Platform&value=uni-app"/>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
|
||||||
|
- **uni-app Vue3 Vite4 pinia2 TypeScript 基础框架**
|
||||||
|
- cli 创建的 Vue3/Vite 项目 与 使用 HBuilderX 导入插件 的包有差异,请直接访问 [开源地址](https://gitee.com/h_mo/uniapp-vue3-vite-ts-template)
|
||||||
|
- 访问[uniapp 插件](https://ext.dcloud.net.cn/plugin?id=8559)
|
||||||
|
- 如有问题请加群【[872378674](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=T3PX4_sWeMXeFGWF6EZJLXABSNyStYR0&authKey=EYXATTrGpmyowFxk9xtX6T7FIRbOF7brLd9uODxl%2B6jIbGfWQGW869V1hkPSlGYT&noverify=0&group_code=872378674)】交流
|
||||||
|
|
||||||
|
### 说明
|
||||||
|
|
||||||
|
- 框架完全基于 Vue3 SFC `<script setup>` 写法,不支持 Vue2;
|
||||||
|
- 可用于学习与交流;
|
||||||
|
- 目前测试 H5、微信小程序,APP(Android),支付宝小程序通过;
|
||||||
|
- 其他平台暂未测试,后续会增加;
|
||||||
|
- 如发现问题或建议可在评论区留言, 或提[Issues](https://gitee.com/h_mo/uniapp-vue3-vite-ts-template/issues)及[PR](https://gitee.com/h_mo/uniapp-vue3-vite-ts-template/pulls),会及时处理;
|
||||||
|
- 如有需求亦可在评论区留言,或在此项目基础上增加;
|
||||||
|
- [代码规范 & 详细解释 husky、prettier、eslint、lint-staged 的作用和使用](https://blog.csdn.net/cookcyq__/article/details/125457031)
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- **最新技术栈**:使用 Vue3/Vite4/pinia ,TypeScript 等前端前沿技术开发;
|
||||||
|
- **[Unocss](https://github.com/unocss/unocss)**: 原子化 CSS, [iconify](https://github.com/iconify/iconify)图标
|
||||||
|
- **Eslint/Prettier**: 规范代码格式,统一编码;
|
||||||
|
- **路由拦截**: [uni-mini-router](https://gitee.com/fant-mini/uni-mini-router),类似Vue Router的API和功能,在uni-app中进行路由跳转、传参、拦截等常用操作;
|
||||||
|
- **请求拦截**: 使用[alova 请求](https://github.com/alovajs/alova),支持请求和响应拦截等;
|
||||||
|
- **Mock 数据**: 配合 alova 请求的[@alova/mock](https://github.com/alovajs/mock),模拟 api 请求(App 不支持);
|
||||||
|
- **缓存加密**: 支持 AES 加密缓存,可设置区分在开发或生成环境中是否加密;
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```shell
|
||||||
|
.
|
||||||
|
├─ src
|
||||||
|
│ ├─assets # 静态资源目录
|
||||||
|
│ │
|
||||||
|
│ ├─components # 组件目录
|
||||||
|
│ │ ├─ BasicButton
|
||||||
|
│ │ │ ├─index.vue
|
||||||
|
│ │ │ └─prpos.ts
|
||||||
|
│ │ └─...
|
||||||
|
│ │
|
||||||
|
│ ├─enums # 枚举/常量
|
||||||
|
│ │ ├─ cacheEnum.ts
|
||||||
|
│ │ └─...
|
||||||
|
│ │
|
||||||
|
│ ├─pages # 页面
|
||||||
|
│ │ ├─ index
|
||||||
|
│ │ │ └─index.vue
|
||||||
|
│ │ └─...
|
||||||
|
│ │
|
||||||
|
│ ├─services # 接口相关
|
||||||
|
│ │ ├─ api # api
|
||||||
|
│ │ │ ├─auth.ts
|
||||||
|
│ │ │ └─...
|
||||||
|
│ │ │
|
||||||
|
│ │ └─ model # 数据模型
|
||||||
|
│ │ ├─authModel.d.ts
|
||||||
|
│ │ └─...
|
||||||
|
│ │
|
||||||
|
│ ├─settings # 设置
|
||||||
|
│ │ └─ encryptionSetting # 加密设置
|
||||||
|
│ │
|
||||||
|
│ ├─state # 状态管理模式(pinia)
|
||||||
|
│ │ ├─ modules # 数据模块
|
||||||
|
│ │ │ ├─auth.ts
|
||||||
|
│ │ │ └─...
|
||||||
|
│ │ │
|
||||||
|
│ │ └─ index.ts
|
||||||
|
│ │
|
||||||
|
│ ├─static # 静态公共文件
|
||||||
|
│ │ ├─ images # 图片
|
||||||
|
│ │ │ ├─avatar.png
|
||||||
|
│ │ │ └─...
|
||||||
|
│ │ │
|
||||||
|
│ │ └─ ...
|
||||||
|
│ │
|
||||||
|
│ ├─types # 类型文件
|
||||||
|
│ │ ├─ http.d.ts
|
||||||
|
│ │ └─ ...
|
||||||
|
│ │
|
||||||
|
│ └─utils # 工具类
|
||||||
|
│ ├─ cache # 缓存相关目录
|
||||||
|
│ ├─ http # request相关目录
|
||||||
|
│ ├─ interceptors # 拦截器相关目录
|
||||||
|
│ └─ ...
|
||||||
|
│
|
||||||
|
├─ .env
|
||||||
|
├─ .env.development
|
||||||
|
├─ .env.production
|
||||||
|
├─ .eslintignore
|
||||||
|
├─ .eslintrc.js
|
||||||
|
├─ .gitignore
|
||||||
|
├─ .prettierignore
|
||||||
|
├─ .prettierrc.js
|
||||||
|
├─ favicon.ico
|
||||||
|
├─ index.html
|
||||||
|
├─ package.json
|
||||||
|
├─ pnpm-lock.yaml
|
||||||
|
├─ README.md
|
||||||
|
├─ tree.txt
|
||||||
|
├─ tsconfig.json
|
||||||
|
└─ vite.config.ts
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 预览
|
||||||
|
|
||||||
|
- 域名到期,暂时已不能预览
|
||||||
|
|
||||||
|
[//]: # '- H5'
|
||||||
|
[//]: # ' '
|
||||||
|
[//]: # '- 小程序(体验版-需申请体验)'
|
||||||
|
[//]: #
|
||||||
|
[//]: # ' '
|
||||||
|
|
||||||
|
## 安装使用
|
||||||
|
|
||||||
|
- 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
- 运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 其他端请查看 package.json script
|
||||||
|
pnpm dev:h5
|
||||||
|
```
|
||||||
|
|
||||||
|
- 打包
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 其他端请查看 package.json script
|
||||||
|
pnpm build:h5
|
||||||
|
```
|
||||||
|
|
||||||
|
- 更新依赖到最新(新手请忽略)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm up
|
||||||
|
# 打开HBuilder X alpha桌面程序-->点击上面的帮助-->历次更新说明-->获取最新版本号(如:3.7.2.20230217-alpha)
|
||||||
|
npx @dcloudio/uvm 3.7.2.20230217-alpha
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gitee 参与贡献
|
||||||
|
|
||||||
|
1. Fork 本仓库
|
||||||
|
2. 新建 Feat_xxx 分支
|
||||||
|
3. 提交代码
|
||||||
|
4. 新建 Pull Request
|
||||||
|
|
||||||
|
## [Gitee 贡献提交规范](https://www.conventionalcommits.org/zh-hans/v1.0.0/)
|
||||||
|
|
||||||
|
### 提交类型
|
||||||
|
|
||||||
|
| 提交类型 | 标题 | 描述 |
|
||||||
|
| ---------- | ------------------ | ------------------------------------------------------------------------------------- |
|
||||||
|
| `feat` | 特征 | 新功能、新特性 |
|
||||||
|
| `fix` | Bug 修复 | bug 修复 |
|
||||||
|
| `docs` | 文档 | 仅文档更改 |
|
||||||
|
| `style` | 风格 | 不影响代码含义的更改(空格、格式、缺少分号等) |
|
||||||
|
| `refactor` | 代码重构 | 重构,在不影响代码内部行为,功能下的代码修改 |
|
||||||
|
| `perf` | 性能改进 | 更改代码,以提高性能 |
|
||||||
|
| `test` | 测试 | 添加缺失的测试或纠正现有的测试 |
|
||||||
|
| `build` | 构建 | 影响构建系统或外部依赖项的更改(示例范围:gulp、broccoli、npm) |
|
||||||
|
| `ci` | 持续集成 | 对我们的 CI 配置文件和脚本的更改(示例范围:Travis、Circle、BrowserStack、SauceLabs) |
|
||||||
|
| `chore` | 其他文件修改 | 不修改 src 或测试文件的其他更改 |
|
||||||
|
| `revert` | 还原 | 恢复之前的提交 |
|
||||||
|
| `release` | 发布新版本 | \- |
|
||||||
|
| `workflow` | 工作流相关文件修改 | \- |
|
||||||
|
|
||||||
|
### 提交别名
|
||||||
|
|
||||||
|
| 提交类型 | 映射到 | 标题 | 描述 |
|
||||||
|
| ------------------ | ------- | -------- | -------------------------- |
|
||||||
|
| `initial` | `feat` | 最初的 | 初始提交 |
|
||||||
|
| `dependencies` | `fix` | 依赖项 | 更新依赖项 |
|
||||||
|
| `peerDependencies` | `fix` | 对等依赖 | 更新对等依赖项 |
|
||||||
|
| `devDependencies` | `chore` | 开发依赖 | 更新开发依赖 |
|
||||||
|
| `metadata` | `fix` | 元数据 | 更新元数据(package.json) |
|
||||||
|
|
||||||
|
### 快捷别名提示
|
||||||
|
|
||||||
|
1. resolve a conflict:解决冲突
|
||||||
|
2. merge branch:合并分支
|
||||||
|
3. feat: [...] : 添加的新功能说明
|
||||||
|
4. fix: [...] : 修复的 bug 说明
|
||||||
|
5. initial project:初始化项目
|
||||||
|
6. style: [...] : 修改的样式范围
|
||||||
|
7. perf:[...] : 优化的范围
|
||||||
|
8. release : 发布新版本
|
||||||
|
9. docs: 文档修改
|
||||||
|
10. refactor: 代码重构
|
||||||
|
11. revert: 还原之前的版本
|
||||||
|
12. dependencies: 依赖项修改
|
||||||
|
13. devDependencies: 开发依赖修改
|
||||||
|
14. review:复习,回顾
|
||||||
|
15. strengthen: 加强,巩固
|
||||||
|
|
||||||
|
##
|
||||||
2
env/.env
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# title
|
||||||
|
VITE_APP_TITLE='Uni-app Vue3 Ts --Vite'
|
||||||
17
env/.env.development
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# 运行环境
|
||||||
|
VITE_ENV=development
|
||||||
|
|
||||||
|
# 是否使用模拟数据
|
||||||
|
VITE_USE_MOCK=true
|
||||||
|
|
||||||
|
VITE_PORT=3000
|
||||||
|
|
||||||
|
# BASE_URL
|
||||||
|
VITE_BASE_URL==/api
|
||||||
|
# 上传域名
|
||||||
|
VITE_UPLOAD_URL=/upload
|
||||||
|
|
||||||
|
# 代理前缀,仅H5有效
|
||||||
|
VITE_PROXY_PREFIX=/api
|
||||||
|
VITE_UPLOAD_PROXY_PREFIX=/upload
|
||||||
|
|
||||||
12
env/.env.production
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# 运行环境
|
||||||
|
VITE_ENV=production
|
||||||
|
|
||||||
|
# 是否使用模拟数据
|
||||||
|
VITE_USE_MOCK=true
|
||||||
|
|
||||||
|
# api域名
|
||||||
|
VITE_BASE_URL=/api/v1
|
||||||
|
|
||||||
|
|
||||||
|
# 上传域名
|
||||||
|
VITE_UPLOAD_URL=/upload
|
||||||
45
eslint.config.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/** @type {import('eslint').Linter.Config} */
|
||||||
|
|
||||||
|
import process from 'node:process';
|
||||||
|
import antfu from '@antfu/eslint-config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://github.com/antfu/eslint-config
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default antfu({
|
||||||
|
formatters: true,
|
||||||
|
vue: true,
|
||||||
|
jsx: true,
|
||||||
|
unocss: true,
|
||||||
|
rules: {
|
||||||
|
'style/indent': ['error', 2, { SwitchCase: 2 }],
|
||||||
|
'style/quotes': ['error', 'single'],
|
||||||
|
'style/semi': ['error', 'always'],
|
||||||
|
'style/semi-style': ['error', 'last'],
|
||||||
|
'style/max-len': ['error', {
|
||||||
|
code: 160,
|
||||||
|
tabWidth: 2,
|
||||||
|
ignoreUrls: true,
|
||||||
|
ignoreComments: true,
|
||||||
|
ignoreStrings: true,
|
||||||
|
ignoreTemplateLiterals: true,
|
||||||
|
ignoreRegExpLiterals: true,
|
||||||
|
}],
|
||||||
|
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
|
||||||
|
'vue/script-indent': ['error', 2, { baseIndent: 0 }],
|
||||||
|
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
'no-useless-catch': 'off',
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
ignores: [
|
||||||
|
'./dist/*',
|
||||||
|
'./.vscode/*',
|
||||||
|
'./.idea/*',
|
||||||
|
'**/androidPrivacy.json',
|
||||||
|
'README.md',
|
||||||
|
],
|
||||||
|
});
|
||||||
BIN
favicon.ico
Normal file
|
After Width: | Height: | Size: 698 B |
25
index.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<script>
|
||||||
|
var coverSupport =
|
||||||
|
'CSS' in window &&
|
||||||
|
typeof CSS.supports === 'function' &&
|
||||||
|
(CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
|
||||||
|
document.write(
|
||||||
|
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||||
|
(coverSupport ? ', viewport-fit=cover' : '') +
|
||||||
|
'" />',
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
<title></title>
|
||||||
|
<!--preload-links-->
|
||||||
|
<!--app-context-->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"><!--app-html--></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
100
package.json
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"name": "uniapp_vue3_vite_ts",
|
||||||
|
"type": "module",
|
||||||
|
"version": "2.0.0-alpha.1",
|
||||||
|
"scripts": {
|
||||||
|
"dev:app": "uni -p app",
|
||||||
|
"dev:custom": "uni -p",
|
||||||
|
"dev:h5": "pnpm git:hooks && uni",
|
||||||
|
"dev:h5:ssr": "uni --ssr",
|
||||||
|
"dev:mp-alipay": "uni -p mp-alipay",
|
||||||
|
"dev:mp-baidu": "uni -p mp-baidu",
|
||||||
|
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||||
|
"dev:mp-lark": "uni -p mp-lark",
|
||||||
|
"dev:mp-qq": "uni -p mp-qq",
|
||||||
|
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||||
|
"dev:mp-weixin": "pnpm git:hooks && uni -p mp-weixin",
|
||||||
|
"dev:quickapp-webview": "uni -p quickapp-webview",
|
||||||
|
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
|
||||||
|
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
|
||||||
|
"build:app": "uni build -p app",
|
||||||
|
"build:custom": "uni build -p",
|
||||||
|
"build:h5": "uni build --minify",
|
||||||
|
"build:h5:ssr": "uni build --ssr",
|
||||||
|
"build:mp-alipay": "uni build -p mp-alipay",
|
||||||
|
"build:mp-baidu": "uni build -p mp-baidu",
|
||||||
|
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||||
|
"build:mp-lark": "uni build -p mp-lark",
|
||||||
|
"build:mp-qq": "uni build -p mp-qq",
|
||||||
|
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||||
|
"build:mp-weixin": "uni build -p mp-weixin --minify",
|
||||||
|
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||||
|
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||||
|
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
|
||||||
|
"lint:fix": "eslint . --fix --ignore-pattern '*/androidPrivacy.json' --ignore-pattern 'README.md'",
|
||||||
|
"git:hooks": "npx simple-git-hooks"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@alova/adapter-uniapp": "^1.2.2",
|
||||||
|
"@alova/mock": "^1.5.1",
|
||||||
|
"@dcloudio/uni-app": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-app-plus": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-components": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-h5": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-i18n": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-mp-alipay": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-mp-baidu": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-mp-kuaishou": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-mp-lark": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-mp-qq": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-mp-toutiao": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@multiavatar/multiavatar": "^1.0.7",
|
||||||
|
"alova": "^2.21.3",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"vue": "^3.4.29"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@antfu/eslint-config": "^2.21.1",
|
||||||
|
"@dcloudio/types": "^3.4.8",
|
||||||
|
"@dcloudio/uni-automator": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-cli-shared": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/uni-stacktracey": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-4010920240607001",
|
||||||
|
"@iconify/vue": "^4.1.2",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/node": "^20.14.2",
|
||||||
|
"@unocss/eslint-plugin": "^0.61.0",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.5",
|
||||||
|
"@vue/runtime-core": "^3.4.29",
|
||||||
|
"eslint": "^9.4.0",
|
||||||
|
"eslint-plugin-format": "^0.1.1",
|
||||||
|
"globals": "^15.4.0",
|
||||||
|
"lint-staged": "^15.2.7",
|
||||||
|
"picocolors": "^1.0.1",
|
||||||
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
|
"sass": "^1.77.5",
|
||||||
|
"simple-git-hooks": "^2.11.1",
|
||||||
|
"tsx": "^4.15.4",
|
||||||
|
"typescript": "^5.4.5",
|
||||||
|
"uni-mini-router": "^0.1.6",
|
||||||
|
"uni-read-pages-vite": "^0.0.6",
|
||||||
|
"unocss": "^0.61.0",
|
||||||
|
"unocss-preset-weapp": "^0.60.1",
|
||||||
|
"unplugin-auto-import": "^0.17.6",
|
||||||
|
"unplugin-transform-class": "^0.5.3",
|
||||||
|
"vite": "^5.3.1",
|
||||||
|
"vite-plugin-restart": "^0.4.0"
|
||||||
|
},
|
||||||
|
"simple-git-hooks": {
|
||||||
|
"pre-commit": "npx lint-staged",
|
||||||
|
"commit-msg": "npx tsx ./scripts/verify-commit.ts"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*": "eslint --fix --ignore-pattern '*/androidPrivacy.json' --ignore-pattern 'README.md'"
|
||||||
|
}
|
||||||
|
}
|
||||||
28
scripts/verify-commit.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 提交信息校验
|
||||||
|
* @link https://github.com/toplenboren/simple-git-hooks
|
||||||
|
* @see 参考:https://github.com/vuejs/vue-next/blob/master/.github/commit-convention.md
|
||||||
|
*/
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
import pico from 'picocolors';
|
||||||
|
|
||||||
|
const msgPath = path.resolve('.git/COMMIT_EDITMSG');
|
||||||
|
const msg = readFileSync(msgPath, 'utf-8').trim();
|
||||||
|
|
||||||
|
const commitRE
|
||||||
|
= /^(?:revert: )?(?:feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|mod|release|strengthen)(?:\(.+\))?: .{1,50}/;
|
||||||
|
|
||||||
|
if (!commitRE.test(msg)) {
|
||||||
|
console.log(pico.yellow(`\n提交的信息: ${msg}\n`));
|
||||||
|
console.error(
|
||||||
|
` ${pico.white(pico.bgRed(' 格式错误 '))} ${pico.red(
|
||||||
|
'无效的提交信息格式.',
|
||||||
|
)}\n\n${
|
||||||
|
pico.red(' 正确的提交消息格式. 例如:\n\n')
|
||||||
|
} ${pico.green('feat: add a new feature')}\n`
|
||||||
|
+ ` ${pico.green('fix: fixed an bug')}`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
10
shims-uni.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/// <reference types='@dcloudio/types' />
|
||||||
|
import 'vue';
|
||||||
|
|
||||||
|
declare module '@vue/runtime-core' {
|
||||||
|
type Hooks = App.AppInstance & Page.PageInstance;
|
||||||
|
|
||||||
|
interface ComponentCustomOptions extends Hooks {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/App.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app';
|
||||||
|
import { useUserStore } from '@/stores/modules/user';
|
||||||
|
|
||||||
|
onLaunch(() => {
|
||||||
|
console.log('App Launch');
|
||||||
|
});
|
||||||
|
onShow(() => {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
userStore.initUserInfo();
|
||||||
|
console.log('App Show');
|
||||||
|
});
|
||||||
|
onHide(() => {
|
||||||
|
console.log('App Hide');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
</style>
|
||||||
29
src/androidPrivacy.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"prompt": "template",
|
||||||
|
"title": "服务协议和隐私政策",
|
||||||
|
"message": " 请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/> 你可阅读<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
|
||||||
|
"buttonAccept": "同意并接受",
|
||||||
|
"buttonRefuse": "暂不同意",
|
||||||
|
// HX 3.4.13之后版本新增,system 使用系统webview 打开隐私协议链接,默认使用uni-app内置web组件
|
||||||
|
"hrefLoader": "default",
|
||||||
|
"second": {
|
||||||
|
"title": "确认提示",
|
||||||
|
"message": " 进入应用前,你需先同意<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>,否则将退出应用。",
|
||||||
|
"buttonAccept": "同意并继续",
|
||||||
|
"buttonRefuse": "退出应用"
|
||||||
|
},
|
||||||
|
"styles": {
|
||||||
|
"backgroundColor": "#00FF00",
|
||||||
|
"borderRadius": "5px",
|
||||||
|
"title": {
|
||||||
|
"color": "#ff00ff"
|
||||||
|
},
|
||||||
|
"buttonAccept": {
|
||||||
|
"color": "#ffff00"
|
||||||
|
},
|
||||||
|
"buttonRefuse": {
|
||||||
|
"color": "#00ffff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/components/AppProvider/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script lang="ts" setup name="AppProvider"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<slot />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
32
src/components/BasicButton/index.vue
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { buttonProps } from '@/components/BasicButton/prpos';
|
||||||
|
|
||||||
|
const props = defineProps(buttonProps);
|
||||||
|
const emits = defineEmits(['click']);
|
||||||
|
function click() {
|
||||||
|
emits('click');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="default-btn" :disabled="props.disabled" @tap="click">
|
||||||
|
<slot />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.default-btn {
|
||||||
|
color: #fff;
|
||||||
|
border-width: 4rpx;
|
||||||
|
border-color: #bfdbfe;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
background-color: #60a5fa;
|
||||||
|
padding: 12rpx 26rpx;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 24rpx;
|
||||||
|
&:hover {
|
||||||
|
background-color: #3b82f6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
4
src/components/BasicButton/prpos.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const buttonProps = {
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
|
click: { type: Function },
|
||||||
|
};
|
||||||
31
src/components/BasicInput/index.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BasicInput',
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'text',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(_props) {
|
||||||
|
// const _props = props;
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<input :type="type" :name="name" :value="value">
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
26
src/components/Iconify/index.vue
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { IconProps } from '@iconify/vue';
|
||||||
|
/**
|
||||||
|
* @name Iconify
|
||||||
|
* @desc 图标
|
||||||
|
* @docs https://iconify.design/docs/icon-components/vue/
|
||||||
|
* @example <Iconify icon="ant-design:home-outlined" />
|
||||||
|
*/
|
||||||
|
import { Icon } from '@iconify/vue';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
icon: string
|
||||||
|
size?: string | number
|
||||||
|
width?: IconProps['width']
|
||||||
|
height?: IconProps['height']
|
||||||
|
color?: IconProps['color']
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Icon v-bind="props" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
16
src/components/Test/index.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps({
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: 'text',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = `TEXT: ${props.text}`;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>{{ text }}</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
0
src/enums/appEnum.ts
Normal file
5
src/enums/cacheEnum.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// token key
|
||||||
|
export const TOKEN_KEY = 'TOKEN__';
|
||||||
|
|
||||||
|
// user info key
|
||||||
|
export const USER_INFO_KEY = 'USER__INFO__';
|
||||||
22
src/enums/httpEnum.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* @description: 请求结果设置
|
||||||
|
*/
|
||||||
|
export enum ResultEnum {
|
||||||
|
SUCCESS = 10000,
|
||||||
|
FAIL = 10001,
|
||||||
|
ERROR = 1,
|
||||||
|
TIMEOUT = 401,
|
||||||
|
TYPE = 'success',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: contentType
|
||||||
|
*/
|
||||||
|
export enum ContentTypeEnum {
|
||||||
|
// json
|
||||||
|
JSON = 'application/json;charset=UTF-8',
|
||||||
|
// form-data qs
|
||||||
|
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||||
|
// form-data upload
|
||||||
|
FORM_DATA = 'multipart/form-data;charset=UTF-8',
|
||||||
|
}
|
||||||
15
src/hooks/app/useGlobalStyle.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import pagesJson from '@/pages.json';
|
||||||
|
|
||||||
|
const { globalStyle } = pagesJson;
|
||||||
|
/**
|
||||||
|
* 全局样式
|
||||||
|
*/
|
||||||
|
export function useGlobalStyle() {
|
||||||
|
const { navigationBarTextStyle, navigationBarTitleText, navigationBarBackgroundColor, backgroundColor } = globalStyle;
|
||||||
|
return {
|
||||||
|
navigationBarTextStyle,
|
||||||
|
navigationBarTitleText,
|
||||||
|
navigationBarBackgroundColor,
|
||||||
|
backgroundColor,
|
||||||
|
};
|
||||||
|
}
|
||||||
11
src/hooks/app/useSystem.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* 系统信息
|
||||||
|
* @description 除去uni官方不推荐使用的返回参数
|
||||||
|
* @link https://uniapp.dcloud.net.cn/api/system/info.html
|
||||||
|
*/
|
||||||
|
export function useSystem() {
|
||||||
|
const systemInfo = reactive<UniNamespace.GetSystemInfoResult>(uni.getSystemInfoSync());
|
||||||
|
return {
|
||||||
|
systemInfo,
|
||||||
|
};
|
||||||
|
}
|
||||||
17
src/main.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { createSSRApp } from 'vue';
|
||||||
|
import App from './App.vue';
|
||||||
|
import 'virtual:uno.css';
|
||||||
|
import { setupRouter } from './router';
|
||||||
|
import { setupStore } from './stores';
|
||||||
|
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App);
|
||||||
|
|
||||||
|
setupStore(app);
|
||||||
|
|
||||||
|
setupRouter(app);
|
||||||
|
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
};
|
||||||
|
}
|
||||||
116
src/manifest.json
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"name": "uniapp_vue3_vite_ts",
|
||||||
|
"appid": "__UNI__4350F4E",
|
||||||
|
"description": "",
|
||||||
|
"versionName": "1.2.1",
|
||||||
|
"versionCode": "100",
|
||||||
|
"transformPx": false,
|
||||||
|
/* 5+App特有相关 */
|
||||||
|
"app-plus": {
|
||||||
|
"usingComponents": true,
|
||||||
|
"nvueStyleCompiler": "uni-app",
|
||||||
|
"compilerVersion": 3,
|
||||||
|
"splashscreen": {
|
||||||
|
"alwaysShowBeforeRender": true,
|
||||||
|
"waiting": true,
|
||||||
|
"autoclose": true,
|
||||||
|
"delay": 0
|
||||||
|
},
|
||||||
|
/* 模块配置 */
|
||||||
|
"modules": {},
|
||||||
|
"optimization": {
|
||||||
|
"subPackages": true
|
||||||
|
},
|
||||||
|
/* 应用发布信息 */
|
||||||
|
"distribute": {
|
||||||
|
/* android打包配置 */
|
||||||
|
"android": {
|
||||||
|
"permissions": [
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||||
|
],
|
||||||
|
"minSdkVersion": 21
|
||||||
|
},
|
||||||
|
/* ios打包配置 */
|
||||||
|
"ios": {
|
||||||
|
"dSYMs": false
|
||||||
|
},
|
||||||
|
/* SDK配置 */
|
||||||
|
"sdkConfigs": {
|
||||||
|
"ad": {}
|
||||||
|
},
|
||||||
|
"splashscreen": {
|
||||||
|
"useOriginalMsgbox": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* 快应用特有相关 */
|
||||||
|
"quickapp": {},
|
||||||
|
/* 小程序特有相关 */
|
||||||
|
"mp-weixin": {
|
||||||
|
"appid": "wxc8e4923d551cd4a4",
|
||||||
|
"setting": {
|
||||||
|
"urlCheck": false,
|
||||||
|
"es6": true,
|
||||||
|
"postcss": true,
|
||||||
|
"minified": true
|
||||||
|
},
|
||||||
|
"usingComponents": true,
|
||||||
|
"permission": {},
|
||||||
|
"optimization": {
|
||||||
|
"subPackages": true
|
||||||
|
},
|
||||||
|
"lazyCodeLoading": "requiredComponents",
|
||||||
|
"style": "v2"
|
||||||
|
},
|
||||||
|
"mp-alipay": {
|
||||||
|
"usingComponents": true,
|
||||||
|
"optimization": {
|
||||||
|
"subPackages": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mp-baidu": {
|
||||||
|
"usingComponents": true
|
||||||
|
},
|
||||||
|
"mp-toutiao": {
|
||||||
|
"usingComponents": true
|
||||||
|
},
|
||||||
|
"uniStatistics": {
|
||||||
|
"enable": false
|
||||||
|
},
|
||||||
|
"vueVersion": "3",
|
||||||
|
"h5": {
|
||||||
|
"router": {
|
||||||
|
"mode": "hash",
|
||||||
|
"base": "./"
|
||||||
|
},
|
||||||
|
"devServer": {
|
||||||
|
"https": false
|
||||||
|
},
|
||||||
|
"title": "uniapp_vue3_vite_ts",
|
||||||
|
"unipush": {
|
||||||
|
"enable": false
|
||||||
|
},
|
||||||
|
"sdkConfigs": {
|
||||||
|
"maps": {}
|
||||||
|
},
|
||||||
|
"optimization": {
|
||||||
|
"treeShaking": {
|
||||||
|
"enable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/mock/index.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { createAlovaMockAdapter } from '@alova/mock';
|
||||||
|
import { uniappMockResponse, uniappRequestAdapter } from '@alova/adapter-uniapp';
|
||||||
|
import { mockGroupV1 } from '@/mock/v1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟数据请求适配器
|
||||||
|
* @link https://alova.js.org/zh-CN/extension/alova-mock
|
||||||
|
*/
|
||||||
|
export const mockAdapter = createAlovaMockAdapter(mockGroupV1, {
|
||||||
|
// 指定uniapp请求适配器后,未匹配模拟接口的请求将使用这个适配器发送请求
|
||||||
|
httpAdapter: uniappRequestAdapter,
|
||||||
|
|
||||||
|
// mock接口响应延迟,单位毫秒
|
||||||
|
delay: 1500,
|
||||||
|
|
||||||
|
// 是否打印mock接口请求信息
|
||||||
|
// mockRequestLogger: false,
|
||||||
|
|
||||||
|
// 模拟响应适配器,指定后响应数据将转换为uniapp兼容的数据格式
|
||||||
|
onMockResponse: uniappMockResponse,
|
||||||
|
});
|
||||||
22
src/mock/utils.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
|
import type { API } from '@/services/model/baseModel';
|
||||||
|
|
||||||
|
interface MockResponseOptions<T = any> {
|
||||||
|
status: number
|
||||||
|
statusText: string
|
||||||
|
responseHeaders: Record<string, any>
|
||||||
|
body: API<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createMock(options: Partial<API>): MockResponseOptions {
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
statusText: 'OK',
|
||||||
|
responseHeaders: {},
|
||||||
|
body: {
|
||||||
|
code: ResultEnum.SUCCESS,
|
||||||
|
message: 'succeed',
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
3
src/mock/v1/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { authMocks } from '@/mock/v1/modules/auth';
|
||||||
|
|
||||||
|
export const mockGroupV1 = [authMocks];
|
||||||
40
src/mock/v1/modules/auth.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { defineMock } from '@alova/mock';
|
||||||
|
import { join, random, sampleSize } from 'lodash-es';
|
||||||
|
import multiavatar from '@multiavatar/multiavatar';
|
||||||
|
import { createMock } from '@/mock/utils';
|
||||||
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
|
import { getRandomChsString } from '@/utils/character';
|
||||||
|
|
||||||
|
function createRandomToken(len = 36 * 6) {
|
||||||
|
const token = join(sampleSize('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-', len), '');
|
||||||
|
return `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const authMocks = defineMock({
|
||||||
|
// 登录
|
||||||
|
'[POST]/api/login': (params) => {
|
||||||
|
const { email, password } = params.data;
|
||||||
|
if (email === 'uni-app@test.com' && (password === 'Vue3_Ts_Vite' || password === '123456')) {
|
||||||
|
const token = createRandomToken();
|
||||||
|
return createMock({ data: { token } });
|
||||||
|
}
|
||||||
|
return createMock({ data: [], code: ResultEnum.FAIL, message: '邮箱或密码错误' });
|
||||||
|
},
|
||||||
|
// 获取用户信息
|
||||||
|
'[GET]/api/users': () => {
|
||||||
|
const generateNicknames = getRandomChsString(random(2, 6));
|
||||||
|
const svgCode = multiavatar(generateNicknames);
|
||||||
|
const base64SVG = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
|
||||||
|
svgCode,
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
return createMock({
|
||||||
|
data: {
|
||||||
|
id: 1,
|
||||||
|
nickname: generateNicknames,
|
||||||
|
avatar: base64SVG,
|
||||||
|
email: 'uni-app@test.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
89
src/pages.json
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"name": "Home",
|
||||||
|
"path": "pages/index/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "Home"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"tabBar": true,
|
||||||
|
"ignoreAuth": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Demo",
|
||||||
|
"path": "pages/demo/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "Demo"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"tabBar": true,
|
||||||
|
"ignoreAuth": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "About",
|
||||||
|
"path": "pages/about/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "关于"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"ignoreAuth": true,
|
||||||
|
"tabBar": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Login",
|
||||||
|
"path": "pages/login/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "登录"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"ignoreAuth": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Log",
|
||||||
|
"path": "pages/log/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "日志"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subPackages": [],
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "uni-app",
|
||||||
|
"navigationBarBackgroundColor": "#F2F2F2",
|
||||||
|
"backgroundColor": "#F2F2F2",
|
||||||
|
"navigationStyle": "default",
|
||||||
|
"renderingMode": "seperated",
|
||||||
|
"pageOrientation": "portrait"
|
||||||
|
},
|
||||||
|
"tabBar": {
|
||||||
|
"color": "#474747",
|
||||||
|
"selectedColor": "#9BC6FC",
|
||||||
|
"backgroundColor": "#F2F2F2",
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"pagePath": "pages/index/index",
|
||||||
|
"text": "首页",
|
||||||
|
"iconPath": "static/images/tabBar/home.png",
|
||||||
|
"selectedIconPath": "static/images/tabBar/selectedHome.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/demo/index",
|
||||||
|
"text": "Demo",
|
||||||
|
"iconPath": "static/images/tabBar/demo.png",
|
||||||
|
"selectedIconPath": "static/images/tabBar/selectedDemo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/about/index",
|
||||||
|
"text": "关于",
|
||||||
|
"iconPath": "static/images/tabBar/about.png",
|
||||||
|
"selectedIconPath": "static/images/tabBar/selectedAbout.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/pages/about/index.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import BasicButton from '@/components/BasicButton/index.vue';
|
||||||
|
import { useUserStore } from '@/stores/modules/user';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const { loggedIn, userInfo } = storeToRefs(userStore);
|
||||||
|
|
||||||
|
function handleJump(url: string) {
|
||||||
|
router.push(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登出
|
||||||
|
function handleLoginOut() {
|
||||||
|
userStore.logout();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="text-md pt-36">
|
||||||
|
<view v-if="loggedIn" class="text-center">
|
||||||
|
<image class="h-56 w-56" :src="userInfo?.avatar" />
|
||||||
|
<view class="mt-2">
|
||||||
|
{{ userInfo?.nickname }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mt-6 flex flex-col gap-y-xl justify-center items-center">
|
||||||
|
<BasicButton @click="handleJump('/pages/log/index?id=4345&title=log&word=关键词')">
|
||||||
|
log
|
||||||
|
</BasicButton>
|
||||||
|
<BasicButton v-if="loggedIn" @click="handleLoginOut">
|
||||||
|
登出
|
||||||
|
</BasicButton>
|
||||||
|
<BasicButton v-else @click="handleJump('/pages/login/index')">
|
||||||
|
登入
|
||||||
|
</BasicButton>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
||||||
12
src/pages/demo/index.vue
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
const demo = ref('Demo');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="pt-36 text-lg font-medium flex justify-center items-center">
|
||||||
|
{{ demo }}
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
||||||
20
src/pages/index/index.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { getRandomIcon } from '@/utils/character';
|
||||||
|
import { platform } from '@/utils/platform';
|
||||||
|
|
||||||
|
const logo = getRandomIcon();
|
||||||
|
const appTitle = 'uniapp-vue3';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="pt-36 flex flex-col gap-y-2 items-center">
|
||||||
|
<image :src="logo" class="h-56 w-56" alt="" mode="widthFix" />
|
||||||
|
<view class="text-xl font-semibold">
|
||||||
|
{{ appTitle }}
|
||||||
|
</view>
|
||||||
|
<view>当前平台:{{ platform }}</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
</style>
|
||||||
20
src/pages/log/index.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useUserStore } from '@/stores/modules/user';
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
// onLoad((query) => {
|
||||||
|
// console.log('log onLoad query', query);
|
||||||
|
// });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="text-center">
|
||||||
|
<view class="mt-36 text-center">
|
||||||
|
登录后访问log
|
||||||
|
</view>
|
||||||
|
<image class="my-4 h-48 w-48" :src="userStore.userInfo?.avatar" mode="aspectFit" lazy-load="false" binderror="" bindload="" />
|
||||||
|
<view>{{ userStore.userInfo?.nickname }}</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
109
src/pages/login/index.vue
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { omit } from 'lodash-es';
|
||||||
|
import { useUserStore } from '@/stores/modules/user';
|
||||||
|
import { Toast } from '@/utils/uniapi/prompt';
|
||||||
|
|
||||||
|
const pageQuery = ref<Record<string, any> | undefined>(undefined);
|
||||||
|
onLoad((query) => {
|
||||||
|
pageQuery.value = query;
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
email: 'uni-app@test.com',
|
||||||
|
password: 'Vue3_Ts_Vite',
|
||||||
|
});
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
function submit(e: any) {
|
||||||
|
userStore.login(e.detail.value).then(() => {
|
||||||
|
Toast('登录成功', { duration: 1500 });
|
||||||
|
setTimeout(() => {
|
||||||
|
if (unref(pageQuery)?.redirect) {
|
||||||
|
// 如果有存在redirect(重定向)参数,登录成功后直接跳转
|
||||||
|
const params = omit(unref(pageQuery), ['redirect', 'tabBar']);
|
||||||
|
if (unref(pageQuery)) {
|
||||||
|
// 这里replace方法无法跳转tabbar页面故改为replaceAll
|
||||||
|
unref(pageQuery)?.tabBar === 'true'
|
||||||
|
? router.replaceAll({ name: unref(pageQuery)?.redirect, params })
|
||||||
|
: router.replace({ name: unref(pageQuery)?.redirect, params });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 不存在则返回上一页
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<view class="title">
|
||||||
|
登录
|
||||||
|
</view>
|
||||||
|
<view class="form-wrap">
|
||||||
|
<form class="form" @submit="submit">
|
||||||
|
<label class="form-item">
|
||||||
|
<view class="form-label">邮箱:</view>
|
||||||
|
<view class="form-element"><input name="email" :value="form.email"></view>
|
||||||
|
</label>
|
||||||
|
<label class="form-item">
|
||||||
|
<view class="form-label">密码:</view>
|
||||||
|
<view class="form-element"><input type="password" name="password" :value="form.password"></view>
|
||||||
|
</label>
|
||||||
|
<button form-type="submit" class="submit-btn" hover-class="none">
|
||||||
|
登录
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 80%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
padding: 320rpx 0 32rpx 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-wrap {
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
box-shadow: 16rpx 16rpx 30rpx #e5e7eb;
|
||||||
|
|
||||||
|
.form {
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
height: 88rpx;
|
||||||
|
border-bottom: 2rpx solid #dbeafe;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
min-width: 96rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-element {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
margin-top: 44rpx;
|
||||||
|
border: 4rpx solid #bfdbfe;
|
||||||
|
background-color: #60a5fa;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
background-color: #3b82f6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
33
src/pages/notFound/404.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
|
import BasicButton from '@/components/BasicButton/index.vue';
|
||||||
|
|
||||||
|
const go = ref<string>('');
|
||||||
|
const router = useRouter();
|
||||||
|
const redirect = ref<string>('');
|
||||||
|
onLoad((query) => {
|
||||||
|
go.value = query?.go || '';
|
||||||
|
redirect.value = query?.redirect || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回首页
|
||||||
|
*/
|
||||||
|
function backHome() {
|
||||||
|
router.pushTab(redirect.value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="pt-320rpx flex flex-col w-screen items-center">
|
||||||
|
<image class="w-360rpx" mode="widthFix" src="/static/svg/weep.svg" />
|
||||||
|
<view class="mb-40rpx">
|
||||||
|
<text>{{ go }} 页面找不到了~</text>
|
||||||
|
</view>
|
||||||
|
<BasicButton @click="backHome">
|
||||||
|
返回首页
|
||||||
|
</BasicButton>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
7
src/pages/template/index.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts" setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view> 页面模板,新建pages,将此页面内容复制粘贴到新建.vue文件 </view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
21
src/pagesA/list/test1/index.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import BasicButton from '@/components/BasicButton/index.vue';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
function jumpTest2() {
|
||||||
|
router.push('/pagesA/list/test2/index?id=256');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="center">
|
||||||
|
Test1
|
||||||
|
</view>
|
||||||
|
<view class="center">
|
||||||
|
<BasicButton @click="jumpTest2">
|
||||||
|
Test2 →
|
||||||
|
</BasicButton>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
19
src/pagesA/list/test2/index.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import BasicButton from '@/components/BasicButton/index.vue';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
function jumpDetail() {
|
||||||
|
router.push('/pagesB/detail/index?page=1&limit=20');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view> Test2 </view>
|
||||||
|
<BasicButton @click="jumpDetail">
|
||||||
|
Detail →
|
||||||
|
</BasicButton>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
7
src/pagesB/detail/index.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts" setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
Detail
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
44
src/router/guard.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import type { Router } from 'uni-mini-router/lib/interfaces';
|
||||||
|
import { isLogin } from '@/utils/auth';
|
||||||
|
|
||||||
|
export function createRouterGuard(router: Router) {
|
||||||
|
createBeforeEachGuard(router);
|
||||||
|
createAfterEachGuard(router);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBeforeEachGuard(router: Router) {
|
||||||
|
router.beforeEach((to, _, next) => {
|
||||||
|
console.log('beforeEach', to);
|
||||||
|
const _isLogin = isLogin();
|
||||||
|
if (to && to?.meta?.ignoreAuth) {
|
||||||
|
// 如果目标路由忽略验证直接跳转
|
||||||
|
next();
|
||||||
|
} else if (!_isLogin && to && to.name !== 'Login') {
|
||||||
|
// 如果没有登录且目标路由不是登录页面则跳转到登录页面
|
||||||
|
// 将目标路由和参数传入登录页面,登录成功后直接跳转到目标路由,优化体验
|
||||||
|
next({ name: 'Login', params: { redirect: to.name!, ...to.query }, navType: 'push' });
|
||||||
|
} else if (_isLogin && to && to.name === 'Login') {
|
||||||
|
// 如果已经登录且目标页面是登录页面则跳转至首页
|
||||||
|
next({ name: 'Home', navType: 'replaceAll' });
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAfterEachGuard(router: Router) {
|
||||||
|
router.afterEach((to) => {
|
||||||
|
if (to && to?.meta?.ignoreAuth)
|
||||||
|
return;
|
||||||
|
const _isLogin = isLogin();
|
||||||
|
if (!_isLogin && to && to.name !== 'Login') {
|
||||||
|
// 如果没有登录且目标路由不是登录页面则跳转到登录页面
|
||||||
|
router.push({ name: 'Login', params: { ...to.query } });
|
||||||
|
} else if (_isLogin && to && to.name === 'Login') {
|
||||||
|
// 如果已经登录且目标页面是登录页面则跳转至首页
|
||||||
|
router.replaceAll({ name: 'Home' });
|
||||||
|
}
|
||||||
|
console.log('afterEach', to);
|
||||||
|
});
|
||||||
|
}
|
||||||
19
src/router/index.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* router
|
||||||
|
* @see https://gitee.com/fant-mini/uni-mini-router
|
||||||
|
*/
|
||||||
|
import { createRouter } from 'uni-mini-router';
|
||||||
|
import type { App } from 'vue';
|
||||||
|
import { createRouterGuard } from './guard';
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
routes: [...ROUTES], // 路由表信息
|
||||||
|
});
|
||||||
|
|
||||||
|
export function setupRouter(app: App<Element>) {
|
||||||
|
// Configure router guard
|
||||||
|
createRouterGuard(router);
|
||||||
|
app.use(router);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { router };
|
||||||
31
src/services/api/auth.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { request } from '@/utils/http';
|
||||||
|
|
||||||
|
const LOGIN = '/login';
|
||||||
|
const LOGIN_OUT = '/logout';
|
||||||
|
const REFRESH_TOKEN = '/refresh/token';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export function login(params: LoginParams) {
|
||||||
|
return request.Post<LoginModel>(LOGIN, params, {
|
||||||
|
meta: {
|
||||||
|
ignoreAuth: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登出
|
||||||
|
*/
|
||||||
|
export function logout() {
|
||||||
|
return request.Post(LOGIN_OUT, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新token
|
||||||
|
*/
|
||||||
|
export function refreshToken() {
|
||||||
|
return request.Post<LoginModel>(REFRESH_TOKEN, {});
|
||||||
|
}
|
||||||
13
src/services/api/user.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { request } from '@/utils/http';
|
||||||
|
import type { UserInfoModel } from '@/services/model/userModel';
|
||||||
|
|
||||||
|
enum Api {
|
||||||
|
GET_USER_INFO = '/users',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息
|
||||||
|
*/
|
||||||
|
export function getUserInfoApi() {
|
||||||
|
return request.Get<UserInfoModel>(Api.GET_USER_INFO);
|
||||||
|
}
|
||||||
7
src/services/model/authModel.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
declare interface LoginParams {
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
declare interface LoginModel {
|
||||||
|
token: string
|
||||||
|
}
|
||||||
7
src/services/model/baseModel.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { ResultEnum } from '@/enums/httpEnum';
|
||||||
|
|
||||||
|
declare interface API<T = any> {
|
||||||
|
code: ResultEnum
|
||||||
|
data?: T
|
||||||
|
message: string
|
||||||
|
}
|
||||||
18
src/services/model/userModel.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export interface UserInfoModel {
|
||||||
|
/**
|
||||||
|
* 用户id
|
||||||
|
*/
|
||||||
|
id?: number
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
nickname?: string
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
avatar?: string
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
email?: string
|
||||||
|
}
|
||||||
15
src/settings/encryptionSetting.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { getEnvValue, getPkgVersion, isDevMode } from '@/utils/env';
|
||||||
|
|
||||||
|
// System default cache time, in seconds
|
||||||
|
export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
|
||||||
|
const PREFIX = getEnvValue<string>('VITE_APP_CACHE_PREFIX') || getEnvValue<string>('VITE_APP_TITLE') || 'UNI_APP_VUE3_TS';
|
||||||
|
export const DEFAULT_PREFIX_KEY = `${PREFIX}${getPkgVersion()}`;
|
||||||
|
|
||||||
|
// aes encryption key
|
||||||
|
export const cacheCipher = {
|
||||||
|
key: 'aQ0{gD1@c_0@oH5:',
|
||||||
|
iv: 'aF0#gC_$hE1$eA1!',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Whether the system cache is encrypted using aes
|
||||||
|
export const enableStorageEncryption = !isDevMode();
|
||||||
BIN
src/static/images/avatar.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/static/images/h5Qcode.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
src/static/images/tabBar/about.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/static/images/tabBar/category.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/static/images/tabBar/demo.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
src/static/images/tabBar/home.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/static/images/tabBar/selectedAbout.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/static/images/tabBar/selectedCategory.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/static/images/tabBar/selectedDemo.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
src/static/images/tabBar/selectedHome.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/static/images/wexinQcode.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
src/static/logo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
1
src/static/svg/404.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1645602826416" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3227" data-spm-anchor-id="a313x.7781069.0.i21" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M260.266667 789.333333c-21.333333 0-38.4-17.066667-38.4-38.4v-59.733333H38.4c-12.8 0-29.866667-8.533333-34.133333-21.333333-4.266667-17.066667-4.266667-29.866667 4.266666-42.666667l221.866667-294.4c8.533333-12.8 25.6-17.066667 42.666667-12.8 17.066667 4.266667 25.6 21.333333 25.6 38.4v256h34.133333c21.333333 0 38.4 17.066667 38.4 38.4s-17.066667 38.4-38.4 38.4H298.666667v59.733333c0 21.333333-17.066667 38.4-38.4 38.4z m-145.066667-179.2h106.666667V469.333333l-106.666667 140.8zM913.066667 742.4c-21.333333 0-38.4-17.066667-38.4-38.4v-59.733333h-183.466667c-12.8 0-29.866667-8.533333-34.133333-21.333334-8.533333-12.8-4.266667-29.866667 4.266666-38.4l221.866667-294.4c8.533333-12.8 25.6-17.066667 42.666667-12.8 17.066667 4.266667 25.6 21.333333 25.6 38.4v256h34.133333c21.333333 0 38.4 17.066667 38.4 38.4s-17.066667 38.4-38.4 38.4h-34.133333v59.733334c0 17.066667-17.066667 34.133333-38.4 34.133333zM768 567.466667h106.666667V426.666667L768 567.466667zM533.333333 597.333333c-46.933333 0-85.333333-25.6-119.466666-68.266666-29.866667-38.4-42.666667-93.866667-42.666667-145.066667 0-55.466667 17.066667-106.666667 42.666667-145.066667 29.866667-42.666667 72.533333-68.266667 119.466666-68.266666 46.933333 0 85.333333 25.6 119.466667 68.266666 29.866667 38.4 42.666667 93.866667 42.666667 145.066667 0 55.466667-17.066667 106.666667-42.666667 145.066667-34.133333 46.933333-76.8 68.266667-119.466667 68.266666z m0-362.666666c-55.466667 0-98.133333 68.266667-98.133333 149.333333s46.933333 149.333333 98.133333 149.333333c55.466667 0 98.133333-68.266667 98.133334-149.333333s-46.933333-149.333333-98.133334-149.333333z" fill="#5e7987" p-id="3228" data-spm-anchor-id="a313x.7781069.0.i16" class=""></path><path d="M354.133333 691.2a162.133333 21.333333 0 1 0 324.266667 0 162.133333 21.333333 0 1 0-324.266667 0Z" fill="#d0dfe6" p-id="3229" data-spm-anchor-id="a313x.7781069.0.i20" class=""></path><path d="M8.533333 832a162.133333 21.333333 0 1 0 324.266667 0 162.133333 21.333333 0 1 0-324.266667 0Z" fill="#d0dfe6" p-id="3230" data-spm-anchor-id="a313x.7781069.0.i22" class=""></path><path d="M661.333333 797.866667a162.133333 21.333333 0 1 0 324.266667 0 162.133333 21.333333 0 1 0-324.266667 0Z" fill="#d0dfe6" p-id="3231" data-spm-anchor-id="a313x.7781069.0.i19" class=""></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
1
src/static/svg/LOGO.svg
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
1
src/static/svg/weep.svg
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
12
src/stores/app.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
interface AppState {
|
||||||
|
sys?: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppStore = defineStore({
|
||||||
|
id: 'app-store',
|
||||||
|
state: (): AppState => ({}),
|
||||||
|
getters: {},
|
||||||
|
actions: {},
|
||||||
|
});
|
||||||
10
src/stores/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { App } from 'vue';
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
|
|
||||||
|
const store = createPinia();
|
||||||
|
|
||||||
|
export function setupStore(app: App<Element>) {
|
||||||
|
app.use(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { store };
|
||||||
69
src/stores/modules/user.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { useRequest } from 'alova';
|
||||||
|
import { getUserInfoApi } from '@/services/api/user';
|
||||||
|
import type { UserInfoModel } from '@/services/model/userModel';
|
||||||
|
import { login as loginApi } from '@/services/api/auth';
|
||||||
|
import { getToken, isLogin, setToken } from '@/utils/auth';
|
||||||
|
import { removeCache } from '@/utils/cache';
|
||||||
|
import { TOKEN_KEY } from '@/enums/cacheEnum';
|
||||||
|
|
||||||
|
export const useUserStore = defineStore('UserStore', () => {
|
||||||
|
const token = ref<string | null>(null);
|
||||||
|
const userInfo = ref<UserInfoModel | null>(null);
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
function initUserInfo() {
|
||||||
|
if (isLogin()) {
|
||||||
|
token.value = getToken();
|
||||||
|
getUserInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否登录
|
||||||
|
const loggedIn = computed(() => !!token.value);
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
const { send: sendLogin } = useRequest(loginApi, { immediate: false });
|
||||||
|
async function login(params: LoginParams) {
|
||||||
|
try {
|
||||||
|
const res = await sendLogin(params);
|
||||||
|
token.value = res.token;
|
||||||
|
setToken(res.token);
|
||||||
|
await getUserInfo();
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
const { send: _getUserInfo } = useRequest(getUserInfoApi, { initialData: null, immediate: false });
|
||||||
|
async function getUserInfo() {
|
||||||
|
try {
|
||||||
|
userInfo.value = await _getUserInfo();
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登出
|
||||||
|
// const { send: sendLogout } = useRequest(logoutApi, { immediate: false });
|
||||||
|
async function logout() {
|
||||||
|
try {
|
||||||
|
// await sendLogout();
|
||||||
|
removeCache(TOKEN_KEY);
|
||||||
|
userInfo.value = null;
|
||||||
|
token.value = null;
|
||||||
|
} catch (err: any) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
userInfo,
|
||||||
|
loggedIn,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
getUserInfo,
|
||||||
|
initUserInfo,
|
||||||
|
};
|
||||||
|
});
|
||||||
0
src/styles/main.css
Normal file
3
src/types.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// type.d.ts
|
||||||
|
declare const ROUTES: [];
|
||||||
|
declare const PLATFORM: string;
|
||||||
24
src/types/env.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// / <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue';
|
||||||
|
|
||||||
|
const component: DefineComponent<NonNullable<unknown>, NonNullable<unknown>, any>;
|
||||||
|
export default component;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_ENV: string
|
||||||
|
readonly VITE_APP_TITLE: string
|
||||||
|
readonly VITE_BASE_URL: string
|
||||||
|
readonly VITE_UPLOAD_URL: string
|
||||||
|
readonly VITE_APP_CACHE_PREFIX: string
|
||||||
|
readonly VITE_PORT: number
|
||||||
|
readonly VITE_USE_MOCK: string | boolean
|
||||||
|
readonly VITE_PROXY_PREFIX: string
|
||||||
|
readonly VITE_UPLOAD_PROXY_PREFIX: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv
|
||||||
|
}
|
||||||
11
src/types/uni-mini-router.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import 'uni-mini-router';
|
||||||
|
|
||||||
|
declare module 'uni-mini-router' {
|
||||||
|
|
||||||
|
interface Route {
|
||||||
|
meta?: {
|
||||||
|
ignoreAuth?: boolean
|
||||||
|
tabBar?: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/types/unplugin-transform-class-vite.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module 'unplugin-transform-class/vite' {
|
||||||
|
const transformClassVitePlugin: any;
|
||||||
|
export default transformClassVitePlugin;
|
||||||
|
}
|
||||||
47
src/uni.scss
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
view,
|
||||||
|
scroll-view,
|
||||||
|
swiper,
|
||||||
|
match-media,
|
||||||
|
movable-area,
|
||||||
|
movable-view,
|
||||||
|
cover-view,
|
||||||
|
cover-image,
|
||||||
|
icon,
|
||||||
|
text,
|
||||||
|
rich-text,
|
||||||
|
progress,
|
||||||
|
button,
|
||||||
|
checkboxe,
|
||||||
|
ditor,
|
||||||
|
form,
|
||||||
|
input,
|
||||||
|
label,
|
||||||
|
picker,
|
||||||
|
picker-view,
|
||||||
|
radio,
|
||||||
|
slider,
|
||||||
|
switch,
|
||||||
|
textarea,
|
||||||
|
navigator,
|
||||||
|
audio,
|
||||||
|
camera,
|
||||||
|
image,
|
||||||
|
video,
|
||||||
|
live-player,
|
||||||
|
live-pusher,
|
||||||
|
map,
|
||||||
|
canvas,
|
||||||
|
web-view,
|
||||||
|
:before,
|
||||||
|
:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏scroll-view的滚动条 */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
width: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
26
src/utils/auth.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { getCache, setCache } from '@/utils/cache';
|
||||||
|
import { TOKEN_KEY } from '@/enums/cacheEnum';
|
||||||
|
|
||||||
|
const authenticationScheme = 'Bearer';
|
||||||
|
|
||||||
|
export function getToken() {
|
||||||
|
return getCache<string>(TOKEN_KEY) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAuthorization() {
|
||||||
|
const token = getToken();
|
||||||
|
return token ? `${authenticationScheme} ${token}` : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setToken(token: string) {
|
||||||
|
return setCache(TOKEN_KEY, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeToken() {
|
||||||
|
return setCache(TOKEN_KEY, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否登录
|
||||||
|
export function isLogin() {
|
||||||
|
return !!getToken();
|
||||||
|
}
|
||||||
29
src/utils/cache/index.ts
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import type { CreateStorageParams } from './storageCache';
|
||||||
|
import { createStorage } from './storageCache';
|
||||||
|
import { DEFAULT_CACHE_TIME, DEFAULT_PREFIX_KEY, cacheCipher, enableStorageEncryption } from '@/settings/encryptionSetting';
|
||||||
|
|
||||||
|
const options: Partial<CreateStorageParams> = {
|
||||||
|
prefixKey: DEFAULT_PREFIX_KEY,
|
||||||
|
key: cacheCipher.key,
|
||||||
|
iv: cacheCipher.iv,
|
||||||
|
hasEncrypt: enableStorageEncryption,
|
||||||
|
timeout: DEFAULT_CACHE_TIME,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const storage = createStorage(options);
|
||||||
|
|
||||||
|
export function setCache(key: string, value: any, expire?: number | null): void {
|
||||||
|
storage.set(key, value, expire);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCache<T = any>(key: string): T {
|
||||||
|
return storage.get<T>(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeCache(key: string): void {
|
||||||
|
return storage.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearCache(): void {
|
||||||
|
return storage.clear();
|
||||||
|
}
|
||||||
110
src/utils/cache/storageCache.ts
vendored
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { cacheCipher } from '@/settings/encryptionSetting';
|
||||||
|
import type { EncryptionParams } from '@/utils/cipher';
|
||||||
|
import { AesEncryption } from '@/utils/cipher';
|
||||||
|
import { isNullOrUnDef } from '@/utils/is';
|
||||||
|
|
||||||
|
export interface CreateStorageParams extends EncryptionParams {
|
||||||
|
prefixKey: string
|
||||||
|
hasEncrypt: boolean
|
||||||
|
timeout?: number | null
|
||||||
|
}
|
||||||
|
export function createStorage({
|
||||||
|
prefixKey = '',
|
||||||
|
key = cacheCipher.key,
|
||||||
|
iv = cacheCipher.iv,
|
||||||
|
timeout = null,
|
||||||
|
hasEncrypt = true,
|
||||||
|
}: Partial<CreateStorageParams> = {}) {
|
||||||
|
if (hasEncrypt && [key.length, iv.length].some(item => item !== 16)) {
|
||||||
|
throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryption = new AesEncryption({ key, iv });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache class
|
||||||
|
* Construction parameters can be passed into sessionStorage, localStorage,
|
||||||
|
* @class Cache
|
||||||
|
* @example
|
||||||
|
*/
|
||||||
|
class Storage {
|
||||||
|
private prefixKey?: string;
|
||||||
|
|
||||||
|
private encryption: AesEncryption;
|
||||||
|
|
||||||
|
private hasEncrypt: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.prefixKey = prefixKey;
|
||||||
|
this.encryption = encryption;
|
||||||
|
this.hasEncrypt = hasEncrypt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getKey(key: string) {
|
||||||
|
return `${this.prefixKey}${key}`.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set cache
|
||||||
|
* @param {string} key
|
||||||
|
* @param {*} value
|
||||||
|
* @param {*} expire Expiration time in seconds
|
||||||
|
* @memberof Cache
|
||||||
|
*/
|
||||||
|
set(key: string, value: any, expire: number | null = timeout) {
|
||||||
|
try {
|
||||||
|
const stringData = JSON.stringify({
|
||||||
|
value,
|
||||||
|
time: Date.now(),
|
||||||
|
expire: !isNullOrUnDef(expire) ? new Date().getTime() + expire * 1000 : null,
|
||||||
|
});
|
||||||
|
const stringifyValue = this.hasEncrypt ? this.encryption.encryptByAES(stringData) : stringData;
|
||||||
|
uni.setStorageSync(this.getKey(key), stringifyValue);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`setStorageSync error: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read cache
|
||||||
|
* @param {string} key
|
||||||
|
* @param {*} def
|
||||||
|
* @memberof Cache
|
||||||
|
*/
|
||||||
|
get<T = any>(key: string, def: any = null): T {
|
||||||
|
const val = uni.getStorageSync(this.getKey(key));
|
||||||
|
if (!val)
|
||||||
|
return def;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decVal = this.hasEncrypt ? this.encryption.decryptByAES(val) : val;
|
||||||
|
const data = JSON.parse(decVal);
|
||||||
|
const { value, expire } = data;
|
||||||
|
if (isNullOrUnDef(expire) || expire < new Date().getTime()) {
|
||||||
|
this.remove(key);
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
} catch (e) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete cache based on key
|
||||||
|
* @param {string} key
|
||||||
|
* @memberof Cache
|
||||||
|
*/
|
||||||
|
remove(key: string) {
|
||||||
|
uni.removeStorageSync(this.getKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all caches of this instance
|
||||||
|
*/
|
||||||
|
clear(): void {
|
||||||
|
uni.clearStorageSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Storage();
|
||||||
|
}
|
||||||
34
src/utils/character.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { random } from 'lodash-es';
|
||||||
|
import multiavatar from '@multiavatar/multiavatar';
|
||||||
|
|
||||||
|
const CHS_RANGE_START = 0x4E00; // 简体中文编码范围开始
|
||||||
|
const CHS_RANGE_END = 0x9FA5; // 简体中文编码范围结束
|
||||||
|
|
||||||
|
// 生成随机简体中文字符的函数
|
||||||
|
function getRandomChsChar() {
|
||||||
|
// 生成 CHS_RANGE_START 和 CHS_RANGE_END 之间的随机整数
|
||||||
|
const randomCharCode
|
||||||
|
= Math.floor(Math.random() * (CHS_RANGE_END - CHS_RANGE_START + 1)) + CHS_RANGE_START;
|
||||||
|
|
||||||
|
// 将随机整数转换为字符
|
||||||
|
return String.fromCharCode(randomCharCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成指定长度的随机简体中文字符串
|
||||||
|
export function getRandomChsString(length: number) {
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += getRandomChsChar();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机 svg 图标
|
||||||
|
*/
|
||||||
|
export function getRandomIcon() {
|
||||||
|
const svgCode = multiavatar(getRandomChsString(random(16, 32)));
|
||||||
|
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
|
||||||
|
svgCode,
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
71
src/utils/cipher.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { decrypt, encrypt } from 'crypto-js/aes';
|
||||||
|
import UTF8, { parse } from 'crypto-js/enc-utf8';
|
||||||
|
import pkcs7 from 'crypto-js/pad-pkcs7';
|
||||||
|
import ECB from 'crypto-js/mode-ecb';
|
||||||
|
import md5 from 'crypto-js/md5';
|
||||||
|
|
||||||
|
import Base64 from 'crypto-js/enc-base64';
|
||||||
|
|
||||||
|
export interface EncryptionParams {
|
||||||
|
key: string
|
||||||
|
iv: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES 加密解密
|
||||||
|
*/
|
||||||
|
export class AesEncryption {
|
||||||
|
private key;
|
||||||
|
|
||||||
|
private iv;
|
||||||
|
|
||||||
|
constructor(opt: Partial<EncryptionParams> = {}) {
|
||||||
|
const { key, iv } = opt;
|
||||||
|
if (key) {
|
||||||
|
this.key = parse(key);
|
||||||
|
}
|
||||||
|
if (iv) {
|
||||||
|
this.iv = parse(iv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get getOptions() {
|
||||||
|
return {
|
||||||
|
mode: ECB,
|
||||||
|
padding: pkcs7,
|
||||||
|
iv: this.iv,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptByAES(cipherText: string) {
|
||||||
|
return encrypt(cipherText, this.key!, this.getOptions).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptByAES(cipherText: string) {
|
||||||
|
return decrypt(cipherText, this.key!, this.getOptions).toString(UTF8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64加密
|
||||||
|
* @param cipherText
|
||||||
|
*/
|
||||||
|
export function encryptByBase64(cipherText: string) {
|
||||||
|
return UTF8.parse(cipherText).toString(Base64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64解密
|
||||||
|
* @param cipherText
|
||||||
|
*/
|
||||||
|
export function decodeByBase64(cipherText: string) {
|
||||||
|
return Base64.parse(cipherText).toString(UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MD5加密
|
||||||
|
* @param password
|
||||||
|
*/
|
||||||
|
export function encryptByMd5(password: string) {
|
||||||
|
return md5(password).toString();
|
||||||
|
}
|
||||||
83
src/utils/env.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import pkg from '../../package.json';
|
||||||
|
import { isH5 } from '@/utils/platform';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Generate cache key according to version
|
||||||
|
*/
|
||||||
|
export function getPkgVersion() {
|
||||||
|
return `${`__${pkg.version}`}__`.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Development mode
|
||||||
|
*/
|
||||||
|
export const devMode = 'development';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Production mode
|
||||||
|
*/
|
||||||
|
export const prodMode = 'production';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Get environment mode
|
||||||
|
* @returns:
|
||||||
|
* @example:
|
||||||
|
*/
|
||||||
|
export function getEnvMode(): string {
|
||||||
|
return getEnvValue('VITE_ENV');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Get environment variables
|
||||||
|
* @returns:
|
||||||
|
* @example:
|
||||||
|
*/
|
||||||
|
export function getEnvValue<T = string>(key: keyof ImportMetaEnv): T {
|
||||||
|
const envValue = import.meta.env[key];
|
||||||
|
return (envValue === 'true' ? true : envValue === 'false' ? false : envValue) as unknown as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Is it a development mode
|
||||||
|
* @returns:
|
||||||
|
* @example:
|
||||||
|
*/
|
||||||
|
export function isDevMode(): boolean {
|
||||||
|
return getEnvMode() === devMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Is it a production mode
|
||||||
|
* @returns:
|
||||||
|
* @example:
|
||||||
|
*/
|
||||||
|
export function isProdMode(): boolean {
|
||||||
|
return getEnvMode() === prodMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Whether to use mock data
|
||||||
|
* @returns:
|
||||||
|
* @example:
|
||||||
|
*/
|
||||||
|
export function isUseMock(): boolean {
|
||||||
|
return getEnvValue('VITE_USE_MOCK');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Get environment VITE_BASE_URL value
|
||||||
|
* @returns:
|
||||||
|
* @example:
|
||||||
|
*/
|
||||||
|
export function getBaseUrl(): string {
|
||||||
|
return (isH5() && isDevMode()) ? getEnvValue<string>('VITE_PROXY_PREFIX') : getEnvValue<string>('VITE_BASE_URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Get environment VITE_UPLOAD_URL value
|
||||||
|
* @returns:
|
||||||
|
* @example:
|
||||||
|
*/
|
||||||
|
export function getUploadUrl(): string {
|
||||||
|
return (isH5() && isDevMode()) ? getEnvValue<string>('VITE_UPLOAD_PROXY_PREFIX') : getEnvValue<string>('VITE_UPLOAD_URL');
|
||||||
|
}
|
||||||
51
src/utils/http/checkStatus.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Toast } from '@/utils/uniapi/prompt';
|
||||||
|
|
||||||
|
export function checkStatus(status: number, msg: string): void {
|
||||||
|
let errMessage = null;
|
||||||
|
switch (status) {
|
||||||
|
case 400:
|
||||||
|
errMessage = `${msg}`;
|
||||||
|
break;
|
||||||
|
// 401: Not logged in
|
||||||
|
// Jump to the login page if not logged in, and carry the path of the current page
|
||||||
|
// Return to the current page after successful login. This step needs to be operated on the login page.
|
||||||
|
case 401:
|
||||||
|
errMessage = '用户没有权限(令牌、用户名、密码错误)!';
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
errMessage = '用户得到授权,但是访问是被禁止的!';
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
errMessage = '网络请求错误,未找到该资源!';
|
||||||
|
break;
|
||||||
|
case 405:
|
||||||
|
errMessage = '网络请求错误,请求方法未允许!';
|
||||||
|
break;
|
||||||
|
case 408:
|
||||||
|
errMessage = '网络请求超时!';
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
errMessage = '服务器错误,请联系管理员!';
|
||||||
|
break;
|
||||||
|
case 501:
|
||||||
|
errMessage = '网络未实现!';
|
||||||
|
break;
|
||||||
|
case 502:
|
||||||
|
errMessage = '服务不可用,服务器暂时过载或维护!';
|
||||||
|
break;
|
||||||
|
case 503:
|
||||||
|
errMessage = '服务不可用,服务器暂时过载或维护!';
|
||||||
|
break;
|
||||||
|
case 504:
|
||||||
|
errMessage = '网络超时!';
|
||||||
|
break;
|
||||||
|
case 505:
|
||||||
|
errMessage = 'http版本不支持该请求!';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errMessage) {
|
||||||
|
Toast(errMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/utils/http/index.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { createAlova } from 'alova';
|
||||||
|
import AdapterUniapp from '@alova/adapter-uniapp';
|
||||||
|
import { assign } from 'lodash-es';
|
||||||
|
import { checkStatus } from './checkStatus';
|
||||||
|
import { getBaseUrl, isUseMock } from '@/utils/env';
|
||||||
|
import { mockAdapter } from '@/mock';
|
||||||
|
import { ContentTypeEnum, ResultEnum } from '@/enums/httpEnum';
|
||||||
|
import type { API } from '@/services/model/baseModel';
|
||||||
|
import { getAuthorization } from '@/utils/auth';
|
||||||
|
|
||||||
|
const BASE_URL = getBaseUrl();
|
||||||
|
|
||||||
|
const ContentType = {
|
||||||
|
'Content-Type': ContentTypeEnum.JSON,
|
||||||
|
'Accept': 'application/json, text/plain, */*',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* alova 请求实例
|
||||||
|
* @link https://github.com/alovajs/alova
|
||||||
|
*/
|
||||||
|
const alovaInstance = createAlova({
|
||||||
|
baseURL: BASE_URL,
|
||||||
|
localCache: null, // 设置为null即可全局关闭全部请求缓存
|
||||||
|
...AdapterUniapp({
|
||||||
|
/* #ifndef APP-PLUS */
|
||||||
|
mockRequest: isUseMock() ? mockAdapter : undefined, // APP 平台无法使用mock
|
||||||
|
/* #endif */
|
||||||
|
}),
|
||||||
|
timeout: 5000,
|
||||||
|
beforeRequest: (method) => {
|
||||||
|
method.config.headers = assign(method.config.headers, ContentType);
|
||||||
|
const { config } = method;
|
||||||
|
const ignoreAuth = !config.meta?.ignoreAuth;
|
||||||
|
const authorization = ignoreAuth ? getAuthorization() : null;
|
||||||
|
if (ignoreAuth && !authorization) {
|
||||||
|
throw new Error('[请求错误]:未登录');
|
||||||
|
}
|
||||||
|
method.config.headers.authorization = getAuthorization();
|
||||||
|
},
|
||||||
|
responded: {
|
||||||
|
/**
|
||||||
|
* 请求成功的拦截器
|
||||||
|
* 第二个参数为当前请求的method实例,你可以用它同步请求前后的配置信息
|
||||||
|
* @param response
|
||||||
|
* @param method
|
||||||
|
*/
|
||||||
|
onSuccess: async (response, method) => {
|
||||||
|
const { config } = method;
|
||||||
|
const { requestType } = config;
|
||||||
|
const { statusCode, data: rawData, errMsg } = response as UniNamespace.RequestSuccessCallbackResult;
|
||||||
|
if (statusCode === 200) {
|
||||||
|
if (requestType) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
const { code, message, data } = rawData as API;
|
||||||
|
if (code === ResultEnum.SUCCESS) {
|
||||||
|
return data as any;
|
||||||
|
}
|
||||||
|
checkStatus(statusCode, message || '');
|
||||||
|
throw new Error(`请求错误[${code}]:${message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`请求错误[${statusCode}]:${errMsg}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求失败的拦截器,请求错误时将会进入该拦截器。
|
||||||
|
*/
|
||||||
|
onError: (err) => {
|
||||||
|
throw new Error(`请求错误:${err}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const request = alovaInstance;
|
||||||
14
src/utils/index.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { isObject } from '@/utils/is';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度合并
|
||||||
|
* @param src
|
||||||
|
* @param target
|
||||||
|
*/
|
||||||
|
export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
|
||||||
|
let key: string;
|
||||||
|
for (key in target) {
|
||||||
|
src[key] = isObject(src[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]);
|
||||||
|
}
|
||||||
|
return src;
|
||||||
|
}
|
||||||
7
src/utils/interceptors/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export function setupInterceptors() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeInterceptor() {
|
||||||
|
|
||||||
|
}
|
||||||
100
src/utils/is.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
const { toString } = Object.prototype;
|
||||||
|
|
||||||
|
export function is(val: unknown, type: string) {
|
||||||
|
return toString.call(val) === `[object ${type}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDef<T = unknown>(val?: T): val is T {
|
||||||
|
return typeof val !== 'undefined';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUnDef<T = unknown>(val?: T): val is T {
|
||||||
|
return !isDef(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isObject(val: any): val is Record<any, any> {
|
||||||
|
return val !== null && is(val, 'Object');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isEmpty<T = unknown>(val: T): val is T {
|
||||||
|
if (isArray(val) || isString(val)) {
|
||||||
|
return val.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val instanceof Map || val instanceof Set) {
|
||||||
|
return val.size === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isObject(val)) {
|
||||||
|
return Object.keys(val).length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDate(val: unknown): val is Date {
|
||||||
|
return is(val, 'Date');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNull(val: unknown): val is null {
|
||||||
|
return val === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNullAndUnDef(val: unknown): val is null | undefined {
|
||||||
|
return isUnDef(val) && isNull(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNullOrUnDef(val: unknown): val is null | undefined {
|
||||||
|
return isUnDef(val) || isNull(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNumber(val: unknown): val is number {
|
||||||
|
return is(val, 'Number');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPromise<T = any>(val: unknown): val is Promise<T> {
|
||||||
|
return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isString(val: unknown): val is string {
|
||||||
|
return is(val, 'String');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFunction(val: unknown): val is Function {
|
||||||
|
return typeof val === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isBoolean(val: unknown): val is boolean {
|
||||||
|
return is(val, 'Boolean');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRegExp(val: unknown): val is RegExp {
|
||||||
|
return is(val, 'RegExp');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isArray(val: any): val is Array<any> {
|
||||||
|
return val && Array.isArray(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWindow(val: any): val is Window {
|
||||||
|
return typeof window !== 'undefined' && is(val, 'Window');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isElement(val: unknown): val is Element {
|
||||||
|
return isObject(val) && !!val.tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMap(val: unknown): val is Map<any, any> {
|
||||||
|
return is(val, 'Map');
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isServer = typeof window === 'undefined';
|
||||||
|
|
||||||
|
export const isClient = !isServer;
|
||||||
|
|
||||||
|
export function isUrl(path: string): boolean {
|
||||||
|
const reg
|
||||||
|
// eslint-disable-next-line regexp/no-super-linear-backtracking
|
||||||
|
= /^(?:https|http|ftp|rtsp|mms):\/\/(?:(?:[\w!~*'().&=+$%-]+: )?[\w!~*'().&=+$%-]+@)?(?:(?:\d{1,3}.){3}\d{1,3}|(?:[\w!~*'()-]+.)*(?:[0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(?::\d{1,4})?(?:\/?|(?:\/[\w!~*'().;?:@&=+$,%#-]+)+\/?)$/;
|
||||||
|
return reg.test(path);
|
||||||
|
}
|
||||||
11
src/utils/log.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { getEnvValue } from '@/utils/env';
|
||||||
|
|
||||||
|
const projectName = getEnvValue<string>('VITE_APP_TITLE');
|
||||||
|
|
||||||
|
export function warn(message: string) {
|
||||||
|
console.warn(`[${projectName} warn]:${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function error(message: string) {
|
||||||
|
throw new Error(`[${projectName} error]:${message}`);
|
||||||
|
}
|
||||||
17
src/utils/platform.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* @description 获取当前平台
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const platform = PLATFORM;
|
||||||
|
|
||||||
|
export function isH5() {
|
||||||
|
return platform === 'h5';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isApp() {
|
||||||
|
return platform === 'app';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMp() {
|
||||||
|
return platform.startsWith('mp-');
|
||||||
|
}
|
||||||
53
src/utils/uniapi/index.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* @description 设置系统剪贴板的内容
|
||||||
|
* @param data 需要设置的内容
|
||||||
|
* @param showToast 配置是否弹出提示,默认弹出提示
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export function SetClipboardData(data: string, showToast = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.setClipboardData({
|
||||||
|
data,
|
||||||
|
showToast,
|
||||||
|
success: (res) => {
|
||||||
|
resolve(res);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取系统剪贴板内容
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export function GetClipboardData() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.getClipboardData({
|
||||||
|
success: (res) => {
|
||||||
|
resolve(res);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rpx 换算为 px
|
||||||
|
* @param upx
|
||||||
|
*/
|
||||||
|
export function rpx2px(upx: number) {
|
||||||
|
return uni.upx2px(upx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* px 换算为 rpx
|
||||||
|
* @param px
|
||||||
|
*/
|
||||||
|
export function px2rpx(px: number) {
|
||||||
|
return px / (uni.upx2px(100) / 100);
|
||||||
|
}
|
||||||
86
src/utils/uniapi/prompt.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* 交互反馈
|
||||||
|
* https://uniapp.dcloud.io/api/ui/prompt.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示消息提示框
|
||||||
|
* @param title
|
||||||
|
* @param options
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export function Toast(title: string, options?: Partial<UniApp.ShowToastOptions>) {
|
||||||
|
uni.showToast({
|
||||||
|
title,
|
||||||
|
duration: 1500,
|
||||||
|
icon: 'none',
|
||||||
|
mask: true,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏消息提示框
|
||||||
|
*/
|
||||||
|
export function HideToast() {
|
||||||
|
uni.hideToast();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示 loading 提示框
|
||||||
|
* @param title
|
||||||
|
* @param options
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export function Loading(title: string, options?: Partial<UniApp.ShowLoadingOptions>) {
|
||||||
|
uni.showLoading({
|
||||||
|
title,
|
||||||
|
mask: true,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏 loading 提示框
|
||||||
|
*/
|
||||||
|
export function HideLoading() {
|
||||||
|
uni.hideLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示模态弹窗,可以只有一个确定按钮,也可以同时有确定和取消按钮
|
||||||
|
* @param options
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export function Modal(options: UniApp.ShowModalOptions) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.showModal({
|
||||||
|
...options,
|
||||||
|
success: (res) => {
|
||||||
|
resolve(res);
|
||||||
|
},
|
||||||
|
fail: (res) => {
|
||||||
|
reject(res);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从底部向上弹出操作菜单
|
||||||
|
* @param options
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export function ActionSheet(options: UniApp.ShowActionSheetOptions) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.showActionSheet({
|
||||||
|
...options,
|
||||||
|
success: (res) => {
|
||||||
|
resolve(res);
|
||||||
|
},
|
||||||
|
fail: (res) => {
|
||||||
|
reject(res);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
59
tsconfig.json
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
{ // 指定要从编译中排除的文件列表
|
||||||
|
"compilerOptions": { // 为所有发出的文件指定一个输出文件夹
|
||||||
|
"target": "ESNext", // 允许export=导出,由import from 导入
|
||||||
|
"lib": ["ESNext", "DOM", "ScriptHost"], // 是否启用对装饰器的实验性支持,装饰器是一种语言特性,还没有完全被 JavaScript 规范批准
|
||||||
|
"emitDecoratorMetadata": true, // 是否通过" . “(obj.key) 语法访问字段和"索引”( obj[“key”]), 以及在类型中声明属性的方式之间的一致性
|
||||||
|
|
||||||
|
/* 实验选项 */
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"baseUrl": "./", // 指定基目录以解析非相对模块名
|
||||||
|
"rootDir": "./src", // 编译出目标语言版本
|
||||||
|
"module": "ESNext", // 生成代码的模板标准
|
||||||
|
"moduleResolution": "node", // 删除注释
|
||||||
|
"paths": { "@/*": ["./src/*"] }, // 有错误时不进行编译
|
||||||
|
"resolveJsonModule": true, // TS需要引用的库
|
||||||
|
"types": ["@types/node"], // 在源文件中指定根文件夹
|
||||||
|
/* 严格检查选项 */
|
||||||
|
"strict": true, // 设置发出文件的换行符
|
||||||
|
// "sourceMap": true, //为发出的JavaScript文件创建源映射文件。
|
||||||
|
// "declaration": true, // 从项目中的TypeScript和JavaScript文件生成.d.ts文件
|
||||||
|
// "declarationMap": true, // 为d.ts文件创建源地图
|
||||||
|
|
||||||
|
"strictBindCallApply": true, // 检查bind、call和apply方法的参数是否与原始函数匹配。
|
||||||
|
"strictFunctionTypes": true, // 不允许this有隐式的any类型
|
||||||
|
"strictNullChecks": true, // 在给函数赋值时,要确保参数和返回值是子类型兼容的。
|
||||||
|
"strictPropertyInitialization": true, // 开启所有严格的类型检查
|
||||||
|
"alwaysStrict": true, // 是否检查子类继承自基类时,其重载的函数命名与基类的函数不同步问题
|
||||||
|
"noFallthroughCasesInSwitch": true, // 在代码中注入'use strict'
|
||||||
|
"noImplicitAny": true, // 检查函数是否不含有隐式返回值
|
||||||
|
"noImplicitOverride": true, // 是否检查未使用的参数
|
||||||
|
"noImplicitReturns": true, // 不允许隐式的any类型
|
||||||
|
"noImplicitThis": true, // 是否通过索引签名来描述对象上有未知键但已知值的对象
|
||||||
|
"noPropertyAccessFromIndexSignature": false, // 检查switch中是否含有case没有使用break跳出
|
||||||
|
"noUncheckedIndexedAccess": true, // 类的实例属性必须初始化
|
||||||
|
|
||||||
|
/* 额外检查 */
|
||||||
|
"noUnusedLocals": true, // 是否检查未使用的局部变量
|
||||||
|
"noUnusedParameters": true, // 在输出中启用颜色和格式,使编译器错误更容易阅读
|
||||||
|
"newLine": "crlf", // 是否查看 TS 在编译时花费的时间
|
||||||
|
"noEmitOnError": true, // 在进行类型检查时,请考虑null和undefined。
|
||||||
|
"outDir": "./dist", // 指定要包含的类型包名,而不需要在源文件中引用
|
||||||
|
"removeComments": true, // 指定TypeScript如何从给定的模块说明符查找文件
|
||||||
|
"esModuleInterop": true, // 指定一组条目,它们将导入重新映射到其他查找位置
|
||||||
|
"pretty": true, // 为装饰器启用对发出类型元数据的实验性支持
|
||||||
|
|
||||||
|
/* 高级选项 */
|
||||||
|
"forceConsistentCasingInFileNames": true, // 是否区分文件系统大小写规则
|
||||||
|
"extendedDiagnostics": false // 是否解析 JSON 模块
|
||||||
|
// "incremental": true // 增量编译
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"vite.config.*",
|
||||||
|
"typings/*.d.ts"
|
||||||
|
], // 指定要包含在编译中的文件匹配列表
|
||||||
|
"exclude": []
|
||||||
|
}
|
||||||
73
unocss.config.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* unocss defineConfig
|
||||||
|
* @link unocss: https://github.com/unocss/unocss
|
||||||
|
* @type {import('unocss').UserConfig}
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineConfig, presetIcons } from 'unocss';
|
||||||
|
import presetWeapp from 'unocss-preset-weapp';
|
||||||
|
import { transformerAttributify, transformerClass } from 'unocss-preset-weapp/transformer';
|
||||||
|
|
||||||
|
const transformRules = {
|
||||||
|
'.': '-d2e-',
|
||||||
|
'/': '-s2f-',
|
||||||
|
':': '-c3a-',
|
||||||
|
'%': '-p25-',
|
||||||
|
'!': '-e21-',
|
||||||
|
'#': '-w23-',
|
||||||
|
'(': '-b28-',
|
||||||
|
')': '-b29-',
|
||||||
|
'[': '-f4b-',
|
||||||
|
']': '-f5d-',
|
||||||
|
'$': '-r24-',
|
||||||
|
',': '-r2c-',
|
||||||
|
};
|
||||||
|
|
||||||
|
const prefix = '';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
// https://github.com/MellowCo/unocss-preset-weapp
|
||||||
|
presetWeapp({
|
||||||
|
nonValuedAttribute: true,
|
||||||
|
prefix,
|
||||||
|
whRpx: true,
|
||||||
|
transform: true,
|
||||||
|
platform: 'uniapp',
|
||||||
|
transformRules,
|
||||||
|
}),
|
||||||
|
presetIcons({
|
||||||
|
scale: 1.2,
|
||||||
|
warn: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
center: 'flex justify-center items-center',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
primary: '#007AFF',
|
||||||
|
secondary: '#4CD964',
|
||||||
|
danger: '#FF3B30',
|
||||||
|
warning: '#FF9500',
|
||||||
|
info: '#5AC8FA',
|
||||||
|
light: '#F0F0F0',
|
||||||
|
dark: '#1A1A1A',
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
mini: ['20rpx', '26rpx'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transformers: [
|
||||||
|
transformerAttributify({
|
||||||
|
classPrefix: prefix,
|
||||||
|
transformRules,
|
||||||
|
nonValuedAttribute: true,
|
||||||
|
}),
|
||||||
|
transformerClass({
|
||||||
|
transformRules,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
128
vite.config.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/**
|
||||||
|
* vite 配置
|
||||||
|
* @see https://cn.vitejs.dev/config/
|
||||||
|
* @type {import('vite').UserConfig}
|
||||||
|
*/
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
import type { UserConfig } from 'vite';
|
||||||
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
|
import TransformPages from 'uni-read-pages-vite';
|
||||||
|
import uni from '@dcloudio/vite-plugin-uni';
|
||||||
|
import UnoCSS from 'unocss/vite';
|
||||||
|
import transformClass from 'unplugin-transform-class/vite';
|
||||||
|
import { visualizer } from 'rollup-plugin-visualizer';
|
||||||
|
import ViteRestart from 'vite-plugin-restart';
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite';
|
||||||
|
|
||||||
|
export default defineConfig(async ({ mode }) => {
|
||||||
|
const root = process.cwd();
|
||||||
|
const env = loadEnv(mode, resolve(root, 'env'));
|
||||||
|
const isProd = mode === 'production';
|
||||||
|
const { UNI_PLATFORM } = process.env;
|
||||||
|
const isH5 = UNI_PLATFORM === 'h5';
|
||||||
|
const { VITE_PROXY_PREFIX, VITE_UPLOAD_PROXY_PREFIX, VITE_BASE_URL, VITE_UPLOAD_URL, VITE_PORT } = env;
|
||||||
|
|
||||||
|
return {
|
||||||
|
base: './',
|
||||||
|
envDir: './env', // 自定义env目录
|
||||||
|
// 设置路径别名
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve('./src'),
|
||||||
|
},
|
||||||
|
extensions: ['.js', '.ts'], // 使用路径别名时想要省略的后缀名,可以自己 增减
|
||||||
|
},
|
||||||
|
// 自定义全局变量
|
||||||
|
define: {
|
||||||
|
'process.env': {},
|
||||||
|
'PLATFORM': JSON.stringify(UNI_PLATFORM),
|
||||||
|
'ROUTES': new TransformPages().routes,
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
additionalData: '@import "./src/uni.scss";',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
// @ts-expect-error TODO uni() 会报错:uni is not a function,暂时使用此方式解决
|
||||||
|
uni?.default(),
|
||||||
|
AutoImport({
|
||||||
|
include: [
|
||||||
|
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
|
||||||
|
/\.vue$/,
|
||||||
|
/\.vue\?vue/, // .vue
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
'vue',
|
||||||
|
'uni-app',
|
||||||
|
'pinia',
|
||||||
|
{
|
||||||
|
'uni-mini-router': ['useRouter', 'useRoute'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
alova: ['useRequest'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dts: 'typings/auto-imports.d.ts',
|
||||||
|
eslintrc: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
UnoCSS(),
|
||||||
|
transformClass(),
|
||||||
|
ViteRestart({
|
||||||
|
restart: ['vite.config.ts'],
|
||||||
|
}),
|
||||||
|
isH5 && isProd
|
||||||
|
&& visualizer({
|
||||||
|
filename: './node_modules/.cache/visualizer/stats.html',
|
||||||
|
open: true,
|
||||||
|
gzipSize: true,
|
||||||
|
brotliSize: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
// 开发服务器配置
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
// open: true,
|
||||||
|
port: Number.parseInt(VITE_PORT!, 10),
|
||||||
|
proxy: {
|
||||||
|
[VITE_PROXY_PREFIX!]: {
|
||||||
|
target: VITE_BASE_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path: string) => path.replace(new RegExp(`^${VITE_PROXY_PREFIX}`), ''),
|
||||||
|
},
|
||||||
|
[VITE_UPLOAD_PROXY_PREFIX!]: {
|
||||||
|
target: VITE_UPLOAD_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path: string) => path.replace(new RegExp(`^${VITE_UPLOAD_PROXY_PREFIX}`), ''),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 构建配置
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
chunkSizeWarningLimit: 1500,
|
||||||
|
sourcemap: !isProd,
|
||||||
|
target: 'es6',
|
||||||
|
minify: isProd ? 'terser' : false,
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
drop_console: isProd,
|
||||||
|
drop_debugger: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
entryFileNames: `assets/[name].${new Date().getTime()}.js`,
|
||||||
|
chunkFileNames: `assets/[name].${new Date().getTime()}.js`,
|
||||||
|
assetFileNames: `assets/[name].${new Date().getTime()}.[ext]`,
|
||||||
|
compact: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as UserConfig;
|
||||||
|
});
|
||||||