commit b817e31b69b3dd84fb8e7a27624a0580e7492ad7
Author: wangxiaowei <1121133807@qq.com>
Date: Tue Aug 12 14:26:38 2025 +0800
初始化仓库
diff --git a/.commitlintrc.cjs b/.commitlintrc.cjs
new file mode 100644
index 0000000..98ee7df
--- /dev/null
+++ b/.commitlintrc.cjs
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: ['@commitlint/config-conventional'],
+}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..7f09864
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,13 @@
+root = true
+
+[*] # 表示所有文件适用
+charset = utf-8 # 设置文件字符集为 utf-8
+indent_style = space # 缩进风格(tab | space)
+indent_size = 2 # 缩进大小
+end_of_line = lf # 控制换行类型(lf | cr | crlf)
+trim_trailing_whitespace = true # 去除行首的任意空白字符
+insert_final_newline = true # 始终在文件末尾插入一个新行
+
+[*.md] # 表示仅 md 文件适用以下规则
+max_line_length = off # 关闭最大行长度限制
+trim_trailing_whitespace = false # 关闭末尾空格修剪
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..34ddbdf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,44 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+*.local
+
+# Editor directories and files
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+.hbuilderx
+
+.stylelintcache
+.eslintcache
+
+docs/.vitepress/dist
+docs/.vitepress/cache
+
+src/types
+
+# lock 文件还是不要了,我主要的版本写死就好了
+# pnpm-lock.yaml
+# package-lock.json
+
+# TIPS:如果某些文件已经加入了版本管理,现在重新加入 .gitignore 是不生效的,需要执行下面的操作
+# `git rm -r --cached .` 然后提交 commit 即可。
+
+# git rm -r --cached file1 file2 ## 针对某些文件
+# git rm -r --cached dir1 dir2 ## 针对某些文件夹
+# git rm -r --cached . ## 针对所有文件
+
+# 更新 uni-app 官方版本
+# npx @dcloudio/uvm@latest
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..10ecfe2
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,8 @@
+# registry = https://registry.npmjs.org
+registry = https://registry.npmmirror.com
+
+strict-peer-dependencies=false
+auto-install-peers=true
+shamefully-hoist=true
+ignore-workspace-root-check=true
+install-workspace-root=true
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..39b01e3
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,19 @@
+{
+ "recommendations": [
+ "vue.volar",
+ "stylelint.vscode-stylelint",
+ "esbenp.prettier-vscode",
+ "dbaeumer.vscode-eslint",
+ "antfu.unocss",
+ "antfu.iconify",
+ "evils.uniapp-vscode",
+ "uni-helper.uni-helper-vscode",
+ "uni-helper.uni-app-schemas-vscode",
+ "uni-helper.uni-highlight-vscode",
+ "uni-helper.uni-ui-snippets-vscode",
+ "uni-helper.uni-app-snippets-vscode",
+ "streetsidesoftware.code-spell-checker",
+ "foxundermoon.shell-format",
+ "christian-kohler.path-intellisense"
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..4fbb64b
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,93 @@
+{
+ // 配置语言的文件关联
+ "files.associations": {
+ "pages.json": "jsonc", // pages.json 可以写注释
+ "manifest.json": "jsonc" // manifest.json 可以写注释
+ },
+
+ "stylelint.enable": false, // 禁用 stylelint
+ "css.validate": false, // 禁用 CSS 内置验证
+ "scss.validate": false, // 禁用 SCSS 内置验证
+ "less.validate": false, // 禁用 LESS 内置验证
+
+ "typescript.tsdk": "node_modules\\typescript\\lib",
+ "explorer.fileNesting.enabled": true,
+ "explorer.fileNesting.expand": false,
+ "explorer.fileNesting.patterns": {
+ "README.md": "index.html,favicon.ico,robots.txt,CHANGELOG.md",
+ "pages.config.ts": "manifest.config.ts,openapi-ts-request.config.ts",
+ "package.json": "tsconfig.json,pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc",
+ "eslint.config.mjs": ".commitlintrc.*,.prettier*,.editorconfig,.commitlint.cjs,.eslint*"
+ },
+
+ // 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", "fixable": true },
+ { "rule": "format/*", "severity": "off", "fixable": true },
+ { "rule": "*-indent", "severity": "off", "fixable": true },
+ { "rule": "*-spacing", "severity": "off", "fixable": true },
+ { "rule": "*-spaces", "severity": "off", "fixable": true },
+ { "rule": "*-order", "severity": "off", "fixable": true },
+ { "rule": "*-dangle", "severity": "off", "fixable": true },
+ { "rule": "*-newline", "severity": "off", "fixable": true },
+ { "rule": "*quotes", "severity": "off", "fixable": true },
+ { "rule": "*semi", "severity": "off", "fixable": true }
+ ],
+
+ // Enable eslint for all supported languages
+ "eslint.validate": [
+ "javascript",
+ "javascriptreact",
+ "typescript",
+ "typescriptreact",
+ "vue",
+ "html",
+ "markdown",
+ "json",
+ "jsonc",
+ "yaml",
+ "toml",
+ "xml",
+ "gql",
+ "graphql",
+ "astro",
+ "svelte",
+ "css",
+ "less",
+ "scss",
+ "pcss",
+ "postcss"
+ ],
+ "cSpell.words": [
+ "alova",
+ "Aplipay",
+ "climblee",
+ "commitlint",
+ "dcloudio",
+ "iconfont",
+ "oxlint",
+ "qrcode",
+ "refresherrefresh",
+ "scrolltolower",
+ "tabbar",
+ "Toutiao",
+ "uniapp",
+ "unibest",
+ "unocss",
+ "uview",
+ "uvui",
+ "Wechat",
+ "WechatMiniprogram",
+ "Weixin"
+ ]
+}
diff --git a/.vscode/vue3.code-snippets b/.vscode/vue3.code-snippets
new file mode 100644
index 0000000..a196e90
--- /dev/null
+++ b/.vscode/vue3.code-snippets
@@ -0,0 +1,68 @@
+{
+ // Place your unibest 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
+ // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
+ // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
+ // used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
+ // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
+ // Placeholders with the same ids are connected.
+ // Example:
+ // "Print to console": {
+ // "scope": "javascript,typescript",
+ // "prefix": "log",
+ // "body": [
+ // "console.log('$1');",
+ // "$2"
+ // ],
+ // "description": "Log output to console"
+ // }
+ "Print unibest Vue3 SFC": {
+ "scope": "vue",
+ "prefix": "v3",
+ "body": [
+ "",
+ "{",
+ " \"layout\": \"default\",",
+ " \"style\": {",
+ " \"navigationBarTitleText\": \"$1\"",
+ " }",
+ "}",
+ "\n",
+ "\n",
+ "",
+ " $3",
+ "\n",
+ "\n",
+ ],
+ },
+ "Print unibest style": {
+ "scope": "vue",
+ "prefix": "st",
+ "body": [
+ "\n"
+ ],
+ },
+ "Print unibest script": {
+ "scope": "vue",
+ "prefix": "sc",
+ "body": [
+ "\n"
+ ],
+ },
+ "Print unibest template": {
+ "scope": "vue",
+ "prefix": "te",
+ "body": [
+ "",
+ " $1",
+ "\n"
+ ],
+ },
+}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9e91d10
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 菲鸽
+
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ad370f6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+
+
+ 茶址
+
+
+
+ 使用Unibest开发,版本号3.8.2
+
\ No newline at end of file
diff --git a/env/.env b/env/.env
new file mode 100644
index 0000000..399f808
--- /dev/null
+++ b/env/.env
@@ -0,0 +1,22 @@
+VITE_APP_TITLE = 'unibest'
+VITE_APP_PORT = 9000
+
+VITE_UNI_APPID = '__UNI__D1E5001'
+VITE_WX_APPID = 'wxa2abb91f64032a2b'
+
+# h5部署网站的base,配置到 manifest.config.ts 里的 h5.router.base
+VITE_APP_PUBLIC_BASE=/
+
+# 登录页面
+VITE_LOGIN_URL = '/pages/login/index'
+# 第一个请求地址
+VITE_SERVER_BASEURL = 'https://ukw0y1.laf.run'
+
+VITE_UPLOAD_BASEURL = 'https://ukw0y1.laf.run/upload'
+
+# h5是否需要配置代理
+VITE_APP_PROXY=false
+VITE_APP_PROXY_PREFIX = '/api'
+
+# 第二个请求地址 (目前alova中可以使用)
+VITE_API_SECONDARY_URL = 'https://ukw0y1.laf.run'
\ No newline at end of file
diff --git a/env/.env.development b/env/.env.development
new file mode 100644
index 0000000..04fa273
--- /dev/null
+++ b/env/.env.development
@@ -0,0 +1,6 @@
+# 变量必须以 VITE_ 为前缀才能暴露给外部读取
+NODE_ENV = 'development'
+# 是否去除console 和 debugger
+VITE_DELETE_CONSOLE = false
+# 是否开启sourcemap
+VITE_SHOW_SOURCEMAP = true
diff --git a/env/.env.production b/env/.env.production
new file mode 100644
index 0000000..8a1b50c
--- /dev/null
+++ b/env/.env.production
@@ -0,0 +1,6 @@
+# 变量必须以 VITE_ 为前缀才能暴露给外部读取
+NODE_ENV = 'development'
+# 是否去除console 和 debugger
+VITE_DELETE_CONSOLE = true
+# 是否开启sourcemap
+VITE_SHOW_SOURCEMAP = false
diff --git a/env/.env.test b/env/.env.test
new file mode 100644
index 0000000..e22f765
--- /dev/null
+++ b/env/.env.test
@@ -0,0 +1,4 @@
+# 变量必须以 VITE_ 为前缀才能暴露给外部读取
+NODE_ENV = 'development'
+# 是否去除console 和 debugger
+VITE_DELETE_CONSOLE = false
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..7383bed
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,49 @@
+import uniHelper from '@uni-helper/eslint-config'
+
+export default uniHelper({
+ unocss: true,
+ vue: true,
+ markdown: false,
+ ignores: [
+ 'src/uni_modules/',
+ 'dist',
+ // unplugin-auto-import 生成的类型文件,每次提交都改变,所以加入这里吧,与 .gitignore 配合使用
+ 'auto-import.d.ts',
+ // vite-plugin-uni-pages 生成的类型文件,每次切换分支都一堆不同的,所以直接 .gitignore
+ 'uni-pages.d.ts',
+ // 插件生成的文件
+ 'src/pages.json',
+ 'src/manifest.json',
+ // 忽略自动生成文件
+ 'src/service/app/**',
+ ],
+ rules: {
+ 'no-console': 'off',
+ 'no-unused-vars': 'off',
+ 'vue/no-unused-refs': 'off',
+ 'unused-imports/no-unused-vars': 'off',
+ 'eslint-comments/no-unlimited-disable': 'off',
+ 'jsdoc/check-param-names': 'off',
+ 'jsdoc/require-returns-description': 'off',
+ 'ts/no-empty-object-type': 'off',
+ 'no-extend-native': 'off',
+ 'vue/singleline-html-element-content-newline': [
+ 'error',
+ {
+ externalIgnores: ['text'],
+ },
+ ],
+ },
+ formatters: {
+ /**
+ * Format CSS, LESS, SCSS files, also the `
diff --git a/src/api/foo-alova.ts b/src/api/foo-alova.ts
new file mode 100644
index 0000000..de35095
--- /dev/null
+++ b/src/api/foo-alova.ts
@@ -0,0 +1,17 @@
+import { API_DOMAINS, http } from '@/http/alova'
+
+export interface IFoo {
+ id: number
+ name: string
+}
+
+export function foo() {
+ return http.Get('/foo', {
+ params: {
+ name: '菲鸽',
+ page: 1,
+ pageSize: 10,
+ },
+ meta: { domain: API_DOMAINS.SECONDARY }, // 用于切换请求地址
+ })
+}
diff --git a/src/api/foo-vue-query.ts b/src/api/foo-vue-query.ts
new file mode 100644
index 0000000..c7cbdd4
--- /dev/null
+++ b/src/api/foo-vue-query.ts
@@ -0,0 +1,11 @@
+import { queryOptions } from '@tanstack/vue-query'
+import { getFooAPI } from './foo'
+
+export function getFooQueryOptions(name: string) {
+ return queryOptions({
+ queryFn: async ({ queryKey }) => {
+ return getFooAPI(queryKey[1])
+ },
+ queryKey: ['getFoo', name],
+ })
+}
diff --git a/src/api/foo.ts b/src/api/foo.ts
new file mode 100644
index 0000000..cd8b188
--- /dev/null
+++ b/src/api/foo.ts
@@ -0,0 +1,43 @@
+import { http } from '@/http/http'
+
+export interface IFoo {
+ id: number
+ name: string
+}
+
+export function foo() {
+ return http.Get('/foo', {
+ params: {
+ name: '菲鸽',
+ page: 1,
+ pageSize: 10,
+ },
+ })
+}
+
+export interface IFooItem {
+ id: string
+ name: string
+}
+
+/** GET 请求 */
+export function getFooAPI(name: string) {
+ return http.get('/foo', { name })
+}
+/** GET 请求;支持 传递 header 的范例 */
+export function getFooAPI2(name: string) {
+ return http.get('/foo', { name }, { 'Content-Type-100': '100' })
+}
+
+/** POST 请求 */
+export function postFooAPI(name: string) {
+ return http.post('/foo', { name })
+}
+/** POST 请求;需要传递 query 参数的范例;微信小程序经常有同时需要query参数和body参数的场景 */
+export function postFooAPI2(name: string) {
+ return http.post('/foo', { name })
+}
+/** POST 请求;支持 传递 header 的范例 */
+export function postFooAPI3(name: string) {
+ return http.post('/foo', { name }, { name }, { 'Content-Type-100': '100' })
+}
diff --git a/src/api/login.ts b/src/api/login.ts
new file mode 100644
index 0000000..de4266d
--- /dev/null
+++ b/src/api/login.ts
@@ -0,0 +1,83 @@
+import type { ICaptcha, IUpdateInfo, IUpdatePassword, IUserInfoVo, IUserLogin } from './types/login'
+import { http } from '@/http/http'
+
+/**
+ * 登录表单
+ */
+export interface ILoginForm {
+ username: string
+ password: string
+ code: string
+ uuid: string
+}
+
+/**
+ * 获取验证码
+ * @returns ICaptcha 验证码
+ */
+export function getCode() {
+ return http.get('/user/getCode')
+}
+
+/**
+ * 用户登录
+ * @param loginForm 登录表单
+ */
+export function login(loginForm: ILoginForm) {
+ return http.post('/user/login', loginForm)
+}
+
+/**
+ * 获取用户信息
+ */
+export function getUserInfo() {
+ return http.get('/user/info')
+}
+
+/**
+ * 退出登录
+ */
+export function logout() {
+ return http.get('/user/logout')
+}
+
+/**
+ * 修改用户信息
+ */
+export function updateInfo(data: IUpdateInfo) {
+ return http.post('/user/updateInfo', data)
+}
+
+/**
+ * 修改用户密码
+ */
+export function updateUserPassword(data: IUpdatePassword) {
+ return http.post('/user/updatePassword', data)
+}
+
+/**
+ * 获取微信登录凭证
+ * @returns Promise 包含微信登录凭证(code)
+ */
+export function getWxCode() {
+ return new Promise((resolve, reject) => {
+ uni.login({
+ provider: 'weixin',
+ success: res => resolve(res),
+ fail: err => reject(new Error(err)),
+ })
+ })
+}
+
+/**
+ * 微信登录参数
+ */
+
+/**
+ * 微信登录
+ * @param params 微信登录参数,包含code
+ * @returns Promise 包含登录结果
+ */
+export function wxLogin(data: { code: string }) {
+ return http.post('/user/wxLogin', data)
+}
diff --git a/src/api/types/login.ts b/src/api/types/login.ts
new file mode 100644
index 0000000..d0638cf
--- /dev/null
+++ b/src/api/types/login.ts
@@ -0,0 +1,57 @@
+/**
+ * 用户信息
+ */
+export interface IUserInfoVo {
+ id: number
+ username: string
+ avatar: string
+ token: string
+}
+
+/**
+ * 登录返回的信息
+ */
+export interface IUserLogin {
+ id: string
+ username: string
+ token: string
+}
+
+/**
+ * 获取验证码
+ */
+export interface ICaptcha {
+ captchaEnabled: boolean
+ uuid: string
+ image: string
+}
+/**
+ * 上传成功的信息
+ */
+export interface IUploadSuccessInfo {
+ fileId: number
+ originalName: string
+ fileName: string
+ storagePath: string
+ fileHash: string
+ fileType: string
+ fileBusinessType: string
+ fileSize: number
+}
+/**
+ * 更新用户信息
+ */
+export interface IUpdateInfo {
+ id: number
+ name: string
+ sex: string
+}
+/**
+ * 更新用户信息
+ */
+export interface IUpdatePassword {
+ id: number
+ oldPassword: string
+ newPassword: string
+ confirmPassword: string
+}
diff --git a/src/components/.gitkeep b/src/components/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/env.d.ts b/src/env.d.ts
new file mode 100644
index 0000000..b4a2c97
--- /dev/null
+++ b/src/env.d.ts
@@ -0,0 +1,34 @@
+///
+///
+
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
+
+interface ImportMetaEnv {
+ /** 网站标题,应用名称 */
+ readonly VITE_APP_TITLE: string
+ /** 服务端口号 */
+ readonly VITE_SERVER_PORT: string
+ /** 后台接口地址 */
+ readonly VITE_SERVER_BASEURL: string
+ /** H5是否需要代理 */
+ readonly VITE_APP_PROXY: 'true' | 'false'
+ /** H5是否需要代理,需要的话有个前缀 */
+ readonly VITE_APP_PROXY_PREFIX: string // 一般是/api
+ /** 上传图片地址 */
+ readonly VITE_UPLOAD_BASEURL: string
+ /** 是否清除console */
+ readonly VITE_DELETE_CONSOLE: string
+ // 更多环境变量...
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv
+}
+
+declare const __VITE_APP_PROXY__: 'true' | 'false'
+declare const __UNI_PLATFORM__: 'app' | 'h5' | 'mp-alipay' | 'mp-baidu' | 'mp-kuaishou' | 'mp-lark' | 'mp-qq' | 'mp-tiktok' | 'mp-weixin' | 'mp-xiaochengxu'
diff --git a/src/hooks/usePageAuth.ts b/src/hooks/usePageAuth.ts
new file mode 100644
index 0000000..fd006c8
--- /dev/null
+++ b/src/hooks/usePageAuth.ts
@@ -0,0 +1,50 @@
+import { onLoad } from '@dcloudio/uni-app'
+import { useUserStore } from '@/store'
+import { needLoginPages as _needLoginPages, getNeedLoginPages } from '@/utils'
+
+const loginRoute = import.meta.env.VITE_LOGIN_URL
+const isDev = import.meta.env.DEV
+function isLogined() {
+ const userStore = useUserStore()
+ return !!userStore.userInfo.username
+}
+// 检查当前页面是否需要登录
+export function usePageAuth() {
+ onLoad((options) => {
+ // 获取当前页面路径
+ const pages = getCurrentPages()
+ const currentPage = pages[pages.length - 1]
+ const currentPath = `/${currentPage.route}`
+
+ // 获取需要登录的页面列表
+ let needLoginPages: string[] = []
+ if (isDev) {
+ needLoginPages = getNeedLoginPages()
+ }
+ else {
+ needLoginPages = _needLoginPages
+ }
+
+ // 检查当前页面是否需要登录
+ const isNeedLogin = needLoginPages.includes(currentPath)
+ if (!isNeedLogin) {
+ return
+ }
+
+ const hasLogin = isLogined()
+ if (hasLogin) {
+ return true
+ }
+
+ // 构建重定向URL
+ const queryString = Object.entries(options || {})
+ .map(([key, value]) => `${key}=${encodeURIComponent(String(value))}`)
+ .join('&')
+
+ const currentFullPath = queryString ? `${currentPath}?${queryString}` : currentPath
+ const redirectRoute = `${loginRoute}?redirect=${encodeURIComponent(currentFullPath)}`
+
+ // 重定向到登录页
+ uni.redirectTo({ url: redirectRoute })
+ })
+}
diff --git a/src/hooks/useRequest.ts b/src/hooks/useRequest.ts
new file mode 100644
index 0000000..017a710
--- /dev/null
+++ b/src/hooks/useRequest.ts
@@ -0,0 +1,51 @@
+import type { Ref } from 'vue'
+
+interface IUseRequestOptions {
+ /** 是否立即执行 */
+ immediate?: boolean
+ /** 初始化数据 */
+ initialData?: T
+}
+
+interface IUseRequestReturn {
+ loading: Ref
+ error: Ref
+ data: Ref
+ run: () => Promise
+}
+
+/**
+ * useRequest是一个定制化的请求钩子,用于处理异步请求和响应。
+ * @param func 一个执行异步请求的函数,返回一个包含响应数据的Promise。
+ * @param options 包含请求选项的对象 {immediate, initialData}。
+ * @param options.immediate 是否立即执行请求,默认为false。
+ * @param options.initialData 初始化数据,默认为undefined。
+ * @returns 返回一个对象{loading, error, data, run},包含请求的加载状态、错误信息、响应数据和手动触发请求的函数。
+ */
+export default function useRequest(
+ func: () => Promise>,
+ options: IUseRequestOptions = { immediate: false },
+): IUseRequestReturn {
+ const loading = ref(false)
+ const error = ref(false)
+ const data = ref(options.initialData) as Ref
+ const run = async () => {
+ loading.value = true
+ return func()
+ .then((res) => {
+ data.value = res.data
+ error.value = false
+ return data.value
+ })
+ .catch((err) => {
+ error.value = err
+ throw err
+ })
+ .finally(() => {
+ loading.value = false
+ })
+ }
+
+ options.immediate && run()
+ return { loading, error, data, run }
+}
diff --git a/src/hooks/useUpload.ts b/src/hooks/useUpload.ts
new file mode 100644
index 0000000..3080d5a
--- /dev/null
+++ b/src/hooks/useUpload.ts
@@ -0,0 +1,160 @@
+import { ref } from 'vue'
+import { getEnvBaseUploadUrl } from '@/utils'
+
+const VITE_UPLOAD_BASEURL = `${getEnvBaseUploadUrl()}`
+
+type TfileType = 'image' | 'file'
+type TImage = 'png' | 'jpg' | 'jpeg' | 'webp' | '*'
+type TFile = 'doc' | 'docx' | 'ppt' | 'zip' | 'xls' | 'xlsx' | 'txt' | TImage
+
+interface TOptions {
+ formData?: Record
+ maxSize?: number
+ accept?: T extends 'image' ? TImage[] : TFile[]
+ fileType?: T
+ success?: (params: any) => void
+ error?: (err: any) => void
+}
+
+export default function useUpload(options: TOptions = {} as TOptions) {
+ const {
+ formData = {},
+ maxSize = 5 * 1024 * 1024,
+ accept = ['*'],
+ fileType = 'image',
+ success,
+ error: onError,
+ } = options
+
+ const loading = ref(false)
+ const error = ref(null)
+ const data = ref(null)
+
+ const handleFileChoose = ({ tempFilePath, size }: { tempFilePath: string, size: number }) => {
+ if (size > maxSize) {
+ uni.showToast({
+ title: `文件大小不能超过 ${maxSize / 1024 / 1024}MB`,
+ icon: 'none',
+ })
+ return
+ }
+
+ // const fileExtension = file?.tempFiles?.name?.split('.').pop()?.toLowerCase()
+ // const isTypeValid = accept.some((type) => type === '*' || type.toLowerCase() === fileExtension)
+
+ // if (!isTypeValid) {
+ // uni.showToast({
+ // title: `仅支持 ${accept.join(', ')} 格式的文件`,
+ // icon: 'none',
+ // })
+ // return
+ // }
+
+ loading.value = true
+ uploadFile({
+ tempFilePath,
+ formData,
+ onSuccess: (res) => {
+ const { data: _data } = JSON.parse(res)
+ data.value = _data
+ // console.log('上传成功', res)
+ success?.(_data)
+ },
+ onError: (err) => {
+ error.value = err
+ onError?.(err)
+ },
+ onComplete: () => {
+ loading.value = false
+ },
+ })
+ }
+
+ const run = () => {
+ // 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。
+ // 微信小程序在2023年10月17日之后,使用本API需要配置隐私协议
+ const chooseFileOptions = {
+ count: 1,
+ success: (res: any) => {
+ console.log('File selected successfully:', res)
+ // 小程序中res:{errMsg: "chooseImage:ok", tempFiles: [{fileType: "image", size: 48976, tempFilePath: "http://tmp/5iG1WpIxTaJf3ece38692a337dc06df7eb69ecb49c6b.jpeg"}]}
+ // h5中res:{errMsg: "chooseImage:ok", tempFilePaths: "blob:http://localhost:9000/f74ab6b8-a14d-4cb6-a10d-fcf4511a0de5", tempFiles: [File]}
+ // h5的File有以下字段:{name: "girl.jpeg", size: 48976, type: "image/jpeg"}
+ // App中res:{errMsg: "chooseImage:ok", tempFilePaths: "file:///Users/feige/xxx/gallery/1522437259-compressed-IMG_0006.jpg", tempFiles: [File]}
+ // App的File有以下字段:{path: "file:///Users/feige/xxx/gallery/1522437259-compressed-IMG_0006.jpg", size: 48976}
+ let tempFilePath = ''
+ let size = 0
+ // #ifdef MP-WEIXIN
+ tempFilePath = res.tempFiles[0].tempFilePath
+ size = res.tempFiles[0].size
+ // #endif
+ // #ifndef MP-WEIXIN
+ tempFilePath = res.tempFilePaths[0]
+ size = res.tempFiles[0].size
+ // #endif
+ handleFileChoose({ tempFilePath, size })
+ },
+ fail: (err: any) => {
+ console.error('File selection failed:', err)
+ error.value = err
+ onError?.(err)
+ },
+ }
+
+ if (fileType === 'image') {
+ // #ifdef MP-WEIXIN
+ uni.chooseMedia({
+ ...chooseFileOptions,
+ mediaType: ['image'],
+ })
+ // #endif
+
+ // #ifndef MP-WEIXIN
+ uni.chooseImage(chooseFileOptions)
+ // #endif
+ }
+ else {
+ uni.chooseFile({
+ ...chooseFileOptions,
+ type: 'all',
+ })
+ }
+ }
+
+ return { loading, error, data, run }
+}
+
+async function uploadFile({
+ tempFilePath,
+ formData,
+ onSuccess,
+ onError,
+ onComplete,
+}: {
+ tempFilePath: string
+ formData: Record
+ onSuccess: (data: any) => void
+ onError: (err: any) => void
+ onComplete: () => void
+}) {
+ uni.uploadFile({
+ url: VITE_UPLOAD_BASEURL,
+ filePath: tempFilePath,
+ name: 'file',
+ formData,
+ success: (uploadFileRes) => {
+ try {
+ const data = uploadFileRes.data
+ onSuccess(data)
+ }
+ catch (err) {
+ onError(err)
+ }
+ },
+ fail: (err) => {
+ console.error('Upload failed:', err)
+ onError(err)
+ },
+ complete: onComplete,
+ })
+}
diff --git a/src/http/README.md b/src/http/README.md
new file mode 100644
index 0000000..bbacd1c
--- /dev/null
+++ b/src/http/README.md
@@ -0,0 +1,13 @@
+# 请求库
+
+目前unibest支持3种请求库:
+- 菲鸽简单封装的 `简单版本http`,路径(src/http/http.ts),对应的示例在 src/api/foo.ts
+- `alova 的 http`,路径(src/http/alova.ts),对应的示例在 src/api/foo-alova.ts
+- `vue-query`, 路径(src/http/vue-query.ts), 目前主要用在自动生成接口,详情看(https://unibest.tech/base/17-generate),示例在 src/service/app 文件夹
+
+## 如何选择
+如果您以前用过 alova 或者 vue-query,可以优先使用您熟悉的。
+如果您的项目简单,简单版本的http 就够了,也不会增加包体积。(发版的时候可以去掉alova和vue-query,如果没有超过包体积,留着也无所谓 ^_^)
+
+## roadmap
+菲鸽最近在优化脚手架,后续可以选择是否使用第三方的请求库,以及选择什么请求库。还在开发中,大概月底出来(7月31号)。
\ No newline at end of file
diff --git a/src/http/alova.ts b/src/http/alova.ts
new file mode 100644
index 0000000..005b54b
--- /dev/null
+++ b/src/http/alova.ts
@@ -0,0 +1,111 @@
+import type { uniappRequestAdapter } from '@alova/adapter-uniapp'
+import type { IResponse } from './types'
+import AdapterUniapp from '@alova/adapter-uniapp'
+import { createAlova } from 'alova'
+import { createServerTokenAuthentication } from 'alova/client'
+import VueHook from 'alova/vue'
+import { toast } from '@/utils/toast'
+import { ContentTypeEnum, ResultEnum, ShowMessage } from './tools/enum'
+
+// 配置动态Tag
+export const API_DOMAINS = {
+ DEFAULT: import.meta.env.VITE_SERVER_BASEURL,
+ SECONDARY: import.meta.env.VITE_API_SECONDARY_URL,
+}
+
+/**
+ * 创建请求实例
+ */
+const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication<
+ typeof VueHook,
+ typeof uniappRequestAdapter
+>({
+ refreshTokenOnError: {
+ isExpired: (error) => {
+ return error.response?.status === ResultEnum.Unauthorized
+ },
+ handler: async () => {
+ try {
+ // await authLogin();
+ }
+ catch (error) {
+ // 切换到登录页
+ await uni.reLaunch({ url: '/pages/common/login/index' })
+ throw error
+ }
+ },
+ },
+})
+
+/**
+ * alova 请求实例
+ */
+const alovaInstance = createAlova({
+ baseURL: import.meta.env.VITE_APP_PROXY_PREFIX,
+ ...AdapterUniapp(),
+ timeout: 5000,
+ statesHook: VueHook,
+
+ beforeRequest: onAuthRequired((method) => {
+ // 设置默认 Content-Type
+ method.config.headers = {
+ ContentType: ContentTypeEnum.JSON,
+ Accept: 'application/json, text/plain, */*',
+ ...method.config.headers,
+ }
+
+ const { config } = method
+ const ignoreAuth = !config.meta?.ignoreAuth
+ console.log('ignoreAuth===>', ignoreAuth)
+ // 处理认证信息 自行处理认证问题
+ if (ignoreAuth) {
+ const token = 'getToken()'
+ if (!token) {
+ throw new Error('[请求错误]:未登录')
+ }
+ // method.config.headers.token = token;
+ }
+
+ // 处理动态域名
+ if (config.meta?.domain) {
+ method.baseURL = config.meta.domain
+ console.log('当前域名', method.baseURL)
+ }
+ }),
+
+ responded: onResponseRefreshToken((response, method) => {
+ const { config } = method
+ const { requestType } = config
+ const {
+ statusCode,
+ data: rawData,
+ errMsg,
+ } = response as UniNamespace.RequestSuccessCallbackResult
+
+ // 处理特殊请求类型(上传/下载)
+ if (requestType === 'upload' || requestType === 'download') {
+ return response
+ }
+
+ // 处理 HTTP 状态码错误
+ if (statusCode !== 200) {
+ const errorMessage = ShowMessage(statusCode) || `HTTP请求错误[${statusCode}]`
+ console.error('errorMessage===>', errorMessage)
+ toast.error(errorMessage)
+ throw new Error(`${errorMessage}:${errMsg}`)
+ }
+
+ // 处理业务逻辑错误
+ const { code, message, data } = rawData as IResponse
+ if (code !== ResultEnum.Success) {
+ if (config.meta?.toast !== false) {
+ toast.warning(message)
+ }
+ throw new Error(`请求错误[${code}]:${message}`)
+ }
+ // 处理成功响应,返回业务数据
+ return data
+ }),
+})
+
+export const http = alovaInstance
diff --git a/src/http/http.ts b/src/http/http.ts
new file mode 100644
index 0000000..98c7324
--- /dev/null
+++ b/src/http/http.ts
@@ -0,0 +1,118 @@
+import type { CustomRequestOptions } from '@/http/types'
+
+export function http(options: CustomRequestOptions) {
+ // 1. 返回 Promise 对象
+ return new Promise>((resolve, reject) => {
+ uni.request({
+ ...options,
+ dataType: 'json',
+ // #ifndef MP-WEIXIN
+ responseType: 'json',
+ // #endif
+ // 响应成功
+ success(res) {
+ // 状态码 2xx,参考 axios 的设计
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ // 2.1 提取核心数据 res.data
+ resolve(res.data as IResData)
+ }
+ else if (res.statusCode === 401) {
+ // 401错误 -> 清理用户信息,跳转到登录页
+ // userStore.clearUserInfo()
+ // uni.navigateTo({ url: '/pages/login/login' })
+ reject(res)
+ }
+ else {
+ // 其他错误 -> 根据后端错误信息轻提示
+ !options.hideErrorToast
+ && uni.showToast({
+ icon: 'none',
+ title: (res.data as IResData).msg || '请求错误',
+ })
+ reject(res)
+ }
+ },
+ // 响应失败
+ fail(err) {
+ uni.showToast({
+ icon: 'none',
+ title: '网络错误,换个网络试试',
+ })
+ reject(err)
+ },
+ })
+ })
+}
+
+/**
+ * GET 请求
+ * @param url 后台地址
+ * @param query 请求query参数
+ * @param header 请求头,默认为json格式
+ * @returns
+ */
+export function httpGet(url: string, query?: Record, header?: Record, options?: Partial) {
+ return http({
+ url,
+ query,
+ method: 'GET',
+ header,
+ ...options,
+ })
+}
+
+/**
+ * POST 请求
+ * @param url 后台地址
+ * @param data 请求body参数
+ * @param query 请求query参数,post请求也支持query,很多微信接口都需要
+ * @param header 请求头,默认为json格式
+ * @returns
+ */
+export function httpPost(url: string, data?: Record, query?: Record, header?: Record, options?: Partial) {
+ return http({
+ url,
+ query,
+ data,
+ method: 'POST',
+ header,
+ ...options,
+ })
+}
+/**
+ * PUT 请求
+ */
+export function httpPut(url: string, data?: Record, query?: Record, header?: Record, options?: Partial) {
+ return http({
+ url,
+ data,
+ query,
+ method: 'PUT',
+ header,
+ ...options,
+ })
+}
+
+/**
+ * DELETE 请求(无请求体,仅 query)
+ */
+export function httpDelete(url: string, query?: Record, header?: Record, options?: Partial) {
+ return http({
+ url,
+ query,
+ method: 'DELETE',
+ header,
+ ...options,
+ })
+}
+
+http.get = httpGet
+http.post = httpPost
+http.put = httpPut
+http.delete = httpDelete
+
+// 支持与 alovaJS 类似的API调用
+http.Get = httpGet
+http.Post = httpPost
+http.Put = httpPut
+http.Delete = httpDelete
diff --git a/src/http/interceptor.ts b/src/http/interceptor.ts
new file mode 100644
index 0000000..357780b
--- /dev/null
+++ b/src/http/interceptor.ts
@@ -0,0 +1,65 @@
+import type { CustomRequestOptions } from '@/http/types'
+import { useUserStore } from '@/store'
+import { getEnvBaseUrl } from '@/utils'
+import { platform } from '@/utils/platform'
+import { stringifyQuery } from './tools/queryString'
+
+// 请求基准地址
+const baseUrl = getEnvBaseUrl()
+
+// 拦截器配置
+const httpInterceptor = {
+ // 拦截前触发
+ invoke(options: CustomRequestOptions) {
+ // 接口请求支持通过 query 参数配置 queryString
+ if (options.query) {
+ const queryStr = stringifyQuery(options.query)
+ if (options.url.includes('?')) {
+ options.url += `&${queryStr}`
+ }
+ else {
+ options.url += `?${queryStr}`
+ }
+ }
+ // 非 http 开头需拼接地址
+ if (!options.url.startsWith('http')) {
+ // #ifdef H5
+ // console.log(__VITE_APP_PROXY__)
+ if (JSON.parse(__VITE_APP_PROXY__)) {
+ // 自动拼接代理前缀
+ options.url = import.meta.env.VITE_APP_PROXY_PREFIX + options.url
+ }
+ else {
+ options.url = baseUrl + options.url
+ }
+ // #endif
+ // 非H5正常拼接
+ // #ifndef H5
+ options.url = baseUrl + options.url
+ // #endif
+ // TIPS: 如果需要对接多个后端服务,也可以在这里处理,拼接成所需要的地址
+ }
+ // 1. 请求超时
+ options.timeout = 10000 // 10s
+ // 2. (可选)添加小程序端请求头标识
+ options.header = {
+ platform, // 可选,与 uniapp 定义的平台一致,告诉后台来源
+ ...options.header,
+ }
+ // 3. 添加 token 请求头标识
+ const userStore = useUserStore()
+ const { token } = userStore.userInfo as unknown as IUserInfo
+ if (token) {
+ options.header.Authorization = `Bearer ${token}`
+ }
+ },
+}
+
+export const requestInterceptor = {
+ install() {
+ // 拦截 request 请求
+ uni.addInterceptor('request', httpInterceptor)
+ // 拦截 uploadFile 文件上传
+ uni.addInterceptor('uploadFile', httpInterceptor)
+ },
+}
diff --git a/src/http/tools/enum.ts b/src/http/tools/enum.ts
new file mode 100644
index 0000000..1868fe0
--- /dev/null
+++ b/src/http/tools/enum.ts
@@ -0,0 +1,66 @@
+export enum ResultEnum {
+ Success = 0, // 成功
+ Error = 400, // 错误
+ Unauthorized = 401, // 未授权
+ Forbidden = 403, // 禁止访问(原为forbidden)
+ NotFound = 404, // 未找到(原为notFound)
+ MethodNotAllowed = 405, // 方法不允许(原为methodNotAllowed)
+ RequestTimeout = 408, // 请求超时(原为requestTimeout)
+ InternalServerError = 500, // 服务器错误(原为internalServerError)
+ NotImplemented = 501, // 未实现(原为notImplemented)
+ BadGateway = 502, // 网关错误(原为badGateway)
+ ServiceUnavailable = 503, // 服务不可用(原为serviceUnavailable)
+ GatewayTimeout = 504, // 网关超时(原为gatewayTimeout)
+ HttpVersionNotSupported = 505, // HTTP版本不支持(原为httpVersionNotSupported)
+}
+export enum ContentTypeEnum {
+ JSON = 'application/json;charset=UTF-8',
+ FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
+ FORM_DATA = 'multipart/form-data;charset=UTF-8',
+}
+/**
+ * 根据状态码,生成对应的错误信息
+ * @param {number|string} status 状态码
+ * @returns {string} 错误信息
+ */
+export function ShowMessage(status: number | string): string {
+ let message: string
+ 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},请检查网络或联系管理员!`
+}
diff --git a/src/http/tools/queryString.ts b/src/http/tools/queryString.ts
new file mode 100644
index 0000000..edf973e
--- /dev/null
+++ b/src/http/tools/queryString.ts
@@ -0,0 +1,29 @@
+/**
+ * 将对象序列化为URL查询字符串,用于替代第三方的 qs 库,节省宝贵的体积
+ * 支持基本类型值和数组,不支持嵌套对象
+ * @param obj 要序列化的对象
+ * @returns 序列化后的查询字符串
+ */
+export function stringifyQuery(obj: Record): string {
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj))
+ return ''
+
+ return Object.entries(obj)
+ .filter(([_, value]) => value !== undefined && value !== null)
+ .map(([key, value]) => {
+ // 对键进行编码
+ const encodedKey = encodeURIComponent(key)
+
+ // 处理数组类型
+ if (Array.isArray(value)) {
+ return value
+ .filter(item => item !== undefined && item !== null)
+ .map(item => `${encodedKey}=${encodeURIComponent(item)}`)
+ .join('&')
+ }
+
+ // 处理基本类型
+ return `${encodedKey}=${encodeURIComponent(value)}`
+ })
+ .join('&')
+}
diff --git a/src/http/types.ts b/src/http/types.ts
new file mode 100644
index 0000000..25cf472
--- /dev/null
+++ b/src/http/types.ts
@@ -0,0 +1,31 @@
+/**
+ * 在 uniapp 的 RequestOptions 和 IUniUploadFileOptions 基础上,添加自定义参数
+ */
+export type CustomRequestOptions = UniApp.RequestOptions & {
+ query?: Record
+ /** 出错时是否隐藏错误提示 */
+ hideErrorToast?: boolean
+} & IUniUploadFileOptions // 添加uni.uploadFile参数类型
+
+// 通用响应格式
+export interface IResponse {
+ code: number | string
+ data: T
+ message: string
+ status: string | number
+}
+
+// 分页请求参数
+export interface PageParams {
+ page: number
+ pageSize: number
+ [key: string]: any
+}
+
+// 分页响应数据
+export interface PageResult {
+ list: T[]
+ total: number
+ page: number
+ pageSize: number
+}
diff --git a/src/http/vue-query.ts b/src/http/vue-query.ts
new file mode 100644
index 0000000..31d1eb3
--- /dev/null
+++ b/src/http/vue-query.ts
@@ -0,0 +1,30 @@
+import type { CustomRequestOptions } from '@/http/types'
+import { http } from './http'
+
+/*
+ * openapi-ts-request 工具的 request 跨客户端适配方法
+ */
+export default function request(
+ url: string,
+ options: Omit & {
+ params?: Record
+ headers?: Record
+ },
+) {
+ const requestOptions = {
+ url,
+ ...options,
+ }
+
+ if (options.params) {
+ requestOptions.query = requestOptions.params
+ delete requestOptions.params
+ }
+
+ if (options.headers) {
+ requestOptions.header = options.headers
+ delete requestOptions.headers
+ }
+
+ return http(requestOptions)
+}
diff --git a/src/layouts/default.vue b/src/layouts/default.vue
new file mode 100644
index 0000000..360c6b2
--- /dev/null
+++ b/src/layouts/default.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/layouts/tabbar.vue b/src/layouts/tabbar.vue
new file mode 100644
index 0000000..7e8d345
--- /dev/null
+++ b/src/layouts/tabbar.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..a357275
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,21 @@
+import { VueQueryPlugin } from '@tanstack/vue-query'
+import { createSSRApp } from 'vue'
+import App from './App.vue'
+import { requestInterceptor } from './http/interceptor'
+import { routeInterceptor } from './router/interceptor'
+
+import store from './store'
+import '@/style/index.scss'
+import 'virtual:uno.css'
+
+export function createApp() {
+ const app = createSSRApp(App)
+ app.use(store)
+ app.use(routeInterceptor)
+ app.use(requestInterceptor)
+ app.use(VueQueryPlugin)
+
+ return {
+ app,
+ }
+}
diff --git a/src/manifest.json b/src/manifest.json
new file mode 100644
index 0000000..c436de6
--- /dev/null
+++ b/src/manifest.json
@@ -0,0 +1,114 @@
+{
+ "name": "unibest",
+ "appid": "__UNI__D1E5001",
+ "description": "",
+ "versionName": "1.0.0",
+ "versionCode": "100",
+ "transformPx": false,
+ "app-plus": {
+ "usingComponents": true,
+ "nvueStyleCompiler": "uni-app",
+ "compilerVersion": 3,
+ "splashscreen": {
+ "alwaysShowBeforeRender": true,
+ "waiting": true,
+ "autoclose": true,
+ "delay": 0
+ },
+ "modules": {},
+ "distribute": {
+ "android": {
+ "permissions": [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ],
+ "minSdkVersion": 30,
+ "targetSdkVersion": 30,
+ "abiFilters": [
+ "armeabi-v7a",
+ "arm64-v8a"
+ ]
+ },
+ "ios": {},
+ "sdkConfigs": {},
+ "icons": {
+ "android": {
+ "hdpi": "static/app/icons/72x72.png",
+ "xhdpi": "static/app/icons/96x96.png",
+ "xxhdpi": "static/app/icons/144x144.png",
+ "xxxhdpi": "static/app/icons/192x192.png"
+ },
+ "ios": {
+ "appstore": "static/app/icons/1024x1024.png",
+ "ipad": {
+ "app": "static/app/icons/76x76.png",
+ "app@2x": "static/app/icons/152x152.png",
+ "notification": "static/app/icons/20x20.png",
+ "notification@2x": "static/app/icons/40x40.png",
+ "proapp@2x": "static/app/icons/167x167.png",
+ "settings": "static/app/icons/29x29.png",
+ "settings@2x": "static/app/icons/58x58.png",
+ "spotlight": "static/app/icons/40x40.png",
+ "spotlight@2x": "static/app/icons/80x80.png"
+ },
+ "iphone": {
+ "app@2x": "static/app/icons/120x120.png",
+ "app@3x": "static/app/icons/180x180.png",
+ "notification@2x": "static/app/icons/40x40.png",
+ "notification@3x": "static/app/icons/60x60.png",
+ "settings@2x": "static/app/icons/58x58.png",
+ "settings@3x": "static/app/icons/87x87.png",
+ "spotlight@2x": "static/app/icons/80x80.png",
+ "spotlight@3x": "static/app/icons/120x120.png"
+ }
+ }
+ }
+ },
+ "compatible": {
+ "ignoreVersion": true
+ }
+ },
+ "quickapp": {},
+ "mp-weixin": {
+ "appid": "wxa2abb91f64032a2b",
+ "setting": {
+ "urlCheck": false,
+ "es6": true,
+ "minified": true
+ },
+ "usingComponents": true,
+ "optimization": {
+ "subPackages": true
+ }
+ },
+ "mp-alipay": {
+ "usingComponents": true,
+ "styleIsolation": "shared"
+ },
+ "mp-baidu": {
+ "usingComponents": true
+ },
+ "mp-toutiao": {
+ "usingComponents": true
+ },
+ "uniStatistics": {
+ "enable": false
+ },
+ "vueVersion": "3",
+ "h5": {
+ "router": {}
+ }
+}
\ No newline at end of file
diff --git a/src/pages-sub/demo/components/request.vue b/src/pages-sub/demo/components/request.vue
new file mode 100644
index 0000000..7d7bff1
--- /dev/null
+++ b/src/pages-sub/demo/components/request.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+ loading...
+
+
+
+ 请求数据如下
+
+
+ {{ JSON.stringify(data) }}
+
+
+
+
+
+
+
+
diff --git a/src/pages-sub/demo/index.vue b/src/pages-sub/demo/index.vue
new file mode 100644
index 0000000..1d6abdd
--- /dev/null
+++ b/src/pages-sub/demo/index.vue
@@ -0,0 +1,34 @@
+
+{
+ "layout": "default",
+ "style": {
+ "navigationBarTitleText": "分包页面"
+ }
+}
+
+
+
+
+
+
+
+ http://localhost:9000/#/pages-sub/demo/index
+
+
+ 分包页面demo
+
+
+ 分包页面里面的components示例
+
+
+
+
+
+
+
+
diff --git a/src/pages.json b/src/pages.json
new file mode 100644
index 0000000..86b05f7
--- /dev/null
+++ b/src/pages.json
@@ -0,0 +1,92 @@
+{
+ "globalStyle": {
+ "navigationStyle": "default",
+ "navigationBarTitleText": "unibest",
+ "navigationBarBackgroundColor": "#f8f8f8",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#FFFFFF"
+ },
+ "easycom": {
+ "autoscan": true,
+ "custom": {
+ "^fg-(.*)": "@/components/fg-$1/fg-$1.vue",
+ "^wd-(.*)": "wot-design-uni/components/wd-$1/wd-$1.vue",
+ "^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$1.vue"
+ }
+ },
+ "tabBar": {
+ "custom": true,
+ "color": "#999999",
+ "selectedColor": "#018d71",
+ "backgroundColor": "#F8F8F8",
+ "borderStyle": "black",
+ "height": "50px",
+ "fontSize": "10px",
+ "iconWidth": "24px",
+ "spacing": "3px",
+ "list": [
+ {
+ "iconPath": "static/tabbar/home.png",
+ "selectedIconPath": "static/tabbar/homeHL.png",
+ "pagePath": "pages/index/index",
+ "text": "首页"
+ },
+ {
+ "iconPath": "static/tabbar/example.png",
+ "selectedIconPath": "static/tabbar/exampleHL.png",
+ "pagePath": "pages/about/about",
+ "text": "关于"
+ }
+ ]
+ },
+ "pages": [
+ {
+ "path": "pages/index/index",
+ "type": "home",
+ "layout": "tabbar",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": "首页"
+ }
+ },
+ {
+ "path": "pages/about/about",
+ "type": "page",
+ "layout": "tabbar",
+ "style": {
+ "navigationBarTitleText": "关于"
+ }
+ },
+ {
+ "path": "pages/about/alova",
+ "type": "page",
+ "layout": "default",
+ "style": {
+ "navigationBarTitleText": "Alova 请求演示"
+ }
+ },
+ {
+ "path": "pages/about/vue-query",
+ "type": "page",
+ "layout": "default",
+ "style": {
+ "navigationBarTitleText": "Vue Query 请求演示"
+ }
+ }
+ ],
+ "subPackages": [
+ {
+ "root": "pages-sub",
+ "pages": [
+ {
+ "path": "demo/index",
+ "type": "page",
+ "layout": "default",
+ "style": {
+ "navigationBarTitleText": "分包页面"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/pages/about/about.vue b/src/pages/about/about.vue
new file mode 100644
index 0000000..a01efdb
--- /dev/null
+++ b/src/pages/about/about.vue
@@ -0,0 +1,84 @@
+
+{
+ "layout": "tabbar",
+ "style": {
+ "navigationBarTitleText": "关于"
+ }
+}
+
+
+
+
+
+
+
+ 组件使用、请求调用、unocss
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 为了方便脚手架动态生成不同UI模板,本页的按钮统一使用UI库无关的原生button
+
+
+
+
+
+
diff --git a/src/pages/about/alova.vue b/src/pages/about/alova.vue
new file mode 100644
index 0000000..70c3af3
--- /dev/null
+++ b/src/pages/about/alova.vue
@@ -0,0 +1,56 @@
+
+{
+ "layout": "default",
+ "style": {
+ "navigationBarTitleText": "Alova 请求演示"
+ }
+}
+
+
+
+
+
+
+
+
+
+ loading...
+
+
+
+ 请求数据如下
+
+
+ {{ JSON.stringify(data) }}
+
+
+
+
+ {{ data?.id }}
+
+
+
+
+
+
+
diff --git a/src/pages/about/components/request.vue b/src/pages/about/components/request.vue
new file mode 100644
index 0000000..ac1b7a3
--- /dev/null
+++ b/src/pages/about/components/request.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+ pages 里面的 vue 文件会扫描成页面,将自动添加到 pages.json 里面。
+
+
+ 但是 pages/components 里面的 vue 不会。
+
+
+
+
+
+
+
+ loading...
+
+
+
+ 请求数据如下
+
+
+ {{ JSON.stringify(data) }}
+
+
+
+
+
+
+
+
diff --git a/src/pages/about/vue-query.vue b/src/pages/about/vue-query.vue
new file mode 100644
index 0000000..cea85c9
--- /dev/null
+++ b/src/pages/about/vue-query.vue
@@ -0,0 +1,53 @@
+
+{
+ "layout": "default",
+ "style": {
+ "navigationBarTitleText": "Vue Query 请求演示"
+ }
+}
+
+
+
+
+
+
+
+
+
+ loading...
+
+
+
+ 请求数据如下
+
+
+ {{ JSON.stringify(data) }}
+
+
+
+
+
+
+
diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue
new file mode 100644
index 0000000..08b1284
--- /dev/null
+++ b/src/pages/index/index.vue
@@ -0,0 +1,122 @@
+
+
+{
+ "layout": "tabbar",
+ "style": {
+ // 'custom' 表示开启自定义导航栏,默认 'default'
+ "navigationStyle": "custom",
+ "navigationBarTitleText": "首页"
+ }
+}
+
+
+
+
+
+
+
+
+
+
+ unibest
+
+
+ 最好用的 uniapp 开发模板
+
+
+
+ {{ description }}
+
+
+ 作者:
+
+ 菲鸽
+
+
+
+ 官网地址:
+
+ https://unibest.tech
+
+
+
+
+
+
+ 新手请看必看章节1:
+
+
+
+
+
+ 新手请看必看章节1:
+
+ https://unibest.tech/base/3-plugin
+
+
+
+
+
+
+ 新手请看必看章节2:
+
+
+
+
+
+ 新手请看必看章节2:
+
+ https://unibest.tech/base/14-faq
+
+
+
+
+
+
+ UI组件按钮
+
+
+
+ UI组件官网:
+ https://wot-design-uni.cn
+
+
+
+
diff --git a/src/router/interceptor.ts b/src/router/interceptor.ts
new file mode 100644
index 0000000..dc6ca40
--- /dev/null
+++ b/src/router/interceptor.ts
@@ -0,0 +1,77 @@
+/**
+ * by 菲鸽 on 2024-03-06
+ * 路由拦截,通常也是登录拦截
+ * 可以设置路由白名单,或者黑名单,看业务需要选哪一个
+ * 我这里应为大部分都可以随便进入,所以使用黑名单
+ */
+import { useUserStore } from '@/store'
+import { tabbarStore } from '@/tabbar/store'
+import { needLoginPages as _needLoginPages, getLastPage, getNeedLoginPages } from '@/utils'
+
+// TODO Check
+const loginRoute = import.meta.env.VITE_LOGIN_URL
+
+function isLogined() {
+ const userStore = useUserStore()
+ return !!userStore.userInfo.username
+}
+
+const isDev = import.meta.env.DEV
+
+// 黑名单登录拦截器 - (适用于大部分页面不需要登录,少部分页面需要登录)
+export const navigateToInterceptor = {
+ // 注意,这里的url是 '/' 开头的,如 '/pages/index/index',跟 'pages.json' 里面的 path 不同
+ // 增加对相对路径的处理,BY 网友 @ideal
+ invoke({ url }: { url: string }) {
+ // console.log(url) // /pages/route-interceptor/index?name=feige&age=30
+ let path = url.split('?')[0]
+
+ // 处理相对路径
+ if (!path.startsWith('/')) {
+ const currentPath = getLastPage().route
+ const normalizedCurrentPath = currentPath.startsWith('/') ? currentPath : `/${currentPath}`
+ const baseDir = normalizedCurrentPath.substring(0, normalizedCurrentPath.lastIndexOf('/'))
+ path = `${baseDir}/${path}`
+ }
+
+ let needLoginPages: string[] = []
+ // 为了防止开发时出现BUG,这里每次都获取一下。生产环境可以移到函数外,性能更好
+ if (isDev) {
+ needLoginPages = getNeedLoginPages()
+ }
+ else {
+ needLoginPages = _needLoginPages
+ }
+ const isNeedLogin = needLoginPages.includes(path)
+ if (!isNeedLogin) {
+ return true
+ }
+ const hasLogin = isLogined()
+ if (hasLogin) {
+ return true
+ }
+ tabbarStore.restorePrevIdx()
+ const redirectRoute = `${loginRoute}?redirect=${encodeURIComponent(url)}`
+ uni.navigateTo({ url: redirectRoute })
+ return false
+ },
+}
+
+export const routeInterceptor = {
+ install() {
+ uni.addInterceptor('navigateTo', navigateToInterceptor)
+ uni.addInterceptor('reLaunch', navigateToInterceptor)
+ uni.addInterceptor('redirectTo', navigateToInterceptor)
+ uni.addInterceptor('switchTab', navigateToInterceptor)
+
+ // // #ifdef H5
+ // // 一个粗糙的实现方式,不满意可以自行修改:https://github.com/unibest-tech/unibest/issues/192
+ // // H5环境路由拦截,监听hashchange事件
+ // window.addEventListener('hashchange', () => {
+ // // 获取当前路径
+ // const currentPath = `/${window.location.hash.split('#/')[1]?.split('?')[0]}`
+ // navigateToInterceptor.invoke({ url: currentPath })
+ // })
+ // // #endif
+ },
+}
diff --git a/src/service/displayEnumLabel.ts b/src/service/displayEnumLabel.ts
new file mode 100644
index 0000000..04b1487
--- /dev/null
+++ b/src/service/displayEnumLabel.ts
@@ -0,0 +1,13 @@
+/* eslint-disable */
+// @ts-ignore
+import * as API from './types';
+
+export function displayStatusEnum(field: API.StatusEnum) {
+ return { available: 'available', pending: 'pending', sold: 'sold' }[field];
+}
+
+export function displayStatusEnum2(field: API.StatusEnum2) {
+ return { placed: 'placed', approved: 'approved', delivered: 'delivered' }[
+ field
+ ];
+}
diff --git a/src/service/index.ts b/src/service/index.ts
new file mode 100644
index 0000000..45b6e53
--- /dev/null
+++ b/src/service/index.ts
@@ -0,0 +1,11 @@
+/* eslint-disable */
+// @ts-ignore
+export * from './types';
+export * from './displayEnumLabel';
+
+export * from './pet';
+export * from './pet.vuequery';
+export * from './store';
+export * from './store.vuequery';
+export * from './user';
+export * from './user.vuequery';
diff --git a/src/service/pet.ts b/src/service/pet.ts
new file mode 100644
index 0000000..1fc0a92
--- /dev/null
+++ b/src/service/pet.ts
@@ -0,0 +1,185 @@
+/* eslint-disable */
+// @ts-ignore
+import request from '@/http/vue-query';
+import type { CustomRequestOptions } from '@/http/types';
+
+import * as API from './types';
+
+/** Update an existing pet PUT /pet */
+export async function petUsingPut({
+ body,
+ options,
+}: {
+ body: API.Pet;
+ options?: CustomRequestOptions;
+}) {
+ return request('/pet', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Add a new pet to the store POST /pet */
+export async function petUsingPost({
+ body,
+ options,
+}: {
+ body: API.Pet;
+ options?: CustomRequestOptions;
+}) {
+ return request('/pet', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Find pet by ID Returns a single pet GET /pet/${param0} */
+export async function petPetIdUsingGet({
+ params,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.petPetIdUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ const { petId: param0, ...queryParams } = params;
+
+ return request(`/pet/${param0}`, {
+ method: 'GET',
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
+
+/** Updates a pet in the store with form data POST /pet/${param0} */
+export async function petPetIdUsingPost({
+ params,
+ body,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.petPetIdUsingPostParams;
+ body: API.PetPetIdUsingPostBody;
+ options?: CustomRequestOptions;
+}) {
+ const { petId: param0, ...queryParams } = params;
+
+ return request(`/pet/${param0}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ params: { ...queryParams },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Deletes a pet DELETE /pet/${param0} */
+export async function petPetIdUsingDelete({
+ params,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.petPetIdUsingDeleteParams;
+ options?: CustomRequestOptions;
+}) {
+ const { petId: param0, ...queryParams } = params;
+
+ return request(`/pet/${param0}`, {
+ method: 'DELETE',
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
+
+/** uploads an image POST /pet/${param0}/uploadImage */
+export async function petPetIdUploadImageUsingPost({
+ params,
+ body,
+ file,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.petPetIdUploadImageUsingPostParams;
+ body: API.PetPetIdUploadImageUsingPostBody;
+ file?: File;
+ options?: CustomRequestOptions;
+}) {
+ const { petId: param0, ...queryParams } = params;
+ const formData = new FormData();
+
+ if (file) {
+ formData.append('file', file);
+ }
+
+ Object.keys(body).forEach((ele) => {
+ const item = (body as { [key: string]: any })[ele];
+
+ if (item !== undefined && item !== null) {
+ if (typeof item === 'object' && !(item instanceof File)) {
+ if (item instanceof Array) {
+ item.forEach((f) => formData.append(ele, f || ''));
+ } else {
+ formData.append(ele, JSON.stringify(item));
+ }
+ } else {
+ formData.append(ele, item);
+ }
+ }
+ });
+
+ return request(`/pet/${param0}/uploadImage`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ params: { ...queryParams },
+ data: formData,
+ ...(options || {}),
+ });
+}
+
+/** Finds Pets by status Multiple status values can be provided with comma separated strings GET /pet/findByStatus */
+export async function petFindByStatusUsingGet({
+ params,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.petFindByStatusUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ return request('/pet/findByStatus', {
+ method: 'GET',
+ params: {
+ ...params,
+ },
+ ...(options || {}),
+ });
+}
+
+/** Finds Pets by tags Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. GET /pet/findByTags */
+export async function petFindByTagsUsingGet({
+ params,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.petFindByTagsUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ return request('/pet/findByTags', {
+ method: 'GET',
+ params: {
+ ...params,
+ },
+ ...(options || {}),
+ });
+}
diff --git a/src/service/pet.vuequery.ts b/src/service/pet.vuequery.ts
new file mode 100644
index 0000000..a2369ea
--- /dev/null
+++ b/src/service/pet.vuequery.ts
@@ -0,0 +1,151 @@
+/* eslint-disable */
+// @ts-ignore
+import { queryOptions, useMutation } from '@tanstack/vue-query';
+import type { DefaultError } from '@tanstack/vue-query';
+import request from '@/http/vue-query';
+import type { CustomRequestOptions } from '@/http/types';
+
+import * as apis from './pet';
+import * as API from './types';
+
+/** Update an existing pet PUT /pet */
+export function usePetUsingPutMutation(options?: {
+ onSuccess?: (value?: unknown) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.petUsingPut,
+ onSuccess(data: unknown) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
+
+/** Add a new pet to the store POST /pet */
+export function usePetUsingPostMutation(options?: {
+ onSuccess?: (value?: unknown) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.petUsingPost,
+ onSuccess(data: unknown) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
+
+/** Find pet by ID Returns a single pet GET /pet/${param0} */
+export function petPetIdUsingGetQueryOptions(options: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.petPetIdUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ return queryOptions({
+ queryFn: async ({ queryKey }) => {
+ return apis.petPetIdUsingGet(queryKey[1] as typeof options);
+ },
+ queryKey: ['petPetIdUsingGet', options],
+ });
+}
+
+/** Updates a pet in the store with form data POST /pet/${param0} */
+export function usePetPetIdUsingPostMutation(options?: {
+ onSuccess?: (value?: unknown) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.petPetIdUsingPost,
+ onSuccess(data: unknown) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
+
+/** Deletes a pet DELETE /pet/${param0} */
+export function usePetPetIdUsingDeleteMutation(options?: {
+ onSuccess?: (value?: unknown) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.petPetIdUsingDelete,
+ onSuccess(data: unknown) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
+
+/** uploads an image POST /pet/${param0}/uploadImage */
+export function usePetPetIdUploadImageUsingPostMutation(options?: {
+ onSuccess?: (value?: API.ApiResponse) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.petPetIdUploadImageUsingPost,
+ onSuccess(data: API.ApiResponse) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
+
+/** Finds Pets by status Multiple status values can be provided with comma separated strings GET /pet/findByStatus */
+export function petFindByStatusUsingGetQueryOptions(options: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.petFindByStatusUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ return queryOptions({
+ queryFn: async ({ queryKey }) => {
+ return apis.petFindByStatusUsingGet(queryKey[1] as typeof options);
+ },
+ queryKey: ['petFindByStatusUsingGet', options],
+ });
+}
+
+/** Finds Pets by tags Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. GET /pet/findByTags */
+export function petFindByTagsUsingGetQueryOptions(options: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.petFindByTagsUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ return queryOptions({
+ queryFn: async ({ queryKey }) => {
+ return apis.petFindByTagsUsingGet(queryKey[1] as typeof options);
+ },
+ queryKey: ['petFindByTagsUsingGet', options],
+ });
+}
diff --git a/src/service/store.ts b/src/service/store.ts
new file mode 100644
index 0000000..cc272f3
--- /dev/null
+++ b/src/service/store.ts
@@ -0,0 +1,72 @@
+/* eslint-disable */
+// @ts-ignore
+import request from '@/http/vue-query';
+import type { CustomRequestOptions } from '@/http/types';
+
+import * as API from './types';
+
+/** Returns pet inventories by status Returns a map of status codes to quantities GET /store/inventory */
+export async function storeInventoryUsingGet({
+ options,
+}: {
+ options?: CustomRequestOptions;
+}) {
+ return request>('/store/inventory', {
+ method: 'GET',
+ ...(options || {}),
+ });
+}
+
+/** Place an order for a pet POST /store/order */
+export async function storeOrderUsingPost({
+ body,
+ options,
+}: {
+ body: API.Order;
+ options?: CustomRequestOptions;
+}) {
+ return request('/store/order', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Find purchase order by ID For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions GET /store/order/${param0} */
+export async function storeOrderOrderIdUsingGet({
+ params,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.storeOrderOrderIdUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ const { orderId: param0, ...queryParams } = params;
+
+ return request(`/store/order/${param0}`, {
+ method: 'GET',
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
+
+/** Delete purchase order by ID For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors DELETE /store/order/${param0} */
+export async function storeOrderOrderIdUsingDelete({
+ params,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.storeOrderOrderIdUsingDeleteParams;
+ options?: CustomRequestOptions;
+}) {
+ const { orderId: param0, ...queryParams } = params;
+
+ return request(`/store/order/${param0}`, {
+ method: 'DELETE',
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
diff --git a/src/service/store.vuequery.ts b/src/service/store.vuequery.ts
new file mode 100644
index 0000000..2f95787
--- /dev/null
+++ b/src/service/store.vuequery.ts
@@ -0,0 +1,75 @@
+/* eslint-disable */
+// @ts-ignore
+import { queryOptions, useMutation } from '@tanstack/vue-query';
+import type { DefaultError } from '@tanstack/vue-query';
+import request from '@/http/vue-query';
+import type { CustomRequestOptions } from '@/http/types';
+
+import * as apis from './store';
+import * as API from './types';
+
+/** Returns pet inventories by status Returns a map of status codes to quantities GET /store/inventory */
+export function storeInventoryUsingGetQueryOptions(options: {
+ options?: CustomRequestOptions;
+}) {
+ return queryOptions({
+ queryFn: async ({ queryKey }) => {
+ return apis.storeInventoryUsingGet(queryKey[1] as typeof options);
+ },
+ queryKey: ['storeInventoryUsingGet', options],
+ });
+}
+
+/** Place an order for a pet POST /store/order */
+export function useStoreOrderUsingPostMutation(options?: {
+ onSuccess?: (value?: API.Order) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.storeOrderUsingPost,
+ onSuccess(data: API.Order) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
+
+/** Find purchase order by ID For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions GET /store/order/${param0} */
+export function storeOrderOrderIdUsingGetQueryOptions(options: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.storeOrderOrderIdUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ return queryOptions({
+ queryFn: async ({ queryKey }) => {
+ return apis.storeOrderOrderIdUsingGet(queryKey[1] as typeof options);
+ },
+ queryKey: ['storeOrderOrderIdUsingGet', options],
+ });
+}
+
+/** Delete purchase order by ID For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors DELETE /store/order/${param0} */
+export function useStoreOrderOrderIdUsingDeleteMutation(options?: {
+ onSuccess?: (value?: unknown) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.storeOrderOrderIdUsingDelete,
+ onSuccess(data: unknown) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
diff --git a/src/service/types.ts b/src/service/types.ts
new file mode 100644
index 0000000..800530e
--- /dev/null
+++ b/src/service/types.ts
@@ -0,0 +1,146 @@
+/* eslint-disable */
+// @ts-ignore
+
+export type ApiResponse = {
+ code?: number;
+ type?: string;
+ message?: string;
+};
+
+export type Category = {
+ id?: number;
+ name?: string;
+};
+
+export type Order = {
+ id?: number;
+ petId?: number;
+ quantity?: number;
+ shipDate?: string;
+ /** Order Status */
+ status?: 'placed' | 'approved' | 'delivered';
+ complete?: boolean;
+};
+
+export type Pet = {
+ id?: number;
+ category?: Category;
+ name: string;
+ photoUrls: string[];
+ tags?: Tag[];
+ /** pet status in the store */
+ status?: 'available' | 'pending' | 'sold';
+};
+
+export type petFindByStatusUsingGetParams = {
+ /** Status values that need to be considered for filter */
+ status: ('available' | 'pending' | 'sold')[];
+};
+
+export type petFindByTagsUsingGetParams = {
+ /** Tags to filter by */
+ tags: string[];
+};
+
+export type PetPetIdUploadImageUsingPostBody = {
+ /** Additional data to pass to server */
+ additionalMetadata?: string;
+ /** file to upload */
+ file?: string;
+};
+
+export type petPetIdUploadImageUsingPostParams = {
+ /** ID of pet to update */
+ petId: number;
+};
+
+export type petPetIdUsingDeleteParams = {
+ /** Pet id to delete */
+ petId: number;
+};
+
+export type petPetIdUsingGetParams = {
+ /** ID of pet to return */
+ petId: number;
+};
+
+export type PetPetIdUsingPostBody = {
+ /** Updated name of the pet */
+ name?: string;
+ /** Updated status of the pet */
+ status?: string;
+};
+
+export type petPetIdUsingPostParams = {
+ /** ID of pet that needs to be updated */
+ petId: number;
+};
+
+export enum StatusEnum {
+ 'available' = 'available',
+ 'pending' = 'pending',
+ 'sold' = 'sold',
+}
+
+export type IStatusEnum = keyof typeof StatusEnum;
+
+export enum StatusEnum2 {
+ 'placed' = 'placed',
+ 'approved' = 'approved',
+ 'delivered' = 'delivered',
+}
+
+export type IStatusEnum2 = keyof typeof StatusEnum2;
+
+export type storeOrderOrderIdUsingDeleteParams = {
+ /** ID of the order that needs to be deleted */
+ orderId: number;
+};
+
+export type storeOrderOrderIdUsingGetParams = {
+ /** ID of pet that needs to be fetched */
+ orderId: number;
+};
+
+export type Tag = {
+ id?: number;
+ name?: string;
+};
+
+export type User = {
+ id?: number;
+ username?: string;
+ firstName?: string;
+ lastName?: string;
+ email?: string;
+ password?: string;
+ phone?: string;
+ /** User Status */
+ userStatus?: number;
+};
+
+export type UserCreateWithArrayUsingPostBody = User[];
+
+export type UserCreateWithListUsingPostBody = User[];
+
+export type userLoginUsingGetParams = {
+ /** The user name for login */
+ username: string;
+ /** The password for login in clear text */
+ password: string;
+};
+
+export type userUsernameUsingDeleteParams = {
+ /** The name that needs to be deleted */
+ username: string;
+};
+
+export type userUsernameUsingGetParams = {
+ /** The name that needs to be fetched. Use user1 for testing. */
+ username: string;
+};
+
+export type userUsernameUsingPutParams = {
+ /** name that need to be updated */
+ username: string;
+};
diff --git a/src/service/user.ts b/src/service/user.ts
new file mode 100644
index 0000000..5653783
--- /dev/null
+++ b/src/service/user.ts
@@ -0,0 +1,150 @@
+/* eslint-disable */
+// @ts-ignore
+import request from '@/http/vue-query';
+import type { CustomRequestOptions } from '@/http/types';
+
+import * as API from './types';
+
+/** Create user This can only be done by the logged in user. 返回值: successful operation POST /user */
+export async function userUsingPost({
+ body,
+ options,
+}: {
+ body: API.User;
+ options?: CustomRequestOptions;
+}) {
+ return request('/user', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Get user by user name GET /user/${param0} */
+export async function userUsernameUsingGet({
+ params,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.userUsernameUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ const { username: param0, ...queryParams } = params;
+
+ return request(`/user/${param0}`, {
+ method: 'GET',
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
+
+/** Updated user This can only be done by the logged in user. PUT /user/${param0} */
+export async function userUsernameUsingPut({
+ params,
+ body,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.userUsernameUsingPutParams;
+ body: API.User;
+ options?: CustomRequestOptions;
+}) {
+ const { username: param0, ...queryParams } = params;
+
+ return request(`/user/${param0}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ params: { ...queryParams },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Delete user This can only be done by the logged in user. DELETE /user/${param0} */
+export async function userUsernameUsingDelete({
+ params,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.userUsernameUsingDeleteParams;
+ options?: CustomRequestOptions;
+}) {
+ const { username: param0, ...queryParams } = params;
+
+ return request(`/user/${param0}`, {
+ method: 'DELETE',
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
+
+/** Creates list of users with given input array 返回值: successful operation POST /user/createWithArray */
+export async function userCreateWithArrayUsingPost({
+ body,
+ options,
+}: {
+ body: API.UserCreateWithArrayUsingPostBody;
+ options?: CustomRequestOptions;
+}) {
+ return request('/user/createWithArray', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Creates list of users with given input array 返回值: successful operation POST /user/createWithList */
+export async function userCreateWithListUsingPost({
+ body,
+ options,
+}: {
+ body: API.UserCreateWithListUsingPostBody;
+ options?: CustomRequestOptions;
+}) {
+ return request('/user/createWithList', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Logs user into the system GET /user/login */
+export async function userLoginUsingGet({
+ params,
+ options,
+}: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.userLoginUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ return request('/user/login', {
+ method: 'GET',
+ params: {
+ ...params,
+ },
+ ...(options || {}),
+ });
+}
+
+/** Logs out current logged in user session 返回值: successful operation GET /user/logout */
+export async function userLogoutUsingGet({
+ options,
+}: {
+ options?: CustomRequestOptions;
+}) {
+ return request('/user/logout', {
+ method: 'GET',
+ ...(options || {}),
+ });
+}
diff --git a/src/service/user.vuequery.ts b/src/service/user.vuequery.ts
new file mode 100644
index 0000000..658060a
--- /dev/null
+++ b/src/service/user.vuequery.ts
@@ -0,0 +1,149 @@
+/* eslint-disable */
+// @ts-ignore
+import { queryOptions, useMutation } from '@tanstack/vue-query';
+import type { DefaultError } from '@tanstack/vue-query';
+import request from '@/http/vue-query';
+import type { CustomRequestOptions } from '@/http/types';
+
+import * as apis from './user';
+import * as API from './types';
+
+/** Create user This can only be done by the logged in user. 返回值: successful operation POST /user */
+export function useUserUsingPostMutation(options?: {
+ onSuccess?: (value?: unknown) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.userUsingPost,
+ onSuccess(data: unknown) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
+
+/** Get user by user name GET /user/${param0} */
+export function userUsernameUsingGetQueryOptions(options: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.userUsernameUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ return queryOptions({
+ queryFn: async ({ queryKey }) => {
+ return apis.userUsernameUsingGet(queryKey[1] as typeof options);
+ },
+ queryKey: ['userUsernameUsingGet', options],
+ });
+}
+
+/** Updated user This can only be done by the logged in user. PUT /user/${param0} */
+export function useUserUsernameUsingPutMutation(options?: {
+ onSuccess?: (value?: unknown) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.userUsernameUsingPut,
+ onSuccess(data: unknown) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
+
+/** Delete user This can only be done by the logged in user. DELETE /user/${param0} */
+export function useUserUsernameUsingDeleteMutation(options?: {
+ onSuccess?: (value?: unknown) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.userUsernameUsingDelete,
+ onSuccess(data: unknown) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
+
+/** Creates list of users with given input array 返回值: successful operation POST /user/createWithArray */
+export function useUserCreateWithArrayUsingPostMutation(options?: {
+ onSuccess?: (value?: unknown) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.userCreateWithArrayUsingPost,
+ onSuccess(data: unknown) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
+
+/** Creates list of users with given input array 返回值: successful operation POST /user/createWithList */
+export function useUserCreateWithListUsingPostMutation(options?: {
+ onSuccess?: (value?: unknown) => void;
+ onError?: (error?: DefaultError) => void;
+}) {
+ const { onSuccess, onError } = options || {};
+
+ const response = useMutation({
+ mutationFn: apis.userCreateWithListUsingPost,
+ onSuccess(data: unknown) {
+ onSuccess?.(data);
+ },
+ onError(error) {
+ onError?.(error);
+ },
+ });
+
+ return response;
+}
+
+/** Logs user into the system GET /user/login */
+export function userLoginUsingGetQueryOptions(options: {
+ // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
+ params: API.userLoginUsingGetParams;
+ options?: CustomRequestOptions;
+}) {
+ return queryOptions({
+ queryFn: async ({ queryKey }) => {
+ return apis.userLoginUsingGet(queryKey[1] as typeof options);
+ },
+ queryKey: ['userLoginUsingGet', options],
+ });
+}
+
+/** Logs out current logged in user session 返回值: successful operation GET /user/logout */
+export function userLogoutUsingGetQueryOptions(options: {
+ options?: CustomRequestOptions;
+}) {
+ return queryOptions({
+ queryFn: async ({ queryKey }) => {
+ return apis.userLogoutUsingGet(queryKey[1] as typeof options);
+ },
+ queryKey: ['userLogoutUsingGet', options],
+ });
+}
diff --git a/src/static/app/icons/1024x1024.png b/src/static/app/icons/1024x1024.png
new file mode 100644
index 0000000..08dbd5f
Binary files /dev/null and b/src/static/app/icons/1024x1024.png differ
diff --git a/src/static/app/icons/120x120.png b/src/static/app/icons/120x120.png
new file mode 100644
index 0000000..718ca79
Binary files /dev/null and b/src/static/app/icons/120x120.png differ
diff --git a/src/static/app/icons/144x144.png b/src/static/app/icons/144x144.png
new file mode 100644
index 0000000..f78346b
Binary files /dev/null and b/src/static/app/icons/144x144.png differ
diff --git a/src/static/app/icons/152x152.png b/src/static/app/icons/152x152.png
new file mode 100644
index 0000000..f979721
Binary files /dev/null and b/src/static/app/icons/152x152.png differ
diff --git a/src/static/app/icons/167x167.png b/src/static/app/icons/167x167.png
new file mode 100644
index 0000000..d0aef20
Binary files /dev/null and b/src/static/app/icons/167x167.png differ
diff --git a/src/static/app/icons/180x180.png b/src/static/app/icons/180x180.png
new file mode 100644
index 0000000..24bd062
Binary files /dev/null and b/src/static/app/icons/180x180.png differ
diff --git a/src/static/app/icons/192x192.png b/src/static/app/icons/192x192.png
new file mode 100644
index 0000000..a8ea1a2
Binary files /dev/null and b/src/static/app/icons/192x192.png differ
diff --git a/src/static/app/icons/20x20.png b/src/static/app/icons/20x20.png
new file mode 100644
index 0000000..0abed04
Binary files /dev/null and b/src/static/app/icons/20x20.png differ
diff --git a/src/static/app/icons/29x29.png b/src/static/app/icons/29x29.png
new file mode 100644
index 0000000..a20d373
Binary files /dev/null and b/src/static/app/icons/29x29.png differ
diff --git a/src/static/app/icons/40x40.png b/src/static/app/icons/40x40.png
new file mode 100644
index 0000000..2b41be6
Binary files /dev/null and b/src/static/app/icons/40x40.png differ
diff --git a/src/static/app/icons/58x58.png b/src/static/app/icons/58x58.png
new file mode 100644
index 0000000..8e18b42
Binary files /dev/null and b/src/static/app/icons/58x58.png differ
diff --git a/src/static/app/icons/60x60.png b/src/static/app/icons/60x60.png
new file mode 100644
index 0000000..167826b
Binary files /dev/null and b/src/static/app/icons/60x60.png differ
diff --git a/src/static/app/icons/72x72.png b/src/static/app/icons/72x72.png
new file mode 100644
index 0000000..ddb91e3
Binary files /dev/null and b/src/static/app/icons/72x72.png differ
diff --git a/src/static/app/icons/76x76.png b/src/static/app/icons/76x76.png
new file mode 100644
index 0000000..0d9d28e
Binary files /dev/null and b/src/static/app/icons/76x76.png differ
diff --git a/src/static/app/icons/80x80.png b/src/static/app/icons/80x80.png
new file mode 100644
index 0000000..1877042
Binary files /dev/null and b/src/static/app/icons/80x80.png differ
diff --git a/src/static/app/icons/87x87.png b/src/static/app/icons/87x87.png
new file mode 100644
index 0000000..251fb24
Binary files /dev/null and b/src/static/app/icons/87x87.png differ
diff --git a/src/static/app/icons/96x96.png b/src/static/app/icons/96x96.png
new file mode 100644
index 0000000..eccf396
Binary files /dev/null and b/src/static/app/icons/96x96.png differ
diff --git a/src/static/images/avatar.jpg b/src/static/images/avatar.jpg
new file mode 100644
index 0000000..2010a70
Binary files /dev/null and b/src/static/images/avatar.jpg differ
diff --git a/src/static/images/default-avatar.png b/src/static/images/default-avatar.png
new file mode 100644
index 0000000..4eb5879
Binary files /dev/null and b/src/static/images/default-avatar.png differ
diff --git a/src/static/logo.svg b/src/static/logo.svg
new file mode 100644
index 0000000..eaee669
--- /dev/null
+++ b/src/static/logo.svg
@@ -0,0 +1,33 @@
+
+
diff --git a/src/static/tabbar/example.png b/src/static/tabbar/example.png
new file mode 100644
index 0000000..fd1e942
Binary files /dev/null and b/src/static/tabbar/example.png differ
diff --git a/src/static/tabbar/exampleHL.png b/src/static/tabbar/exampleHL.png
new file mode 100644
index 0000000..7501011
Binary files /dev/null and b/src/static/tabbar/exampleHL.png differ
diff --git a/src/static/tabbar/home.png b/src/static/tabbar/home.png
new file mode 100644
index 0000000..8f82e21
Binary files /dev/null and b/src/static/tabbar/home.png differ
diff --git a/src/static/tabbar/homeHL.png b/src/static/tabbar/homeHL.png
new file mode 100644
index 0000000..26d3761
Binary files /dev/null and b/src/static/tabbar/homeHL.png differ
diff --git a/src/static/tabbar/personal.png b/src/static/tabbar/personal.png
new file mode 100644
index 0000000..0a569a2
Binary files /dev/null and b/src/static/tabbar/personal.png differ
diff --git a/src/static/tabbar/personalHL.png b/src/static/tabbar/personalHL.png
new file mode 100644
index 0000000..8c3e66e
Binary files /dev/null and b/src/static/tabbar/personalHL.png differ
diff --git a/src/static/tabbar/scan.png b/src/static/tabbar/scan.png
new file mode 100644
index 0000000..f0f60c2
Binary files /dev/null and b/src/static/tabbar/scan.png differ
diff --git a/src/store/index.ts b/src/store/index.ts
new file mode 100644
index 0000000..74b1b2f
--- /dev/null
+++ b/src/store/index.ts
@@ -0,0 +1,17 @@
+import { createPinia } from 'pinia'
+import { createPersistedState } from 'pinia-plugin-persistedstate' // 数据持久化
+
+const store = createPinia()
+store.use(
+ createPersistedState({
+ storage: {
+ getItem: uni.getStorageSync,
+ setItem: uni.setStorageSync,
+ },
+ }),
+)
+
+export default store
+
+// 模块统一导出
+export * from './user'
diff --git a/src/store/user.ts b/src/store/user.ts
new file mode 100644
index 0000000..cec931a
--- /dev/null
+++ b/src/store/user.ts
@@ -0,0 +1,111 @@
+import type { IUserInfoVo } from '@/api/types/login'
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import {
+ getUserInfo as _getUserInfo,
+ login as _login,
+ logout as _logout,
+ wxLogin as _wxLogin,
+ getWxCode,
+} from '@/api/login'
+import { toast } from '@/utils/toast'
+
+// 初始化状态
+const userInfoState: IUserInfoVo = {
+ id: 0,
+ username: '',
+ avatar: '/static/images/default-avatar.png',
+ token: '',
+}
+
+export const useUserStore = defineStore(
+ 'user',
+ () => {
+ // 定义用户信息
+ const userInfo = ref({ ...userInfoState })
+ // 设置用户信息
+ const setUserInfo = (val: IUserInfoVo) => {
+ console.log('设置用户信息', val)
+ // 若头像为空 则使用默认头像
+ if (!val.avatar) {
+ val.avatar = userInfoState.avatar
+ }
+ else {
+ val.avatar = 'https://oss.laf.run/ukw0y1-site/avatar.jpg?feige'
+ }
+ userInfo.value = val
+ }
+ const setUserAvatar = (avatar: string) => {
+ userInfo.value.avatar = avatar
+ console.log('设置用户头像', avatar)
+ console.log('userInfo', userInfo.value)
+ }
+ // 删除用户信息
+ const removeUserInfo = () => {
+ userInfo.value = { ...userInfoState }
+ uni.removeStorageSync('userInfo')
+ uni.removeStorageSync('token')
+ }
+ /**
+ * 获取用户信息
+ */
+ const getUserInfo = async () => {
+ const res = await _getUserInfo()
+ const userInfo = res.data
+ setUserInfo(userInfo)
+ uni.setStorageSync('userInfo', userInfo)
+ uni.setStorageSync('token', userInfo.token)
+ // TODO 这里可以增加获取用户路由的方法 根据用户的角色动态生成路由
+ return res
+ }
+ /**
+ * 用户登录
+ * @param credentials 登录参数
+ * @returns R
+ */
+ const login = async (credentials: {
+ username: string
+ password: string
+ code: string
+ uuid: string
+ }) => {
+ const res = await _login(credentials)
+ console.log('登录信息', res)
+ toast.success('登录成功')
+ await getUserInfo()
+ return res
+ }
+
+ /**
+ * 退出登录 并 删除用户信息
+ */
+ const logout = async () => {
+ _logout()
+ removeUserInfo()
+ }
+ /**
+ * 微信登录
+ */
+ const wxLogin = async () => {
+ // 获取微信小程序登录的code
+ const data = await getWxCode()
+ console.log('微信登录code', data)
+
+ const res = await _wxLogin(data)
+ await getUserInfo()
+ return res
+ }
+
+ return {
+ userInfo,
+ login,
+ wxLogin,
+ getUserInfo,
+ setUserAvatar,
+ logout,
+ }
+ },
+ {
+ persist: true,
+ },
+)
diff --git a/src/style/iconfont.css b/src/style/iconfont.css
new file mode 100644
index 0000000..35da86c
--- /dev/null
+++ b/src/style/iconfont.css
@@ -0,0 +1,28 @@
+@font-face {
+ font-family: 'iconfont'; /* Project id 4543091 */
+ src:
+ url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAOwAAsAAAAAB9AAAANjAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACDHAqDBIJqATYCJAMQCwoABCAFhGcHPRvnBsgusG3kMyE15/44PsBX09waBHv0REDt97oHAQDFrOIyPirRiULQ+TJcXV0hCYTuVFcBC915/2vX/32Q80hkZ5PZGZ9snvwruVLloidKqYN6iKC53bOtbKwVLSIi3W6zCWZbs3VbER3j9JpGX3ySYcc94IQRTK5s4epS/jSqIgvg37qlY2/jwQN7D9ADpfRCmIknQByTscVZPTBr+hnnCKg2o4bjakvXEPjuY65DJGeJNtBUhn1JxOBuB2UZmUpBOXdsFp4oxOv4GHgs3h/+wRDcicqSZJG1q9kK1z/Af9NpqxjpC2QaAdpHlCFh4spcYXs5sMWpSk5wUj31G2dLQKVKkZ/w7f/8/i/A3JVUSZK9f7xIKJeU14IFpBI/Qfkkz46GT/CuaGREfCtKJUougWeQWHvVC5Lcz2BGS+SePR99vj3yjJx7h574tp7uWcOh4yfaTjS/245TT/vkQrN+a7RLkK8+Vd+bz+FSGh+9srDQKPeJ2s29z7ah4+efdoxefRbbGwfy7ht+SuIWukzsu1b6ePP+6kN1aamb47qsPim1Ia3xdEpDcl1dckPKGYnneI23+57r2W1Mmkqs6ajrChRCs5qyQ66rTVWhgZaG7toOeHm5cxn0sSQuNDEgcUTdNTSupKI1JRZih/JssAUKezPeOJJzbNozF6zWJuuVavVU5Tgtkop/SDzHa7ytvnCTq0PhkEfi4xLLtb0PuwyOAYqmrYQApFJyoJjTnfz+ve94vvv2f/yWgxl8Jd8Di2DRDPuob59mU/+VfDCROQyR8xSnmP9fXm7liagmN39OlmbvjqG0sMsJKrU0EFXogaRSH5bNY1CmxhyUq7QC1cY1T67RwuQk5CoM2RUQNLoEUb03kDS6h2XzcyjT7iOUa/QXqq1Hn6/GUBAaGcGcWJFlGUmCoVOp8kLvABHnVczGYiOE2SVEUH5OXj/TSnTCDjHAviAWcE4RZYaGWszNiKoayGSGTASeY+PcrMjNpVMvyREMDRoxBMYRVojFMkQiMOhohubdzxtAiOapMMbERpKMnQT9SL4ceQysVdJZVa9kEbsFogIcRyEUE2kN0mL7CDVIGhBzupWMEHA5bDvipgq5hKJcKef8ivbx1kC15KgcYkghhzLxYNntxoKCReJ82jAHAAA=')
+ format('woff2'),
+ url('//at.alicdn.com/t/c/font_4543091_njpo5b95nl.woff?t=1715485842402') format('woff'),
+ url('//at.alicdn.com/t/c/font_4543091_njpo5b95nl.ttf?t=1715485842402') format('truetype');
+}
+
+.iconfont {
+ font-family: 'iconfont' !important;
+ font-size: 16px;
+ font-style: normal;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-my:before {
+ content: '\e78c';
+}
+
+.icon-package:before {
+ content: '\e9c2';
+}
+
+.icon-chat:before {
+ content: '\e600';
+}
diff --git a/src/style/index.scss b/src/style/index.scss
new file mode 100644
index 0000000..1c98204
--- /dev/null
+++ b/src/style/index.scss
@@ -0,0 +1,34 @@
+// 测试用的 iconfont,可生效
+// @import './iconfont.css';
+
+.test {
+ // 可以通过 @apply 多个样式封装整体样式
+ @apply mt-4 ml-4;
+
+ padding-top: 4px;
+ color: red;
+}
+
+:root,
+page {
+ // 修改按主题色
+ // --wot-color-theme: #37c2bc;
+
+ // 修改按钮背景色
+ // --wot-button-primary-bg-color: green;
+}
+
+/*
+由于uniapp中无法使用*选择器,使用魔法代替*,加上此规则可以简化border与divide的使用,并提升布局的兼容性
+1. 防止padding和border影响元素宽度。 (https://github.com/mozdevs/cssremedy/issues/4)
+2. 允许仅通过添加边框宽度来向元素添加边框。 (https://github.com/tailwindcss/tailwindcss/pull/116)
+3. [UnoCSS]: 允许使用css变量'--un-default-border-color'覆盖默认边框颜色
+*/
+:not(not),
+::before,
+::after {
+ box-sizing: border-box; /* 1 */
+ border-width: 0; /* 2 */
+ border-style: solid; /* 2 */
+ border-color: var(--un-default-border-color, #e5e7eb); /* 3 */
+}
diff --git a/src/tabbar/README.md b/src/tabbar/README.md
new file mode 100644
index 0000000..15a9427
--- /dev/null
+++ b/src/tabbar/README.md
@@ -0,0 +1,32 @@
+# tabbar 说明
+
+## tabbar 4种策略
+
+`tabbar` 分为 `4 种` 情况:
+
+- 0 `无 tabbar`,只有一个页面入口,底部无 `tabbar` 显示;常用语临时活动页。
+- 1 `原生 tabbar`,使用 `switchTab` 切换 tabbar,`tabbar` 页面有缓存。
+ - 优势:原生自带的 tabbar,最先渲染,有缓存。
+ - 劣势:只能使用 2 组图片来切换选中和非选中状态,修改颜色只能重新换图片(或者用 iconfont)。
+- 2 `有缓存自定义 tabbar`,使用 `switchTab` 切换 tabbar,`tabbar` 页面有缓存。使用了第三方 UI 库的 `tabbar` 组件,并隐藏了原生 `tabbar` 的显示。
+ - 优势:可以随意配置自己想要的 `svg icon`,切换字体颜色方便。有缓存。可以实现各种花里胡哨的动效等。
+ - 劣势:首次点击 tababr 会闪烁。
+- 3 `无缓存自定义 tabbar`,使用 `navigateTo` 切换 `tabbar`,`tabbar` 页面无缓存。使用了第三方 UI 库的 `tabbar` 组件。
+ - 优势:可以随意配置自己想要的 svg icon,切换字体颜色方便。可以实现各种花里胡哨的动效等。
+ - 劣势:首次点击 `tababr` 会闪烁,无缓存。
+
+
+> 注意:花里胡哨的效果需要自己实现,本模版不提供。
+
+## tabbar 配置说明
+
+- 如果使用的是原生tabbar, 则每个 `item` 需要配置 `path`、`text`、`iconPath`、`selectedIconPath` 等属性。
+- 如果使用的是自定义tabbar, 则每个 `item` 需要配置 `path`、`text`、`icon` 、`iconType` 等属性(如果是local还需要配置2种图片)。
+
+## 接口拿到tabbar列表怎么处理?
+
+首先,接口的配置需要跟原生tabbar的 `path` 对应上。
+
+然后,可以直接在 `index.vue` 文件请求接口拿到 `tabbarList`,然后赋值给 `tabbarList` 即可。
+
+最后,如果用的是 `unocss` 图标,还需要在 `uno.config.ts` 的 `safelist` 中添加图标名称。
\ No newline at end of file
diff --git a/src/tabbar/config.ts b/src/tabbar/config.ts
new file mode 100644
index 0000000..94c4020
--- /dev/null
+++ b/src/tabbar/config.ts
@@ -0,0 +1,127 @@
+import type { TabBar } from '@uni-helper/vite-plugin-uni-pages'
+
+type NativeTabBarItem = TabBar['list'][0]
+
+type CustomTabBarItem = (Pick & {
+ iconType: 'uniUi' | 'uiLib' | 'unocss' | 'iconfont' | 'image' // 不建议用 image 模式,需要配置2张图
+ icon: any // 其实是 string 类型,这里是为了避免 ts 报错 (tabbar/index.vue 里面 uni-icons 那行)
+ activeIcon?: string // 只有在 image 模式下才需要,传递的是高亮的图片(PS: 不建议用 image 模式)
+ badge?: number | 'dot' // badge 显示一个数字或 小红点(样式可以直接在 tabbar/index.vue 里面修改)
+ isBulge?: boolean // 是否是中间的鼓包tabbarItem
+})
+
+/**
+ * tabbar 选择的策略,更详细的介绍见 tabbar.md 文件
+ * 0: 'NO_TABBAR' `无 tabbar`
+ * 1: 'NATIVE_TABBAR' `完全原生 tabbar`
+ * 2: 'CUSTOM_TABBAR_WITH_CACHE' `有缓存自定义 tabbar`
+ * 3: 'CUSTOM_TABBAR_WITHOUT_CACHE' `无缓存自定义 tabbar`
+ *
+ * 温馨提示:本文件的任何代码更改了之后,都需要重新运行,否则 pages.json 不会更新导致配置不生效
+ */
+export const TABBAR_MAP = {
+ NO_TABBAR: 0,
+ NATIVE_TABBAR: 1,
+ CUSTOM_TABBAR_WITH_CACHE: 2,
+ CUSTOM_TABBAR_WITHOUT_CACHE: 3,
+}
+
+// TODO: 1/3. 通过这里切换使用tabbar的策略
+export const selectedTabbarStrategy = TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE
+
+// TODO: 2/3. 更新下面的 tabbar 配置
+// 如果是使用 NO_TABBAR(0),nativeTabbarList 和 customTabbarList 都不生效(里面的配置不用管)
+// 如果是使用 NATIVE_TABBAR(1),customTabbarList 不生效(里面的配置不用管)
+// 如果是使用 CUSTOM_TABBAR(2,3),nativeTabbarList 不生效(里面的配置不用管)
+// pagePath 是 nativeTabbarList 和 customTabbarList 的关联点,如果没有对应上,会有问题!!
+export const nativeTabbarList: NativeTabBarItem[] = [
+ {
+ iconPath: 'static/tabbar/home.png',
+ selectedIconPath: 'static/tabbar/homeHL.png',
+ pagePath: 'pages/index/index',
+ text: '首页',
+ },
+ {
+ iconPath: 'static/tabbar/example.png',
+ selectedIconPath: 'static/tabbar/exampleHL.png',
+ pagePath: 'pages/about/about',
+ text: '关于',
+ },
+]
+
+// pagePath 是 nativeTabbarList 和 customTabbarList 的关联点,如果没有对应上,会有问题!!
+// 如果希望通过接口调用 customTabbarList,可以在 tabbar/index.vue 文件里面调用接口
+// 本文件因为需要提前编译生成 pages.json, 接口拦截还不生效,无法正常调用接口
+export const customTabbarList: CustomTabBarItem[] = [
+ {
+ // text 和 pagePath 可以自己直接写,也可以通过索引从 nativeTabbarList 中获取
+ text: '首页',
+ pagePath: 'pages/index/index', // pagePath 是两者的关联点
+ // 本框架内置了 uniapp 官方UI库 (uni-ui)的图标库
+ // 使用方式如:
+ // 图标列表地址:https://uniapp.dcloud.net.cn/component/uniui/uni-icons.html
+ iconType: 'uniUi',
+ icon: 'home',
+ // badge: 'dot',
+ },
+ {
+ text: nativeTabbarList[1].text,
+ pagePath: nativeTabbarList[1].pagePath, // pagePath 是两者的关联点
+ // 注意 unocss 图标需要如下处理:(二选一)
+ // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
+ // 2)配置到 unocss.config.ts 的 safelist 中
+ iconType: 'unocss',
+ icon: 'i-carbon-code',
+ // badge: 10,
+ },
+
+ // {
+ // pagePath: 'pages/mine/index',
+ // text: '我的',
+ // // 注意 iconfont 图标需要额外加上 'iconfont',如下
+ // iconType: 'iconfont',
+ // icon: 'iconfont icon-my',
+ // },
+ // {
+ // pagePath: 'pages/index/index',
+ // text: '首页',
+ // // 使用 ‘image’时,需要配置 icon + iconActive 2张图片(不推荐)
+ // // 既然已经用了自定义tabbar了,就不建议用图片了,所以不推荐
+ // iconType: 'image',
+ // icon: '/static/tabbar/home.png',
+ // iconActive: '/static/tabbar/homeHL.png',
+ // },
+]
+
+// NATIVE_TABBAR(1) 和 CUSTOM_TABBAR_WITH_CACHE(2) 时,需要tabbar缓存
+/** 是否启用 tabbar 缓存 */
+export const tabbarCacheEnable
+ = [TABBAR_MAP.NATIVE_TABBAR, TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE].includes(selectedTabbarStrategy)
+
+// CUSTOM_TABBAR_WITH_CACHE(2) 和 CUSTOM_TABBAR_WITHOUT_CACHE(3) 时,启用自定义tabbar
+/** 是否启用自定义 tabbar */
+export const customTabbarEnable
+ = [TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE, TABBAR_MAP.CUSTOM_TABBAR_WITHOUT_CACHE].includes(selectedTabbarStrategy)
+
+// CUSTOM_TABBAR_WITH_CACHE(2)时,需要隐藏原生tabbar
+/** 是否需要隐藏原生 tabbar */
+export const nativeTabbarNeedHide = selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE
+
+const _tabbar: TabBar = {
+ // 只有微信小程序支持 custom。App 和 H5 不生效
+ custom: selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE,
+ color: '#999999',
+ selectedColor: '#018d71',
+ backgroundColor: '#F8F8F8',
+ borderStyle: 'black',
+ height: '50px',
+ fontSize: '10px',
+ iconWidth: '24px',
+ spacing: '3px',
+ list: nativeTabbarList as unknown as TabBar['list'],
+}
+
+export const tabbarList = nativeTabbarList
+
+// 0和1 需要显示底部的tabbar的各种配置,以利用缓存
+export const tabBar = tabbarCacheEnable ? _tabbar : undefined
diff --git a/src/tabbar/index.vue b/src/tabbar/index.vue
new file mode 100644
index 0000000..ba3884f
--- /dev/null
+++ b/src/tabbar/index.vue
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.text }}
+
+
+
+
+
+
+
+
+ {{ item.badge }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tabbar/store.ts b/src/tabbar/store.ts
new file mode 100644
index 0000000..61c39d5
--- /dev/null
+++ b/src/tabbar/store.ts
@@ -0,0 +1,19 @@
+/**
+ * tabbar 状态,增加 storageSync 保证刷新浏览器时在正确的 tabbar 页面
+ * 使用reactive简单状态,而不是 pinia 全局状态
+ */
+export const tabbarStore = reactive({
+ curIdx: uni.getStorageSync('app-tabbar-index') || 0,
+ prevIdx: uni.getStorageSync('app-tabbar-index') || 0,
+ setCurIdx(idx: number) {
+ this.curIdx = idx
+ uni.setStorageSync('app-tabbar-index', idx)
+ },
+
+ restorePrevIdx() {
+ if (this.prevIdx === this.curIdx)
+ return
+ this.setCurIdx(this.prevIdx)
+ this.prevIdx = uni.getStorageSync('app-tabbar-index') || 0
+ },
+})
diff --git a/src/typings.d.ts b/src/typings.d.ts
new file mode 100644
index 0000000..9ead3fb
--- /dev/null
+++ b/src/typings.d.ts
@@ -0,0 +1,28 @@
+// 全局要用的类型放到这里
+
+declare global {
+ interface IResData {
+ code: number
+ msg: string
+ data: T
+ }
+
+ // uni.uploadFile文件上传参数
+ interface IUniUploadFileOptions {
+ file?: File
+ files?: UniApp.UploadFileOptionFiles[]
+ filePath?: string
+ name?: string
+ formData?: any
+ }
+
+ interface IUserInfo {
+ nickname?: string
+ avatar?: string
+ /** 微信的 openid,非微信没有这个字段 */
+ openid?: string
+ token?: string
+ }
+}
+
+export {} // 防止模块污染
diff --git a/src/typings.ts b/src/typings.ts
new file mode 100644
index 0000000..b48b630
--- /dev/null
+++ b/src/typings.ts
@@ -0,0 +1,15 @@
+// 枚举定义
+
+export enum TestEnum {
+ A = '1',
+ B = '2',
+}
+
+// uni.uploadFile文件上传参数
+export interface IUniUploadFileOptions {
+ file?: File
+ files?: UniApp.UploadFileOptionFiles[]
+ filePath?: string
+ name?: string
+ formData?: any
+}
diff --git a/src/uni.scss b/src/uni.scss
new file mode 100644
index 0000000..21b9e5f
--- /dev/null
+++ b/src/uni.scss
@@ -0,0 +1,77 @@
+/* stylelint-disable comment-empty-line-before */
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color: #333; // 基本色
+$uni-text-color-inverse: #fff; // 反色
+$uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable: #c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color: #fff;
+$uni-bg-color-grey: #f8f8f8;
+$uni-bg-color-hover: #f1f1f1; // 点击状态颜色
+$uni-bg-color-mask: rgb(0 0 0 / 40%); // 遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color: #c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm: 12px;
+$uni-font-size-base: 14px;
+$uni-font-size-lg: 16;
+
+/* 图片尺寸 */
+$uni-img-size-sm: 20px;
+$uni-img-size-base: 26px;
+$uni-img-size-lg: 40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2c405a; // 文章标题颜色
+$uni-font-size-title: 20px;
+$uni-color-subtitle: #555; // 二级标题颜色
+$uni-font-size-subtitle: 18px;
+$uni-color-paragraph: #3f536e; // 文章段落颜色
+$uni-font-size-paragraph: 15px;
diff --git a/src/uni_modules/.gitkeep b/src/uni_modules/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/uni_modules/uni-icons/changelog.md b/src/uni_modules/uni-icons/changelog.md
new file mode 100644
index 0000000..0261131
--- /dev/null
+++ b/src/uni_modules/uni-icons/changelog.md
@@ -0,0 +1,42 @@
+## 2.0.10(2024-06-07)
+- 优化 uni-app x 中,size 属性的类型
+## 2.0.9(2024-01-12)
+fix: 修复图标大小默认值错误的问题
+## 2.0.8(2023-12-14)
+- 修复 项目未使用 ts 情况下,打包报错的bug
+## 2.0.7(2023-12-14)
+- 修复 size 属性为 string 时,不加单位导致尺寸异常的bug
+## 2.0.6(2023-12-11)
+- 优化 兼容老版本icon类型,如 top ,bottom 等
+## 2.0.5(2023-12-11)
+- 优化 兼容老版本icon类型,如 top ,bottom 等
+## 2.0.4(2023-12-06)
+- 优化 uni-app x 下示例项目图标排序
+## 2.0.3(2023-12-06)
+- 修复 nvue下引入组件报错的bug
+## 2.0.2(2023-12-05)
+-优化 size 属性支持单位
+## 2.0.1(2023-12-05)
+- 新增 uni-app x 支持定义图标
+## 1.3.5(2022-01-24)
+- 优化 size 属性可以传入不带单位的字符串数值
+## 1.3.4(2022-01-24)
+- 优化 size 支持其他单位
+## 1.3.3(2022-01-17)
+- 修复 nvue 有些图标不显示的bug,兼容老版本图标
+## 1.3.2(2021-12-01)
+- 优化 示例可复制图标名称
+## 1.3.1(2021-11-23)
+- 优化 兼容旧组件 type 值
+## 1.3.0(2021-11-19)
+- 新增 更多图标
+- 优化 自定义图标使用方式
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons)
+## 1.1.7(2021-11-08)
+## 1.2.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.1.5(2021-05-12)
+- 新增 组件示例地址
+## 1.1.4(2021-02-05)
+- 调整为uni_modules目录规范
diff --git a/src/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue b/src/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue
new file mode 100644
index 0000000..8740559
--- /dev/null
+++ b/src/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue
@@ -0,0 +1,91 @@
+
+
+ {{unicode}}
+
+
+
+
+
+
diff --git a/src/uni_modules/uni-icons/components/uni-icons/uni-icons.vue b/src/uni_modules/uni-icons/components/uni-icons/uni-icons.vue
new file mode 100644
index 0000000..7da5356
--- /dev/null
+++ b/src/uni_modules/uni-icons/components/uni-icons/uni-icons.vue
@@ -0,0 +1,110 @@
+
+
+ {{unicode}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/uni_modules/uni-icons/components/uni-icons/uniicons.css b/src/uni_modules/uni-icons/components/uni-icons/uniicons.css
new file mode 100644
index 0000000..0a6b6fe
--- /dev/null
+++ b/src/uni_modules/uni-icons/components/uni-icons/uniicons.css
@@ -0,0 +1,664 @@
+
+.uniui-cart-filled:before {
+ content: "\e6d0";
+}
+
+.uniui-gift-filled:before {
+ content: "\e6c4";
+}
+
+.uniui-color:before {
+ content: "\e6cf";
+}
+
+.uniui-wallet:before {
+ content: "\e6b1";
+}
+
+.uniui-settings-filled:before {
+ content: "\e6ce";
+}
+
+.uniui-auth-filled:before {
+ content: "\e6cc";
+}
+
+.uniui-shop-filled:before {
+ content: "\e6cd";
+}
+
+.uniui-staff-filled:before {
+ content: "\e6cb";
+}
+
+.uniui-vip-filled:before {
+ content: "\e6c6";
+}
+
+.uniui-plus-filled:before {
+ content: "\e6c7";
+}
+
+.uniui-folder-add-filled:before {
+ content: "\e6c8";
+}
+
+.uniui-color-filled:before {
+ content: "\e6c9";
+}
+
+.uniui-tune-filled:before {
+ content: "\e6ca";
+}
+
+.uniui-calendar-filled:before {
+ content: "\e6c0";
+}
+
+.uniui-notification-filled:before {
+ content: "\e6c1";
+}
+
+.uniui-wallet-filled:before {
+ content: "\e6c2";
+}
+
+.uniui-medal-filled:before {
+ content: "\e6c3";
+}
+
+.uniui-fire-filled:before {
+ content: "\e6c5";
+}
+
+.uniui-refreshempty:before {
+ content: "\e6bf";
+}
+
+.uniui-location-filled:before {
+ content: "\e6af";
+}
+
+.uniui-person-filled:before {
+ content: "\e69d";
+}
+
+.uniui-personadd-filled:before {
+ content: "\e698";
+}
+
+.uniui-arrowthinleft:before {
+ content: "\e6d2";
+}
+
+.uniui-arrowthinup:before {
+ content: "\e6d3";
+}
+
+.uniui-arrowthindown:before {
+ content: "\e6d4";
+}
+
+.uniui-back:before {
+ content: "\e6b9";
+}
+
+.uniui-forward:before {
+ content: "\e6ba";
+}
+
+.uniui-arrow-right:before {
+ content: "\e6bb";
+}
+
+.uniui-arrow-left:before {
+ content: "\e6bc";
+}
+
+.uniui-arrow-up:before {
+ content: "\e6bd";
+}
+
+.uniui-arrow-down:before {
+ content: "\e6be";
+}
+
+.uniui-arrowthinright:before {
+ content: "\e6d1";
+}
+
+.uniui-down:before {
+ content: "\e6b8";
+}
+
+.uniui-bottom:before {
+ content: "\e6b8";
+}
+
+.uniui-arrowright:before {
+ content: "\e6d5";
+}
+
+.uniui-right:before {
+ content: "\e6b5";
+}
+
+.uniui-up:before {
+ content: "\e6b6";
+}
+
+.uniui-top:before {
+ content: "\e6b6";
+}
+
+.uniui-left:before {
+ content: "\e6b7";
+}
+
+.uniui-arrowup:before {
+ content: "\e6d6";
+}
+
+.uniui-eye:before {
+ content: "\e651";
+}
+
+.uniui-eye-filled:before {
+ content: "\e66a";
+}
+
+.uniui-eye-slash:before {
+ content: "\e6b3";
+}
+
+.uniui-eye-slash-filled:before {
+ content: "\e6b4";
+}
+
+.uniui-info-filled:before {
+ content: "\e649";
+}
+
+.uniui-reload:before {
+ content: "\e6b2";
+}
+
+.uniui-micoff-filled:before {
+ content: "\e6b0";
+}
+
+.uniui-map-pin-ellipse:before {
+ content: "\e6ac";
+}
+
+.uniui-map-pin:before {
+ content: "\e6ad";
+}
+
+.uniui-location:before {
+ content: "\e6ae";
+}
+
+.uniui-starhalf:before {
+ content: "\e683";
+}
+
+.uniui-star:before {
+ content: "\e688";
+}
+
+.uniui-star-filled:before {
+ content: "\e68f";
+}
+
+.uniui-calendar:before {
+ content: "\e6a0";
+}
+
+.uniui-fire:before {
+ content: "\e6a1";
+}
+
+.uniui-medal:before {
+ content: "\e6a2";
+}
+
+.uniui-font:before {
+ content: "\e6a3";
+}
+
+.uniui-gift:before {
+ content: "\e6a4";
+}
+
+.uniui-link:before {
+ content: "\e6a5";
+}
+
+.uniui-notification:before {
+ content: "\e6a6";
+}
+
+.uniui-staff:before {
+ content: "\e6a7";
+}
+
+.uniui-vip:before {
+ content: "\e6a8";
+}
+
+.uniui-folder-add:before {
+ content: "\e6a9";
+}
+
+.uniui-tune:before {
+ content: "\e6aa";
+}
+
+.uniui-auth:before {
+ content: "\e6ab";
+}
+
+.uniui-person:before {
+ content: "\e699";
+}
+
+.uniui-email-filled:before {
+ content: "\e69a";
+}
+
+.uniui-phone-filled:before {
+ content: "\e69b";
+}
+
+.uniui-phone:before {
+ content: "\e69c";
+}
+
+.uniui-email:before {
+ content: "\e69e";
+}
+
+.uniui-personadd:before {
+ content: "\e69f";
+}
+
+.uniui-chatboxes-filled:before {
+ content: "\e692";
+}
+
+.uniui-contact:before {
+ content: "\e693";
+}
+
+.uniui-chatbubble-filled:before {
+ content: "\e694";
+}
+
+.uniui-contact-filled:before {
+ content: "\e695";
+}
+
+.uniui-chatboxes:before {
+ content: "\e696";
+}
+
+.uniui-chatbubble:before {
+ content: "\e697";
+}
+
+.uniui-upload-filled:before {
+ content: "\e68e";
+}
+
+.uniui-upload:before {
+ content: "\e690";
+}
+
+.uniui-weixin:before {
+ content: "\e691";
+}
+
+.uniui-compose:before {
+ content: "\e67f";
+}
+
+.uniui-qq:before {
+ content: "\e680";
+}
+
+.uniui-download-filled:before {
+ content: "\e681";
+}
+
+.uniui-pyq:before {
+ content: "\e682";
+}
+
+.uniui-sound:before {
+ content: "\e684";
+}
+
+.uniui-trash-filled:before {
+ content: "\e685";
+}
+
+.uniui-sound-filled:before {
+ content: "\e686";
+}
+
+.uniui-trash:before {
+ content: "\e687";
+}
+
+.uniui-videocam-filled:before {
+ content: "\e689";
+}
+
+.uniui-spinner-cycle:before {
+ content: "\e68a";
+}
+
+.uniui-weibo:before {
+ content: "\e68b";
+}
+
+.uniui-videocam:before {
+ content: "\e68c";
+}
+
+.uniui-download:before {
+ content: "\e68d";
+}
+
+.uniui-help:before {
+ content: "\e679";
+}
+
+.uniui-navigate-filled:before {
+ content: "\e67a";
+}
+
+.uniui-plusempty:before {
+ content: "\e67b";
+}
+
+.uniui-smallcircle:before {
+ content: "\e67c";
+}
+
+.uniui-minus-filled:before {
+ content: "\e67d";
+}
+
+.uniui-micoff:before {
+ content: "\e67e";
+}
+
+.uniui-closeempty:before {
+ content: "\e66c";
+}
+
+.uniui-clear:before {
+ content: "\e66d";
+}
+
+.uniui-navigate:before {
+ content: "\e66e";
+}
+
+.uniui-minus:before {
+ content: "\e66f";
+}
+
+.uniui-image:before {
+ content: "\e670";
+}
+
+.uniui-mic:before {
+ content: "\e671";
+}
+
+.uniui-paperplane:before {
+ content: "\e672";
+}
+
+.uniui-close:before {
+ content: "\e673";
+}
+
+.uniui-help-filled:before {
+ content: "\e674";
+}
+
+.uniui-paperplane-filled:before {
+ content: "\e675";
+}
+
+.uniui-plus:before {
+ content: "\e676";
+}
+
+.uniui-mic-filled:before {
+ content: "\e677";
+}
+
+.uniui-image-filled:before {
+ content: "\e678";
+}
+
+.uniui-locked-filled:before {
+ content: "\e668";
+}
+
+.uniui-info:before {
+ content: "\e669";
+}
+
+.uniui-locked:before {
+ content: "\e66b";
+}
+
+.uniui-camera-filled:before {
+ content: "\e658";
+}
+
+.uniui-chat-filled:before {
+ content: "\e659";
+}
+
+.uniui-camera:before {
+ content: "\e65a";
+}
+
+.uniui-circle:before {
+ content: "\e65b";
+}
+
+.uniui-checkmarkempty:before {
+ content: "\e65c";
+}
+
+.uniui-chat:before {
+ content: "\e65d";
+}
+
+.uniui-circle-filled:before {
+ content: "\e65e";
+}
+
+.uniui-flag:before {
+ content: "\e65f";
+}
+
+.uniui-flag-filled:before {
+ content: "\e660";
+}
+
+.uniui-gear-filled:before {
+ content: "\e661";
+}
+
+.uniui-home:before {
+ content: "\e662";
+}
+
+.uniui-home-filled:before {
+ content: "\e663";
+}
+
+.uniui-gear:before {
+ content: "\e664";
+}
+
+.uniui-smallcircle-filled:before {
+ content: "\e665";
+}
+
+.uniui-map-filled:before {
+ content: "\e666";
+}
+
+.uniui-map:before {
+ content: "\e667";
+}
+
+.uniui-refresh-filled:before {
+ content: "\e656";
+}
+
+.uniui-refresh:before {
+ content: "\e657";
+}
+
+.uniui-cloud-upload:before {
+ content: "\e645";
+}
+
+.uniui-cloud-download-filled:before {
+ content: "\e646";
+}
+
+.uniui-cloud-download:before {
+ content: "\e647";
+}
+
+.uniui-cloud-upload-filled:before {
+ content: "\e648";
+}
+
+.uniui-redo:before {
+ content: "\e64a";
+}
+
+.uniui-images-filled:before {
+ content: "\e64b";
+}
+
+.uniui-undo-filled:before {
+ content: "\e64c";
+}
+
+.uniui-more:before {
+ content: "\e64d";
+}
+
+.uniui-more-filled:before {
+ content: "\e64e";
+}
+
+.uniui-undo:before {
+ content: "\e64f";
+}
+
+.uniui-images:before {
+ content: "\e650";
+}
+
+.uniui-paperclip:before {
+ content: "\e652";
+}
+
+.uniui-settings:before {
+ content: "\e653";
+}
+
+.uniui-search:before {
+ content: "\e654";
+}
+
+.uniui-redo-filled:before {
+ content: "\e655";
+}
+
+.uniui-list:before {
+ content: "\e644";
+}
+
+.uniui-mail-open-filled:before {
+ content: "\e63a";
+}
+
+.uniui-hand-down-filled:before {
+ content: "\e63c";
+}
+
+.uniui-hand-down:before {
+ content: "\e63d";
+}
+
+.uniui-hand-up-filled:before {
+ content: "\e63e";
+}
+
+.uniui-hand-up:before {
+ content: "\e63f";
+}
+
+.uniui-heart-filled:before {
+ content: "\e641";
+}
+
+.uniui-mail-open:before {
+ content: "\e643";
+}
+
+.uniui-heart:before {
+ content: "\e639";
+}
+
+.uniui-loop:before {
+ content: "\e633";
+}
+
+.uniui-pulldown:before {
+ content: "\e632";
+}
+
+.uniui-scan:before {
+ content: "\e62a";
+}
+
+.uniui-bars:before {
+ content: "\e627";
+}
+
+.uniui-checkbox:before {
+ content: "\e62b";
+}
+
+.uniui-checkbox-filled:before {
+ content: "\e62c";
+}
+
+.uniui-shop:before {
+ content: "\e62f";
+}
+
+.uniui-headphones:before {
+ content: "\e630";
+}
+
+.uniui-cart:before {
+ content: "\e631";
+}
diff --git a/src/uni_modules/uni-icons/components/uni-icons/uniicons.ttf b/src/uni_modules/uni-icons/components/uni-icons/uniicons.ttf
new file mode 100644
index 0000000..14696d0
Binary files /dev/null and b/src/uni_modules/uni-icons/components/uni-icons/uniicons.ttf differ
diff --git a/src/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts b/src/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts
new file mode 100644
index 0000000..98e93aa
--- /dev/null
+++ b/src/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts
@@ -0,0 +1,664 @@
+
+export type IconsData = {
+ id : string
+ name : string
+ font_family : string
+ css_prefix_text : string
+ description : string
+ glyphs : Array
+}
+
+export type IconsDataItem = {
+ font_class : string
+ unicode : string
+}
+
+
+export const fontData = [
+ {
+ "font_class": "arrow-down",
+ "unicode": "\ue6be"
+ },
+ {
+ "font_class": "arrow-left",
+ "unicode": "\ue6bc"
+ },
+ {
+ "font_class": "arrow-right",
+ "unicode": "\ue6bb"
+ },
+ {
+ "font_class": "arrow-up",
+ "unicode": "\ue6bd"
+ },
+ {
+ "font_class": "auth",
+ "unicode": "\ue6ab"
+ },
+ {
+ "font_class": "auth-filled",
+ "unicode": "\ue6cc"
+ },
+ {
+ "font_class": "back",
+ "unicode": "\ue6b9"
+ },
+ {
+ "font_class": "bars",
+ "unicode": "\ue627"
+ },
+ {
+ "font_class": "calendar",
+ "unicode": "\ue6a0"
+ },
+ {
+ "font_class": "calendar-filled",
+ "unicode": "\ue6c0"
+ },
+ {
+ "font_class": "camera",
+ "unicode": "\ue65a"
+ },
+ {
+ "font_class": "camera-filled",
+ "unicode": "\ue658"
+ },
+ {
+ "font_class": "cart",
+ "unicode": "\ue631"
+ },
+ {
+ "font_class": "cart-filled",
+ "unicode": "\ue6d0"
+ },
+ {
+ "font_class": "chat",
+ "unicode": "\ue65d"
+ },
+ {
+ "font_class": "chat-filled",
+ "unicode": "\ue659"
+ },
+ {
+ "font_class": "chatboxes",
+ "unicode": "\ue696"
+ },
+ {
+ "font_class": "chatboxes-filled",
+ "unicode": "\ue692"
+ },
+ {
+ "font_class": "chatbubble",
+ "unicode": "\ue697"
+ },
+ {
+ "font_class": "chatbubble-filled",
+ "unicode": "\ue694"
+ },
+ {
+ "font_class": "checkbox",
+ "unicode": "\ue62b"
+ },
+ {
+ "font_class": "checkbox-filled",
+ "unicode": "\ue62c"
+ },
+ {
+ "font_class": "checkmarkempty",
+ "unicode": "\ue65c"
+ },
+ {
+ "font_class": "circle",
+ "unicode": "\ue65b"
+ },
+ {
+ "font_class": "circle-filled",
+ "unicode": "\ue65e"
+ },
+ {
+ "font_class": "clear",
+ "unicode": "\ue66d"
+ },
+ {
+ "font_class": "close",
+ "unicode": "\ue673"
+ },
+ {
+ "font_class": "closeempty",
+ "unicode": "\ue66c"
+ },
+ {
+ "font_class": "cloud-download",
+ "unicode": "\ue647"
+ },
+ {
+ "font_class": "cloud-download-filled",
+ "unicode": "\ue646"
+ },
+ {
+ "font_class": "cloud-upload",
+ "unicode": "\ue645"
+ },
+ {
+ "font_class": "cloud-upload-filled",
+ "unicode": "\ue648"
+ },
+ {
+ "font_class": "color",
+ "unicode": "\ue6cf"
+ },
+ {
+ "font_class": "color-filled",
+ "unicode": "\ue6c9"
+ },
+ {
+ "font_class": "compose",
+ "unicode": "\ue67f"
+ },
+ {
+ "font_class": "contact",
+ "unicode": "\ue693"
+ },
+ {
+ "font_class": "contact-filled",
+ "unicode": "\ue695"
+ },
+ {
+ "font_class": "down",
+ "unicode": "\ue6b8"
+ },
+ {
+ "font_class": "bottom",
+ "unicode": "\ue6b8"
+ },
+ {
+ "font_class": "download",
+ "unicode": "\ue68d"
+ },
+ {
+ "font_class": "download-filled",
+ "unicode": "\ue681"
+ },
+ {
+ "font_class": "email",
+ "unicode": "\ue69e"
+ },
+ {
+ "font_class": "email-filled",
+ "unicode": "\ue69a"
+ },
+ {
+ "font_class": "eye",
+ "unicode": "\ue651"
+ },
+ {
+ "font_class": "eye-filled",
+ "unicode": "\ue66a"
+ },
+ {
+ "font_class": "eye-slash",
+ "unicode": "\ue6b3"
+ },
+ {
+ "font_class": "eye-slash-filled",
+ "unicode": "\ue6b4"
+ },
+ {
+ "font_class": "fire",
+ "unicode": "\ue6a1"
+ },
+ {
+ "font_class": "fire-filled",
+ "unicode": "\ue6c5"
+ },
+ {
+ "font_class": "flag",
+ "unicode": "\ue65f"
+ },
+ {
+ "font_class": "flag-filled",
+ "unicode": "\ue660"
+ },
+ {
+ "font_class": "folder-add",
+ "unicode": "\ue6a9"
+ },
+ {
+ "font_class": "folder-add-filled",
+ "unicode": "\ue6c8"
+ },
+ {
+ "font_class": "font",
+ "unicode": "\ue6a3"
+ },
+ {
+ "font_class": "forward",
+ "unicode": "\ue6ba"
+ },
+ {
+ "font_class": "gear",
+ "unicode": "\ue664"
+ },
+ {
+ "font_class": "gear-filled",
+ "unicode": "\ue661"
+ },
+ {
+ "font_class": "gift",
+ "unicode": "\ue6a4"
+ },
+ {
+ "font_class": "gift-filled",
+ "unicode": "\ue6c4"
+ },
+ {
+ "font_class": "hand-down",
+ "unicode": "\ue63d"
+ },
+ {
+ "font_class": "hand-down-filled",
+ "unicode": "\ue63c"
+ },
+ {
+ "font_class": "hand-up",
+ "unicode": "\ue63f"
+ },
+ {
+ "font_class": "hand-up-filled",
+ "unicode": "\ue63e"
+ },
+ {
+ "font_class": "headphones",
+ "unicode": "\ue630"
+ },
+ {
+ "font_class": "heart",
+ "unicode": "\ue639"
+ },
+ {
+ "font_class": "heart-filled",
+ "unicode": "\ue641"
+ },
+ {
+ "font_class": "help",
+ "unicode": "\ue679"
+ },
+ {
+ "font_class": "help-filled",
+ "unicode": "\ue674"
+ },
+ {
+ "font_class": "home",
+ "unicode": "\ue662"
+ },
+ {
+ "font_class": "home-filled",
+ "unicode": "\ue663"
+ },
+ {
+ "font_class": "image",
+ "unicode": "\ue670"
+ },
+ {
+ "font_class": "image-filled",
+ "unicode": "\ue678"
+ },
+ {
+ "font_class": "images",
+ "unicode": "\ue650"
+ },
+ {
+ "font_class": "images-filled",
+ "unicode": "\ue64b"
+ },
+ {
+ "font_class": "info",
+ "unicode": "\ue669"
+ },
+ {
+ "font_class": "info-filled",
+ "unicode": "\ue649"
+ },
+ {
+ "font_class": "left",
+ "unicode": "\ue6b7"
+ },
+ {
+ "font_class": "link",
+ "unicode": "\ue6a5"
+ },
+ {
+ "font_class": "list",
+ "unicode": "\ue644"
+ },
+ {
+ "font_class": "location",
+ "unicode": "\ue6ae"
+ },
+ {
+ "font_class": "location-filled",
+ "unicode": "\ue6af"
+ },
+ {
+ "font_class": "locked",
+ "unicode": "\ue66b"
+ },
+ {
+ "font_class": "locked-filled",
+ "unicode": "\ue668"
+ },
+ {
+ "font_class": "loop",
+ "unicode": "\ue633"
+ },
+ {
+ "font_class": "mail-open",
+ "unicode": "\ue643"
+ },
+ {
+ "font_class": "mail-open-filled",
+ "unicode": "\ue63a"
+ },
+ {
+ "font_class": "map",
+ "unicode": "\ue667"
+ },
+ {
+ "font_class": "map-filled",
+ "unicode": "\ue666"
+ },
+ {
+ "font_class": "map-pin",
+ "unicode": "\ue6ad"
+ },
+ {
+ "font_class": "map-pin-ellipse",
+ "unicode": "\ue6ac"
+ },
+ {
+ "font_class": "medal",
+ "unicode": "\ue6a2"
+ },
+ {
+ "font_class": "medal-filled",
+ "unicode": "\ue6c3"
+ },
+ {
+ "font_class": "mic",
+ "unicode": "\ue671"
+ },
+ {
+ "font_class": "mic-filled",
+ "unicode": "\ue677"
+ },
+ {
+ "font_class": "micoff",
+ "unicode": "\ue67e"
+ },
+ {
+ "font_class": "micoff-filled",
+ "unicode": "\ue6b0"
+ },
+ {
+ "font_class": "minus",
+ "unicode": "\ue66f"
+ },
+ {
+ "font_class": "minus-filled",
+ "unicode": "\ue67d"
+ },
+ {
+ "font_class": "more",
+ "unicode": "\ue64d"
+ },
+ {
+ "font_class": "more-filled",
+ "unicode": "\ue64e"
+ },
+ {
+ "font_class": "navigate",
+ "unicode": "\ue66e"
+ },
+ {
+ "font_class": "navigate-filled",
+ "unicode": "\ue67a"
+ },
+ {
+ "font_class": "notification",
+ "unicode": "\ue6a6"
+ },
+ {
+ "font_class": "notification-filled",
+ "unicode": "\ue6c1"
+ },
+ {
+ "font_class": "paperclip",
+ "unicode": "\ue652"
+ },
+ {
+ "font_class": "paperplane",
+ "unicode": "\ue672"
+ },
+ {
+ "font_class": "paperplane-filled",
+ "unicode": "\ue675"
+ },
+ {
+ "font_class": "person",
+ "unicode": "\ue699"
+ },
+ {
+ "font_class": "person-filled",
+ "unicode": "\ue69d"
+ },
+ {
+ "font_class": "personadd",
+ "unicode": "\ue69f"
+ },
+ {
+ "font_class": "personadd-filled",
+ "unicode": "\ue698"
+ },
+ {
+ "font_class": "personadd-filled-copy",
+ "unicode": "\ue6d1"
+ },
+ {
+ "font_class": "phone",
+ "unicode": "\ue69c"
+ },
+ {
+ "font_class": "phone-filled",
+ "unicode": "\ue69b"
+ },
+ {
+ "font_class": "plus",
+ "unicode": "\ue676"
+ },
+ {
+ "font_class": "plus-filled",
+ "unicode": "\ue6c7"
+ },
+ {
+ "font_class": "plusempty",
+ "unicode": "\ue67b"
+ },
+ {
+ "font_class": "pulldown",
+ "unicode": "\ue632"
+ },
+ {
+ "font_class": "pyq",
+ "unicode": "\ue682"
+ },
+ {
+ "font_class": "qq",
+ "unicode": "\ue680"
+ },
+ {
+ "font_class": "redo",
+ "unicode": "\ue64a"
+ },
+ {
+ "font_class": "redo-filled",
+ "unicode": "\ue655"
+ },
+ {
+ "font_class": "refresh",
+ "unicode": "\ue657"
+ },
+ {
+ "font_class": "refresh-filled",
+ "unicode": "\ue656"
+ },
+ {
+ "font_class": "refreshempty",
+ "unicode": "\ue6bf"
+ },
+ {
+ "font_class": "reload",
+ "unicode": "\ue6b2"
+ },
+ {
+ "font_class": "right",
+ "unicode": "\ue6b5"
+ },
+ {
+ "font_class": "scan",
+ "unicode": "\ue62a"
+ },
+ {
+ "font_class": "search",
+ "unicode": "\ue654"
+ },
+ {
+ "font_class": "settings",
+ "unicode": "\ue653"
+ },
+ {
+ "font_class": "settings-filled",
+ "unicode": "\ue6ce"
+ },
+ {
+ "font_class": "shop",
+ "unicode": "\ue62f"
+ },
+ {
+ "font_class": "shop-filled",
+ "unicode": "\ue6cd"
+ },
+ {
+ "font_class": "smallcircle",
+ "unicode": "\ue67c"
+ },
+ {
+ "font_class": "smallcircle-filled",
+ "unicode": "\ue665"
+ },
+ {
+ "font_class": "sound",
+ "unicode": "\ue684"
+ },
+ {
+ "font_class": "sound-filled",
+ "unicode": "\ue686"
+ },
+ {
+ "font_class": "spinner-cycle",
+ "unicode": "\ue68a"
+ },
+ {
+ "font_class": "staff",
+ "unicode": "\ue6a7"
+ },
+ {
+ "font_class": "staff-filled",
+ "unicode": "\ue6cb"
+ },
+ {
+ "font_class": "star",
+ "unicode": "\ue688"
+ },
+ {
+ "font_class": "star-filled",
+ "unicode": "\ue68f"
+ },
+ {
+ "font_class": "starhalf",
+ "unicode": "\ue683"
+ },
+ {
+ "font_class": "trash",
+ "unicode": "\ue687"
+ },
+ {
+ "font_class": "trash-filled",
+ "unicode": "\ue685"
+ },
+ {
+ "font_class": "tune",
+ "unicode": "\ue6aa"
+ },
+ {
+ "font_class": "tune-filled",
+ "unicode": "\ue6ca"
+ },
+ {
+ "font_class": "undo",
+ "unicode": "\ue64f"
+ },
+ {
+ "font_class": "undo-filled",
+ "unicode": "\ue64c"
+ },
+ {
+ "font_class": "up",
+ "unicode": "\ue6b6"
+ },
+ {
+ "font_class": "top",
+ "unicode": "\ue6b6"
+ },
+ {
+ "font_class": "upload",
+ "unicode": "\ue690"
+ },
+ {
+ "font_class": "upload-filled",
+ "unicode": "\ue68e"
+ },
+ {
+ "font_class": "videocam",
+ "unicode": "\ue68c"
+ },
+ {
+ "font_class": "videocam-filled",
+ "unicode": "\ue689"
+ },
+ {
+ "font_class": "vip",
+ "unicode": "\ue6a8"
+ },
+ {
+ "font_class": "vip-filled",
+ "unicode": "\ue6c6"
+ },
+ {
+ "font_class": "wallet",
+ "unicode": "\ue6b1"
+ },
+ {
+ "font_class": "wallet-filled",
+ "unicode": "\ue6c2"
+ },
+ {
+ "font_class": "weibo",
+ "unicode": "\ue68b"
+ },
+ {
+ "font_class": "weixin",
+ "unicode": "\ue691"
+ }
+] as IconsDataItem[]
+
+// export const fontData = JSON.parse(fontDataJson)
diff --git a/src/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js b/src/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js
new file mode 100644
index 0000000..1cd11e1
--- /dev/null
+++ b/src/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js
@@ -0,0 +1,649 @@
+
+export const fontData = [
+ {
+ "font_class": "arrow-down",
+ "unicode": "\ue6be"
+ },
+ {
+ "font_class": "arrow-left",
+ "unicode": "\ue6bc"
+ },
+ {
+ "font_class": "arrow-right",
+ "unicode": "\ue6bb"
+ },
+ {
+ "font_class": "arrow-up",
+ "unicode": "\ue6bd"
+ },
+ {
+ "font_class": "auth",
+ "unicode": "\ue6ab"
+ },
+ {
+ "font_class": "auth-filled",
+ "unicode": "\ue6cc"
+ },
+ {
+ "font_class": "back",
+ "unicode": "\ue6b9"
+ },
+ {
+ "font_class": "bars",
+ "unicode": "\ue627"
+ },
+ {
+ "font_class": "calendar",
+ "unicode": "\ue6a0"
+ },
+ {
+ "font_class": "calendar-filled",
+ "unicode": "\ue6c0"
+ },
+ {
+ "font_class": "camera",
+ "unicode": "\ue65a"
+ },
+ {
+ "font_class": "camera-filled",
+ "unicode": "\ue658"
+ },
+ {
+ "font_class": "cart",
+ "unicode": "\ue631"
+ },
+ {
+ "font_class": "cart-filled",
+ "unicode": "\ue6d0"
+ },
+ {
+ "font_class": "chat",
+ "unicode": "\ue65d"
+ },
+ {
+ "font_class": "chat-filled",
+ "unicode": "\ue659"
+ },
+ {
+ "font_class": "chatboxes",
+ "unicode": "\ue696"
+ },
+ {
+ "font_class": "chatboxes-filled",
+ "unicode": "\ue692"
+ },
+ {
+ "font_class": "chatbubble",
+ "unicode": "\ue697"
+ },
+ {
+ "font_class": "chatbubble-filled",
+ "unicode": "\ue694"
+ },
+ {
+ "font_class": "checkbox",
+ "unicode": "\ue62b"
+ },
+ {
+ "font_class": "checkbox-filled",
+ "unicode": "\ue62c"
+ },
+ {
+ "font_class": "checkmarkempty",
+ "unicode": "\ue65c"
+ },
+ {
+ "font_class": "circle",
+ "unicode": "\ue65b"
+ },
+ {
+ "font_class": "circle-filled",
+ "unicode": "\ue65e"
+ },
+ {
+ "font_class": "clear",
+ "unicode": "\ue66d"
+ },
+ {
+ "font_class": "close",
+ "unicode": "\ue673"
+ },
+ {
+ "font_class": "closeempty",
+ "unicode": "\ue66c"
+ },
+ {
+ "font_class": "cloud-download",
+ "unicode": "\ue647"
+ },
+ {
+ "font_class": "cloud-download-filled",
+ "unicode": "\ue646"
+ },
+ {
+ "font_class": "cloud-upload",
+ "unicode": "\ue645"
+ },
+ {
+ "font_class": "cloud-upload-filled",
+ "unicode": "\ue648"
+ },
+ {
+ "font_class": "color",
+ "unicode": "\ue6cf"
+ },
+ {
+ "font_class": "color-filled",
+ "unicode": "\ue6c9"
+ },
+ {
+ "font_class": "compose",
+ "unicode": "\ue67f"
+ },
+ {
+ "font_class": "contact",
+ "unicode": "\ue693"
+ },
+ {
+ "font_class": "contact-filled",
+ "unicode": "\ue695"
+ },
+ {
+ "font_class": "down",
+ "unicode": "\ue6b8"
+ },
+ {
+ "font_class": "bottom",
+ "unicode": "\ue6b8"
+ },
+ {
+ "font_class": "download",
+ "unicode": "\ue68d"
+ },
+ {
+ "font_class": "download-filled",
+ "unicode": "\ue681"
+ },
+ {
+ "font_class": "email",
+ "unicode": "\ue69e"
+ },
+ {
+ "font_class": "email-filled",
+ "unicode": "\ue69a"
+ },
+ {
+ "font_class": "eye",
+ "unicode": "\ue651"
+ },
+ {
+ "font_class": "eye-filled",
+ "unicode": "\ue66a"
+ },
+ {
+ "font_class": "eye-slash",
+ "unicode": "\ue6b3"
+ },
+ {
+ "font_class": "eye-slash-filled",
+ "unicode": "\ue6b4"
+ },
+ {
+ "font_class": "fire",
+ "unicode": "\ue6a1"
+ },
+ {
+ "font_class": "fire-filled",
+ "unicode": "\ue6c5"
+ },
+ {
+ "font_class": "flag",
+ "unicode": "\ue65f"
+ },
+ {
+ "font_class": "flag-filled",
+ "unicode": "\ue660"
+ },
+ {
+ "font_class": "folder-add",
+ "unicode": "\ue6a9"
+ },
+ {
+ "font_class": "folder-add-filled",
+ "unicode": "\ue6c8"
+ },
+ {
+ "font_class": "font",
+ "unicode": "\ue6a3"
+ },
+ {
+ "font_class": "forward",
+ "unicode": "\ue6ba"
+ },
+ {
+ "font_class": "gear",
+ "unicode": "\ue664"
+ },
+ {
+ "font_class": "gear-filled",
+ "unicode": "\ue661"
+ },
+ {
+ "font_class": "gift",
+ "unicode": "\ue6a4"
+ },
+ {
+ "font_class": "gift-filled",
+ "unicode": "\ue6c4"
+ },
+ {
+ "font_class": "hand-down",
+ "unicode": "\ue63d"
+ },
+ {
+ "font_class": "hand-down-filled",
+ "unicode": "\ue63c"
+ },
+ {
+ "font_class": "hand-up",
+ "unicode": "\ue63f"
+ },
+ {
+ "font_class": "hand-up-filled",
+ "unicode": "\ue63e"
+ },
+ {
+ "font_class": "headphones",
+ "unicode": "\ue630"
+ },
+ {
+ "font_class": "heart",
+ "unicode": "\ue639"
+ },
+ {
+ "font_class": "heart-filled",
+ "unicode": "\ue641"
+ },
+ {
+ "font_class": "help",
+ "unicode": "\ue679"
+ },
+ {
+ "font_class": "help-filled",
+ "unicode": "\ue674"
+ },
+ {
+ "font_class": "home",
+ "unicode": "\ue662"
+ },
+ {
+ "font_class": "home-filled",
+ "unicode": "\ue663"
+ },
+ {
+ "font_class": "image",
+ "unicode": "\ue670"
+ },
+ {
+ "font_class": "image-filled",
+ "unicode": "\ue678"
+ },
+ {
+ "font_class": "images",
+ "unicode": "\ue650"
+ },
+ {
+ "font_class": "images-filled",
+ "unicode": "\ue64b"
+ },
+ {
+ "font_class": "info",
+ "unicode": "\ue669"
+ },
+ {
+ "font_class": "info-filled",
+ "unicode": "\ue649"
+ },
+ {
+ "font_class": "left",
+ "unicode": "\ue6b7"
+ },
+ {
+ "font_class": "link",
+ "unicode": "\ue6a5"
+ },
+ {
+ "font_class": "list",
+ "unicode": "\ue644"
+ },
+ {
+ "font_class": "location",
+ "unicode": "\ue6ae"
+ },
+ {
+ "font_class": "location-filled",
+ "unicode": "\ue6af"
+ },
+ {
+ "font_class": "locked",
+ "unicode": "\ue66b"
+ },
+ {
+ "font_class": "locked-filled",
+ "unicode": "\ue668"
+ },
+ {
+ "font_class": "loop",
+ "unicode": "\ue633"
+ },
+ {
+ "font_class": "mail-open",
+ "unicode": "\ue643"
+ },
+ {
+ "font_class": "mail-open-filled",
+ "unicode": "\ue63a"
+ },
+ {
+ "font_class": "map",
+ "unicode": "\ue667"
+ },
+ {
+ "font_class": "map-filled",
+ "unicode": "\ue666"
+ },
+ {
+ "font_class": "map-pin",
+ "unicode": "\ue6ad"
+ },
+ {
+ "font_class": "map-pin-ellipse",
+ "unicode": "\ue6ac"
+ },
+ {
+ "font_class": "medal",
+ "unicode": "\ue6a2"
+ },
+ {
+ "font_class": "medal-filled",
+ "unicode": "\ue6c3"
+ },
+ {
+ "font_class": "mic",
+ "unicode": "\ue671"
+ },
+ {
+ "font_class": "mic-filled",
+ "unicode": "\ue677"
+ },
+ {
+ "font_class": "micoff",
+ "unicode": "\ue67e"
+ },
+ {
+ "font_class": "micoff-filled",
+ "unicode": "\ue6b0"
+ },
+ {
+ "font_class": "minus",
+ "unicode": "\ue66f"
+ },
+ {
+ "font_class": "minus-filled",
+ "unicode": "\ue67d"
+ },
+ {
+ "font_class": "more",
+ "unicode": "\ue64d"
+ },
+ {
+ "font_class": "more-filled",
+ "unicode": "\ue64e"
+ },
+ {
+ "font_class": "navigate",
+ "unicode": "\ue66e"
+ },
+ {
+ "font_class": "navigate-filled",
+ "unicode": "\ue67a"
+ },
+ {
+ "font_class": "notification",
+ "unicode": "\ue6a6"
+ },
+ {
+ "font_class": "notification-filled",
+ "unicode": "\ue6c1"
+ },
+ {
+ "font_class": "paperclip",
+ "unicode": "\ue652"
+ },
+ {
+ "font_class": "paperplane",
+ "unicode": "\ue672"
+ },
+ {
+ "font_class": "paperplane-filled",
+ "unicode": "\ue675"
+ },
+ {
+ "font_class": "person",
+ "unicode": "\ue699"
+ },
+ {
+ "font_class": "person-filled",
+ "unicode": "\ue69d"
+ },
+ {
+ "font_class": "personadd",
+ "unicode": "\ue69f"
+ },
+ {
+ "font_class": "personadd-filled",
+ "unicode": "\ue698"
+ },
+ {
+ "font_class": "personadd-filled-copy",
+ "unicode": "\ue6d1"
+ },
+ {
+ "font_class": "phone",
+ "unicode": "\ue69c"
+ },
+ {
+ "font_class": "phone-filled",
+ "unicode": "\ue69b"
+ },
+ {
+ "font_class": "plus",
+ "unicode": "\ue676"
+ },
+ {
+ "font_class": "plus-filled",
+ "unicode": "\ue6c7"
+ },
+ {
+ "font_class": "plusempty",
+ "unicode": "\ue67b"
+ },
+ {
+ "font_class": "pulldown",
+ "unicode": "\ue632"
+ },
+ {
+ "font_class": "pyq",
+ "unicode": "\ue682"
+ },
+ {
+ "font_class": "qq",
+ "unicode": "\ue680"
+ },
+ {
+ "font_class": "redo",
+ "unicode": "\ue64a"
+ },
+ {
+ "font_class": "redo-filled",
+ "unicode": "\ue655"
+ },
+ {
+ "font_class": "refresh",
+ "unicode": "\ue657"
+ },
+ {
+ "font_class": "refresh-filled",
+ "unicode": "\ue656"
+ },
+ {
+ "font_class": "refreshempty",
+ "unicode": "\ue6bf"
+ },
+ {
+ "font_class": "reload",
+ "unicode": "\ue6b2"
+ },
+ {
+ "font_class": "right",
+ "unicode": "\ue6b5"
+ },
+ {
+ "font_class": "scan",
+ "unicode": "\ue62a"
+ },
+ {
+ "font_class": "search",
+ "unicode": "\ue654"
+ },
+ {
+ "font_class": "settings",
+ "unicode": "\ue653"
+ },
+ {
+ "font_class": "settings-filled",
+ "unicode": "\ue6ce"
+ },
+ {
+ "font_class": "shop",
+ "unicode": "\ue62f"
+ },
+ {
+ "font_class": "shop-filled",
+ "unicode": "\ue6cd"
+ },
+ {
+ "font_class": "smallcircle",
+ "unicode": "\ue67c"
+ },
+ {
+ "font_class": "smallcircle-filled",
+ "unicode": "\ue665"
+ },
+ {
+ "font_class": "sound",
+ "unicode": "\ue684"
+ },
+ {
+ "font_class": "sound-filled",
+ "unicode": "\ue686"
+ },
+ {
+ "font_class": "spinner-cycle",
+ "unicode": "\ue68a"
+ },
+ {
+ "font_class": "staff",
+ "unicode": "\ue6a7"
+ },
+ {
+ "font_class": "staff-filled",
+ "unicode": "\ue6cb"
+ },
+ {
+ "font_class": "star",
+ "unicode": "\ue688"
+ },
+ {
+ "font_class": "star-filled",
+ "unicode": "\ue68f"
+ },
+ {
+ "font_class": "starhalf",
+ "unicode": "\ue683"
+ },
+ {
+ "font_class": "trash",
+ "unicode": "\ue687"
+ },
+ {
+ "font_class": "trash-filled",
+ "unicode": "\ue685"
+ },
+ {
+ "font_class": "tune",
+ "unicode": "\ue6aa"
+ },
+ {
+ "font_class": "tune-filled",
+ "unicode": "\ue6ca"
+ },
+ {
+ "font_class": "undo",
+ "unicode": "\ue64f"
+ },
+ {
+ "font_class": "undo-filled",
+ "unicode": "\ue64c"
+ },
+ {
+ "font_class": "up",
+ "unicode": "\ue6b6"
+ },
+ {
+ "font_class": "top",
+ "unicode": "\ue6b6"
+ },
+ {
+ "font_class": "upload",
+ "unicode": "\ue690"
+ },
+ {
+ "font_class": "upload-filled",
+ "unicode": "\ue68e"
+ },
+ {
+ "font_class": "videocam",
+ "unicode": "\ue68c"
+ },
+ {
+ "font_class": "videocam-filled",
+ "unicode": "\ue689"
+ },
+ {
+ "font_class": "vip",
+ "unicode": "\ue6a8"
+ },
+ {
+ "font_class": "vip-filled",
+ "unicode": "\ue6c6"
+ },
+ {
+ "font_class": "wallet",
+ "unicode": "\ue6b1"
+ },
+ {
+ "font_class": "wallet-filled",
+ "unicode": "\ue6c2"
+ },
+ {
+ "font_class": "weibo",
+ "unicode": "\ue68b"
+ },
+ {
+ "font_class": "weixin",
+ "unicode": "\ue691"
+ }
+]
+
+// export const fontData = JSON.parse(fontDataJson)
diff --git a/src/uni_modules/uni-icons/package.json b/src/uni_modules/uni-icons/package.json
new file mode 100644
index 0000000..6b681b4
--- /dev/null
+++ b/src/uni_modules/uni-icons/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uni-icons",
+ "displayName": "uni-icons 图标",
+ "version": "2.0.10",
+ "description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。",
+ "keywords": [
+ "uni-ui",
+ "uniui",
+ "icon",
+ "图标"
+],
+ "repository": "https://github.com/dcloudio/uni-ui",
+ "engines": {
+ "HBuilderX": "^3.2.14"
+ },
+ "directories": {
+ "example": "../../temps/example_temps"
+ },
+"dcloudext": {
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ },
+ "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+ "type": "component-vue"
+ },
+ "uni_modules": {
+ "dependencies": ["uni-scss"],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y",
+ "alipay": "n"
+ },
+ "client": {
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y",
+ "app-uvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "y",
+ "快手": "y",
+ "飞书": "y",
+ "京东": "y"
+ },
+ "快应用": {
+ "华为": "y",
+ "联盟": "y"
+ },
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ }
+ }
+ }
+ }
+}
diff --git a/src/uni_modules/uni-icons/readme.md b/src/uni_modules/uni-icons/readme.md
new file mode 100644
index 0000000..86234ba
--- /dev/null
+++ b/src/uni_modules/uni-icons/readme.md
@@ -0,0 +1,8 @@
+## Icons 图标
+> **组件名:uni-icons**
+> 代码块: `uIcons`
+
+用于展示 icons 图标 。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
diff --git a/src/uni_modules/uni-scss/changelog.md b/src/uni_modules/uni-scss/changelog.md
new file mode 100644
index 0000000..b863bb0
--- /dev/null
+++ b/src/uni_modules/uni-scss/changelog.md
@@ -0,0 +1,8 @@
+## 1.0.3(2022-01-21)
+- 优化 组件示例
+## 1.0.2(2021-11-22)
+- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题
+## 1.0.1(2021-11-22)
+- 修复 vue3中scss语法兼容问题
+## 1.0.0(2021-11-18)
+- init
diff --git a/src/uni_modules/uni-scss/index.scss b/src/uni_modules/uni-scss/index.scss
new file mode 100644
index 0000000..1744a5f
--- /dev/null
+++ b/src/uni_modules/uni-scss/index.scss
@@ -0,0 +1 @@
+@import './styles/index.scss';
diff --git a/src/uni_modules/uni-scss/package.json b/src/uni_modules/uni-scss/package.json
new file mode 100644
index 0000000..7cc0ccb
--- /dev/null
+++ b/src/uni_modules/uni-scss/package.json
@@ -0,0 +1,82 @@
+{
+ "id": "uni-scss",
+ "displayName": "uni-scss 辅助样式",
+ "version": "1.0.3",
+ "description": "uni-sass是uni-ui提供的一套全局样式 ,通过一些简单的类名和sass变量,实现简单的页面布局操作,比如颜色、边距、圆角等。",
+ "keywords": [
+ "uni-scss",
+ "uni-ui",
+ "辅助样式"
+],
+ "repository": "https://github.com/dcloudio/uni-ui",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "category": [
+ "JS SDK",
+ "通用 SDK"
+ ],
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ },
+ "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+ },
+ "uni_modules": {
+ "dependencies": [],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "u"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y"
+ },
+ "快应用": {
+ "华为": "n",
+ "联盟": "n"
+ },
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ }
+ }
+ }
+ }
+}
diff --git a/src/uni_modules/uni-scss/readme.md b/src/uni_modules/uni-scss/readme.md
new file mode 100644
index 0000000..b7d1c25
--- /dev/null
+++ b/src/uni_modules/uni-scss/readme.md
@@ -0,0 +1,4 @@
+`uni-sass` 是 `uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
\ No newline at end of file
diff --git a/src/uni_modules/uni-scss/styles/index.scss b/src/uni_modules/uni-scss/styles/index.scss
new file mode 100644
index 0000000..ffac4fe
--- /dev/null
+++ b/src/uni_modules/uni-scss/styles/index.scss
@@ -0,0 +1,7 @@
+@import './setting/_variables.scss';
+@import './setting/_border.scss';
+@import './setting/_color.scss';
+@import './setting/_space.scss';
+@import './setting/_radius.scss';
+@import './setting/_text.scss';
+@import './setting/_styles.scss';
diff --git a/src/uni_modules/uni-scss/styles/setting/_border.scss b/src/uni_modules/uni-scss/styles/setting/_border.scss
new file mode 100644
index 0000000..12a11c3
--- /dev/null
+++ b/src/uni_modules/uni-scss/styles/setting/_border.scss
@@ -0,0 +1,3 @@
+.uni-border {
+ border: 1px $uni-border-1 solid;
+}
\ No newline at end of file
diff --git a/src/uni_modules/uni-scss/styles/setting/_color.scss b/src/uni_modules/uni-scss/styles/setting/_color.scss
new file mode 100644
index 0000000..1ededd9
--- /dev/null
+++ b/src/uni_modules/uni-scss/styles/setting/_color.scss
@@ -0,0 +1,66 @@
+
+// TODO 暂时不需要 class ,需要用户使用变量实现 ,如果使用类名其实并不推荐
+// @mixin get-styles($k,$c) {
+// @if $k == size or $k == weight{
+// font-#{$k}:#{$c}
+// }@else{
+// #{$k}:#{$c}
+// }
+// }
+$uni-ui-color:(
+ // 主色
+ primary: $uni-primary,
+ primary-disable: $uni-primary-disable,
+ primary-light: $uni-primary-light,
+ // 辅助色
+ success: $uni-success,
+ success-disable: $uni-success-disable,
+ success-light: $uni-success-light,
+ warning: $uni-warning,
+ warning-disable: $uni-warning-disable,
+ warning-light: $uni-warning-light,
+ error: $uni-error,
+ error-disable: $uni-error-disable,
+ error-light: $uni-error-light,
+ info: $uni-info,
+ info-disable: $uni-info-disable,
+ info-light: $uni-info-light,
+ // 中性色
+ main-color: $uni-main-color,
+ base-color: $uni-base-color,
+ secondary-color: $uni-secondary-color,
+ extra-color: $uni-extra-color,
+ // 背景色
+ bg-color: $uni-bg-color,
+ // 边框颜色
+ border-1: $uni-border-1,
+ border-2: $uni-border-2,
+ border-3: $uni-border-3,
+ border-4: $uni-border-4,
+ // 黑色
+ black:$uni-black,
+ // 白色
+ white:$uni-white,
+ // 透明
+ transparent:$uni-transparent
+) !default;
+@each $key, $child in $uni-ui-color {
+ .uni-#{"" + $key} {
+ color: $child;
+ }
+ .uni-#{"" + $key}-bg {
+ background-color: $child;
+ }
+}
+.uni-shadow-sm {
+ box-shadow: $uni-shadow-sm;
+}
+.uni-shadow-base {
+ box-shadow: $uni-shadow-base;
+}
+.uni-shadow-lg {
+ box-shadow: $uni-shadow-lg;
+}
+.uni-mask {
+ background-color:$uni-mask;
+}
diff --git a/src/uni_modules/uni-scss/styles/setting/_radius.scss b/src/uni_modules/uni-scss/styles/setting/_radius.scss
new file mode 100644
index 0000000..9a0428b
--- /dev/null
+++ b/src/uni_modules/uni-scss/styles/setting/_radius.scss
@@ -0,0 +1,55 @@
+@mixin radius($r,$d:null ,$important: false){
+ $radius-value:map-get($uni-radius, $r) if($important, !important, null);
+ // Key exists within the $uni-radius variable
+ @if (map-has-key($uni-radius, $r) and $d){
+ @if $d == t {
+ border-top-left-radius:$radius-value;
+ border-top-right-radius:$radius-value;
+ }@else if $d == r {
+ border-top-right-radius:$radius-value;
+ border-bottom-right-radius:$radius-value;
+ }@else if $d == b {
+ border-bottom-left-radius:$radius-value;
+ border-bottom-right-radius:$radius-value;
+ }@else if $d == l {
+ border-top-left-radius:$radius-value;
+ border-bottom-left-radius:$radius-value;
+ }@else if $d == tl {
+ border-top-left-radius:$radius-value;
+ }@else if $d == tr {
+ border-top-right-radius:$radius-value;
+ }@else if $d == br {
+ border-bottom-right-radius:$radius-value;
+ }@else if $d == bl {
+ border-bottom-left-radius:$radius-value;
+ }
+ }@else{
+ border-radius:$radius-value;
+ }
+}
+
+@each $key, $child in $uni-radius {
+ @if($key){
+ .uni-radius-#{"" + $key} {
+ @include radius($key)
+ }
+ }@else{
+ .uni-radius {
+ @include radius($key)
+ }
+ }
+}
+
+@each $direction in t, r, b, l,tl, tr, br, bl {
+ @each $key, $child in $uni-radius {
+ @if($key){
+ .uni-radius-#{"" + $direction}-#{"" + $key} {
+ @include radius($key,$direction,false)
+ }
+ }@else{
+ .uni-radius-#{$direction} {
+ @include radius($key,$direction,false)
+ }
+ }
+ }
+}
diff --git a/src/uni_modules/uni-scss/styles/setting/_space.scss b/src/uni_modules/uni-scss/styles/setting/_space.scss
new file mode 100644
index 0000000..3c89528
--- /dev/null
+++ b/src/uni_modules/uni-scss/styles/setting/_space.scss
@@ -0,0 +1,56 @@
+
+@mixin fn($space,$direction,$size,$n) {
+ @if $n {
+ #{$space}-#{$direction}: #{$size*$uni-space-root}px
+ } @else {
+ #{$space}-#{$direction}: #{-$size*$uni-space-root}px
+ }
+}
+@mixin get-styles($direction,$i,$space,$n){
+ @if $direction == t {
+ @include fn($space, top,$i,$n);
+ }
+ @if $direction == r {
+ @include fn($space, right,$i,$n);
+ }
+ @if $direction == b {
+ @include fn($space, bottom,$i,$n);
+ }
+ @if $direction == l {
+ @include fn($space, left,$i,$n);
+ }
+ @if $direction == x {
+ @include fn($space, left,$i,$n);
+ @include fn($space, right,$i,$n);
+ }
+ @if $direction == y {
+ @include fn($space, top,$i,$n);
+ @include fn($space, bottom,$i,$n);
+ }
+ @if $direction == a {
+ @if $n {
+ #{$space}:#{$i*$uni-space-root}px;
+ } @else {
+ #{$space}:#{-$i*$uni-space-root}px;
+ }
+ }
+}
+
+@each $orientation in m,p {
+ $space: margin;
+ @if $orientation == m {
+ $space: margin;
+ } @else {
+ $space: padding;
+ }
+ @for $i from 0 through 16 {
+ @each $direction in t, r, b, l, x, y, a {
+ .uni-#{$orientation}#{$direction}-#{$i} {
+ @include get-styles($direction,$i,$space,true);
+ }
+ .uni-#{$orientation}#{$direction}-n#{$i} {
+ @include get-styles($direction,$i,$space,false);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/uni_modules/uni-scss/styles/setting/_styles.scss b/src/uni_modules/uni-scss/styles/setting/_styles.scss
new file mode 100644
index 0000000..689afec
--- /dev/null
+++ b/src/uni_modules/uni-scss/styles/setting/_styles.scss
@@ -0,0 +1,167 @@
+/* #ifndef APP-NVUE */
+
+$-color-white:#fff;
+$-color-black:#000;
+@mixin base-style($color) {
+ color: #fff;
+ background-color: $color;
+ border-color: mix($-color-black, $color, 8%);
+ &:not([hover-class]):active {
+ background: mix($-color-black, $color, 10%);
+ border-color: mix($-color-black, $color, 20%);
+ color: $-color-white;
+ outline: none;
+ }
+}
+@mixin is-color($color) {
+ @include base-style($color);
+ &[loading] {
+ @include base-style($color);
+ &::before {
+ margin-right:5px;
+ }
+ }
+ &[disabled] {
+ &,
+ &[loading],
+ &:not([hover-class]):active {
+ color: $-color-white;
+ border-color: mix(darken($color,10%), $-color-white);
+ background-color: mix($color, $-color-white);
+ }
+ }
+
+}
+@mixin base-plain-style($color) {
+ color:$color;
+ background-color: mix($-color-white, $color, 90%);
+ border-color: mix($-color-white, $color, 70%);
+ &:not([hover-class]):active {
+ background: mix($-color-white, $color, 80%);
+ color: $color;
+ outline: none;
+ border-color: mix($-color-white, $color, 50%);
+ }
+}
+@mixin is-plain($color){
+ &[plain] {
+ @include base-plain-style($color);
+ &[loading] {
+ @include base-plain-style($color);
+ &::before {
+ margin-right:5px;
+ }
+ }
+ &[disabled] {
+ &,
+ &:active {
+ color: mix($-color-white, $color, 40%);
+ background-color: mix($-color-white, $color, 90%);
+ border-color: mix($-color-white, $color, 80%);
+ }
+ }
+ }
+}
+
+
+.uni-btn {
+ margin: 5px;
+ color: #393939;
+ border:1px solid #ccc;
+ font-size: 16px;
+ font-weight: 200;
+ background-color: #F9F9F9;
+ // TODO 暂时处理边框隐藏一边的问题
+ overflow: visible;
+ &::after{
+ border: none;
+ }
+
+ &:not([type]),&[type=default] {
+ color: #999;
+ &[loading] {
+ background: none;
+ &::before {
+ margin-right:5px;
+ }
+ }
+
+
+
+ &[disabled]{
+ color: mix($-color-white, #999, 60%);
+ &,
+ &[loading],
+ &:active {
+ color: mix($-color-white, #999, 60%);
+ background-color: mix($-color-white,$-color-black , 98%);
+ border-color: mix($-color-white, #999, 85%);
+ }
+ }
+
+ &[plain] {
+ color: #999;
+ background: none;
+ border-color: $uni-border-1;
+ &:not([hover-class]):active {
+ background: none;
+ color: mix($-color-white, $-color-black, 80%);
+ border-color: mix($-color-white, $-color-black, 90%);
+ outline: none;
+ }
+ &[disabled]{
+ &,
+ &[loading],
+ &:active {
+ background: none;
+ color: mix($-color-white, #999, 60%);
+ border-color: mix($-color-white, #999, 85%);
+ }
+ }
+ }
+ }
+
+ &:not([hover-class]):active {
+ color: mix($-color-white, $-color-black, 50%);
+ }
+
+ &[size=mini] {
+ font-size: 16px;
+ font-weight: 200;
+ border-radius: 8px;
+ }
+
+
+
+ &.uni-btn-small {
+ font-size: 14px;
+ }
+ &.uni-btn-mini {
+ font-size: 12px;
+ }
+
+ &.uni-btn-radius {
+ border-radius: 999px;
+ }
+ &[type=primary] {
+ @include is-color($uni-primary);
+ @include is-plain($uni-primary)
+ }
+ &[type=success] {
+ @include is-color($uni-success);
+ @include is-plain($uni-success)
+ }
+ &[type=error] {
+ @include is-color($uni-error);
+ @include is-plain($uni-error)
+ }
+ &[type=warning] {
+ @include is-color($uni-warning);
+ @include is-plain($uni-warning)
+ }
+ &[type=info] {
+ @include is-color($uni-info);
+ @include is-plain($uni-info)
+ }
+}
+/* #endif */
diff --git a/src/uni_modules/uni-scss/styles/setting/_text.scss b/src/uni_modules/uni-scss/styles/setting/_text.scss
new file mode 100644
index 0000000..a34d08f
--- /dev/null
+++ b/src/uni_modules/uni-scss/styles/setting/_text.scss
@@ -0,0 +1,24 @@
+@mixin get-styles($k,$c) {
+ @if $k == size or $k == weight{
+ font-#{$k}:#{$c}
+ }@else{
+ #{$k}:#{$c}
+ }
+}
+
+@each $key, $child in $uni-headings {
+ /* #ifndef APP-NVUE */
+ .uni-#{$key} {
+ @each $k, $c in $child {
+ @include get-styles($k,$c)
+ }
+ }
+ /* #endif */
+ /* #ifdef APP-NVUE */
+ .container .uni-#{$key} {
+ @each $k, $c in $child {
+ @include get-styles($k,$c)
+ }
+ }
+ /* #endif */
+}
diff --git a/src/uni_modules/uni-scss/styles/setting/_variables.scss b/src/uni_modules/uni-scss/styles/setting/_variables.scss
new file mode 100644
index 0000000..557d3d7
--- /dev/null
+++ b/src/uni_modules/uni-scss/styles/setting/_variables.scss
@@ -0,0 +1,146 @@
+// @use "sass:math";
+@import '../tools/functions.scss';
+// 间距基础倍数
+$uni-space-root: 2 !default;
+// 边框半径默认值
+$uni-radius-root:5px !default;
+$uni-radius: () !default;
+// 边框半径断点
+$uni-radius: map-deep-merge(
+ (
+ 0: 0,
+ // TODO 当前版本暂时不支持 sm 属性
+ // 'sm': math.div($uni-radius-root, 2),
+ null: $uni-radius-root,
+ 'lg': $uni-radius-root * 2,
+ 'xl': $uni-radius-root * 6,
+ 'pill': 9999px,
+ 'circle': 50%
+ ),
+ $uni-radius
+);
+// 字体家族
+$body-font-family: 'Roboto', sans-serif !default;
+// 文本
+$heading-font-family: $body-font-family !default;
+$uni-headings: () !default;
+$letterSpacing: -0.01562em;
+$uni-headings: map-deep-merge(
+ (
+ 'h1': (
+ size: 32px,
+ weight: 300,
+ line-height: 50px,
+ // letter-spacing:-0.01562em
+ ),
+ 'h2': (
+ size: 28px,
+ weight: 300,
+ line-height: 40px,
+ // letter-spacing: -0.00833em
+ ),
+ 'h3': (
+ size: 24px,
+ weight: 400,
+ line-height: 32px,
+ // letter-spacing: normal
+ ),
+ 'h4': (
+ size: 20px,
+ weight: 400,
+ line-height: 30px,
+ // letter-spacing: 0.00735em
+ ),
+ 'h5': (
+ size: 16px,
+ weight: 400,
+ line-height: 24px,
+ // letter-spacing: normal
+ ),
+ 'h6': (
+ size: 14px,
+ weight: 500,
+ line-height: 18px,
+ // letter-spacing: 0.0125em
+ ),
+ 'subtitle': (
+ size: 12px,
+ weight: 400,
+ line-height: 20px,
+ // letter-spacing: 0.00937em
+ ),
+ 'body': (
+ font-size: 14px,
+ font-weight: 400,
+ line-height: 22px,
+ // letter-spacing: 0.03125em
+ ),
+ 'caption': (
+ 'size': 12px,
+ 'weight': 400,
+ 'line-height': 20px,
+ // 'letter-spacing': 0.03333em,
+ // 'text-transform': false
+ )
+ ),
+ $uni-headings
+);
+
+
+
+// 主色
+$uni-primary: #2979ff !default;
+$uni-primary-disable:lighten($uni-primary,20%) !default;
+$uni-primary-light: lighten($uni-primary,25%) !default;
+
+// 辅助色
+// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。
+$uni-success: #18bc37 !default;
+$uni-success-disable:lighten($uni-success,20%) !default;
+$uni-success-light: lighten($uni-success,25%) !default;
+
+$uni-warning: #f3a73f !default;
+$uni-warning-disable:lighten($uni-warning,20%) !default;
+$uni-warning-light: lighten($uni-warning,25%) !default;
+
+$uni-error: #e43d33 !default;
+$uni-error-disable:lighten($uni-error,20%) !default;
+$uni-error-light: lighten($uni-error,25%) !default;
+
+$uni-info: #8f939c !default;
+$uni-info-disable:lighten($uni-info,20%) !default;
+$uni-info-light: lighten($uni-info,25%) !default;
+
+// 中性色
+// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。
+$uni-main-color: #3a3a3a !default; // 主要文字
+$uni-base-color: #6a6a6a !default; // 常规文字
+$uni-secondary-color: #909399 !default; // 次要文字
+$uni-extra-color: #c7c7c7 !default; // 辅助说明
+
+// 边框颜色
+$uni-border-1: #F0F0F0 !default;
+$uni-border-2: #EDEDED !default;
+$uni-border-3: #DCDCDC !default;
+$uni-border-4: #B9B9B9 !default;
+
+// 常规色
+$uni-black: #000000 !default;
+$uni-white: #ffffff !default;
+$uni-transparent: rgba($color: #000000, $alpha: 0) !default;
+
+// 背景色
+$uni-bg-color: #f7f7f7 !default;
+
+/* 水平间距 */
+$uni-spacing-sm: 8px !default;
+$uni-spacing-base: 15px !default;
+$uni-spacing-lg: 30px !default;
+
+// 阴影
+$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default;
+$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
+$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default;
+
+// 蒙版
+$uni-mask: rgba($color: #000000, $alpha: 0.4) !default;
diff --git a/src/uni_modules/uni-scss/styles/tools/functions.scss b/src/uni_modules/uni-scss/styles/tools/functions.scss
new file mode 100644
index 0000000..ac6f63e
--- /dev/null
+++ b/src/uni_modules/uni-scss/styles/tools/functions.scss
@@ -0,0 +1,19 @@
+// 合并 map
+@function map-deep-merge($parent-map, $child-map){
+ $result: $parent-map;
+ @each $key, $child in $child-map {
+ $parent-has-key: map-has-key($result, $key);
+ $parent-value: map-get($result, $key);
+ $parent-type: type-of($parent-value);
+ $child-type: type-of($child);
+ $parent-is-map: $parent-type == map;
+ $child-is-map: $child-type == map;
+
+ @if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){
+ $result: map-merge($result, ( $key: $child ));
+ }@else {
+ $result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) ));
+ }
+ }
+ @return $result;
+};
diff --git a/src/uni_modules/uni-scss/theme.scss b/src/uni_modules/uni-scss/theme.scss
new file mode 100644
index 0000000..80ee62f
--- /dev/null
+++ b/src/uni_modules/uni-scss/theme.scss
@@ -0,0 +1,31 @@
+// 间距基础倍数
+$uni-space-root: 2;
+// 边框半径默认值
+$uni-radius-root:5px;
+// 主色
+$uni-primary: #2979ff;
+// 辅助色
+$uni-success: #4cd964;
+// 警告色
+$uni-warning: #f0ad4e;
+// 错误色
+$uni-error: #dd524d;
+// 描述色
+$uni-info: #909399;
+// 中性色
+$uni-main-color: #303133;
+$uni-base-color: #606266;
+$uni-secondary-color: #909399;
+$uni-extra-color: #C0C4CC;
+// 背景色
+$uni-bg-color: #f5f5f5;
+// 边框颜色
+$uni-border-1: #DCDFE6;
+$uni-border-2: #E4E7ED;
+$uni-border-3: #EBEEF5;
+$uni-border-4: #F2F6FC;
+
+// 常规色
+$uni-black: #000000;
+$uni-white: #ffffff;
+$uni-transparent: rgba($color: #000000, $alpha: 0);
diff --git a/src/uni_modules/uni-scss/variables.scss b/src/uni_modules/uni-scss/variables.scss
new file mode 100644
index 0000000..1c062d4
--- /dev/null
+++ b/src/uni_modules/uni-scss/variables.scss
@@ -0,0 +1,62 @@
+@import './styles/setting/_variables.scss';
+// 间距基础倍数
+$uni-space-root: 2;
+// 边框半径默认值
+$uni-radius-root:5px;
+
+// 主色
+$uni-primary: #2979ff;
+$uni-primary-disable:mix(#fff,$uni-primary,50%);
+$uni-primary-light: mix(#fff,$uni-primary,80%);
+
+// 辅助色
+// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。
+$uni-success: #18bc37;
+$uni-success-disable:mix(#fff,$uni-success,50%);
+$uni-success-light: mix(#fff,$uni-success,80%);
+
+$uni-warning: #f3a73f;
+$uni-warning-disable:mix(#fff,$uni-warning,50%);
+$uni-warning-light: mix(#fff,$uni-warning,80%);
+
+$uni-error: #e43d33;
+$uni-error-disable:mix(#fff,$uni-error,50%);
+$uni-error-light: mix(#fff,$uni-error,80%);
+
+$uni-info: #8f939c;
+$uni-info-disable:mix(#fff,$uni-info,50%);
+$uni-info-light: mix(#fff,$uni-info,80%);
+
+// 中性色
+// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。
+$uni-main-color: #3a3a3a; // 主要文字
+$uni-base-color: #6a6a6a; // 常规文字
+$uni-secondary-color: #909399; // 次要文字
+$uni-extra-color: #c7c7c7; // 辅助说明
+
+// 边框颜色
+$uni-border-1: #F0F0F0;
+$uni-border-2: #EDEDED;
+$uni-border-3: #DCDCDC;
+$uni-border-4: #B9B9B9;
+
+// 常规色
+$uni-black: #000000;
+$uni-white: #ffffff;
+$uni-transparent: rgba($color: #000000, $alpha: 0);
+
+// 背景色
+$uni-bg-color: #f7f7f7;
+
+/* 水平间距 */
+$uni-spacing-sm: 8px;
+$uni-spacing-base: 15px;
+$uni-spacing-lg: 30px;
+
+// 阴影
+$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5);
+$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2);
+$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5);
+
+// 蒙版
+$uni-mask: rgba($color: #000000, $alpha: 0.4);
diff --git a/src/utils/index.ts b/src/utils/index.ts
new file mode 100644
index 0000000..3aa8e52
--- /dev/null
+++ b/src/utils/index.ts
@@ -0,0 +1,190 @@
+import { pages, subPackages } from '@/pages.json'
+import { tabbarList } from '@/tabbar/config'
+import { isMpWeixin } from './platform'
+
+export function getLastPage() {
+ // getCurrentPages() 至少有1个元素,所以不再额外判断
+ // const lastPage = getCurrentPages().at(-1)
+ // 上面那个在低版本安卓中打包会报错,所以改用下面这个【虽然我加了 src/interceptions/prototype.ts,但依然报错】
+ const pages = getCurrentPages()
+ return pages[pages.length - 1]
+}
+
+/**
+ * 获取当前页面路由的 path 路径和 redirectPath 路径
+ * path 如 '/pages/login/index'
+ * redirectPath 如 '/pages/demo/base/route-interceptor'
+ */
+export function currRoute() {
+ const lastPage = getLastPage()
+ const currRoute = (lastPage as any).$page
+ // console.log('lastPage.$page:', currRoute)
+ // console.log('lastPage.$page.fullpath:', currRoute.fullPath)
+ // console.log('lastPage.$page.options:', currRoute.options)
+ // console.log('lastPage.options:', (lastPage as any).options)
+ // 经过多端测试,只有 fullPath 靠谱,其他都不靠谱
+ const { fullPath } = currRoute as { fullPath: string }
+ // console.log(fullPath)
+ // eg: /pages/login/index?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor (小程序)
+ // eg: /pages/login/index?redirect=%2Fpages%2Froute-interceptor%2Findex%3Fname%3Dfeige%26age%3D30(h5)
+ return getUrlObj(fullPath)
+}
+
+function ensureDecodeURIComponent(url: string) {
+ if (url.startsWith('%')) {
+ return ensureDecodeURIComponent(decodeURIComponent(url))
+ }
+ return url
+}
+/**
+ * 解析 url 得到 path 和 query
+ * 比如输入url: /pages/login/index?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor
+ * 输出: {path: /pages/login/index, query: {redirect: /pages/demo/base/route-interceptor}}
+ */
+export function getUrlObj(url: string) {
+ const [path, queryStr] = url.split('?')
+ // console.log(path, queryStr)
+
+ if (!queryStr) {
+ return {
+ path,
+ query: {},
+ }
+ }
+ const query: Record = {}
+ queryStr.split('&').forEach((item) => {
+ const [key, value] = item.split('=')
+ // console.log(key, value)
+ query[key] = ensureDecodeURIComponent(value) // 这里需要统一 decodeURIComponent 一下,可以兼容h5和微信y
+ })
+ return { path, query }
+}
+/**
+ * 得到所有的需要登录的 pages,包括主包和分包的
+ * 这里设计得通用一点,可以传递 key 作为判断依据,默认是 needLogin, 与 route-block 配对使用
+ * 如果没有传 key,则表示所有的 pages,如果传递了 key, 则表示通过 key 过滤
+ */
+export function getAllPages(key = 'needLogin') {
+ // 这里处理主包
+ const mainPages = pages
+ .filter(page => !key || page[key])
+ .map(page => ({
+ ...page,
+ path: `/${page.path}`,
+ }))
+
+ // 这里处理分包
+ const subPages: any[] = []
+ subPackages.forEach((subPageObj) => {
+ // console.log(subPageObj)
+ const { root } = subPageObj
+
+ subPageObj.pages
+ .filter(page => !key || page[key])
+ .forEach((page: { path: string } & Record) => {
+ subPages.push({
+ ...page,
+ path: `/${root}/${page.path}`,
+ })
+ })
+ })
+ const result = [...mainPages, ...subPages]
+ // console.log(`getAllPages by ${key} result: `, result)
+ return result
+}
+
+export function isCurrentPageTabbar() {
+ const routeObj = currRoute()
+ return tabbarList.some(item => `/${item.pagePath}` === routeObj.path)
+}
+
+export function getCurrentPageI18nKey() {
+ const routeObj = currRoute()
+ const currPage = pages.find(page => `/${page.path}` === routeObj.path)
+ if (!currPage) {
+ console.warn('路由不正确')
+ return ''
+ }
+ console.log(currPage)
+ console.log(currPage.style.navigationBarTitleText)
+ return currPage.style.navigationBarTitleText
+}
+
+/**
+ * 得到所有的需要登录的 pages,包括主包和分包的
+ * 只得到 path 数组
+ */
+export const getNeedLoginPages = (): string[] => getAllPages('needLogin').map(page => page.path)
+
+/**
+ * 得到所有的需要登录的 pages,包括主包和分包的
+ * 只得到 path 数组
+ */
+export const needLoginPages: string[] = getAllPages('needLogin').map(page => page.path)
+
+/**
+ * 根据微信小程序当前环境,判断应该获取的 baseUrl
+ */
+export function getEnvBaseUrl() {
+ // 请求基准地址
+ let baseUrl = import.meta.env.VITE_SERVER_BASEURL
+
+ // # 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。
+ const VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'https://ukw0y1.laf.run'
+ const VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'https://ukw0y1.laf.run'
+ const VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'https://ukw0y1.laf.run'
+
+ // 微信小程序端环境区分
+ if (isMpWeixin) {
+ const {
+ miniProgram: { envVersion },
+ } = uni.getAccountInfoSync()
+
+ switch (envVersion) {
+ case 'develop':
+ baseUrl = VITE_SERVER_BASEURL__WEIXIN_DEVELOP || baseUrl
+ break
+ case 'trial':
+ baseUrl = VITE_SERVER_BASEURL__WEIXIN_TRIAL || baseUrl
+ break
+ case 'release':
+ baseUrl = VITE_SERVER_BASEURL__WEIXIN_RELEASE || baseUrl
+ break
+ }
+ }
+
+ return baseUrl
+}
+
+/**
+ * 根据微信小程序当前环境,判断应该获取的 UPLOAD_BASEURL
+ */
+export function getEnvBaseUploadUrl() {
+ // 请求基准地址
+ let baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL
+
+ const VITE_UPLOAD_BASEURL__WEIXIN_DEVELOP = 'https://ukw0y1.laf.run/upload'
+ const VITE_UPLOAD_BASEURL__WEIXIN_TRIAL = 'https://ukw0y1.laf.run/upload'
+ const VITE_UPLOAD_BASEURL__WEIXIN_RELEASE = 'https://ukw0y1.laf.run/upload'
+
+ // 微信小程序端环境区分
+ if (isMpWeixin) {
+ const {
+ miniProgram: { envVersion },
+ } = uni.getAccountInfoSync()
+
+ switch (envVersion) {
+ case 'develop':
+ baseUploadUrl = VITE_UPLOAD_BASEURL__WEIXIN_DEVELOP || baseUploadUrl
+ break
+ case 'trial':
+ baseUploadUrl = VITE_UPLOAD_BASEURL__WEIXIN_TRIAL || baseUploadUrl
+ break
+ case 'release':
+ baseUploadUrl = VITE_UPLOAD_BASEURL__WEIXIN_RELEASE || baseUploadUrl
+ break
+ }
+ }
+
+ return baseUploadUrl
+}
diff --git a/src/utils/platform.ts b/src/utils/platform.ts
new file mode 100644
index 0000000..86801f1
--- /dev/null
+++ b/src/utils/platform.ts
@@ -0,0 +1,26 @@
+/*
+ * @Author: 菲鸽
+ * @Date: 2024-03-28 19:13:55
+ * @Last Modified by: 菲鸽
+ * @Last Modified time: 2024-03-28 19:24:55
+ */
+export const platform = __UNI_PLATFORM__
+export const isH5 = __UNI_PLATFORM__ === 'h5'
+export const isApp = __UNI_PLATFORM__ === 'app'
+export const isMp = __UNI_PLATFORM__.startsWith('mp-')
+export const isMpWeixin = __UNI_PLATFORM__.startsWith('mp-weixin')
+export const isMpAplipay = __UNI_PLATFORM__.startsWith('mp-alipay')
+export const isMpToutiao = __UNI_PLATFORM__.startsWith('mp-toutiao')
+export const isHarmony = __UNI_PLATFORM__.startsWith('app-harmony')
+
+const PLATFORM = {
+ platform,
+ isH5,
+ isApp,
+ isMp,
+ isMpWeixin,
+ isMpAplipay,
+ isMpToutiao,
+ isHarmony,
+}
+export default PLATFORM
diff --git a/src/utils/toast.ts b/src/utils/toast.ts
new file mode 100644
index 0000000..1584335
--- /dev/null
+++ b/src/utils/toast.ts
@@ -0,0 +1,77 @@
+/**
+ * toast 弹窗组件
+ * 支持 success/error/warning/info 四种状态
+ * 可配置 duration, position 等参数
+ */
+
+type ToastType = 'success' | 'error' | 'warning' | 'info'
+
+interface ToastOptions {
+ type?: ToastType
+ duration?: number
+ position?: 'top' | 'middle' | 'bottom'
+ icon?: 'success' | 'error' | 'none' | 'loading' | 'fail' | 'exception'
+ message: string
+ /**
+ * 是否显示透明蒙层,防止触摸穿透
+ * @default true
+ */
+ mask?: boolean
+}
+/**
+ * 显示 toast
+ */
+export function showToast(message: string): void
+export function showToast(options: ToastOptions): void
+export function showToast(options: ToastOptions | string): void {
+ const defaultOptions: ToastOptions = {
+ type: 'info',
+ duration: 2000,
+ position: 'middle',
+ message: '',
+ mask: true,
+ }
+ const mergedOptions
+ = typeof options === 'string'
+ ? { ...defaultOptions, message: options }
+ : { ...defaultOptions, ...options }
+ // 映射position到uniapp支持的格式
+ const positionMap: Record = {
+ top: 'top',
+ middle: 'center',
+ bottom: 'bottom',
+ }
+
+ // 映射图标类型
+ const iconMap: Record<
+ ToastType,
+ 'success' | 'error' | 'none' | 'loading' | 'fail' | 'exception'
+ > = {
+ success: 'success',
+ error: 'error',
+ warning: 'fail',
+ info: 'none',
+ }
+
+ // 调用uni.showToast显示提示
+ uni.showToast({
+ title: mergedOptions.message,
+ duration: mergedOptions.duration,
+ position: positionMap[mergedOptions.position],
+ icon: mergedOptions.icon || iconMap[mergedOptions.type],
+ mask: mergedOptions.mask,
+ })
+}
+
+type _ToastOptions = Omit
+
+export const toast = {
+ success: (message: string, options?: _ToastOptions) =>
+ showToast({ ...options, type: 'success', message }),
+ error: (message: string, options?: _ToastOptions) =>
+ showToast({ ...options, type: 'error', message }),
+ warning: (message: string, options?: _ToastOptions) =>
+ showToast({ ...options, type: 'warning', message }),
+ info: (message: string, options?: _ToastOptions) =>
+ showToast({ ...options, type: 'info', message }),
+}
diff --git a/src/utils/uploadFile.ts b/src/utils/uploadFile.ts
new file mode 100644
index 0000000..416d39c
--- /dev/null
+++ b/src/utils/uploadFile.ts
@@ -0,0 +1,324 @@
+import { toast } from './toast'
+
+/**
+ * 文件上传钩子函数使用示例
+ * @example
+ * const { loading, error, data, progress, run } = useUpload(
+ * uploadUrl,
+ * {},
+ * {
+ * maxSize: 5, // 最大5MB
+ * sourceType: ['album'], // 仅支持从相册选择
+ * onProgress: (p) => console.log(`上传进度:${p}%`),
+ * onSuccess: (res) => console.log('上传成功', res),
+ * onError: (err) => console.error('上传失败', err),
+ * },
+ * )
+ */
+
+/**
+ * 上传文件的URL配置
+ */
+export const uploadFileUrl = {
+ /** 用户头像上传地址 */
+ USER_AVATAR: `${import.meta.env.VITE_SERVER_BASEURL}/user/avatar`,
+}
+
+/**
+ * 通用文件上传函数(支持直接传入文件路径)
+ * @param url 上传地址
+ * @param filePath 本地文件路径
+ * @param formData 额外表单数据
+ * @param options 上传选项
+ */
+export function useFileUpload(url: string, filePath: string, formData: Record = {}, options: Omit = {}) {
+ return useUpload(
+ url,
+ formData,
+ {
+ ...options,
+ sourceType: ['album'],
+ sizeType: ['original'],
+ },
+ filePath,
+ )
+}
+
+export interface UploadOptions {
+ /** 最大可选择的图片数量,默认为1 */
+ count?: number
+ /** 所选的图片的尺寸,original-原图,compressed-压缩图 */
+ sizeType?: Array<'original' | 'compressed'>
+ /** 选择图片的来源,album-相册,camera-相机 */
+ sourceType?: Array<'album' | 'camera'>
+ /** 文件大小限制,单位:MB */
+ maxSize?: number //
+ /** 上传进度回调函数 */
+ onProgress?: (progress: number) => void
+ /** 上传成功回调函数 */
+ onSuccess?: (res: Record) => void
+ /** 上传失败回调函数 */
+ onError?: (err: Error | UniApp.GeneralCallbackResult) => void
+ /** 上传完成回调函数(无论成功失败) */
+ onComplete?: () => void
+}
+
+/**
+ * 文件上传钩子函数
+ * @template T 上传成功后返回的数据类型
+ * @param url 上传地址
+ * @param formData 额外的表单数据
+ * @param options 上传选项
+ * @returns 上传状态和控制对象
+ */
+export function useUpload(url: string, formData: Record = {}, options: UploadOptions = {},
+ /** 直接传入文件路径,跳过选择器 */
+ directFilePath?: string) {
+ /** 上传中状态 */
+ const loading = ref(false)
+ /** 上传错误状态 */
+ const error = ref(false)
+ /** 上传成功后的响应数据 */
+ const data = ref()
+ /** 上传进度(0-100) */
+ const progress = ref(0)
+
+ /** 解构上传选项,设置默认值 */
+ const {
+ /** 最大可选择的图片数量 */
+ count = 1,
+ /** 所选的图片的尺寸 */
+ sizeType = ['original', 'compressed'],
+ /** 选择图片的来源 */
+ sourceType = ['album', 'camera'],
+ /** 文件大小限制(MB) */
+ maxSize = 10,
+ /** 进度回调 */
+ onProgress,
+ /** 成功回调 */
+ onSuccess,
+ /** 失败回调 */
+ onError,
+ /** 完成回调 */
+ onComplete,
+ } = options
+
+ /**
+ * 检查文件大小是否超过限制
+ * @param size 文件大小(字节)
+ * @returns 是否通过检查
+ */
+ const checkFileSize = (size: number) => {
+ const sizeInMB = size / 1024 / 1024
+ if (sizeInMB > maxSize) {
+ toast.warning(`文件大小不能超过${maxSize}MB`)
+ return false
+ }
+ return true
+ }
+ /**
+ * 触发文件选择和上传
+ * 根据平台使用不同的选择器:
+ * - 微信小程序使用 chooseMedia
+ * - 其他平台使用 chooseImage
+ */
+ const run = () => {
+ if (directFilePath) {
+ // 直接使用传入的文件路径
+ loading.value = true
+ progress.value = 0
+ uploadFile({
+ url,
+ tempFilePath: directFilePath,
+ formData,
+ data,
+ error,
+ loading,
+ progress,
+ onProgress,
+ onSuccess,
+ onError,
+ onComplete,
+ })
+ return
+ }
+
+ // #ifdef MP-WEIXIN
+ // 微信小程序环境下使用 chooseMedia API
+ uni.chooseMedia({
+ count,
+ mediaType: ['image'], // 仅支持图片类型
+ sourceType,
+ success: (res) => {
+ const file = res.tempFiles[0]
+ // 检查文件大小是否符合限制
+ if (!checkFileSize(file.size))
+ return
+
+ // 开始上传
+ loading.value = true
+ progress.value = 0
+ uploadFile({
+ url,
+ tempFilePath: file.tempFilePath,
+ formData,
+ data,
+ error,
+ loading,
+ progress,
+ onProgress,
+ onSuccess,
+ onError,
+ onComplete,
+ })
+ },
+ fail: (err) => {
+ console.error('选择媒体文件失败:', err)
+ error.value = true
+ onError?.(err)
+ },
+ })
+ // #endif
+
+ // #ifndef MP-WEIXIN
+ // 非微信小程序环境下使用 chooseImage API
+ uni.chooseImage({
+ count,
+ sizeType,
+ sourceType,
+ success: (res) => {
+ console.log('选择图片成功:', res)
+
+ // 开始上传
+ loading.value = true
+ progress.value = 0
+ uploadFile({
+ url,
+ tempFilePath: res.tempFilePaths[0],
+ formData,
+ data,
+ error,
+ loading,
+ progress,
+ onProgress,
+ onSuccess,
+ onError,
+ onComplete,
+ })
+ },
+ fail: (err) => {
+ console.error('选择图片失败:', err)
+ error.value = true
+ onError?.(err)
+ },
+ })
+ // #endif
+ }
+
+ return { loading, error, data, progress, run }
+}
+
+/**
+ * 文件上传选项接口
+ * @template T 上传成功后返回的数据类型
+ */
+interface UploadFileOptions {
+ /** 上传地址 */
+ url: string
+ /** 临时文件路径 */
+ tempFilePath: string
+ /** 额外的表单数据 */
+ formData: Record
+ /** 上传成功后的响应数据 */
+ data: Ref
+ /** 上传错误状态 */
+ error: Ref
+ /** 上传中状态 */
+ loading: Ref
+ /** 上传进度(0-100) */
+ progress: Ref
+ /** 上传进度回调 */
+ onProgress?: (progress: number) => void
+ /** 上传成功回调 */
+ onSuccess?: (res: Record) => void
+ /** 上传失败回调 */
+ onError?: (err: Error | UniApp.GeneralCallbackResult) => void
+ /** 上传完成回调 */
+ onComplete?: () => void
+}
+
+/**
+ * 执行文件上传
+ * @template T 上传成功后返回的数据类型
+ * @param options 上传选项
+ */
+function uploadFile({
+ url,
+ tempFilePath,
+ formData,
+ data,
+ error,
+ loading,
+ progress,
+ onProgress,
+ onSuccess,
+ onError,
+ onComplete,
+}: UploadFileOptions) {
+ try {
+ // 创建上传任务
+ const uploadTask = uni.uploadFile({
+ url,
+ filePath: tempFilePath,
+ name: 'file', // 文件对应的 key
+ formData,
+ header: {
+ // H5环境下不需要手动设置Content-Type,让浏览器自动处理multipart格式
+ // #ifndef H5
+ 'Content-Type': 'multipart/form-data',
+ // #endif
+ },
+ // 确保文件名称合法
+ success: (uploadFileRes) => {
+ console.log('上传文件成功:', uploadFileRes)
+ try {
+ // 解析响应数据
+ const { data: _data } = JSON.parse(uploadFileRes.data)
+ // 上传成功
+ data.value = _data as T
+ onSuccess?.(_data)
+ }
+ catch (err) {
+ // 响应解析错误
+ console.error('解析上传响应失败:', err)
+ error.value = true
+ onError?.(new Error('上传响应解析失败'))
+ }
+ },
+ fail: (err) => {
+ // 上传请求失败
+ console.error('上传文件失败:', err)
+ error.value = true
+ onError?.(err)
+ },
+ complete: () => {
+ // 无论成功失败都执行
+ loading.value = false
+ onComplete?.()
+ },
+ })
+
+ // 监听上传进度
+ uploadTask.onProgressUpdate((res) => {
+ progress.value = res.progress
+ onProgress?.(res.progress)
+ })
+ }
+ catch (err) {
+ // 创建上传任务失败
+ console.error('创建上传任务失败:', err)
+ error.value = true
+ loading.value = false
+ onError?.(new Error('创建上传任务失败'))
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..c95ea2f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,41 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "lib": ["esnext", "dom"],
+ "baseUrl": ".",
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "paths": {
+ "@/*": ["./src/*"],
+ "@img/*": ["./src/static/*"]
+ },
+ "resolveJsonModule": true,
+ "types": [
+ "@dcloudio/types",
+ "@uni-helper/uni-types",
+ "miniprogram-api-typings",
+ "wot-design-uni/global.d.ts",
+ "z-paging/types",
+ "./src/typings.d.ts"
+ ],
+ "allowJs": true,
+ "noImplicitThis": true,
+ "outDir": "dist",
+ "sourceMap": true,
+ "allowSyntheticDefaultImports": true,
+ "skipLibCheck": true
+ },
+ "vueCompilerOptions": {
+ "plugins": ["@uni-helper/uni-types/volar-plugin"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.js",
+ "src/**/*.d.ts",
+ "src/**/*.tsx",
+ "src/**/*.jsx",
+ "src/**/*.vue",
+ "src/**/*.json"
+ ],
+ "exclude": ["node_modules"]
+}
diff --git a/uno.config.ts b/uno.config.ts
new file mode 100644
index 0000000..5055f45
--- /dev/null
+++ b/uno.config.ts
@@ -0,0 +1,66 @@
+// https://www.npmjs.com/package/@uni-helper/unocss-preset-uni
+import { presetUni } from '@uni-helper/unocss-preset-uni'
+import {
+ defineConfig,
+ presetAttributify,
+ presetIcons,
+ transformerDirectives,
+ transformerVariantGroup,
+} from 'unocss'
+
+export default defineConfig({
+ presets: [
+ presetUni({
+ attributify: {
+ // prefix: 'fg-', // 如果加前缀,则需要在代码里面使用 `fg-` 前缀,如:
+ prefixedOnly: true,
+ },
+ }),
+ presetIcons({
+ scale: 1.2,
+ warn: true,
+ extraProperties: {
+ 'display': 'inline-block',
+ 'vertical-align': 'middle',
+ },
+ }),
+ // 支持css class属性化
+ presetAttributify(),
+ ],
+ transformers: [
+ // 启用指令功能:主要用于支持 @apply、@screen 和 theme() 等 CSS 指令
+ transformerDirectives(),
+ // 启用 () 分组功能
+ // 支持css class组合,eg: `测试 unocss
`
+ transformerVariantGroup(),
+ ],
+ shortcuts: [
+ {
+ center: 'flex justify-center items-center',
+ },
+ ],
+ // 动态图标需要在这里配置,或者写在vue页面中注释掉
+ safelist: ['i-carbon-code'],
+ rules: [
+ [
+ 'p-safe',
+ {
+ padding:
+ 'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)',
+ },
+ ],
+ ['pt-safe', { 'padding-top': 'env(safe-area-inset-top)' }],
+ ['pb-safe', { 'padding-bottom': 'env(safe-area-inset-bottom)' }],
+ ],
+ theme: {
+ colors: {
+ /** 主题色,用法如: text-primary */
+ primary: 'var(--wot-color-theme,#0957DE)',
+ },
+ fontSize: {
+ /** 提供更小号的字体,用法如:text-2xs */
+ '2xs': ['20rpx', '28rpx'],
+ '3xs': ['18rpx', '26rpx'],
+ },
+ },
+})
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..59e6086
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,183 @@
+import path from 'node:path'
+import process from 'node:process'
+import Uni from '@uni-helper/plugin-uni'
+import Components from '@uni-helper/vite-plugin-uni-components'
+// @see https://uni-helper.js.org/vite-plugin-uni-layouts
+import UniLayouts from '@uni-helper/vite-plugin-uni-layouts'
+// @see https://github.com/uni-helper/vite-plugin-uni-manifest
+import UniManifest from '@uni-helper/vite-plugin-uni-manifest'
+// @see https://uni-helper.js.org/vite-plugin-uni-pages
+import UniPages from '@uni-helper/vite-plugin-uni-pages'
+// @see https://github.com/uni-helper/vite-plugin-uni-platform
+// 需要与 @uni-helper/vite-plugin-uni-pages 插件一起使用
+import UniPlatform from '@uni-helper/vite-plugin-uni-platform'
+/**
+ * 分包优化、模块异步跨包调用、组件异步跨包引用
+ * @see https://github.com/uni-ku/bundle-optimizer
+ */
+import Optimization from '@uni-ku/bundle-optimizer'
+import dayjs from 'dayjs'
+import { visualizer } from 'rollup-plugin-visualizer'
+import UnoCSS from 'unocss/vite'
+import AutoImport from 'unplugin-auto-import/vite'
+import { defineConfig, loadEnv } from 'vite'
+import ViteRestart from 'vite-plugin-restart'
+
+// https://vitejs.dev/config/
+export default ({ command, mode }) => {
+ // @see https://unocss.dev/
+ // const UnoCSS = (await import('unocss/vite')).default
+ // console.log(mode === process.env.NODE_ENV) // true
+
+ // mode: 区分生产环境还是开发环境
+ console.log('command, mode -> ', command, mode)
+ // pnpm dev:h5 时得到 => serve development
+ // pnpm build:h5 时得到 => build production
+ // pnpm dev:mp-weixin 时得到 => build development (注意区别,command为build)
+ // pnpm build:mp-weixin 时得到 => build production
+ // pnpm dev:app 时得到 => build development (注意区别,command为build)
+ // pnpm build:app 时得到 => build production
+ // dev 和 build 命令可以分别使用 .env.development 和 .env.production 的环境变量
+
+ const { UNI_PLATFORM } = process.env
+ console.log('UNI_PLATFORM -> ', UNI_PLATFORM) // 得到 mp-weixin, h5, app 等
+
+ const env = loadEnv(mode, path.resolve(process.cwd(), 'env'))
+ const {
+ VITE_APP_PORT,
+ VITE_SERVER_BASEURL,
+ VITE_DELETE_CONSOLE,
+ VITE_SHOW_SOURCEMAP,
+ VITE_APP_PUBLIC_BASE,
+ VITE_APP_PROXY,
+ VITE_APP_PROXY_PREFIX,
+ } = env
+ console.log('环境变量 env -> ', env)
+
+ return defineConfig({
+ envDir: './env', // 自定义env目录
+ base: VITE_APP_PUBLIC_BASE,
+ plugins: [
+ UniPages({
+ exclude: ['**/components/**/**.*'],
+ // homePage 通过 vue 文件的 route-block 的type="home"来设定
+ // pages 目录为 src/pages,分包目录不能配置在pages目录下
+ subPackages: ['src/pages-sub'], // 是个数组,可以配置多个,但是不能为pages里面的目录
+ dts: 'src/types/uni-pages.d.ts',
+ }),
+ UniLayouts(),
+ UniPlatform(),
+ UniManifest(),
+ // UniXXX 需要在 Uni 之前引入
+ {
+ // 临时解决 dcloudio 官方的 @dcloudio/uni-mp-compiler 出现的编译 BUG
+ // 参考 github issue: https://github.com/dcloudio/uni-app/issues/4952
+ // 自定义插件禁用 vite:vue 插件的 devToolsEnabled,强制编译 vue 模板时 inline 为 true
+ name: 'fix-vite-plugin-vue',
+ configResolved(config) {
+ const plugin = config.plugins.find(p => p.name === 'vite:vue')
+ if (plugin && plugin.api && plugin.api.options) {
+ plugin.api.options.devToolsEnabled = false
+ }
+ },
+ },
+ UnoCSS(),
+ AutoImport({
+ imports: ['vue', 'uni-app'],
+ dts: 'src/types/auto-import.d.ts',
+ dirs: ['src/hooks'], // 自动导入 hooks
+ vueTemplate: true, // default false
+ }),
+ // Optimization 插件需要 page.json 文件,故应在 UniPages 插件之后执行
+ Optimization({
+ enable: {
+ 'optimization': true,
+ 'async-import': true,
+ 'async-component': true,
+ },
+ dts: {
+ base: 'src/types',
+ },
+ logger: false,
+ }),
+
+ ViteRestart({
+ // 通过这个插件,在修改vite.config.js文件则不需要重新运行也生效配置
+ restart: ['vite.config.js'],
+ }),
+ // h5环境增加 BUILD_TIME 和 BUILD_BRANCH
+ UNI_PLATFORM === 'h5' && {
+ name: 'html-transform',
+ transformIndexHtml(html) {
+ return html.replace('%BUILD_TIME%', dayjs().format('YYYY-MM-DD HH:mm:ss'))
+ },
+ },
+ // 打包分析插件,h5 + 生产环境才弹出
+ UNI_PLATFORM === 'h5'
+ && mode === 'production'
+ && visualizer({
+ filename: './node_modules/.cache/visualizer/stats.html',
+ open: true,
+ gzipSize: true,
+ brotliSize: true,
+ }),
+ // 只有在 app 平台时才启用 copyNativeRes 插件
+ // UNI_PLATFORM === 'app' && copyNativeRes(),
+ Components({
+ extensions: ['vue'],
+ deep: true, // 是否递归扫描子目录,
+ directoryAsNamespace: false, // 是否把目录名作为命名空间前缀,true 时组件名为 目录名+组件名,
+ dts: 'src/types/components.d.ts', // 自动生成的组件类型声明文件路径(用于 TypeScript 支持)
+ }),
+ Uni(),
+ ],
+ define: {
+ __UNI_PLATFORM__: JSON.stringify(UNI_PLATFORM),
+ __VITE_APP_PROXY__: JSON.stringify(VITE_APP_PROXY),
+ },
+ css: {
+ postcss: {
+ plugins: [
+ // autoprefixer({
+ // // 指定目标浏览器
+ // overrideBrowserslist: ['> 1%', 'last 2 versions'],
+ // }),
+ ],
+ },
+ },
+
+ resolve: {
+ alias: {
+ '@': path.join(process.cwd(), './src'),
+ '@img': path.join(process.cwd(), './src/static/images'),
+ },
+ },
+ server: {
+ host: '0.0.0.0',
+ hmr: true,
+ port: Number.parseInt(VITE_APP_PORT, 10),
+ // 仅 H5 端生效,其他端不生效(其他端走build,不走devServer)
+ proxy: JSON.parse(VITE_APP_PROXY)
+ ? {
+ [VITE_APP_PROXY_PREFIX]: {
+ target: VITE_SERVER_BASEURL,
+ changeOrigin: true,
+ rewrite: path => path.replace(new RegExp(`^${VITE_APP_PROXY_PREFIX}`), ''),
+ },
+ }
+ : undefined,
+ },
+ esbuild: {
+ drop: VITE_DELETE_CONSOLE === 'true' ? ['console', 'debugger'] : ['debugger'],
+ },
+ build: {
+ sourcemap: false,
+ // 方便非h5端调试
+ // sourcemap: VITE_SHOW_SOURCEMAP === 'true', // 默认是false
+ target: 'es6',
+ // 开发环境不用压缩
+ minify: mode === 'development' ? false : 'esbuild',
+
+ },
+ })
+}