This commit is contained in:
Waiting 2024-02-02 22:31:01 +08:00
parent 06a5f041bf
commit 486ea5013a
77 changed files with 17507 additions and 122 deletions

22
.editorconfig Normal file
View File

@ -0,0 +1,22 @@
# 告诉EditorConfig插件这是根文件不用继续往上查找
root = true
# 匹配全部文件
[*]
# 设置字符集
charset = utf-8
# 缩进风格可选space、tab
indent_style = space
# 缩进的空格数
indent_size = 2
# 结尾换行符可选lf、cr、crlf
end_of_line = lf
# 在文件结尾插入新行
insert_final_newline = true
# 删除一行中的前后空格
trim_trailing_whitespace = true
# 匹配md结尾的文件
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

14
.env.development Normal file
View File

@ -0,0 +1,14 @@
# 页面标题
VITE_APP_TITLE='uniapp-vue3-project'
# 开发环境配置
VITE_APP_ENV='development'
# 接口地址
VITE_APP_BASE_API='/api'
# 删除console
VITE_DROP_CONSOLE=false
# 是否开启Mock
VITE_USE_MOCK=true

11
.env.production Normal file
View File

@ -0,0 +1,11 @@
# 页面标题
VITE_APP_TITLE='uniapp-vue3-project'
# 生产环境配置
VITE_APP_ENV='production'
# 接口地址
VITE_APP_BASE_API='/'
# 删除console
VITE_DROP_CONSOLE=true

11
.env.test Normal file
View File

@ -0,0 +1,11 @@
# 页面标题
VITE_APP_TITLE='uniapp-vue3-project'
# 生产环境配置
VITE_APP_ENV='production'
# 接口地址
VITE_APP_BASE_API='/'
# 删除console
VITE_DROP_CONSOLE=true

138
.gitignore vendored
View File

@ -1,132 +1,28 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
node_modules
.DS_Store
dist
*.local
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
#user
.hbuilderx
unpackage
# pnpm-lock.yaml
yarn.lock
package-lock.json

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
registry=https://registry.npmmirror.com/
shamefully-hoist=true

10
.prettierrc Normal file
View File

@ -0,0 +1,10 @@
{
"eslintIntegration": true,
"singleQuote": true,
"semi": true,
"tabWidth": 2,
"useTabs": false,
"printWidth": 120,
"arrowParens": "always",
"trailingComma": "none"
}

182
README.md
View File

