commit 9061da3a292dc2fca753540f2455a8cb15a00ebd
Author: 奏 <1978476055@qq.com>
Date: Fri Oct 18 18:09:15 2024 +0800
1
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..fccdb00
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+# .editorconfig 文件
+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 # 关闭末尾空格修剪
+
+[*.json]
+insert_final_newline = false
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..690dffc
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+uni_modules/
diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json
new file mode 100644
index 0000000..be77d70
--- /dev/null
+++ b/.eslintrc-auto-import.json
@@ -0,0 +1,91 @@
+{
+ "globals": {
+ "Component": true,
+ "ComponentPublicInstance": true,
+ "ComputedRef": true,
+ "EffectScope": true,
+ "ExtractDefaultPropTypes": true,
+ "ExtractPropTypes": true,
+ "ExtractPublicPropTypes": true,
+ "InjectionKey": true,
+ "PropType": true,
+ "Ref": true,
+ "VNode": true,
+ "WritableComputedRef": true,
+ "computed": true,
+ "createApp": true,
+ "customRef": true,
+ "defineAsyncComponent": true,
+ "defineComponent": true,
+ "effectScope": true,
+ "getCurrentInstance": true,
+ "getCurrentScope": true,
+ "h": true,
+ "inject": true,
+ "isProxy": true,
+ "isReactive": true,
+ "isReadonly": true,
+ "isRef": true,
+ "markRaw": true,
+ "nextTick": true,
+ "onActivated": true,
+ "onAddToFavorites": true,
+ "onBackPress": true,
+ "onBeforeMount": true,
+ "onBeforeUnmount": true,
+ "onBeforeUpdate": true,
+ "onDeactivated": true,
+ "onError": true,
+ "onErrorCaptured": true,
+ "onHide": true,
+ "onLaunch": true,
+ "onLoad": true,
+ "onMounted": true,
+ "onNavigationBarButtonTap": true,
+ "onNavigationBarSearchInputChanged": true,
+ "onNavigationBarSearchInputClicked": true,
+ "onNavigationBarSearchInputConfirmed": true,
+ "onNavigationBarSearchInputFocusChanged": true,
+ "onPageNotFound": true,
+ "onPageScroll": true,
+ "onPullDownRefresh": true,
+ "onReachBottom": true,
+ "onReady": true,
+ "onRenderTracked": true,
+ "onRenderTriggered": true,
+ "onResize": true,
+ "onScopeDispose": true,
+ "onServerPrefetch": true,
+ "onShareAppMessage": true,
+ "onShareTimeline": true,
+ "onShow": true,
+ "onTabItemTap": true,
+ "onThemeChange": true,
+ "onUnhandledRejection": true,
+ "onUnload": true,
+ "onUnmounted": true,
+ "onUpdated": true,
+ "provide": true,
+ "reactive": true,
+ "readonly": true,
+ "ref": true,
+ "resolveComponent": true,
+ "shallowReactive": true,
+ "shallowReadonly": true,
+ "shallowRef": true,
+ "toRaw": true,
+ "toRef": true,
+ "toRefs": true,
+ "toValue": true,
+ "triggerRef": true,
+ "unref": true,
+ "useAttrs": true,
+ "useCssModule": true,
+ "useCssVars": true,
+ "useSlots": true,
+ "watch": true,
+ "watchEffect": true,
+ "watchPostEffect": true,
+ "watchSyncEffect": true
+ }
+}
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
new file mode 100644
index 0000000..2371f0b
--- /dev/null
+++ b/.eslintrc.cjs
@@ -0,0 +1,94 @@
+module.exports = {
+ env: {
+ browser: true,
+ es2021: true,
+ node: true,
+ },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:vue/vue3-essential',
+ // eslint-plugin-import 插件, @see https://www.npmjs.com/package/eslint-plugin-import
+ 'plugin:import/recommended',
+ // eslint-config-airbnb-base 插件 已经改用 eslint-config-standard 插件
+ 'standard',
+ // 1. 接入 prettier 的规则
+ 'prettier',
+ 'plugin:prettier/recommended',
+ './.eslintrc-auto-import.json',
+ ],
+ overrides: [
+ {
+ env: {
+ node: true,
+ },
+ files: ['.eslintrc.{js,cjs}'],
+ parserOptions: {
+ sourceType: 'script',
+ },
+ },
+ ],
+ parserOptions: {
+ ecmaVersion: 'latest',
+ parser: '@typescript-eslint/parser',
+ sourceType: 'module',
+ },
+ plugins: [
+ '@typescript-eslint',
+ 'vue',
+ // 2. 加入 prettier 的 eslint 插件
+ 'prettier',
+ // eslint-import-resolver-typescript 插件,@see https://www.npmjs.com/package/eslint-import-resolver-typescript
+ 'import',
+ ],
+ rules: {
+ // 3. 注意要加上这一句,开启 prettier 自动修复的功能
+ 'prettier/prettier': 'error',
+ // turn on errors for missing imports
+ 'import/no-unresolved': 'off',
+ // 对后缀的检测,否则 import 一个ts文件也会报错,需要手动添加'.ts', 增加了下面的配置后就不用了
+ 'import/extensions': [
+ 'error',
+ 'ignorePackages',
+ { js: 'never', jsx: 'never', ts: 'never', tsx: 'never' },
+ ],
+ // 只允许1个默认导出,关闭,否则不能随意export xxx
+ 'import/prefer-default-export': ['off'],
+ 'no-console': ['off'],
+ // 'no-unused-vars': ['off'],
+ // '@typescript-eslint/no-unused-vars': ['off'],
+ // 解决vite.config.ts报错问题
+ 'import/no-extraneous-dependencies': 'off',
+ 'no-plusplus': 'off',
+ 'no-shadow': 'off',
+ 'vue/multi-word-component-names': 'off',
+ '@typescript-eslint/no-explicit-any': 'off',
+ 'no-underscore-dangle': 'off',
+ 'no-use-before-define': 'off',
+ 'no-undef': 'off',
+ 'no-unused-vars': 'off',
+ 'no-param-reassign': 'off',
+ '@typescript-eslint/no-unused-vars': 'off',
+ },
+ // eslint-import-resolver-typescript 插件,@see https://www.npmjs.com/package/eslint-import-resolver-typescript
+ settings: {
+ 'import/parsers': {
+ '@typescript-eslint/parser': ['.ts', '.tsx'],
+ },
+ 'import/resolver': {
+ typescript: {},
+ },
+ },
+ globals: {
+ $t: true,
+ uni: true,
+ UniApp: true,
+ wx: true,
+ WechatMiniprogram: true,
+ getCurrentPages: true,
+ UniHelper: true,
+ Page: true,
+ App: true,
+ NodeJS: true,
+ },
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e02747a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,43 @@
+# 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
+
+# unplugin-auto-import 生成的类型文件
+# auto-import.d.ts
+# vite-plugin-uni-pages 生成的类型文件
+uni-pages.d.ts
+# 插件生成的文件
+pages.json
+manifest.json
+
+# 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 . ## 针对所有文件
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100644
index 0000000..7241764
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx --no-install commitlint --edit
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..fc7c89d
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx --no-install -- lint-staged
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..356a656
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,6 @@
+# registry = https://registry.npmjs.org
+registry = https://registry.npmmirror.com
+
+strict-peer-dependencies=false
+auto-install-peers=true
+shamefully-hoist=true
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..4917b0e
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+# unplugin-auto-import 生成的类型文件,每次提交都改变,所以加入这里吧,与 .gitignore 配合使用
+auto-import.d.ts
diff --git a/.prettierrc.cjs b/.prettierrc.cjs
new file mode 100644
index 0000000..3986355
--- /dev/null
+++ b/.prettierrc.cjs
@@ -0,0 +1,19 @@
+// @see https://prettier.io/docs/en/options
+module.exports = {
+ singleQuote: true,
+ printWidth: 100,
+ tabWidth: 2,
+ useTabs: false,
+ semi: false,
+ trailingComma: 'all',
+ endOfLine: 'auto',
+ htmlWhitespaceSensitivity: 'ignore',
+ overrides: [
+ {
+ files: '*.json',
+ options: {
+ trailingComma: 'none',
+ },
+ },
+ ],
+}
diff --git a/.stylelintignore b/.stylelintignore
new file mode 100644
index 0000000..2025d99
--- /dev/null
+++ b/.stylelintignore
@@ -0,0 +1 @@
+uni_modules/
\ No newline at end of file
diff --git a/.stylelintrc.cjs b/.stylelintrc.cjs
new file mode 100644
index 0000000..2cb661a
--- /dev/null
+++ b/.stylelintrc.cjs
@@ -0,0 +1,57 @@
+// .stylelintrc.cjs
+
+module.exports = {
+ root: true,
+ extends: [
+ // stylelint-config-standard 替换成了更宽松的 stylelint-config-recommended
+ 'stylelint-config-recommended',
+ // stylelint-config-standard-scss 替换成了更宽松的 stylelint-config-recommended-scss
+ 'stylelint-config-recommended-scss',
+ 'stylelint-config-recommended-vue/scss',
+ 'stylelint-config-html/vue',
+ 'stylelint-config-recess-order',
+ ],
+ plugins: ['stylelint-prettier'],
+ overrides: [
+ // 扫描 .vue/html 文件中的\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"],
+ },
+}
diff --git a/App.vue b/App.vue
new file mode 100644
index 0000000..d77f902
--- /dev/null
+++ b/App.vue
@@ -0,0 +1,17 @@
+
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9c3438e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 菲鸽
+
+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..2b31703
--- /dev/null
+++ b/README.md
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+[![GitHub Repo stars](https://img.shields.io/github/stars/codercup/unibest?style=flat&logo=github)](https://github.com/codercup/unibest)
+[![GitHub forks](https://img.shields.io/github/forks/codercup/unibest?style=flat&logo=github)](https://github.com/codercup/unibest)
+[![star](https://gitee.com/codercup/unibest/badge/star.svg?theme=dark)](https://gitee.com/codercup/unibest/stargazers)
+[![fork](https://gitee.com/codercup/unibest/badge/fork.svg?theme=dark)](https://gitee.com/codercup/unibest/members)
+![node version](https://img.shields.io/badge/node-%3E%3D18-green)
+![pnpm version](https://img.shields.io/badge/pnpm-%3E%3D7.30-green)
+![GitHub package.json version (subfolder of monorepo)](https://img.shields.io/github/package-json/v/codercup/unibest)
+![GitHub License](https://img.shields.io/github/license/codercup/unibest)
+
+
+
+`unibest` 是一个 uniapp 跨端解决方案,由 `uniapp` + `Vue3` + `Ts` + `Vite4` + `UnoCss` + `VSCode`(可选 `webstorm`) 实现。它使用了最新的前端技术栈,可以通过命令行方式运行 `web`、`小程序` 和 `App`,同时也支持 `HBuilderX` 运行,当前版本为 `HBuilderX` 运行版本。如需通过命令行运行,请安装命令行版(unibest)。
+
+`unibest` 内置了 `约定式路由`、`layout布局`、`请求封装`、`请求拦截`、`登录拦截`、`UnoCSS`、`i18n多语言` 等基础功能,提供了 `代码提示`、`自动格式化`、`统一配置`、`代码片段` 等辅助功能,让你编写 `uniapp` 拥有 `best` 体验 ( `unibest 的由来`)。
+
+![](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png)
+
+
+ 📱 在线预览
+ |
+ 📖 阅读文档
+
+
+## ✨ 特性
+
+- ⚡️ [Vue 3](https://github.com/vuejs/core), [Vite](https://github.com/vitejs/vite), [pnpm](https://pnpm.io/), [esbuild](https://github.com/evanw/esbuild) - 就是快!
+- 🔥 最新语法 - `
diff --git a/components/fly-header/fly-header.vue b/components/fly-header/fly-header.vue
new file mode 100644
index 0000000..9e34a4b
--- /dev/null
+++ b/components/fly-header/fly-header.vue
@@ -0,0 +1,3 @@
+
+ header
+
diff --git a/components/fly-login/README.md b/components/fly-login/README.md
new file mode 100644
index 0000000..c931549
--- /dev/null
+++ b/components/fly-login/README.md
@@ -0,0 +1,7 @@
+# fly-login
+
+点击“点击显示微信头像”按钮后,出现的半屏登录弹窗,可以在任意页面引入。
+
+仿“掘金小册”小程序。
+
+![掘金小册登录](screenshot.png)
diff --git a/components/fly-login/defaultAvatar.png b/components/fly-login/defaultAvatar.png
new file mode 100644
index 0000000..e69596a
Binary files /dev/null and b/components/fly-login/defaultAvatar.png differ
diff --git a/components/fly-login/fly-login.vue b/components/fly-login/fly-login.vue
new file mode 100644
index 0000000..8a6dc1f
--- /dev/null
+++ b/components/fly-login/fly-login.vue
@@ -0,0 +1,120 @@
+
+
+
+
+ 获取您的昵称、头像
+
+
+
+
+
+ 头像
+
+
+
+
+ 昵称
+
+
+
+
+
+
+
+
+
+
diff --git a/components/fly-login/screenshot.png b/components/fly-login/screenshot.png
new file mode 100644
index 0000000..7ca7a26
Binary files /dev/null and b/components/fly-login/screenshot.png differ
diff --git a/components/fly-navbar/README.md b/components/fly-navbar/README.md
new file mode 100644
index 0000000..cd5cf97
--- /dev/null
+++ b/components/fly-navbar/README.md
@@ -0,0 +1,3 @@
+# fly-navbar
+
+建议本导航栏组件在设置 `"navigationStyle": "custom"` 的页面使用,目前支持微信小程序的页面滚动动画。
diff --git a/components/fly-navbar/fly-navbar.vue b/components/fly-navbar/fly-navbar.vue
new file mode 100644
index 0000000..5636cb4
--- /dev/null
+++ b/components/fly-navbar/fly-navbar.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ title || '' }}
+
+
+
+
+
+
diff --git a/env.d.ts b/env.d.ts
new file mode 100644
index 0000000..fa8c7d8
--- /dev/null
+++ b/env.d.ts
@@ -0,0 +1,21 @@
+///
+///
+
+declare module '*.vue' {
+ import { DefineComponent } from 'vue'
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
+
+interface ImportMetaEnv {
+ readonly VITE_APP_TITLE: string
+ readonly VITE_SERVER_PORT: string
+ readonly VITE_SERVER_BASEURL: string
+ readonly VITE_DELETE_CONSOLE: string
+ // 更多环境变量...
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv
+}
diff --git a/env/.env b/env/.env
new file mode 100644
index 0000000..d1bbf28
--- /dev/null
+++ b/env/.env
@@ -0,0 +1,18 @@
+VITE_APP_TITLE = 'unibest'
+VITE_APP_PORT = 9000
+
+# github actions 部署地址根路径,用在 manifest.config.ts 里面的 h5.router.base
+VITE_APP_PUBLIC_BASE=/
+
+# TODO: 记得修改
+VITE_UNI_APPID = 'H5871D791'
+VITE_WX_APPID = 'wxa2abb91f64032a2b'
+
+# fallback lacale:en, zh-Hans, zh-Hant 等
+# 必须要在 lacale 文件夹中配置对应的 json 文件!!!
+# 参考文档如下
+# https://uniapp.dcloud.net.cn/tutorial/i18n.html
+# https://uniapp.dcloud.net.cn/api/ui/locale.html#onlocalechange 打开页面后最下面的注意事项
+VITE_FALLBACK_LOCALE = 'zh-Hans'
+
+VITE_SERVER_BASEURL = 'https://ukw0y1.laf.run'
diff --git a/env/.env.development b/env/.env.development
new file mode 100644
index 0000000..dd0a1f2
--- /dev/null
+++ b/env/.env.development
@@ -0,0 +1,6 @@
+# 变量必须以 VITE_ 为前缀才能暴露给外部读取
+NODE_ENV = 'development'
+# 是否去除console 和 debugger
+VITE_DELETE_CONSOLE = false
+
+# VITE_SERVER_BASEURL = 'https://ukw0y1.laf.run'
diff --git a/env/.env.production b/env/.env.production
new file mode 100644
index 0000000..94b3031
--- /dev/null
+++ b/env/.env.production
@@ -0,0 +1,6 @@
+# 变量必须以 VITE_ 为前缀才能暴露给外部读取
+NODE_ENV = 'development'
+# 是否去除console 和 debugger
+VITE_DELETE_CONSOLE = true
+
+# VITE_SERVER_BASEURL = 'https://xxx.com'
diff --git a/env/.env.test b/env/.env.test
new file mode 100644
index 0000000..a0dcad1
--- /dev/null
+++ b/env/.env.test
@@ -0,0 +1,6 @@
+# 变量必须以 VITE_ 为前缀才能暴露给外部读取
+NODE_ENV = 'development'
+# 是否去除console 和 debugger
+VITE_DELETE_CONSOLE = false
+
+# VITE_SERVER_BASEURL = 'https://xxx.com'
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000..47c577c
Binary files /dev/null and b/favicon.ico differ
diff --git a/hooks/.gitkeep b/hooks/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/hooks/useNavbarWeixin.ts b/hooks/useNavbarWeixin.ts
new file mode 100644
index 0000000..73e12da
--- /dev/null
+++ b/hooks/useNavbarWeixin.ts
@@ -0,0 +1,62 @@
+import { onReady } from '@dcloudio/uni-app'
+import { getIsTabbar, getArrElementByIdx } from '@/utils/index'
+
+export default () => {
+ // 获取页面栈
+ const pages = getCurrentPages()
+ const isTabbar = getIsTabbar()
+
+ // 页面滚动到底部时的操作,通常用于加载更多数据
+ const onScrollToLower = () => {}
+ // 获取屏幕边界到安全区域距离
+ const { safeAreaInsets } = uni.getSystemInfoSync()
+
+ // #ifdef MP-WEIXIN
+ // 基于小程序的 Page 类型扩展 uni-app 的 Page
+ type PageInstance = Page.PageInstance & WechatMiniprogram.Page.InstanceMethods
+ // 获取当前页面实例,数组最后一项
+ const pageInstance = getArrElementByIdx(getCurrentPages(), -1) as PageInstance
+
+ // 页面渲染完毕,绑定动画效果
+ onReady(() => {
+ // 动画效果,导航栏背景色
+ pageInstance.animate(
+ '.fly-navbar',
+ [{ backgroundColor: 'transparent' }, { backgroundColor: '#f8f8f8' }],
+ 1000,
+ {
+ scrollSource: '#scroller',
+ timeRange: 1000,
+ startScrollOffset: 0,
+ endScrollOffset: 50,
+ },
+ )
+ // 动画效果,导航栏标题
+ pageInstance.animate(
+ '.fly-navbar .title',
+ [{ color: 'transparent' }, { color: '#000' }],
+ 1000,
+ {
+ scrollSource: '#scroller',
+ timeRange: 1000,
+ startScrollOffset: 0,
+ endScrollOffset: 50,
+ },
+ )
+ // 动画效果,导航栏返回按钮
+ pageInstance.animate('.fly-navbar .left-icon', [{ color: '#fff' }, { color: '#000' }], 1000, {
+ scrollSource: '#scroller',
+ timeRange: 1000,
+ startScrollOffset: 0,
+ endScrollOffset: 50,
+ })
+ })
+ // #endif
+
+ return {
+ pages,
+ isTabbar,
+ onScrollToLower,
+ safeAreaInsets,
+ }
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..3a000ae
--- /dev/null
+++ b/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/interceptors/index.ts b/interceptors/index.ts
new file mode 100644
index 0000000..477545a
--- /dev/null
+++ b/interceptors/index.ts
@@ -0,0 +1,2 @@
+export { routeInterceptor } from './route'
+export { requestInterceptor } from './request'
diff --git a/interceptors/request.ts b/interceptors/request.ts
new file mode 100644
index 0000000..8e18f62
--- /dev/null
+++ b/interceptors/request.ts
@@ -0,0 +1,53 @@
+/* eslint-disable no-param-reassign */
+import qs from 'qs'
+import { useUserStore } from '@/store'
+
+export type CustomRequestOptions = UniApp.RequestOptions & {
+ query?: Record
+} & IUniUploadFileOptions // 添加uni.uploadFile参数类型
+
+// 请求基地址
+const baseURL = import.meta.env.VITE_SERVER_BASEURL
+
+// 拦截器配置
+const httpInterceptor = {
+ // 拦截前触发
+ invoke(options: CustomRequestOptions) {
+ // 接口请求支持通过 query 参数配置 queryString
+ if (options.query) {
+ const queryStr = qs.stringify(options.query)
+ if (options.url.includes('?')) {
+ options.url += `&${queryStr}`
+ } else {
+ options.url += `?${queryStr}`
+ }
+ }
+
+ // 1. 非 http 开头需拼接地址
+ if (!options.url.startsWith('http')) {
+ options.url = baseURL + options.url
+ }
+ // 2. 请求超时
+ options.timeout = 10000 // 10s
+ // 3. 添加小程序端请求头标识
+ options.header = {
+ platform: 'mp-weixin', // 可选值与 uniapp 定义的平台一致,告诉后台来源
+ ...options.header,
+ }
+ // 4. 添加 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/interceptors/route.ts b/interceptors/route.ts
new file mode 100644
index 0000000..e103bee
--- /dev/null
+++ b/interceptors/route.ts
@@ -0,0 +1,54 @@
+/**
+ * by 菲鸽 on 2024-03-06
+ * 路由拦截,通常也是登录拦截
+ * 可以设置路由白名单,或者黑名单,看业务需要选哪一个
+ * 我这里应为大部分都可以随便进入,所以使用黑名单
+ */
+import { useUserStore } from '@/store'
+import { getNeedLoginPages, needLoginPages as _needLoginPages } from '@/utils'
+
+// TODO Check
+const loginRoute = '/pages/login/index'
+
+const isLogined = () => {
+ const userStore = useUserStore()
+ return userStore.isLogined
+}
+
+const isDev = import.meta.env.DEV
+
+// 黑名单登录拦截器 - (适用于大部分页面不需要登录,少部分页面需要登录)
+const navigateToInterceptor = {
+ // 注意,这里的url是 '/' 开头的,如 '/pages/index/index',跟 'pages.json' 里面的 path 不同
+ invoke({ url }: { url: string }) {
+ console.log(url) // /pages/route-interceptor/index?name=feige&age=30
+ const path = url.split('?')[0]
+ let needLoginPages: string[] = []
+ // 为了防止开发时出现BUG,这里每次都获取一下。生产环境可以移到函数外,性能更好
+ if (isDev) {
+ needLoginPages = getNeedLoginPages()
+ } else {
+ needLoginPages = _needLoginPages
+ }
+ console.log(needLoginPages.includes(path))
+
+ if (needLoginPages.includes(path)) {
+ const isLogin = isLogined()
+ if (isLogin) {
+ return true
+ }
+ const redirectRoute = `${loginRoute}?redirect=${encodeURIComponent(url)}`
+ uni.navigateTo({ url: redirectRoute })
+ return false
+ }
+ return true
+ },
+}
+
+export const routeInterceptor = {
+ install() {
+ uni.addInterceptor('navigateTo', navigateToInterceptor)
+ uni.addInterceptor('reLaunch', navigateToInterceptor)
+ uni.addInterceptor('redirectTo', navigateToInterceptor)
+ },
+}
diff --git a/layouts/default.vue b/layouts/default.vue
new file mode 100644
index 0000000..2e4a040
--- /dev/null
+++ b/layouts/default.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/layouts/demo.vue b/layouts/demo.vue
new file mode 100644
index 0000000..2de39ab
--- /dev/null
+++ b/layouts/demo.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..b057799
--- /dev/null
+++ b/main.js
@@ -0,0 +1,15 @@
+import { createSSRApp } from 'vue'
+import App from './App.vue'
+import store from './store'
+import { routeInterceptor, requestInterceptor } from './interceptors'
+import 'virtual:uno.css'
+
+export function createApp() {
+ const app = createSSRApp(App)
+ app.use(store)
+ app.use(routeInterceptor)
+ app.use(requestInterceptor)
+ return {
+ app,
+ }
+}
diff --git a/manifest.config.ts b/manifest.config.ts
new file mode 100644
index 0000000..25af925
--- /dev/null
+++ b/manifest.config.ts
@@ -0,0 +1,103 @@
+// manifest.config.ts
+import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
+import path from 'node:path'
+import { loadEnv } from 'vite'
+
+// 获取环境变量的范例
+const env = loadEnv(process.env.NODE_ENV!, path.resolve(process.env.VITE_ROOT_DIR, 'env'))
+// console.log(env)
+const {
+ VITE_APP_TITLE,
+ VITE_UNI_APPID,
+ VITE_WX_APPID,
+ VITE_APP_PUBLIC_BASE,
+ VITE_FALLBACK_LOCALE,
+} = env
+
+export default defineManifestConfig({
+ name: VITE_APP_TITLE,
+ appid: VITE_UNI_APPID,
+ description: '',
+ versionName: '1.0.0',
+ versionCode: '100',
+ transformPx: false,
+ locale: VITE_FALLBACK_LOCALE, // 'zh-Hans'
+ h5: {
+ router: {
+ base: VITE_APP_PUBLIC_BASE,
+ },
+ },
+ /* 5+App特有相关 */
+ 'app-plus': {
+ usingComponents: true,
+ nvueStyleCompiler: 'uni-app',
+ compilerVersion: 3,
+ splashscreen: {
+ alwaysShowBeforeRender: true,
+ waiting: true,
+ autoclose: true,
+ delay: 0,
+ },
+ /* 模块配置 */
+ modules: {},
+ /* 应用发布信息 */
+ distribute: {
+ /* android打包配置 */
+ android: {
+ minSdkVersion: 30,
+ targetSdkVersion: 30,
+ abiFilters: ['armeabi-v7a', 'arm64-v8a'],
+ permissions: [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ],
+ },
+ /* ios打包配置 */
+ ios: {},
+ /* SDK配置 */
+ sdkConfigs: {},
+ /* 图标配置 */
+ icons: {
+ android: {},
+ ios: {},
+ },
+ },
+ },
+ /* 快应用特有相关 */
+ quickapp: {},
+ /* 小程序特有相关 */
+ 'mp-weixin': {
+ appid: VITE_WX_APPID,
+ setting: {
+ urlCheck: false,
+ },
+ usingComponents: true,
+ // __usePrivacyCheck__: true,
+ },
+ 'mp-alipay': {
+ usingComponents: true,
+ },
+ 'mp-baidu': {
+ usingComponents: true,
+ },
+ 'mp-toutiao': {
+ usingComponents: true,
+ },
+ uniStatistics: {
+ enable: false,
+ },
+ vueVersion: '3',
+})
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..c2cdf19
--- /dev/null
+++ b/package.json
@@ -0,0 +1,97 @@
+{
+ "name": "my-project",
+ "version": "0.2.0",
+ "description": "unibest - 最好的 uniapp 开发模板, HBX 版本",
+ "main": "main.js",
+ "author": {
+ "name": "codercup",
+ "zhName": "菲鸽",
+ "email": "1020103647@qq.com",
+ "github": "https://github.com/codercup",
+ "gitee": "https://gitee.com/codercup"
+ },
+ "license": "MIT",
+ "repository": "https://github.com/codercup/unibest-hbx",
+ "repository-gitee": "https://gitee.com/codercup/unibest-hbx",
+ "bugs": {
+ "url": "https://github.com/codercup/unibest-hbx/issues"
+ },
+ "homepage": "https://codercup.github.io/unibest-hbx/",
+ "engines": {
+ "node": ">=18",
+ "pnpm": ">=7.30"
+ },
+ "scripts": {
+ "prepare": "node ./shell/postinstall.js & git init && husky install"
+ },
+ "lint-staged": {
+ "**/*.{html,vue,ts,cjs,json,md}": [
+ "prettier --write"
+ ],
+ "**/*.{vue,js,ts,jsx,tsx}": [
+ "eslint --fix"
+ ],
+ "**/*.{vue,css,scss,html}": [
+ "stylelint --fix"
+ ]
+ },
+ "resolutions": {
+ "bin-wrapper": "npm:bin-wrapper-china"
+ },
+ "dependencies": {
+ "dayjs": "^1.11.11",
+ "pinia": "2.0.36",
+ "pinia-plugin-persistedstate": "^3.2.1",
+ "qs": "6.5.3",
+ "vue": "3.4.21",
+ "wot-design-uni": "^1.2.20"
+ },
+ "devDependencies": {
+ "@commitlint/cli": "^19.3.0",
+ "@commitlint/config-conventional": "^19.2.2",
+ "@dcloudio/types": "^3.4.8",
+ "@dcloudio/vite-plugin-uni": "3.0.0-4010520240507001",
+ "@iconify-json/carbon": "^1.1.33",
+ "@types/node": "^20.12.12",
+ "@types/wechat-miniprogram": "^3.4.7",
+ "@typescript-eslint/eslint-plugin": "^7.9.0",
+ "@typescript-eslint/parser": "^7.9.0",
+ "@uni-helper/uni-app-types": "^0.5.13",
+ "@uni-helper/uni-ui-types": "^0.5.14",
+ "@uni-helper/vite-plugin-uni-layouts": "^0.1.8",
+ "@uni-helper/vite-plugin-uni-manifest": "^0.2.6",
+ "@uni-helper/vite-plugin-uni-pages": "^0.2.20",
+ "@uni-helper/vite-plugin-uni-platform": "^0.0.4",
+ "@unocss/preset-legacy-compat": "^0.59.4",
+ "@vitejs/plugin-vue": "^5.0.4",
+ "autoprefixer": "^10.4.19",
+ "commitlint": "^19.3.0",
+ "esbuild": "0.20.1",
+ "eslint": "^8.57.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-config-standard": "^17.1.0",
+ "eslint-import-resolver-typescript": "^3.6.1",
+ "eslint-plugin-import": "^2.29.1",
+ "eslint-plugin-prettier": "^5.1.3",
+ "eslint-plugin-vue": "^9.26.0",
+ "husky": "^9.0.11",
+ "lint-staged": "^15.2.2",
+ "postcss": "^8.4.38",
+ "postcss-html": "^1.7.0",
+ "postcss-scss": "^4.0.9",
+ "sass": "^1.77.2",
+ "stylelint": "^16.5.0",
+ "stylelint-config-html": "^1.1.0",
+ "stylelint-config-recess-order": "^5.0.1",
+ "stylelint-config-recommended": "^14.0.0",
+ "stylelint-config-recommended-scss": "^14.0.0",
+ "stylelint-config-recommended-vue": "^1.5.0",
+ "stylelint-prettier": "^5.0.0",
+ "terser": "^5.31.0",
+ "unocss": "^0.58.9",
+ "unocss-applet": "^0.7.8",
+ "unplugin-auto-import": "^0.17.6",
+ "vite": "5.2.8",
+ "vite-plugin-restart": "^0.4.0"
+ }
+}
\ No newline at end of file
diff --git a/pages-sub/demo.vue b/pages-sub/demo.vue
new file mode 100644
index 0000000..6126ddc
--- /dev/null
+++ b/pages-sub/demo.vue
@@ -0,0 +1,20 @@
+
+{
+ style: { navigationBarTitleText: '分包页面 标题' },
+}
+
+
+
+
+ http://localhost:9000/#/pages-sub/demo/index
+ 分包页面demo
+
+
+
+
+
+
diff --git a/pages.config.ts b/pages.config.ts
new file mode 100644
index 0000000..32c8bf9
--- /dev/null
+++ b/pages.config.ts
@@ -0,0 +1,45 @@
+import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
+
+export default defineUniPages({
+ globalStyle: {
+ navigationStyle: 'default',
+ navigationBarTitleText: 'unibest',
+ navigationBarBackgroundColor: '#f8f8f8',
+ navigationBarTextStyle: 'black',
+ backgroundColor: '#FFFFFF',
+ h5: {
+ navigationStyle: 'custom',
+ },
+ },
+ easycom: {
+ autoscan: true,
+ custom: {
+ '^wd-(.*)': 'wot-design-uni/components/wd-$1/wd-$1.vue',
+ '^layout-(.*)-uni': '@/layouts/$1.vue',
+ },
+ },
+ tabBar: {
+ 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/index/about',
+ text: '关于',
+ },
+ ],
+ },
+})
diff --git a/pages/index/about.vue b/pages/index/about.vue
new file mode 100644
index 0000000..038ca88
--- /dev/null
+++ b/pages/index/about.vue
@@ -0,0 +1,37 @@
+
+{
+ layout: 'default',
+ style: {
+ navigationBarTitleText: '关于',
+ },
+}
+
+
+
+
+ 关于页面
+
+ 鸽友们好,我是
+ 菲鸽
+
+
+ wot 组件库测试
+
+ 测试设计稿样式
+ 设计稿是750px,css里面全部写rpx 即可
+
+
+
+
+
+
diff --git a/pages/index/index.vue b/pages/index/index.vue
new file mode 100644
index 0000000..ca27cdf
--- /dev/null
+++ b/pages/index/index.vue
@@ -0,0 +1,75 @@
+
+
+{
+ style: {
+ navigationStyle: 'custom',
+ navigationBarTitleText: '首页',
+ },
+}
+
+
+
+
+
+
+ unibest
+ 最好用的 uniapp 开发模板
+ hbx版(HBuilderX)
+ {{ description }}
+
+ 在线文档:
+
+ https://codercup.github.io/unibest-docs/
+
+
+
+ https://codercup.github.io/unibest-docs/
+
+
+
+
+ 在线预览:
+
+ https://codercup.github.io/unibest/
+
+
+
+ https://codercup.github.io/unibest/
+
+
+
+
+
+
+
+
+
diff --git a/service/foo.d.ts b/service/foo.d.ts
new file mode 100644
index 0000000..81bc41a
--- /dev/null
+++ b/service/foo.d.ts
@@ -0,0 +1,4 @@
+export interface IFooItem {
+ id: string
+ name: string
+}
diff --git a/service/foo.ts b/service/foo.ts
new file mode 100644
index 0000000..57cf584
--- /dev/null
+++ b/service/foo.ts
@@ -0,0 +1,32 @@
+import { http, uniFileUpload } from '@/utils/http'
+import type { IFooItem } from './foo.d'
+
+export { IFooItem }
+
+/** get 请求 */
+export const getFooAPI = (name: string) => {
+ return http({
+ url: `/foo`,
+ method: 'GET',
+ query: { name },
+ })
+}
+
+/** get 请求 */
+export const postFooAPI = (name: string) => {
+ return http({
+ url: `/foo`,
+ method: 'POST',
+ query: { name }, // post 请求也支持 query
+ data: { name },
+ })
+}
+
+// 文件上传
+export const fileUpload = (data: IUniUploadFileOptions) => {
+ return uniFileUpload({
+ url: `/foo/upload`,
+ method: 'POST',
+ ...data,
+ })
+}
diff --git a/shell/postinstall.js b/shell/postinstall.js
new file mode 100644
index 0000000..0aba101
--- /dev/null
+++ b/shell/postinstall.js
@@ -0,0 +1,47 @@
+/**
+ * 本文件会在依赖包安装时执行,用以生成 `src/manifest.json`
+ * 如果不存在 `src/manifest.json` 会运行报错,提示找不到 `src/manifest.json`
+ * 如果中途自己删除了 'src/manifest.json' 文件,记得手动执行本文件,可以右键 `Run Code` 快速执行
+ *
+ * 本文件是为了兼容 window 系统才生成的
+ */
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const fs = require('fs')
+
+const filePath = './manifest.json'
+
+if (fs.existsSync(filePath)) {
+ // console.log(`${filePath}存在`)
+} else {
+ // console.log(`${filePath}不存在,需要创建`)
+ fs.writeFile(filePath, '{"vueVersion": "3"}\n', {}, () => {
+ // console.log(`${filePath}已经成功创建,并写入{}`)
+ })
+}
+
+const filePath2 = './pages.json'
+if (fs.existsSync(filePath2)) {
+ // console.log(`${filePath}存在`)
+} else {
+ // console.log(`${filePath}不存在,需要创建`)
+ fs.writeFile(
+ filePath2,
+ `{
+ "pages": [
+ {
+ "path": "pages/index/index",
+ "type": "home",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": "首页"
+ }
+ }
+ ]
+}\n`,
+ {},
+ () => {
+ // console.log(`${filePath}已经成功创建,并写入{}`)
+ },
+ )
+}
diff --git a/static/logo.png b/static/logo.png
new file mode 100644
index 0000000..b5771e2
Binary files /dev/null and b/static/logo.png differ
diff --git a/static/logo.svg b/static/logo.svg
new file mode 100644
index 0000000..eaee669
--- /dev/null
+++ b/static/logo.svg
@@ -0,0 +1,33 @@
+
+
diff --git a/static/tabbar/example.png b/static/tabbar/example.png
new file mode 100644
index 0000000..fd1e942
Binary files /dev/null and b/static/tabbar/example.png differ
diff --git a/static/tabbar/exampleHL.png b/static/tabbar/exampleHL.png
new file mode 100644
index 0000000..7501011
Binary files /dev/null and b/static/tabbar/exampleHL.png differ
diff --git a/static/tabbar/home.png b/static/tabbar/home.png
new file mode 100644
index 0000000..8f82e21
Binary files /dev/null and b/static/tabbar/home.png differ
diff --git a/static/tabbar/homeHL.png b/static/tabbar/homeHL.png
new file mode 100644
index 0000000..26d3761
Binary files /dev/null and b/static/tabbar/homeHL.png differ
diff --git a/static/tabbar/personal.png b/static/tabbar/personal.png
new file mode 100644
index 0000000..0a569a2
Binary files /dev/null and b/static/tabbar/personal.png differ
diff --git a/static/tabbar/personalHL.png b/static/tabbar/personalHL.png
new file mode 100644
index 0000000..8c3e66e
Binary files /dev/null and b/static/tabbar/personalHL.png differ
diff --git a/store/count.ts b/store/count.ts
new file mode 100644
index 0000000..946ccd7
--- /dev/null
+++ b/store/count.ts
@@ -0,0 +1,28 @@
+// src/store/useCountStore.ts
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+
+export const useCountStore = defineStore(
+ 'count',
+ () => {
+ const count = ref(0)
+ const increment = () => {
+ count.value++
+ }
+ const decrement = () => {
+ count.value--
+ }
+ const reset = () => {
+ count.value = 0
+ }
+ return {
+ count,
+ decrement,
+ increment,
+ reset,
+ }
+ },
+ {
+ persist: true,
+ },
+)
diff --git a/store/index.ts b/store/index.ts
new file mode 100644
index 0000000..93bd4d4
--- /dev/null
+++ b/store/index.ts
@@ -0,0 +1,18 @@
+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'
+export * from './count'
diff --git a/store/user.ts b/store/user.ts
new file mode 100644
index 0000000..a94351c
--- /dev/null
+++ b/store/user.ts
@@ -0,0 +1,34 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+
+const initState = { nickname: '', avatar: '' }
+
+export const useUserStore = defineStore(
+ 'user',
+ () => {
+ const userInfo = ref({ ...initState })
+
+ const setUserInfo = (val: IUserInfo) => {
+ userInfo.value = val
+ }
+
+ const clearUserInfo = () => {
+ userInfo.value = { ...initState }
+ }
+ const reset = () => {
+ userInfo.value = { ...initState }
+ }
+ const isLogined = computed(() => !!userInfo.value.token)
+
+ return {
+ userInfo,
+ setUserInfo,
+ clearUserInfo,
+ isLogined,
+ reset,
+ }
+ },
+ {
+ persist: true,
+ },
+)
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..9f7f9bc
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,41 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "noImplicitThis": true,
+ "allowSyntheticDefaultImports": true,
+ "allowJs": true,
+ "sourceMap": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./*"]
+ },
+ "outDir": "dist",
+ "lib": ["esnext", "dom"],
+ "types": [
+ "@dcloudio/types",
+ "@types/wechat-miniprogram",
+ "@uni-helper/uni-app-types",
+ "@uni-helper/uni-ui-types",
+ "wot-design-uni/global.d.ts"
+ ]
+ },
+ "vueCompilerOptions": {
+ "target": 3,
+ "nativeTags": ["block", "template", "component", "slot"]
+ },
+ "exclude": ["node_modules"],
+ "include": [
+ "./**/*.ts",
+ "./**/*.js",
+ "./**/*.d.ts",
+ "./**/*.tsx",
+ "./**/*.jsx",
+ "./**/*.vue",
+ "./**/*.json",
+ "main.js"
+ ]
+}
diff --git a/types/auto-import.d.ts b/types/auto-import.d.ts
new file mode 100644
index 0000000..f5405c5
--- /dev/null
+++ b/types/auto-import.d.ts
@@ -0,0 +1,257 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// noinspection JSUnusedGlobalSymbols
+// Generated by unplugin-auto-import
+export {}
+declare global {
+ const EffectScope: typeof import('vue')['EffectScope']
+ const computed: typeof import('vue')['computed']
+ const createApp: typeof import('vue')['createApp']
+ const customRef: typeof import('vue')['customRef']
+ const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
+ const defineComponent: typeof import('vue')['defineComponent']
+ const effectScope: typeof import('vue')['effectScope']
+ const getCurrentInstance: typeof import('vue')['getCurrentInstance']
+ const getCurrentScope: typeof import('vue')['getCurrentScope']
+ const h: typeof import('vue')['h']
+ const inject: typeof import('vue')['inject']
+ const isProxy: typeof import('vue')['isProxy']
+ const isReactive: typeof import('vue')['isReactive']
+ const isReadonly: typeof import('vue')['isReadonly']
+ const isRef: typeof import('vue')['isRef']
+ const markRaw: typeof import('vue')['markRaw']
+ const nextTick: typeof import('vue')['nextTick']
+ const onActivated: typeof import('vue')['onActivated']
+ const onAddToFavorites: typeof import('@dcloudio/uni-app')['onAddToFavorites']
+ const onBackPress: typeof import('@dcloudio/uni-app')['onBackPress']
+ const onBeforeMount: typeof import('vue')['onBeforeMount']
+ const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
+ const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
+ const onDeactivated: typeof import('vue')['onDeactivated']
+ const onError: typeof import('@dcloudio/uni-app')['onError']
+ const onErrorCaptured: typeof import('vue')['onErrorCaptured']
+ const onHide: typeof import('@dcloudio/uni-app')['onHide']
+ const onLaunch: typeof import('@dcloudio/uni-app')['onLaunch']
+ const onLoad: typeof import('@dcloudio/uni-app')['onLoad']
+ const onMounted: typeof import('vue')['onMounted']
+ const onNavigationBarButtonTap: typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']
+ const onNavigationBarSearchInputChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']
+ const onNavigationBarSearchInputClicked: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']
+ const onNavigationBarSearchInputConfirmed: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']
+ const onNavigationBarSearchInputFocusChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']
+ const onPageNotFound: typeof import('@dcloudio/uni-app')['onPageNotFound']
+ const onPageScroll: typeof import('@dcloudio/uni-app')['onPageScroll']
+ const onPullDownRefresh: typeof import('@dcloudio/uni-app')['onPullDownRefresh']
+ const onReachBottom: typeof import('@dcloudio/uni-app')['onReachBottom']
+ const onReady: typeof import('@dcloudio/uni-app')['onReady']
+ const onRenderTracked: typeof import('vue')['onRenderTracked']
+ const onRenderTriggered: typeof import('vue')['onRenderTriggered']
+ const onResize: typeof import('@dcloudio/uni-app')['onResize']
+ const onScopeDispose: typeof import('vue')['onScopeDispose']
+ const onServerPrefetch: typeof import('vue')['onServerPrefetch']
+ const onShareAppMessage: typeof import('@dcloudio/uni-app')['onShareAppMessage']
+ const onShareTimeline: typeof import('@dcloudio/uni-app')['onShareTimeline']
+ const onShow: typeof import('@dcloudio/uni-app')['onShow']
+ const onTabItemTap: typeof import('@dcloudio/uni-app')['onTabItemTap']
+ const onThemeChange: typeof import('@dcloudio/uni-app')['onThemeChange']
+ const onUnhandledRejection: typeof import('@dcloudio/uni-app')['onUnhandledRejection']
+ const onUnload: typeof import('@dcloudio/uni-app')['onUnload']
+ const onUnmounted: typeof import('vue')['onUnmounted']
+ const onUpdated: typeof import('vue')['onUpdated']
+ const provide: typeof import('vue')['provide']
+ const reactive: typeof import('vue')['reactive']
+ const readonly: typeof import('vue')['readonly']
+ const ref: typeof import('vue')['ref']
+ const resolveComponent: typeof import('vue')['resolveComponent']
+ const shallowReactive: typeof import('vue')['shallowReactive']
+ const shallowReadonly: typeof import('vue')['shallowReadonly']
+ const shallowRef: typeof import('vue')['shallowRef']
+ const toRaw: typeof import('vue')['toRaw']
+ const toRef: typeof import('vue')['toRef']
+ const toRefs: typeof import('vue')['toRefs']
+ const toValue: typeof import('vue')['toValue']
+ const triggerRef: typeof import('vue')['triggerRef']
+ const unref: typeof import('vue')['unref']
+ const useAttrs: typeof import('vue')['useAttrs']
+ const useCssModule: typeof import('vue')['useCssModule']
+ const useCssVars: typeof import('vue')['useCssVars']
+ const useNavbarWeixin: typeof import('../hooks/useNavbarWeixin')['default']
+ const useSlots: typeof import('vue')['useSlots']
+ const watch: typeof import('vue')['watch']
+ const watchEffect: typeof import('vue')['watchEffect']
+ const watchPostEffect: typeof import('vue')['watchPostEffect']
+ const watchSyncEffect: typeof import('vue')['watchSyncEffect']
+}
+// for type re-export
+declare global {
+ // @ts-ignore
+ export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
+ import('vue')
+}
+// for vue template auto import
+import { UnwrapRef } from 'vue'
+declare module 'vue' {
+ interface GlobalComponents {}
+ interface ComponentCustomProperties {
+ readonly EffectScope: UnwrapRef
+ readonly computed: UnwrapRef
+ readonly createApp: UnwrapRef
+ readonly customRef: UnwrapRef
+ readonly defineAsyncComponent: UnwrapRef
+ readonly defineComponent: UnwrapRef
+ readonly effectScope: UnwrapRef
+ readonly getCurrentInstance: UnwrapRef
+ readonly getCurrentScope: UnwrapRef
+ readonly h: UnwrapRef
+ readonly inject: UnwrapRef
+ readonly isProxy: UnwrapRef
+ readonly isReactive: UnwrapRef
+ readonly isReadonly: UnwrapRef
+ readonly isRef: UnwrapRef
+ readonly markRaw: UnwrapRef
+ readonly nextTick: UnwrapRef
+ readonly onActivated: UnwrapRef
+ readonly onAddToFavorites: UnwrapRef
+ readonly onBackPress: UnwrapRef
+ readonly onBeforeMount: UnwrapRef
+ readonly onBeforeUnmount: UnwrapRef
+ readonly onBeforeUpdate: UnwrapRef
+ readonly onDeactivated: UnwrapRef
+ readonly onError: UnwrapRef
+ readonly onErrorCaptured: UnwrapRef
+ readonly onHide: UnwrapRef
+ readonly onLaunch: UnwrapRef
+ readonly onLoad: UnwrapRef
+ readonly onMounted: UnwrapRef
+ readonly onNavigationBarButtonTap: UnwrapRef
+ readonly onNavigationBarSearchInputChanged: UnwrapRef
+ readonly onNavigationBarSearchInputClicked: UnwrapRef
+ readonly onNavigationBarSearchInputConfirmed: UnwrapRef
+ readonly onNavigationBarSearchInputFocusChanged: UnwrapRef
+ readonly onPageNotFound: UnwrapRef
+ readonly onPageScroll: UnwrapRef
+ readonly onPullDownRefresh: UnwrapRef
+ readonly onReachBottom: UnwrapRef
+ readonly onReady: UnwrapRef
+ readonly onRenderTracked: UnwrapRef
+ readonly onRenderTriggered: UnwrapRef
+ readonly onResize: UnwrapRef
+ readonly onScopeDispose: UnwrapRef
+ readonly onServerPrefetch: UnwrapRef
+ readonly onShareAppMessage: UnwrapRef
+ readonly onShareTimeline: UnwrapRef
+ readonly onShow: UnwrapRef
+ readonly onTabItemTap: UnwrapRef
+ readonly onThemeChange: UnwrapRef
+ readonly onUnhandledRejection: UnwrapRef
+ readonly onUnload: UnwrapRef
+ readonly onUnmounted: UnwrapRef
+ readonly onUpdated: UnwrapRef
+ readonly provide: UnwrapRef
+ readonly reactive: UnwrapRef
+ readonly readonly: UnwrapRef
+ readonly ref: UnwrapRef
+ readonly resolveComponent: UnwrapRef
+ readonly shallowReactive: UnwrapRef
+ readonly shallowReadonly: UnwrapRef
+ readonly shallowRef: UnwrapRef
+ readonly toRaw: UnwrapRef
+ readonly toRef: UnwrapRef
+ readonly toRefs: UnwrapRef
+ readonly toValue: UnwrapRef
+ readonly triggerRef: UnwrapRef
+ readonly unref: UnwrapRef
+ readonly useAttrs: UnwrapRef
+ readonly useCssModule: UnwrapRef
+ readonly useCssVars: UnwrapRef
+ readonly useNavbarWeixin: UnwrapRef
+ readonly useSlots: UnwrapRef
+ readonly watch: UnwrapRef
+ readonly watchEffect: UnwrapRef
+ readonly watchPostEffect: UnwrapRef
+ readonly watchSyncEffect: UnwrapRef
+ }
+}
+declare module '@vue/runtime-core' {
+ interface GlobalComponents {}
+ interface ComponentCustomProperties {
+ readonly EffectScope: UnwrapRef
+ readonly computed: UnwrapRef
+ readonly createApp: UnwrapRef
+ readonly customRef: UnwrapRef
+ readonly defineAsyncComponent: UnwrapRef
+ readonly defineComponent: UnwrapRef
+ readonly effectScope: UnwrapRef
+ readonly getCurrentInstance: UnwrapRef
+ readonly getCurrentScope: UnwrapRef
+ readonly h: UnwrapRef
+ readonly inject: UnwrapRef
+ readonly isProxy: UnwrapRef
+ readonly isReactive: UnwrapRef
+ readonly isReadonly: UnwrapRef
+ readonly isRef: UnwrapRef
+ readonly markRaw: UnwrapRef
+ readonly nextTick: UnwrapRef
+ readonly onActivated: UnwrapRef
+ readonly onAddToFavorites: UnwrapRef
+ readonly onBackPress: UnwrapRef
+ readonly onBeforeMount: UnwrapRef
+ readonly onBeforeUnmount: UnwrapRef
+ readonly onBeforeUpdate: UnwrapRef
+ readonly onDeactivated: UnwrapRef
+ readonly onError: UnwrapRef
+ readonly onErrorCaptured: UnwrapRef
+ readonly onHide: UnwrapRef
+ readonly onLaunch: UnwrapRef
+ readonly onLoad: UnwrapRef
+ readonly onMounted: UnwrapRef
+ readonly onNavigationBarButtonTap: UnwrapRef
+ readonly onNavigationBarSearchInputChanged: UnwrapRef
+ readonly onNavigationBarSearchInputClicked: UnwrapRef
+ readonly onNavigationBarSearchInputConfirmed: UnwrapRef
+ readonly onNavigationBarSearchInputFocusChanged: UnwrapRef
+ readonly onPageNotFound: UnwrapRef
+ readonly onPageScroll: UnwrapRef
+ readonly onPullDownRefresh: UnwrapRef
+ readonly onReachBottom: UnwrapRef
+ readonly onReady: UnwrapRef
+ readonly onRenderTracked: UnwrapRef
+ readonly onRenderTriggered: UnwrapRef
+ readonly onResize: UnwrapRef
+ readonly onScopeDispose: UnwrapRef
+ readonly onServerPrefetch: UnwrapRef
+ readonly onShareAppMessage: UnwrapRef
+ readonly onShareTimeline: UnwrapRef
+ readonly onShow: UnwrapRef
+ readonly onTabItemTap: UnwrapRef
+ readonly onThemeChange: UnwrapRef
+ readonly onUnhandledRejection: UnwrapRef
+ readonly onUnload: UnwrapRef
+ readonly onUnmounted: UnwrapRef
+ readonly onUpdated: UnwrapRef
+ readonly provide: UnwrapRef
+ readonly reactive: UnwrapRef
+ readonly readonly: UnwrapRef
+ readonly ref: UnwrapRef
+ readonly resolveComponent: UnwrapRef
+ readonly shallowReactive: UnwrapRef
+ readonly shallowReadonly: UnwrapRef
+ readonly shallowRef: UnwrapRef
+ readonly toRaw: UnwrapRef
+ readonly toRef: UnwrapRef
+ readonly toRefs: UnwrapRef
+ readonly toValue: UnwrapRef
+ readonly triggerRef: UnwrapRef
+ readonly unref: UnwrapRef
+ readonly useAttrs: UnwrapRef
+ readonly useCssModule: UnwrapRef
+ readonly useCssVars: UnwrapRef
+ readonly useNavbarWeixin: UnwrapRef
+ readonly useSlots: UnwrapRef
+ readonly watch: UnwrapRef
+ readonly watchEffect: UnwrapRef
+ readonly watchPostEffect: UnwrapRef
+ readonly watchSyncEffect: UnwrapRef
+ }
+}
diff --git a/typing.ts b/typing.ts
new file mode 100644
index 0000000..c0cb8ee
--- /dev/null
+++ b/typing.ts
@@ -0,0 +1,31 @@
+/* eslint-disable no-unused-vars */
+/* eslint-disable @typescript-eslint/no-unused-vars */
+// 全局要用的类型放到这里
+
+type IResData = {
+ code: number
+ msg: string
+ result: T
+}
+
+// uni.uploadFile文件上传参数
+type IUniUploadFileOptions = {
+ file?: File
+ files?: UniApp.UploadFileOptionFiles[]
+ filePath?: string
+ name?: string
+ formData?: any
+}
+
+type IUserInfo = {
+ nickname?: string
+ avatar?: string
+ /** 微信的 openid,非微信没有这个字段 */
+ openid?: string
+ token?: string
+}
+
+enum TestEnum {
+ A = 'a',
+ B = 'b',
+}
diff --git a/uni.scss b/uni.scss
new file mode 100644
index 0000000..6c5bb60
--- /dev/null
+++ b/uni.scss
@@ -0,0 +1,76 @@
+/**
+ * 这里是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: #ffffff;
+$uni-bg-color-grey: #f8f8f8;
+$uni-bg-color-hover: #f1f1f1; //点击状态颜色
+$uni-bg-color-mask: rgba(0, 0, 0, 0.4); //遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color: #c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm: 12px;
+$uni-font-size-base: 14px;
+$uni-font-size-lg: 16px;
+
+/* 图片尺寸 */
+$uni-img-size-sm: 20px;
+$uni-img-size-base: 26px;
+$uni-img-size-lg: 40px;
+
+/* 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: #555555; // 二级标题颜色
+$uni-font-size-subtitle: 26px;
+$uni-color-paragraph: #3f536e; // 文章段落颜色
+$uni-font-size-paragraph: 15px;
diff --git a/uno.config.ts b/uno.config.ts
new file mode 100644
index 0000000..0fc1288
--- /dev/null
+++ b/uno.config.ts
@@ -0,0 +1,96 @@
+// uno.config.ts
+import {
+ type Preset,
+ defineConfig,
+ presetUno,
+ presetAttributify,
+ presetIcons,
+ transformerDirectives,
+ transformerVariantGroup,
+} from 'unocss'
+
+import { presetApplet, presetRemRpx, transformerAttributify } from 'unocss-applet'
+
+// @see https://unocss.dev/presets/legacy-compat
+import { presetLegacyCompat } from '@unocss/preset-legacy-compat'
+
+const isMp = process.env?.UNI_PLATFORM?.startsWith('mp') ?? false
+
+const presets: Preset[] = []
+if (isMp) {
+ // 使用小程序预设
+ presets.push(presetApplet(), presetRemRpx())
+} else {
+ presets.push(
+ // 非小程序用官方预设
+ presetUno(),
+ // 支持css class属性化
+ presetAttributify(),
+ )
+}
+export default defineConfig({
+ presets: [
+ ...presets,
+ // 支持图标,需要搭配图标库,eg: @iconify-json/carbon, 使用 ``
+ presetIcons({
+ scale: 1.2,
+ warn: true,
+ extraProperties: {
+ display: 'inline-block',
+ 'vertical-align': 'middle',
+ },
+ // 可以指定需要的图标集合
+ collections: {
+ carbon: () => import('@iconify-json/carbon').then((i) => i.icons as any),
+ },
+ }),
+ // 将颜色函数 (rgb()和hsl()) 从空格分隔转换为逗号分隔,更好的兼容性app端,example:
+ // `rgb(255 0 0)` -> `rgb(255, 0, 0)`
+ // `rgba(255 0 0 / 0.5)` -> `rgba(255, 0, 0, 0.5)`
+ presetLegacyCompat({
+ commaStyleColorFunction: true,
+ }) as Preset,
+ ],
+ /**
+ * 自定义快捷语句
+ * @see https://github.com/unocss/unocss#shortcuts
+ */
+ shortcuts: [['center', 'flex justify-center items-center']],
+ transformers: [
+ // 启用 @apply 功能
+ transformerDirectives(),
+ // 启用 () 分组功能
+ // 支持css class组合,eg: `测试 unocss
`
+ transformerVariantGroup(),
+ // Don't change the following order
+ transformerAttributify({
+ // 解决与第三方框架样式冲突问题
+ prefixedOnly: true,
+ prefix: 'fg',
+ }),
+ ],
+ 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)' }],
+ ],
+})
+
+/**
+ * 最终这一套组合下来会得到:
+ * mp 里面:mt-4 => margin-top: 32rpx == 16px
+ * h5 里面:mt-4 => margin-top: 1rem == 16px
+ *
+ * 另外,我们还可以推算出 UnoCSS 单位与设计稿差别4倍。
+ * 375 * 4 = 1500,把设计稿设置为1500,那么设计稿里多少px,unocss就写多少述职。
+ * 举个例子,设计稿显示某元素宽度100px,就写w-100即可。
+ *
+ * 如果是传统方式写样式,则推荐设计稿设置为 750,这样设计稿1px,代码写1rpx。
+ * rpx是响应式的,可以让不同设备的屏幕显示效果保持一致。
+ */
diff --git a/unocss.css b/unocss.css
new file mode 100644
index 0000000..9beb715
--- /dev/null
+++ b/unocss.css
@@ -0,0 +1,45 @@
+/* layer: preflights */
+page,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}
+/* layer: icons */
+.i-carbon-car{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' display='inline-block' vertical-align='middle' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m29.338 15.934l-7.732-2.779l-3.232-4.058A2.99 2.99 0 0 0 16.054 8H8.058a2.998 2.998 0 0 0-2.48 1.312l-2.712 3.983A4.988 4.988 0 0 0 2 16.107V24a1 1 0 0 0 1 1h2.142a3.98 3.98 0 0 0 7.716 0h6.284a3.98 3.98 0 0 0 7.716 0H29a1 1 0 0 0 1-1v-7.125a1 1 0 0 0-.662-.941M9 26a2 2 0 1 1 2-2a2.003 2.003 0 0 1-2 2m14 0a2 2 0 1 1 2-2a2.003 2.003 0 0 1-2 2m5-3h-1.142a3.98 3.98 0 0 0-7.716 0h-6.284a3.98 3.98 0 0 0-7.716 0H4v-6.893a2.998 2.998 0 0 1 .52-1.688l2.711-3.981A1 1 0 0 1 8.058 10h7.996a.993.993 0 0 1 .764.355l3.4 4.268a1 1 0 0 0 .444.318L28 17.578Z'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;display:inline-block;vertical-align:middle;width:1.2em;height:1.2em;}
+.i-carbon-sun{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' display='inline-block' vertical-align='middle' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6M5.394 6.813L6.81 5.399l3.505 3.506L8.9 10.319zM2 15.005h5v2H2zm3.394 10.193L8.9 21.692l1.414 1.414l-3.505 3.506zM15 25.005h2v5h-2zm6.687-1.9l1.414-1.414l3.506 3.506l-1.414 1.414zm3.313-8.1h5v2h-5zm-3.313-6.101l3.506-3.506l1.414 1.414l-3.506 3.506zM15 2.005h2v5h-2z'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;display:inline-block;vertical-align:middle;width:1.2em;height:1.2em;}
+.i-carbon-user-avatar{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' display='inline-block' vertical-align='middle' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M16 8a5 5 0 1 0 5 5a5 5 0 0 0-5-5m0 8a3 3 0 1 1 3-3a3.003 3.003 0 0 1-3 3'/%3E%3Cpath fill='currentColor' d='M16 2a14 14 0 1 0 14 14A14.016 14.016 0 0 0 16 2m-6 24.377V25a3.003 3.003 0 0 1 3-3h6a3.003 3.003 0 0 1 3 3v1.377a11.899 11.899 0 0 1-12 0m13.993-1.451A5.002 5.002 0 0 0 19 20h-6a5.002 5.002 0 0 0-4.992 4.926a12 12 0 1 1 15.985 0'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;display:inline-block;vertical-align:middle;width:1.2em;height:1.2em;}
+/* layer: applet_shortcuts */
+.dark .dark_a_i-carbon-moon{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' display='inline-block' vertical-align='middle' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;display:inline-block;vertical-align:middle;width:1.2em;height:1.2em;}
+/* layer: default */
+.m-8{margin:64rpx;}
+.m-auto{margin:auto;}
+.mx-auto{margin-left:auto;margin-right:auto;}
+.mb-2{margin-bottom:16rpx;}
+.mb-4{margin-bottom:32rpx;}
+.mb-8{margin-bottom:64rpx;}
+.ml-2{margin-left:16rpx;}
+.mr-2{margin-right:16rpx;}
+.mt-12{margin-top:96rpx;}
+.mt-2{margin-top:16rpx;}
+.mt-4{margin-top:32rpx;}
+.mt-8{margin-top:64rpx;}
+.block{display:block;}
+.h-28{height:224rpx;}
+.max-w-100{max-width:800rpx;}
+.w-20{width:160rpx;}
+.w-28{width:224rpx;}
+.flex{display:flex;}
+.items-center{align-items:center;}
+.justify-center{justify-content:center;}
+.overflow-hidden{overflow:hidden;}
+.bg-white{--un-bg-opacity:1;background-color:rgb(255 255 255 / var(--un-bg-opacity));}
+.p-4{padding:32rpx;}
+.px,
+.px-4{padding-left:32rpx;padding-right:32rpx;}
+.pt-2{padding-top:16rpx;}
+.text-center{text-align:center;}
+.text-justify{text-align:justify;}
+.indent{text-indent:48rpx;}
+.text-2xl{font-size:48rpx;line-height:64rpx;}
+.text-4{font-size:32rpx;}
+.text-4xl{font-size:72rpx;line-height:80rpx;}
+.text-blue-500{--un-text-opacity:1;color:rgb(59 130 246 / var(--un-text-opacity));}
+.text-green-400{--un-text-opacity:1;color:rgb(74 222 128 / var(--un-text-opacity));}
+.text-red{--un-text-opacity:1;color:rgb(248 113 113 / var(--un-text-opacity));}
+.leading-8{line-height:64rpx;}
\ No newline at end of file
diff --git a/utils/http.ts b/utils/http.ts
new file mode 100644
index 0000000..c34a6d3
--- /dev/null
+++ b/utils/http.ts
@@ -0,0 +1,81 @@
+import { CustomRequestOptions } from '@/interceptors/request'
+
+export const 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 {
+ // 其他错误 -> 根据后端错误信息轻提示
+ uni.showToast({
+ icon: 'none',
+ title: (res.data as IResData).msg || '请求错误',
+ })
+ reject(res)
+ }
+ },
+ // 响应失败
+ fail(err) {
+ uni.showToast({
+ icon: 'none',
+ title: '网络错误,换个网络试试',
+ })
+ reject(err)
+ },
+ })
+ })
+}
+
+// uni.uploadFile封装
+export const uniFileUpload = (options: CustomRequestOptions) => {
+ // 1. 返回 Promise 对象
+ return new Promise>((resolve, reject) => {
+ uni.uploadFile({
+ ...options,
+ // 响应成功
+ success(res) {
+ // 状态码 2xx,参考 axios 的设计
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ // 文件上传接口的rea.data的类型为string,这里转一下
+ const resData = JSON.parse(res.data) as IResData
+ resolve(resData)
+ } else if (res.statusCode === 401) {
+ // 401错误 -> 清理用户信息,跳转到登录页
+ // userStore.clearUserInfo()
+ // uni.navigateTo({ url: '/pages/login/login' })
+ reject(res)
+ } else {
+ // 其他错误 -> 根据后端错误信息轻提示
+ uni.showToast({
+ icon: 'none',
+ title: '文件上传错误',
+ })
+ reject(res)
+ }
+ },
+ // 响应失败
+ fail(err) {
+ uni.showToast({
+ icon: 'none',
+ title: '网络错误,换个网络试试',
+ })
+ reject(err)
+ },
+ })
+ })
+}
diff --git a/utils/index.ts b/utils/index.ts
new file mode 100644
index 0000000..1ee9ac0
--- /dev/null
+++ b/utils/index.ts
@@ -0,0 +1,110 @@
+import pagesJson from '@/pages.json'
+
+console.log(pagesJson)
+
+/** 判断当前页面是否是tabbar页 */
+export const getIsTabbar = () => {
+ if (!Object.keys(pagesJson).includes('tabBar')) {
+ return false
+ }
+ const pages = getCurrentPages()
+ const currPath = pages.at(-1).route
+ return !!pagesJson.tabBar.list.find((e) => e.pagePath === currPath)
+}
+
+/**
+ * 获取当前页面路由的 path 路劲和 redirectPath 路径
+ * path 如 ‘/pages/login/index’
+ * redirectPath 如 ‘/pages/demo/base/route-interceptor’
+ */
+export const currRoute = () => {
+ const pages = getCurrentPages()
+ console.log('pages:', pages)
+ const currRoute = (pages.at(-1) 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:', (pages.at(-1) 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)
+}
+
+const 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 const getUrlObj = (url: string) => {
+ const [path, queryStr] = url.split('?')
+ console.log(path, queryStr)
+
+ 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 const getAllPages = (key = 'needLogin') => {
+ // 这里处理主包
+ const pages = [
+ ...pagesJson.pages
+ .filter((page) => !key || page[key])
+ .map((page) => ({
+ ...page,
+ path: `/${page.path}`,
+ })),
+ ]
+ // 这里处理分包
+ const subPages: any[] = []
+ pagesJson.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 = [...pages, ...subPages]
+ console.log(`getAllPages by ${key} result: `, result)
+ return result
+}
+
+/**
+ * 得到所有的需要登录的pages,包括主包和分包的
+ * 只得到 path 数组
+ */
+export const getNeedLoginPages = (): string[] => getAllPages('needLogin').map((page) => page.path)
+
+/**
+ * 得到所有的需要登录的pages,包括主包和分包的
+ * 只得到 path 数组
+ */
+export const needLoginPages: string[] = getAllPages('needLogin').map((page) => page.path)
+
+export const getArrElementByIdx = (arr: any[], index: number) => {
+ if (index < 0) return arr[arr.length + index]
+ if (index >= arr.length) return undefined
+ return arr[index]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..cfb1f19
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,118 @@
+import path from 'node:path'
+import dayjs from 'dayjs'
+import { defineConfig, loadEnv } from 'vite'
+import Uni from '@dcloudio/vite-plugin-uni'
+// @see https://uni-helper.js.org/vite-plugin-uni-pages
+import UniPages from '@uni-helper/vite-plugin-uni-pages'
+// @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-platform
+// 需要与 @uni-helper/vite-plugin-uni-pages 插件一起使用
+// import UniPlatform from '@uni-helper/vite-plugin-uni-platform'
+// @see https://github.com/uni-helper/vite-plugin-uni-manifest
+import UniManifest from '@uni-helper/vite-plugin-uni-manifest'
+// @see https://unocss.dev/
+import UnoCSS from 'unocss/vite'
+// import autoprefixer from 'autoprefixer'
+import AutoImport from 'unplugin-auto-import/vite'
+// import viteCompression from 'vite-plugin-compression'
+import ViteRestart from 'vite-plugin-restart'
+
+// https://vitejs.dev/config/
+export default ({ command, mode }) => {
+ // console.log(mode === process.env.NODE_ENV) // true
+
+ // mode: 区分生产环境还是开发环境
+ console.log('command, mode -> ', command, mode)
+ // dev 和 build 命令可以分别使用 .env.development 和 .env.production 的环境变量
+
+ // process.env.VITE_ROOT_DIR: 获取当前文件的目录跟地址
+ // loadEnv(): 返回当前环境env文件中额外定义的变量
+ const env = loadEnv(mode, path.resolve(process.env.VITE_ROOT_DIR, 'env'))
+ const { VITE_ROOT_DIR, VITE_APP_PORT, VITE_DELETE_CONSOLE, VITE_SHOW_SOURCEMAP } = env
+ console.log('环境变量 env -> ', env)
+ const { UNI_PLATFORM } = process.env
+ console.log('UNI_PLATFORM -> ', UNI_PLATFORM) // 得到 mp-weixin, h5, app 等
+
+ return defineConfig({
+ // root: VITE_ROOT_DIR,
+ envDir: './env', // 自定义env目录
+ plugins: [
+ UniPages({
+ dts: path.join(VITE_ROOT_DIR, 'types/uni-pages.d.ts'),
+ homePage: 'pages/index/index', // 设置默认路由入口
+ dir: 'pages', // 主目录
+ subPackages: ['pages-sub'], // 子目录,是个数组,可以配置多个,但是不能为pages里面的目录
+ outDir: './',
+ exclude: ['**/components/**/**.*'],
+ routeBlockLang: 'json5', // 虽然设了默认值,但是vue文件还是要加上 lang="json5", 这样才能很好地格式化
+ }),
+ UniLayouts({
+ layoutDir: 'layouts',
+ layout: '',
+ cwd: VITE_ROOT_DIR,
+ }),
+ // UniPlatform(),
+ UniManifest(),
+ // UniXXX 需要在 Uni 之前引入
+ Uni(),
+ UnoCSS(),
+ AutoImport({
+ imports: ['vue', 'uni-app'],
+ dts: 'types/auto-import.d.ts',
+ dirs: ['hooks'], // 自动导入 hooks
+ eslintrc: { enabled: true },
+ vueTemplate: true, // default false
+ }),
+
+ // viteCompression(),
+ ViteRestart({
+ // 通过这个插件,在修改vite.config.js文件则不需要重新运行也生效配置
+ restart: ['vite.config.js'],
+ }),
+ // h5环境增加编译时间
+ UNI_PLATFORM === 'h5' && {
+ name: 'html-transform',
+ transformIndexHtml(html) {
+ return html.replace('%BUILD_DATE%', dayjs().format('YYYY-MM-DD HH:mm:ss'))
+ },
+ },
+ ],
+ define: {
+ __UNI_PLATFORM__: JSON.stringify(UNI_PLATFORM),
+ },
+ css: {
+ postcss: {
+ plugins: [
+ // autoprefixer({
+ // // 指定目标浏览器
+ // overrideBrowserslist: ['> 1%', 'last 2 versions'],
+ // }),
+ ],
+ },
+ },
+
+ resolve: {
+ alias: {
+ '@': path.join(VITE_ROOT_DIR, ''),
+ },
+ },
+ server: {
+ host: '0.0.0.0',
+ hmr: true,
+ port: Number.parseInt(VITE_APP_PORT, 10),
+ },
+ build: {
+ // 方便非h5端调试
+ sourcemap: VITE_SHOW_SOURCEMAP === 'true', // 默认是false
+ target: 'es6',
+ minify: 'terser',
+ terserOptions: {
+ compress: {
+ drop_console: VITE_DELETE_CONSOLE === 'true',
+ drop_debugger: true,
+ },
+ },
+ },
+ })
+}