@ -1,2 +1,182 @@
# suke-mp
### 使用uniapp+vite+vue3+uview-plus3.0 搭建的微信小程序快速开发模版
使用uniapp+vite+vue3+typescript+uview-plus3.0 搭建的H5和微信小程序快速开发模版
[uview-plus官方文档](https://uiadmin.net/uview-plus/)
本项目集众多项目的优点,打造最适合团队协作开发的项目模板。
在线预览地址:[https://oyjt.github.io/uniapp-vue3-template/](https://oyjt.github.io/uniapp-vue3-template/)
### 特性
- [x] 集成uview-plus3.0 ui库
- [x] 支持多环境打包构建
- [x] 使用pinia状态管理
- [x] 封装网络请求并支持Typescript
- [x] 支持路径别名
- [x] 支持自动加载组件和API
- [x] 自动校验git提交代码格式
- [x] 集成ESLint、StyleLint、EditorConfig代码格式规范
- [x] Typescript支持
- [x] 集成UnoCSS
- [x] 集成z-paging下拉刷新功能
- [x] 添加页面跳转拦截,登录权限校验
- [x] 支持token无痛刷新
- [x] 支持持续集成
- [x] 项目分包
### 目录结构
项目中采用目前最新的技术方案来实现,目录结构清晰。
```
uniapp-vue3-project
├ build vite插件统一管理
│ ├ vite
│ └ constant.ts
├ scripts 一些脚本
│ └ verifyCommit.js
├ src
│ ├ api 接口管理
│ ├ components 公共组件
│ ├ hooks 常用hooks封装
│ ├ pages 页面管理
│ ├ static 静态资源
│ ├ store 状态管理
│ ├ utils 一些工具
│ ├ App.vue
│ ├ main.ts
│ ├ manifest.json
│ ├ pages.json
│ ├ permission.ts 页面访问权限控制
│ └ uni.scss
├ types 全局typescript类型文件
│ ├ auto-imports.d.ts
│ ├ components.d.ts
│ ├ global.d.ts
│ └ module.d.ts
├ README.md
├ eslint.config.js
├ index.html
├ package.json
├ pnpm-lock.yaml
├ tsconfig.json
├ uno.config.ts
└ vite.config.ts
```
#### vite插件管理
```
build
├ vite
│ ├ plugins
│ │ ├ autoImport.ts 自动导入api
│ │ ├ component.ts 自动导入组件
│ │ ├ imagemin.ts 图片压缩
│ │ ├ index.ts 入口文件
│ │ └ unocss.ts unocss插件
│ └ proxy.ts 跨域代理配置
└ constant.ts 一些常量
```
#### 接口管理
```
api
├ common 通用api
│ ├ index.ts
│ └ types.ts
├ user 用户相关api
│ ├ index.ts
│ └ types.ts
└ index.ts 入口文件
```
#### hooks管理
```
hooks
├ use-clipboard 剪切板
│ └ index.ts
├ use-loading loading
│ └ index.ts
├ use-modal 模态框
│ └ index.ts
├ use-share 分享
│ └ index.ts
└ index.ts 入口文件
```
### 页面管理
```
pages
├ common 公共页面分包common
│ ├ login
│ │ └ index.vue
│ └ webview
│ └ index.vue
└ tab 主页面(主包)
├ home
│ └ index.vue
├ list
│ └ index.vue
└ user
└ index.vue
```
#### 状态管理
```
store
├ modules
│ ├ app app状态
│ │ ├ index.ts
│ │ └ types.ts
│ └ user 用户状态
│ ├ index.ts
│ └ types.ts
└ index.ts 入口文件
```
### 工具方法
```
utils
├ auth token相关方法
│ └ index.ts
├ common 通用方法
│ └ index.ts
├ modals 弹窗相关方法
│ └ index.ts
├ request 网络请求相关方法
│ ├ index.ts
│ ├ interceptors.ts
│ ├ status.ts
│ └ type.ts
└ index.ts 入口文件
```
### 使用方法
```bash
# 安装依赖
pnpm install
# 启动H5
pnpm dev:h5
# 启动微信小程序
pnpm dev:mp-weixin
```
### 发布
```bash
# 构建测试环境
pnpm build:h5
pnpm build:mp-weixin
# 构建生产环境
pnpm build:h5-prod
pnpm build:mp-weixin-prod
```
### 代码提交
```bash
pnpm cz
```

15
build/constant.ts Normal file
View File

@ -0,0 +1,15 @@
/**
* @name Config
* @description
*/
// prefix
export const API_PREFIX = '/api';
// serve
export const API_BASE_URL = '/api';
export const API_TARGET_URL = 'http://localhost:8080';
// mock
export const MOCK_API_BASE_URL = '/mock/api';
export const MOCK_API_TARGET_URL = 'http://localhost:3000';

View File

@ -0,0 +1,13 @@
/**
* @name AutoImportDeps
* @description
*/
import AutoImport from 'unplugin-auto-import/vite';
export const AutoImportDeps = () => {
return AutoImport({
imports: ['vue', 'uni-app', 'pinia'],
dts: 'types/auto-imports.d.ts',
vueTemplate: true,
});
};

View File

@ -0,0 +1,11 @@
/**
* @name AutoRegistryComponents
* @description
*/
import Components from 'unplugin-vue-components/vite';
export const AutoRegistryComponents = () => {
return Components({
dts: 'types/components.d.ts',
});
};

View File

@ -0,0 +1,36 @@
/**
* @name ConfigImageminPlugin
* @description
*/
import viteImagemin from 'vite-plugin-imagemin';
export const ConfigImageminPlugin = () => {
const plugin = viteImagemin({
gifsicle: {
optimizationLevel: 7,
interlaced: false,
},
mozjpeg: {
quality: 20,
},
optipng: {
optimizationLevel: 7,
},
pngquant: {
quality: [0.8, 0.9],
speed: 4,
},
svgo: {
plugins: [
{
name: 'removeViewBox',
},
{
name: 'removeEmptyAttrs',
active: false,
},
],
},
});
return plugin;
};

View File

@ -0,0 +1,30 @@
/**
* @name createVitePlugins
* @description plugins数组统一调用
*/
import type { PluginOption } from 'vite';
import uniPlugin from '@dcloudio/vite-plugin-uni';
import { AutoImportDeps } from './autoImport';
import { AutoRegistryComponents } from './component';
import { ConfigUnoCSSPlugin } from './unocss';
import { ConfigImageminPlugin } from './imagemin';
export default function createVitePlugins(isBuild: boolean) {
const vitePlugins: (PluginOption | PluginOption[])[] = [
// UnoCSS配置
ConfigUnoCSSPlugin(),
// 自动按需引入依赖
AutoImportDeps(),
// 自动按需引入组件(注意:需注册至 uni 之前,否则不会生效)
AutoRegistryComponents(),
// uni支持
uniPlugin(),
];
if (isBuild) {
// vite-plugin-imagemin
vitePlugins.push(ConfigImageminPlugin());
}
return vitePlugins;
}

View File

@ -0,0 +1,9 @@
/**
* @name ConfigUnoCSSPlugin
* @description UnoCSS相关配置
*/
import UnoCSS from 'unocss/vite';
export const ConfigUnoCSSPlugin = () => {
return UnoCSS();
};

21
build/vite/proxy.ts Normal file
View File

@ -0,0 +1,21 @@
import type { ProxyOptions } from 'vite';
import { API_BASE_URL, API_TARGET_URL, MOCK_API_BASE_URL, MOCK_API_TARGET_URL } from '../constant';
type ProxyTargetList = Record<string, ProxyOptions>;
const init: ProxyTargetList = {
// test
[API_BASE_URL]: {
target: API_TARGET_URL,
changeOrigin: true,
rewrite: path => path.replace(new RegExp(`^${API_BASE_URL}`), ''),
},
// mock
[MOCK_API_BASE_URL]: {
target: MOCK_API_TARGET_URL,
changeOrigin: true,
rewrite: path => path.replace(new RegExp(`^${MOCK_API_BASE_URL}`), '/api'),
},
};
export default init;

58
cz.config.js Normal file
View File

@ -0,0 +1,58 @@
/** @type {import('cz-git').CommitizenGitOptions} */
module.exports = {
"alias": { "fd": "docs: fix typos" },
"messages": {
"type": "选择你要提交的类型 :",
"scope": "选择一个提交范围(可选):",
"customScope": "请输入自定义的提交范围 :",
"subject": "填写简短精炼的变更描述 :\n",
"body": "填写更加详细的变更描述(可选)。使用 '|' 换行 :\n",
"breaking": "列举非兼容性重大的变更(可选)。使用 '|' 换行 :\n",
"footerPrefixesSelect": "选择关联issue前缀可选:",
"customFooterPrefix": "输入自定义issue前缀 :",
"footer": "列举关联issue (可选) 例如: #31, #I3244 :\n",
"confirmCommit": "是否提交或修改commit ?"
},
"types": [
{ "value": "feat", "name": "feat: 新增功能 | A new feature", "emoji": ":sparkles:" },
{ "value": "fix", "name": "fix: 修复缺陷 | A bug fix", "emoji": ":bug:" },
{ "value": "docs", "name": "docs: 文档更新 | Documentation only changes", "emoji": ":memo:" },
{ "value": "style", "name": "style: 代码格式 | Changes that do not affect the meaning of the code", "emoji": ":lipstick:" },
{ "value": "refactor", "name": "refactor: 代码重构 | A code change that neither fixes a bug nor adds a feature", "emoji": ":recycle:" },
{ "value": "perf", "name": "perf: 性能提升 | A code change that improves performance", "emoji": ":zap:" },
{ "value": "test", "name": "test: 测试相关 | Adding missing tests or correcting existing tests", "emoji": ":white_check_mark:" },
{ "value": "build", "name": "build: 构建相关 | Changes that affect the build system or external dependencies", "emoji": ":package:" },
{ "value": "ci", "name": "ci: 持续集成 | Changes to our CI configuration files and scripts", "emoji": ":ferris_wheel:" },
{ "value": "chore", "name": "chore: 其他修改 | Other changes that don't modify src or test files", "emoji": ":hammer:" },
{ "value": "revert", "name": "revert: 回退代码 | Reverts a previous commit", "emoji": ":rewind:" }
],
"useEmoji": false,
"emojiAlign": "center",
"useAI": false,
"aiNumber": 1,
"themeColorCode": "",
"scopes": [],
"allowCustomScopes": true,
"allowEmptyScopes": true,
"customScopesAlign": "bottom",
"customScopesAlias": "custom",
"emptyScopesAlias": "empty",
"upperCaseSubject": false,
"markBreakingChangeMode": false,
"allowBreakingChanges": ["feat", "fix"],
"breaklineNumber": 100,
"breaklineChar": "|",
"skipQuestions": [],
"issuePrefixes": [{ "value": "closed", "name": "closed: 标记 ISSUES 已完成" }],
"customIssuePrefixAlign": "top",
"emptyIssuePrefixAlias": "skip",
"customIssuePrefixAlias": "custom",
"allowCustomIssuePrefix": true,
"allowEmptyIssuePrefix": true,
"confirmColorize": true,
"minSubjectLength": 0,
"defaultBody": "",
"defaultIssues": "",
"defaultScope": "",
"defaultSubject": ""
}

44
deploy.ts Normal file
View File

@ -0,0 +1,44 @@
// @ts-ignore
const fs = require( 'fs');
// @ts-ignore
const ci = require ('miniprogram-ci');
// @ts-ignore
const path = require ('path');
// @ts-ignore
const envType = process.argv[process.argv.length - 1]; // 获取传入的环境变量名称
const WX_DESC = envType == 'production' ? '正式环境' : '测试环境' // 备注
; // @ts-ignore
(async () => {
// @ts-ignore
const manifest = path.resolve(__dirname, './src/manifest.json')
// @ts-ignore
const manifestConfig = JSON.parse(fs.readFileSync(manifest).toString())
const appId = manifestConfig['mp-weixin'].appid
const versionName = manifestConfig.versionName || '1.0.0'
const project = new ci.Project({
appid: appId,
type: 'miniProgram',
// @ts-ignore
projectPath: path.join(__dirname, './dist/build/mp-weixin'), // 获取打包的路径
// @ts-ignore
privateKeyPath: path.join(__dirname, `./keys/private.${appId}.key`), // 你要上传的小程序APPid
ignores: ['node_modules/**/*'],
})
await ci.upload({
project,
version: versionName,
desc: WX_DESC,
robot: 1,
setting: {
es7: true,
minifyJS: true, // 压缩 JS 代码
minify: true // 压缩所有代码,对应小程序开发者工具的 "压缩代码"
},
// @ts-ignore
onProgressUpdate: console.log,
})
})()

34
eslint.config.js Normal file
View File

@ -0,0 +1,34 @@
const antfu = require('@antfu/eslint-config').default
const unocss = require('@unocss/eslint-plugin').default
module.exports = antfu(
{
ignores: ['uni_modules'],
},
{
files: ['**/*.vue'],
rules: {
'vue/block-order': [
"error",
{
"order": ["template", "script", "style"]
}
],
},
},
{
rules: {
// 需要尾随逗号
// "comma-dangle": ["error", "always"]
// 允许console
"no-console": "off",
// 需要尾随分号
"semi": ["error", "always"],
// 分号位置
"style/semi": "off",
// 块内的空行
"padded-blocks": ["error", "never"]
},
},
unocss,
)

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<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>

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAtAuySwDuVUqiKAgtB8yhmDXMbFtgSZQiC4FtahrqAxcwcett
khfncnldZZGMUtK3dM/xA5YaKe18vish3qbymwc+863r2NKIFtWhWIglXDuutGgT
U9/IqCnh4Dx8Owlw5/EzbvHSXj1/uzwuAVO6cxkkEvz9mxYixuB3YCd8yKQ5rLnu
EwNf063fI+jDVUBeyEejSZJ0pi3OzWdXJOjS9UYv2bAYOfJluGucfDXMbfdz8euy
T8WRCPJp4X2QoQU5WcSxWnvRSllwAfposZTIcu/msXm77QwoYqCHvJ2suGzL+SXa
c73wQB289dXqHbYmSa7bPmTmOEyCPYao0oIh8wIDAQABAoIBACfpb9ywmCTQJqI5
LcJM2oWjZ22d0/p7nBBw4JBk5vtIaLTYO7HnGqeFv+6EFVdG3uRGNGdXHvWsb7q4
VQnGeIFlWc8q/t1RzllBgkLd62pG0LOtXpt5QEIHBstunRt5PE4uafhuIvRBLc+T
x58DsJFTQWv0KUP35D2yBMSL/F19qqF6oe43kYbAei8w608XXKOT/rODdk1Ie3yx
AOgM4DvTqcSdqL/SDJ6r8xoplDNk4l+QYCJ9z7JodnJfU5OQVWw8WFWRg3jP9k/S
DsojcusTs1s14X8V3/TbR1vVW6g4Q5QnNFozHaKRZAD9DuBStrL02hgAq7oXdg+g
IoMR+AECgYEA7VVuOWwt2aWXks9aPpOVs9knMdrpxbO1nE/B2JECICElFtL8xwUi
BxWcM++wJNtKTAjJfpnFGZkyHfmnB8V0itGIz1lCZTs/yraJqaLxQdbHjEnCoCRq
tl1751hsBS15XFDskKHGFZj0wv9nq3I0wUoMjhsRV+Lopq+uHCodcgMCgYEAwjTO
dwh/0cTjiGOV8iX9WdM0I3wnYfcAgDbxGKNyZNw8oQ52vzTn1VG+LcxMcy+OUq/a
B/XBjkUI4WyHfK7zM6rhlQB8pWR3i+yPyLXJ69oRQwYAeHQzgZ6Z+sSsusHcG/wN
J/Z9EqiWQK/tZsBXQxDpHqxwz9Xl1BlL0xX9BVECgYBHKsr2pR3KmgEtoMfq486M
M52xMXfQNOdMjA4QpssAX3ADvBjYhQ2DGlPQrxsesjNBQZFKSUn1Nx70JhyUE/2y
csqXgqiKOo4Sd1IocBfwKjuEMcoOw1zMepPg937MvqoZqJqHdDs11rvujS/FFWYE
X/QL2MoGlKA2+482GtrhiQKBgQCTt8jnn45hx2nuXxk5w42umkiJSTFHgbJe0+uU
+xXTA/YV50OJcrt4daG7gi8QWjbeTCYCcfrUtUvo8z0nKIeSYEMPq/wjbYTE6J4B
Y8z/2bHRkiofdPuMd0/V/20G7Nf4bUKwh/tgit0mvOpNgrWdLKq1CyMP4znal5cm
Kw520QKBgB+I8yfp/E01ruSccdgRH3WMGStuHhSxcQ8WWD9oFdOn+klxr8h4al3+
Ibo6SJkKmwg2/IEsuc52qZPH5594XfQ7HxfVf9RVwdR96epRqah/cHofIVCX1wQB
CCirZNNiBe2hQxDyAHn492a6dzcjMojDN5JS+Yt071CB0Tph7+2c
-----END RSA PRIVATE KEY-----

76
package.json Normal file
View File

@ -0,0 +1,76 @@
{
"name": "uniapp-vue3-project",
"version": "1.0.2",
"license": "MIT",
"scripts": {
"dev": "uni -p mp-weixin",
"build:test": "uni build --mode test -p mp-weixin",
"build:prod": "uni build --mode production -p mp-weixin",
"deploy:test": "uni build --mode test -p mp-weixin && node deploy.ts test",
"deploy:prod": "uni build --mode production -p mp-weixin && node deploy.ts production",
"type-check": "vue-tsc --noEmit",
"eslint": "eslint --fix",
"stylelint": "stylelint \"src/**/*.(vue|scss|css)\" --fix",
"cz": "git add . && npx czg",
"postinstall": "simple-git-hooks"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-3090920231225001",
"@dcloudio/uni-app-plus": "3.0.0-3090920231225001",
"@dcloudio/uni-components": "3.0.0-3090920231225001",
"@dcloudio/uni-h5": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-weixin": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-xhs": "3.0.0-3090920231225001",
"pinia": "2.0.36",
"uview-plus": "^3.1.38",
"vue": "3.2.47",
"vue-i18n": "^9.1.9",
"z-paging": "^2.6.2"
},
"devDependencies": {
"@antfu/eslint-config": "1.1.0",
"@dcloudio/types": "^3.4.3",
"@dcloudio/uni-automator": "3.0.0-3090920231225001",
"@dcloudio/uni-cli-shared": "3.0.0-3090920231225001",
"@dcloudio/uni-stacktracey": "3.0.0-3090920231225001",
"@dcloudio/vite-plugin-uni": "3.0.0-3090920231225001",
"@types/node": "^20.8.10",
"@typescript-eslint/parser": "^6.10.0",
"@uni-helper/uni-app-types": "^0.5.9",
"@unocss/eslint-plugin": "^0.57.2",
"@vue/runtime-core": "^3.2.45",
"@vue/tsconfig": "^0.4.0",
"czg": "^1.7.1",
"eslint": "^8.53.0",
"lint-staged": "^15.0.2",
"miniprogram-ci": "^1.9.8",
"picocolors": "^1.0.0",
"postcss-html": "^1.5.0",
"postcss-scss": "^4.0.9",
"sass": "^1.69.5",
"simple-git-hooks": "^2.9.0",
"stylelint": "^15.11.0",
"stylelint-config-recess-order": "^4.3.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-config-standard-vue": "^1.0.0",
"stylelint-order": "^6.0.3",
"typescript": "^5.2.2",
"unocss": "^0.57.2",
"unocss-applet": "^0.7.7",
"unplugin-auto-import": "^0.16.7",
"unplugin-vue-components": "^0.25.2",
"vite": "^4.5.2",
"vite-plugin-imagemin": "^0.6.1",
"vue-tsc": "^1.8.22"
},
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx}": "eslint --fix",
"*.{scss,less,style,html}": "stylelint --fix",
"*.vue": [
"stylelint --fix"
]
},
"simple-git-hooks": {
"commit-msg": "node scripts/verifyCommit.js"
}
}

14956
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

19
scripts/verifyCommit.js Normal file
View File

@ -0,0 +1,19 @@
const path = require('node:path');
const fs = require('node:fs');
const pc = require('picocolors');
const msgPath = path.resolve('.git/COMMIT_EDITMSG');
const msg = fs.readFileSync(msgPath, 'utf-8').trim();
const commitRE =
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?: .{1,50}/;
if(!commitRE.test(msg)) {
console.log();
console.error(
` ${pc.bgRed(pc.white(' ERROR '))} ${pc.red('Git提交信息不符合规范!')}\n\n${pc.green(
'推荐使用命令 npm run cz 生成符合规范的Git提交信息'
)}\n`
);
process.exit(1);
}

22
src/App.vue Normal file
View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { mpUpdate } from '@/utils';
onLaunch(() => {
console.log('App Launch');
// #ifdef MP
mpUpdate();
// #endif
});
onShow(() => {
console.log('App Show');
});
onHide(() => {
console.log('App Hide');
});
</script>
<style lang="scss">
/* 每个页面公共css */
@import 'uview-plus/index.scss';
@import '@/static/styles/common.scss';
</style>

17
src/api/common/index.ts Normal file
View File

@ -0,0 +1,17 @@
/**
*
*/
import type { SendCodeParams, SendCodeResult, UploadImageResult } from './types';
import { post, upload } from '@/utils/request';
enum URL {
upload = '/common/upload',
sendCode = '/sendCode',
}
// 图片上传
export const uploadImage = (imagePath: string) =>
upload<UploadImageResult>({ url: URL.upload, filePath: imagePath, name: 'file' });
// 发送验证码
export const sendCode = (data: SendCodeParams) => post<SendCodeResult>({ url: URL.sendCode, data });

13
src/api/common/types.ts Normal file
View File

@ -0,0 +1,13 @@
export interface UploadImageResult {
file: string;
url: string;
}
export interface SendCodeParams {
phone: number;
code: number;
}
export interface SendCodeResult {
code: number;
}

4
src/api/index.ts Normal file
View File

@ -0,0 +1,4 @@
import * as CommonApi from './common';
import * as UserApi from './user';
export { CommonApi, UserApi };

18
src/api/user/index.ts Normal file
View File

@ -0,0 +1,18 @@
/**
*
*/
import type { LoginByCodeParams, LoginParams, LoginResult } from './types';
import { get, post } from '@/utils/request';
import type { UserState } from '@/store/modules/user/types';
enum URL {
login = '/user/login',
loginByCode = '/user/loginByCode',
logout = '/user/logout',
profile = '/user/profile',
}
export const getUserProfile = () => get<UserState>({ url: URL.profile });
export const login = (data: LoginParams) => post<LoginResult>({ url: URL.login, data });
export const loginByCode = (data: LoginByCodeParams) => post<any>({ url: URL.loginByCode, data });
export const logout = () => post<any>({ url: URL.logout });

15
src/api/user/types.ts Normal file
View File

@ -0,0 +1,15 @@
export interface LoginParams {
phone: string;
code: string;
}
export interface LoginByCodeParams {
code: string;
}
export interface LoginResult {
token: string;
user_id: number;
user_name: string;
avatar: string;
}

View File

@ -0,0 +1,103 @@
<template>
<view class="nav-wrap">
<view class="nav-title">
<u--image :show-loading="true" src="./static/logo.png" width="70px" height="70px" />
<view class="nav-info">
<view class="nav-info__title">
<text class="nav-info__title__text">uview-plus3</text>
</view>
<text class="nav-slogan">多平台快速开发的UI框架</text>
</view>
</view>
<text class="nav-desc">{{ desc }}</text>
</view>
</template>
<script>
export default {
name: 'PageNav',
props: {
desc: String,
title: String,
},
data() {
return {};
},
};
</script>
<style lang="scss" scoped>
.nav-wrap {
position: relative;
padding: 15px;
}
.lang {
position: absolute;
top: 15px;
right: 15px;
}
.nav-title {
/* #ifndef APP-NVUE */
display: flex;
justify-content: flex-start;
align-items: center;
/* #endif */
flex-direction: row;
}
.nav-info {
margin-left: 15px;
&__title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
&__text {
/* #ifndef APP-NVUE */
display: flex;
font-size: 25px;
text-align: left;
/* #endif */
color: $u-main-color;
font-weight: bold;
}
&__jump {
margin-left: 20px;
font-size: 12px;
color: $u-primary;
font-weight: normal;
}
}
}
.logo {
width: 70px;
height: 70px;
/* #ifndef APP-NVUE */
height: auto;
/* #endif */
}
.nav-slogan {
font-size: 14px;
color: $u-tips-color;
}
.nav-desc {
margin-top: 10px;
font-size: 14px;
color: $u-content-color;
line-height: 20px;
}
</style>

6
src/hooks/index.ts Normal file
View File

@ -0,0 +1,6 @@
import useShare from './use-share';
import useLoading from './use-loading';
import useModal from './use-modal';
import useClipboard from './use-clipboard';
export { useShare, useLoading, useModal, useClipboard };

View File

@ -0,0 +1,33 @@
/**
*
*/
interface SetClipboardDataOptions {
data: string;
showToast?: boolean;
}
export default function useClipboard() {
const setClipboardData = ({ data, showToast = true }: SetClipboardDataOptions) => {
return new Promise<string>((resolve, reject) => {
uni.setClipboardData({
data,
showToast,
success: ({ data }) => resolve(data),
fail: error => reject(error),
});
});
};
const getClipboardData = () => {
return new Promise<string>((resolve, reject) => {
uni.getClipboardData({
success: ({ data }) => resolve(data),
fail: error => reject(error),
});
});
};
return {
setClipboardData,
getClipboardData,
};
}

View File

@ -0,0 +1,19 @@
/**
* loading
*/
export default function useLoading() {
const showLoading = (content = '加载中') => {
uni.showLoading({
title: content,
mask: true,
});
};
const hideLoading = () => {
uni.hideLoading();
};
return {
showLoading,
hideLoading,
};
}

View File

@ -0,0 +1,21 @@
/**
* Dialog
*/
export default function useModal() {
const showModal = (content: string, options: UniApp.ShowModalOptions) => {
return new Promise((resolve, reject) => {
uni.showModal({
title: '温馨提示',
content,
showCancel: false,
confirmColor: '#1677FF',
success: res => resolve(res),
fail: () => reject(new Error('Alert 调用失败 !')),
...options,
})
})
}
return {
showModal,
}
}

View File

@ -0,0 +1,32 @@
/**
*
*/
interface UseShareOptions {
title?: string;
path?: string;
query?: string;
imageUrl?: string;
}
export default function useShare(options?: UseShareOptions) {
// #ifdef MP-WEIXIN
const title = options?.title ?? '';
const path = options?.path ?? '';
const query = options?.query ?? '';
const imageUrl = options?.imageUrl ?? '';
onShareAppMessage(() => {
return {
title,
path: path ? `${path}${query ? `?${query}` : ''}` : '',
imageUrl,
};
});
onShareTimeline(() => {
return {
title,
query: options?.query ?? '',
imageUrl,
};
});
// #endif
}

32
src/main.ts Normal file
View File

@ -0,0 +1,32 @@
import { createSSRApp } from 'vue';
// 引入UnoCSS
import 'uno.css';
// 引入uview-plus
import uviewPlus from 'uview-plus';
import App from '@/App.vue';
// 引入状态管理
import setupStore from '@/store';
// 引入请求封装
import setupRequest from '@/utils/request';
// 权限管理
import '@/permission';
// #ifdef VUE3
export function createApp() {
const app = createSSRApp(App);
app.use(uviewPlus);
// 状态管理
setupStore(app);
// 网络请求
setupRequest();
return {
app,
};
}
// #endif

47
src/manifest.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "SUKE-MP",
"appid": "",
"description": "",
"versionName": "1.0.0",
"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": {}
},
/* */
"mp-weixin":
{
"appid": "wx67a750d0ceed4d88",
"setting":
{
"urlCheck": false
},
"usingComponents": true
},
"uniStatistics":
{
"enable": false
},
"vueVersion": "3",
"h5":
{
"router":
{
"mode": "hash",
"base": "/uniapp-vue3-template/"
}
}
}

97
src/pages.json Normal file
View File

@ -0,0 +1,97 @@
{
"easycom": {
"custom": {
"^u--(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^up-(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue",
"^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$1.vue"
}
},
"pages": [
{
"path": "pages/home/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/qrcode/index",
"style": {
"navigationBarTitleText": "支付码"
}
},
{
"path": "pages/mall/index",
"style": {
"navigationBarTitleText": "商城"
}
},
{
"path": "pages/mine/index",
"style": {
"navigationBarTitleText": "我的"
}
}
],
"subPackages": [
{
"root": "pages/common",
"pages": [
{
"path": "login/index",
"navigationStyle": "custom"
},
{
"path": "webview/index",
"navigationBarTitleText": "网页"
}
]
}
],
"preloadRule": {
"pages/home/index": {
"network": "all",
"packages": [
"pages/common"
]
}
},
"tabBar": {
"color": "#ABABAB",
"selectedColor": "#333333",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/home/index",
"iconPath": "static/images/ic_tab_home_normal.png",
"selectedIconPath": "static/images/ic_tab_home_active.png",
"text": "首页"
},
{
"pagePath": "pages/qrcode/index",
"iconPath": "static/images/ic_tab_qrcode_normal.png",
"selectedIconPath": "static/images/ic_tab_qrcode_active.png",
"text": "支付码"
},
{
"pagePath": "pages/mall/index",
"iconPath": "static/images/ic_tab_mall_normal.png",
"selectedIconPath": "static/images/ic_tab_mall_active.png",
"text": "商城"
},
{
"pagePath": "pages/mine/index",
"iconPath": "static/images/ic_tab_mine_normal.png",
"selectedIconPath": "static/images/ic_tab_mine_active.png",
"text": "我的"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}

View File

@ -0,0 +1,167 @@
<template>
<view>
<view class="login-form-wrap">
<view class="title">
欢迎登录
</view>
<input v-model="tel" class="u-border-bottom" type="number" placeholder="请输入手机号">
<view class="u-border-bottom my-40rpx flex">
<input v-model="code" class="flex-1" type="number" placeholder="请输入验证码">
<view>
<u-code ref="uCodeRef" @change="codeChange" />
<u-button :text="tips" type="success" size="mini" @click="getCode" />
</view>
</view>
<button :style="[inputStyle]" class="login-btn" @tap="submit">
登录
</button>
<view class="alternative">
<view class="password">
密码登录
</view>
<view class="issue">
遇到问题
</view>
</view>
</view>
<view class="login-type-wrap">
<view class="item wechat">
<view class="icon">
<u-icon size="35" name="weixin-fill" color="rgb(83,194,64)" />
</view>
微信
</view>
<view class="item QQ">
<view class="icon">
<u-icon size="35" name="qq-fill" color="rgb(17,183,233)" />
</view>
QQ
</view>
</view>
<view class="hint">
登录代表同意
<text class="link">
用户协议隐私政策
</text>
并授权使用您的账号信息如昵称头像收获地址以便您统一管理
</view>
</view>
</template>
<script setup lang="ts">
import uCode from 'uview-plus/components/u-code/u-code.vue';
import { setToken } from '@/utils/auth';
const tel = ref<string>('18502811111');
const code = ref<string>('1234');
const tips = ref<string>();
const uCodeRef = ref<InstanceType<typeof uCode> | null>(null);
const inputStyle = computed<CSSStyleDeclaration>(() => {
const style = {} as CSSStyleDeclaration;
if (tel.value && code.value) {
style.color = '#fff';
style.backgroundColor = uni.$u.color.warning;
}
return style;
});
function codeChange(text: string) {
tips.value = text;
}
function getCode() {
if (uCodeRef.value?.canGetCode) {
//
uni.showLoading({
title: '正在获取验证码',
});
setTimeout(() => {
uni.hideLoading();
uni.$u.toast('验证码已发送');
//
uCodeRef.value?.start();
}, 1000);
}
else {
uni.$u.toast('倒计时结束后再发送');
}
}
function submit() {
if (uni.$u.test.mobile(tel.value)) {
setToken('1234567890');
uni.reLaunch({ url: '/pages/home/index' });
}
}
</script>
<style lang="scss" scoped>
.login-form-wrap {
margin: 80rpx auto 0;
width: 600rpx;
.title {
margin-bottom: 100rpx;
font-size: 60rpx;
text-align: left;
font-weight: 500;
}
input {
padding-bottom: 6rpx;
margin-bottom: 10rpx;
text-align: left;
}
.tips {
margin-top: 8rpx;
margin-bottom: 60rpx;
color: $u-info;
}
.login-btn {
padding: 12rpx 0;
font-size: 30rpx;
color: $u-tips-color;
background-color: rgb(253 243 208);
border: none;
&::after {
border: none;
}
}
.alternative {
display: flex;
justify-content: space-between;
margin-top: 30rpx;
color: $u-tips-color;
}
}
.login-type-wrap {
display: flex;
justify-content: space-between;
padding: 350rpx 150rpx 150rpx;
.item {
display: flex;
align-items: center;
font-size: 28rpx;
color: $u-content-color;
flex-direction: column;
}
}
.hint {
padding: 20rpx 40rpx;
font-size: 20rpx;
color: $u-tips-color;
.link {
color: $u-warning;
}
}
</style>

View File

@ -0,0 +1,12 @@
<template>
<web-view class="h-full" :src="url" />
</template>
<script setup lang="ts">
const url = ref<string>('');
onLoad((params: any) => {
if (params.url)
url.value = params.url;
});
</script>

18
src/pages/home/index.vue Normal file
View File

@ -0,0 +1,18 @@
<template>
<view class='flex flex-col items-center justify-center'>
<swiper class='swiper'>
<swiper-item v-for='(item, index) in bannerList' :key='index'>
<image :src='item' mode='aspectFill' />
</swiper-item>
</swiper>
</view>
</template>
<script setup lang='ts'>
import { useUserStore } from '@/store';
const store = useUserStore();
const bannerList = ref<string[]>(['1', '2', '3', '4']);
console.log('store.user_name', store.user_name);
</script>

25
src/pages/mall/index.vue Normal file
View File

@ -0,0 +1,25 @@
<template>
<view class="flex flex-col items-center justify-center">
<image
class="mb-50rpx mt-200rpx h-200rpx w-200rpx"
src="@/static/images/logo.png"
width="200rpx"
height="200rpx"
/>
<view class="flex justify-center">
<text class="font-size-36rpx color-gray-700">
{{ title }}
</text>
</view>
</view>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store';
const title = ref<string>();
title.value = import.meta.env.VITE_APP_TITLE;
const store = useUserStore();
console.log('store.user_name', store.user_name);
</script>

25
src/pages/mine/index.vue Normal file
View File

@ -0,0 +1,25 @@
<template>
<view class="flex flex-col items-center justify-center">
<image
class="mb-50rpx mt-200rpx h-200rpx w-200rpx"
src="@/static/images/logo.png"
width="200rpx"
height="200rpx"
/>
<view class="flex justify-center">
<text class="font-size-36rpx color-gray-700">
{{ title }}
</text>
</view>
</view>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store';
const title = ref<string>();
title.value = import.meta.env.VITE_APP_TITLE;
const store = useUserStore();
console.log('store.user_name', store.user_name);
</script>

View File

@ -0,0 +1,25 @@
<template>
<view class="flex flex-col items-center justify-center">
<image
class="mb-50rpx mt-200rpx h-200rpx w-200rpx"
src="@/static/images/logo.png"
width="200rpx"
height="200rpx"
/>
<view class="flex justify-center">
<text class="font-size-36rpx color-gray-700">
{{ title }}
</text>
</view>
</view>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store';
const title = ref<string>();
title.value = import.meta.env.VITE_APP_TITLE;
const store = useUserStore();
console.log('store.user_name', store.user_name);
</script>

37
src/permission.ts Normal file
View File

@ -0,0 +1,37 @@
import { getToken } from '@/utils/auth';
// 登录页面
const loginPage = '/pages/common/login/index';
// 页面白名单
const whiteList = ['/', '/pages/common/login/index', '/pages/home/index'];
// 检查地址白名单
function checkWhite(url: string) {
const path = url.split('?')[0];
return whiteList.includes(path);
}
// 页面跳转验证拦截器
const list = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'];
list.forEach((item) => {
uni.addInterceptor(item, {
invoke(to) {
if (getToken()) {
if (to.url === loginPage)
uni.reLaunch({ url: '/' });
return true;
}
else {
if (checkWhite(to.url))
return true;
uni.reLaunch({ url: loginPage });
return false;
}
},
fail(err) {
console.log(err);
},
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
src/static/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,4 @@
page {
font-size: 28rpx;
background-color: #f9f9f8;
}

20
src/store/index.ts Normal file
View File

@ -0,0 +1,20 @@
import type { App } from 'vue';
import { createPinia } from 'pinia';
// 导入子模块
import useAppStore from './modules/app';
import useUserStore from './modules/user';
// import piniaPersist from 'pinia-plugin-persist-uni';
// 安装pinia状态管理插件
function setupStore(app: App) {
const store = createPinia();
// store.use(piniaPersist);
app.use(store);
}
// 导出模块
export { useAppStore, useUserStore };
export default setupStore;

View File

@ -0,0 +1,58 @@
import { defineStore } from 'pinia';
import type { AppState } from './types';
const useAppStore = defineStore('app', {
state: (): AppState => ({
systemInfo: {} as UniApp.GetSystemInfoResult,
}),
getters: {
getSystemInfo(): UniApp.GetSystemInfoResult {
return this.systemInfo;
},
},
actions: {
setSystemInfo(info: UniApp.GetSystemInfoResult) {
this.systemInfo = info;
},
initSystemInfo() {
uni.getSystemInfo({
success: (res: UniApp.GetSystemInfoResult) => {
this.setSystemInfo(res);
},
fail: (err: any) => {
console.error(err);
},
});
},
checkUpdate() {
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate((res: UniApp.OnCheckForUpdateResult) => {
// 请求完新版本信息的回调
console.log(res.hasUpdate);
});
updateManager.onUpdateReady(() => {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success(res) {
if (res.confirm) {
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
updateManager.applyUpdate();
}
},
});
});
updateManager.onUpdateFailed((res: any) => {
console.error(res);
// 新的版本下载失败
uni.showToast({
title: '更新失败',
icon: 'error',
});
});
},
},
});
export default useAppStore;

View File

@ -0,0 +1,3 @@
export interface AppState {
systemInfo: UniApp.GetSystemInfoResult;
}

View File

@ -0,0 +1,82 @@
import { defineStore } from 'pinia';
import type { UserState, providerType } from './types';
import {
getUserProfile,
loginByCode,
login as userLogin,
logout as userLogout,
} from '@/api/user/index';
import { clearToken, setToken } from '@/utils/auth';
import type { LoginParams } from '@/api/user/types';
const useUserStore = defineStore('user', {
state: (): UserState => ({
user_id: '',
user_name: '江阳小道',
avatar: '',
token: '',
}),
getters: {
userInfo(state: UserState): UserState {
return { ...state };
},
},
actions: {
// 设置用户的信息
setInfo(partial: Partial<UserState>) {
this.$patch(partial);
},
// 重置用户信息
resetInfo() {
this.$reset();
},
// 获取用户信息
async info() {
const result = await getUserProfile();
this.setInfo(result);
},
// 异步登录并存储token
login(loginForm: LoginParams) {
return new Promise(async (resolve, reject) => {
try {
const result = await userLogin(loginForm);
const token = result?.token;
if (token) {
setToken(token);
}
resolve(result);
} catch (error) {
reject(error)
}
});
},
// Logout
async logout() {
await userLogout();
this.resetInfo();
clearToken();
},
// 小程序授权登录
authLogin(provider: providerType = 'weixin') {
return new Promise((resolve, reject) => {
uni.login({
provider,
success: async (result: UniApp.LoginRes) => {
if (result.code) {
const res = await loginByCode({ code: result.code });
resolve(res);
} else {
reject(new Error(result.errMsg));
}
},
fail: (err: any) => {
console.error(`login error: ${err}`);
reject(err);
},
});
});
},
},
});
export default useUserStore;

View File

@ -0,0 +1,16 @@
export type RoleType = '' | '*' | 'user';
export interface UserState {
user_id?: string;
user_name?: string;
avatar?: string;
token?: string;
}
export type providerType =
| 'weixin'
| 'qq'
| 'sinaweibo'
| 'xiaomi'
| 'apple'
| 'univerify'
| undefined;

1
src/uni.scss Normal file
View File

@ -0,0 +1 @@
@import 'uview-plus/theme.scss';

15
src/utils/auth/index.ts Normal file
View File

@ -0,0 +1,15 @@
const TokenKey = 'admin-token';
const TokenPrefix = 'Bearer ';
function isLogin() {
return !!uni.getStorageSync(TokenKey);
}
function getToken() {
return uni.getStorageSync(TokenKey);
}
function setToken(token: string) {
uni.setStorageSync(TokenKey, token);
}
function clearToken() {
uni.removeStorageSync(TokenKey);
}
export { TokenPrefix, isLogin, getToken, setToken, clearToken };

28
src/utils/common/index.ts Normal file
View File

@ -0,0 +1,28 @@
// 小程序更新检测
export function mpUpdate() {
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate((res) => {
// 请求完新版本信息的回调
console.log(res.hasUpdate);
});
updateManager.onUpdateReady(() => {
uni.showModal({
title: '更新提示',
content: '检测到新版本,是否下载新版本并重启小程序?',
success(res) {
if (res.confirm) {
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
updateManager.applyUpdate();
}
},
});
});
updateManager.onUpdateFailed(() => {
// 新的版本下载失败
uni.showModal({
title: '已经有新版本了哟~',
content: '新版本已经上线啦~,请您删除当前小程序,重新搜索打开哟~',
showCancel: false,
});
});
}

4
src/utils/index.ts Normal file
View File

@ -0,0 +1,4 @@
export * from './auth';
export * from './common';
export * from './modals';
export * from './request';

81
src/utils/modals/index.ts Normal file
View File

@ -0,0 +1,81 @@
interface IShowToastOptions {
title?: string
icon?: 'success' | 'loading' | 'error' | 'none'
image?: string
duration?: number
position?: 'top' | 'center' | 'bottom'
mask?: boolean
}
interface ILoadingOptions {
show?: (content?: string) => void
hide?: () => void
}
interface IShowModalOptions {
title?: string
content?: string
showCancel?: boolean
cancelText?: string
cancelColor?: string
confirmText?: string
confirmColor?: string
editable?: boolean
placeholderText?: string
}
/**
*
* @param {string} content
* @param {object} option
*/
export function Toast(content: string, option: IShowToastOptions = {}) {
uni.showToast({
title: content,
icon: 'none',
mask: true,
duration: 1500,
...option,
});
}
/**
* Loading
* @param {string} content
*/
export const Loading: ILoadingOptions = {
show: (content = '加载中') => {
uni.showLoading({
title: content,
mask: true,
});
},
hide: () => {
uni.hideLoading();
},
};
/**
* Dialog
* @param {string} content
* @param {object} option
*/
export function Dialog(content: string, option: IShowModalOptions = {}) {
option.showCancel = false;
return new Promise((resolve, reject) => {
uni.showModal({
title: '温馨提示',
content,
showCancel: false,
confirmColor: '#1677FF',
success(res) {
if (res.confirm)
resolve(res);
},
fail() {
reject(new Error('Alert 调用失败 !'));
},
...option,
});
});
}

View File

@ -0,0 +1,42 @@
// 引入配置
import type { HttpRequestConfig } from 'uview-plus/libs/luch-request/index';
import { requestInterceptors, responseInterceptors } from './interceptors';
import type { IResponse } from './type';
// 引入拦截器配置
export function setupRequest() {
uni.$u.http.setConfig((defaultConfig: HttpRequestConfig) => {
/* defaultConfig 为默认全局配置 */
defaultConfig.baseURL = import.meta.env.VITE_APP_BASE_API;
return defaultConfig;
});
requestInterceptors();
responseInterceptors();
}
export function request<T = any>(config: HttpRequestConfig): Promise<T> {
return new Promise((resolve) => {
uni.$u.http.request(config).then((res: IResponse) => {
const { result } = res;
resolve(result as T);
});
});
}
export function get<T = any>(config: HttpRequestConfig): Promise<T> {
return request({ ...config, method: 'GET' });
}
export function post<T = any>(config: HttpRequestConfig): Promise<T> {
return request({ ...config, method: 'POST' });
}
export function upload<T = any>(config: HttpRequestConfig): Promise<T> {
return request({ ...config, method: 'UPLOAD' });
}
export function download<T = any>(config: HttpRequestConfig): Promise<T> {
return request({ ...config, method: 'DOWNLOAD' });
}
export default setupRequest;

View File

@ -0,0 +1,104 @@
import type {
HttpError,
HttpRequestConfig,
HttpResponse,
} from 'uview-plus/libs/luch-request/index';
import { showMessage } from './status';
import { getToken } from '@/utils/auth';
import useUserStore from '@/store/modules/user';
// 是否正在刷新token的标记
let isRefreshing: boolean = false;
// 重试队列,每一项将是一个待执行的函数形式
let requestQueue: (() => void)[] = [];
function requestInterceptors() {
/**
*
* @param {Object} http
*/
uni.$u.http.interceptors.request.use(
(config: HttpRequestConfig) => {
// 可使用async await 做异步操作
// 初始化请求拦截器时会执行此方法此时data为undefined赋予默认{}
config.data = config.data || {};
// token设置
const token = getToken();
if (token && config.header) {
config.header.token = token;
}
return config;
},
(
config: any, // 可使用async await 做异步操作
) => Promise.reject(config),
);
}
function responseInterceptors() {
/**
*
* @param {Object} http
*/
uni.$u.http.interceptors.response.use(
async (response: HttpResponse) => {
/* 对响应成功做点什么 可使用async await 做异步操作 */
const data = response.data;
// 配置参数
const config = response.config;
// 自定义参数
const custom = config?.custom;
// 请求成功则返回结果
if (data.code === 200) {
return data || {};
}
// 登录状态失效,重新登录
if (data.code === 401) {
// 是否在获取token中,防止重复获取
if (!isRefreshing) {
// 修改登录状态为true
isRefreshing = true;
await useUserStore().authLogin();
// 登录完成之后,开始执行队列请求
requestQueue.forEach(cb => cb());
// 重试完了清空这个队列
requestQueue = [];
isRefreshing = false;
// 重新执行本次请求
return uni.$u.http.request(config);
} else {
return new Promise(resolve => {
// 将resolve放进队列用一个函数形式来保存等登录后直接执行
requestQueue.push(() => {
resolve(uni.$u.http.request(config));
});
});
}
}
// 如果没有显式定义custom的toast参数为false的话默认对报错进行toast弹出提示
if (custom?.toast !== false) {
uni.$u.toast(data.message);
}
// 如果需要catch返回则进行reject
if (custom?.catch) {
return Promise.reject(data);
} else {
// 否则返回一个pending中的promise
return new Promise(() => {});
}
},
(response: HttpError) => {
if (response.statusCode) {
// 请求已发出但是不在2xx的范围
showMessage(response.statusCode);
return Promise.reject(response.data);
}
showMessage('网络连接异常,请稍后再试!');
},
);
}
export { requestInterceptors, responseInterceptors };

View File

@ -0,0 +1,41 @@
export const showMessage = (status: number | string): string => {
let message = '';
switch (status) {
case 400:
message = '请求错误(400)';
break;
case 401:
message = '未授权,请重新登录(401)';
break;
case 403:
message = '拒绝访问(403)';
break;
case 404:
message = '请求出错(404)';
break;
case 408:
message = '请求超时(408)';
break;
case 500:
message = '服务器错误(500)';
break;
case 501:
message = '服务未实现(501)';
break;
case 502:
message = '网络错误(502)';
break;
case 503:
message = '服务不可用(503)';
break;
case 504:
message = '网络超时(504)';
break;
case 505:
message = 'HTTP版本不受支持(505)';
break;
default:
message = `连接出错(${status})!`;
}
return `${message},请检查网络或联系管理员!`;
};

View File

@ -0,0 +1,7 @@
// 返回res.data的interface
export interface IResponse<T = any> {
code: number | string;
result: T;
message: string;
status: string | number;
}

116
stylelint.config.js Normal file
View File

@ -0,0 +1,116 @@
module.exports = {
"extends": ["stylelint-config-standard", "stylelint-config-standard-vue"],
"plugins": ["stylelint-order"],
"overrides": [
{
"files": ["**/*.(scss|css|vue|html)"],
"customSyntax": "postcss-scss"
},
{
"files": ["**/*.(html|vue)"],
"customSyntax": "postcss-html"
}
],
"ignoreFiles": [
"**/*.js",
"**/*.jsx",
"**/*.tsx",
"**/*.ts",
"**/*.json",
"**/*.md",
"**/*.yaml",
"dist/*",
"uni_modules/*"
],
"rules": {
"import-notation": "string",
"unit-no-unknown": [true, { "ignoreUnits": ["rpx"] }],
"no-descending-specificity": null,
"selector-pseudo-element-no-unknown": [
true,
{
"ignorePseudoElements": ["v-deep"]
}
],
"selector-pseudo-class-no-unknown": [
true,
{
"ignorePseudoClasses": ["deep"]
}
],
"selector-type-no-unknown": [true, { "ignoreTypes": ["page", "radio", "checkbox", "scroll-view"] }],
"order/properties-order": [
"position",
"top",
"right",
"bottom",
"left",
"z-index",
"display",
"justify-content",
"align-items",
"float",
"clear",
"overflow",
"overflow-x",
"overflow-y",
"padding",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"margin",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"width",
"min-width",
"max-width",
"height",
"min-height",
"max-height",
"font-size",
"font-family",
"text-align",
"text-justify",
"text-indent",
"text-overflow",
"text-decoration",
"white-space",
"color",
"background",
"background-position",
"background-repeat",
"background-size",
"background-color",
"background-clip",
"border",
"border-style",
"border-width",
"border-color",
"border-top-style",
"border-top-width",
"border-top-color",
"border-right-style",
"border-right-width",
"border-right-color",
"border-bottom-style",
"border-bottom-width",
"border-bottom-color",
"border-left-style",
"border-left-width",
"border-left-color",
"border-radius",
"opacity",
"filter",
"list-style",
"outline",
"visibility",
"box-shadow",
"text-shadow",
"resize",
"transition"
]
}
}

37
tsconfig.json Normal file
View File

@ -0,0 +1,37 @@
{
"compilerOptions": {
"sourceMap": true,
"baseUrl": ".",
"module": "ESNext",
"target": "es2016",
"lib": ["DOM", "ESNext"],
"strict": true,
"jsx": "preserve",
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"@/*": ["src/*"]
},
"types": ["@dcloudio/types", "@uni-helper/uni-app-types"]
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"types/**/*.d.ts",
"types/**/*.ts"
],
"exclude": ["dist", "node_modules", "uni_modules"],
// uni-app Component type prompt
"vueCompilerOptions": {
"nativeTags": ["block", "component", "template", "slot"]
}
}

288
types/auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,288 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const createPinia: typeof import('pinia')['createPinia']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
const effectScope: typeof import('vue')['effectScope']
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onAddToFavorites: typeof import('@dcloudio/uni-app')['onAddToFavorites']
const onBackPress: typeof import('@dcloudio/uni-app')['onBackPress']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onError: typeof import('@dcloudio/uni-app')['onError']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onHide: typeof import('@dcloudio/uni-app')['onHide']
const onLaunch: typeof import('@dcloudio/uni-app')['onLaunch']
const onLoad: typeof import('@dcloudio/uni-app')['onLoad']
const onMounted: typeof import('vue')['onMounted']
const onNavigationBarButtonTap: typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']
const onNavigationBarSearchInputChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']
const onNavigationBarSearchInputClicked: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']
const onNavigationBarSearchInputConfirmed: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']
const onNavigationBarSearchInputFocusChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']
const onPageNotFound: typeof import('@dcloudio/uni-app')['onPageNotFound']
const onPageScroll: typeof import('@dcloudio/uni-app')['onPageScroll']
const onPullDownRefresh: typeof import('@dcloudio/uni-app')['onPullDownRefresh']
const onReachBottom: typeof import('@dcloudio/uni-app')['onReachBottom']
const onReady: typeof import('@dcloudio/uni-app')['onReady']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onResize: typeof import('@dcloudio/uni-app')['onResize']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onShareAppMessage: typeof import('@dcloudio/uni-app')['onShareAppMessage']
const onShareTimeline: typeof import('@dcloudio/uni-app')['onShareTimeline']
const onShow: typeof import('@dcloudio/uni-app')['onShow']
const onTabItemTap: typeof import('@dcloudio/uni-app')['onTabItemTap']
const onThemeChange: typeof import('@dcloudio/uni-app')['onThemeChange']
const onUnhandledRejection: typeof import('@dcloudio/uni-app')['onUnhandledRejection']
const onUnload: typeof import('@dcloudio/uni-app')['onUnload']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const storeToRefs: typeof import('pinia')['storeToRefs']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
import('vue')
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module 'vue' {
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onAddToFavorites: UnwrapRef<typeof import('@dcloudio/uni-app')['onAddToFavorites']>
readonly onBackPress: UnwrapRef<typeof import('@dcloudio/uni-app')['onBackPress']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onError: UnwrapRef<typeof import('@dcloudio/uni-app')['onError']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onHide: UnwrapRef<typeof import('@dcloudio/uni-app')['onHide']>
readonly onLaunch: UnwrapRef<typeof import('@dcloudio/uni-app')['onLaunch']>
readonly onLoad: UnwrapRef<typeof import('@dcloudio/uni-app')['onLoad']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onNavigationBarButtonTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']>
readonly onNavigationBarSearchInputChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']>
readonly onNavigationBarSearchInputClicked: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']>
readonly onNavigationBarSearchInputConfirmed: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']>
readonly onNavigationBarSearchInputFocusChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']>
readonly onPageNotFound: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageNotFound']>
readonly onPageScroll: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageScroll']>
readonly onPullDownRefresh: UnwrapRef<typeof import('@dcloudio/uni-app')['onPullDownRefresh']>
readonly onReachBottom: UnwrapRef<typeof import('@dcloudio/uni-app')['onReachBottom']>
readonly onReady: UnwrapRef<typeof import('@dcloudio/uni-app')['onReady']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onResize: UnwrapRef<typeof import('@dcloudio/uni-app')['onResize']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onShareAppMessage: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareAppMessage']>
readonly onShareTimeline: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareTimeline']>
readonly onShow: UnwrapRef<typeof import('@dcloudio/uni-app')['onShow']>
readonly onTabItemTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onTabItemTap']>
readonly onThemeChange: UnwrapRef<typeof import('@dcloudio/uni-app')['onThemeChange']>
readonly onUnhandledRejection: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnhandledRejection']>
readonly onUnload: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnload']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
}
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onAddToFavorites: UnwrapRef<typeof import('@dcloudio/uni-app')['onAddToFavorites']>
readonly onBackPress: UnwrapRef<typeof import('@dcloudio/uni-app')['onBackPress']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onError: UnwrapRef<typeof import('@dcloudio/uni-app')['onError']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onHide: UnwrapRef<typeof import('@dcloudio/uni-app')['onHide']>
readonly onLaunch: UnwrapRef<typeof import('@dcloudio/uni-app')['onLaunch']>
readonly onLoad: UnwrapRef<typeof import('@dcloudio/uni-app')['onLoad']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onNavigationBarButtonTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']>
readonly onNavigationBarSearchInputChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']>
readonly onNavigationBarSearchInputClicked: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']>
readonly onNavigationBarSearchInputConfirmed: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']>
readonly onNavigationBarSearchInputFocusChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']>
readonly onPageNotFound: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageNotFound']>
readonly onPageScroll: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageScroll']>
readonly onPullDownRefresh: UnwrapRef<typeof import('@dcloudio/uni-app')['onPullDownRefresh']>
readonly onReachBottom: UnwrapRef<typeof import('@dcloudio/uni-app')['onReachBottom']>
readonly onReady: UnwrapRef<typeof import('@dcloudio/uni-app')['onReady']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onResize: UnwrapRef<typeof import('@dcloudio/uni-app')['onResize']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onShareAppMessage: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareAppMessage']>
readonly onShareTimeline: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareTimeline']>
readonly onShow: UnwrapRef<typeof import('@dcloudio/uni-app')['onShow']>
readonly onTabItemTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onTabItemTap']>
readonly onThemeChange: UnwrapRef<typeof import('@dcloudio/uni-app')['onThemeChange']>
readonly onUnhandledRejection: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnhandledRejection']>
readonly onUnload: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnload']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
}
}

14
types/components.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
PageNav: typeof import('./../src/components/page-nav/page-nav.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}

27
types/global.d.ts vendored Normal file
View File

@ -0,0 +1,27 @@
import type { VNodeChild } from 'vue';
declare global {
// vue
declare type VueNode = VNodeChild | JSX.Element;
declare type TimeoutHandle = ReturnType<typeof setTimeout>;
declare type IntervalHandle = ReturnType<typeof setInterval>;
interface ImportMetaEnv extends ViteEnv {
__: unknown;
}
declare interface ViteEnv {
VITE_APP_TITLE?: string;
VITE_APP_BASE_API: string;
VITE_DROP_CONSOLE: Boolean;
}
declare function parseInt(s: string | number, radix?: number): number;
declare function parseFloat(string: string | number): number;
declare interface Uni {
$u: any
}
}

10
types/module.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare module 'uview-plus';
declare module 'uview-plus/libs/mixin/mpShare.js';

57
uno.config.ts Normal file
View File

@ -0,0 +1,57 @@
import type { Preset, SourceCodeTransformer } from 'unocss';
import {
defineConfig,
presetAttributify,
presetUno,
transformerDirectives,
transformerVariantGroup,
} from 'unocss';
import {
presetApplet,
presetRemRpx,
transformerApplet,
transformerAttributify,
} from 'unocss-applet';
// eslint-disable-next-line n/prefer-global/process
const isApplet = process.env?.UNI_PLATFORM?.startsWith('mp-') ?? false;
const presets: Preset[] = [];
const transformers: SourceCodeTransformer[] = [];
if (isApplet) {
/**
* UnoCSS Applet
* @see https://github.com/unocss-applet/unocss-applet
*/
presets.push(presetApplet());
presets.push(presetRemRpx()); // 如果需要使用 rem 转 rpx 单位,需要启用此插件
transformers.push(transformerAttributify({ ignoreAttributes: ['block'] }));
transformers.push(transformerApplet());
} else {
presets.push(presetUno());
presets.push(presetAttributify());
}
export default defineConfig({
presets,
/**
*
* @see https://github.com/unocss/unocss#shortcuts
*/
shortcuts: [
['center', 'flex justify-center items-center'],
[
'btn',
'px-4 py-1 rounded inline-block bg-teal-600 text-white cursor-pointer hover:bg-teal-700 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50',
],
[
'icon-btn',
'text-[0.9em] inline-block cursor-pointer select-none opacity-75 transition duration-200 ease-in-out hover:opacity-100 hover:text-teal-600 !outline-none',
],
],
transformers: [
transformerDirectives(), // 启用 @apply 功能
transformerVariantGroup(), // 启用 () 分组功能
...transformers,
],
});

27
vite.config.ts Normal file
View File

@ -0,0 +1,27 @@
import { resolve } from 'node:path';
import { defineConfig } from 'vite';
import type { UserConfig } from 'vite';
import createVitePlugins from './build/vite/plugins';
import proxy from './build/vite/proxy';
// https://vitejs.dev/config/
export default defineConfig((): UserConfig => {
const isBuild = process.env.NODE_ENV === 'production';
return {
resolve: {
// https://cn.vitejs.dev/config/#resolve-alias
alias: {
// 设置别名
'@': resolve(__dirname, './src'),
},
},
// vite 相关配置
server: {
port: 8080,
host: true,
open: true,
proxy,
},
plugins: createVitePlugins(isBuild),
};
});