This commit is contained in:
faiz 2024-10-18 18:09:15 +08:00
commit 9061da3a29
71 changed files with 2591 additions and 0 deletions

17
.editorconfig Normal file
View File

@ -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

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
uni_modules/

View File

@ -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
}
}

94
.eslintrc.cjs Normal file
View File

@ -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,
},
}

43
.gitignore vendored Normal file
View File

@ -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 . ## 针对所有文件

4
.husky/commit-msg Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install commitlint --edit

4
.husky/pre-commit Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install -- lint-staged

6
.npmrc Normal file
View File

@ -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

2
.prettierignore Normal file
View File

@ -0,0 +1,2 @@
# unplugin-auto-import 生成的类型文件,每次提交都改变,所以加入这里吧,与 .gitignore 配合使用
auto-import.d.ts

19
.prettierrc.cjs Normal file
View File

@ -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',
},
},
],
}

1
.stylelintignore Normal file
View File

@ -0,0 +1 @@
uni_modules/

57
.stylelintrc.cjs Normal file
View File

@ -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 文件中的<style>标签内的样式
{
files: ['**/*.{vue,html}'],
customSyntax: 'postcss-html',
},
{
files: ['**/*.{css,scss}'],
customSyntax: 'postcss-scss',
},
],
// 自定义规则
rules: {
'prettier/prettier': true,
// 允许 global 、export 、v-deep等伪类
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global', 'export', 'v-deep', 'deep'],
},
],
'unit-no-unknown': [
true,
{
ignoreUnits: ['rpx'],
},
],
// 处理小程序page标签不认识的问题
'selector-type-no-unknown': [
true,
{
ignoreTypes: ['page'],
},
],
'comment-empty-line-before': 'never', // never|always|always-multi-line|never-multi-line
'custom-property-empty-line-before': 'never',
'no-empty-source': null,
'comment-no-empty': null,
'no-duplicate-selectors': null,
'scss/comment-no-empty': null,
'selector-class-pattern': null,
},
}

18
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,18 @@
{
"recommendations": [
"vue.volar",
"stylelint.vscode-stylelint",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"antfu.unocss",
"antfu.iconify",
"evils.uniapp-vscode",
"mrmaoddxxaa.create-uniapp-view",
"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",
"uni-helper.uni-cloud-snippets-vscode"
]
}

56
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,56 @@
{
// prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",
//
"editor.formatOnSave": true,
//
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
// stylelint
"stylelint.validate": ["css", "scss", "vue", "html"], // package.jsonscripts
"stylelint.enable": true,
"css.validate": false,
"less.validate": false,
"scss.validate": false,
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"[dotenv]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
//
"files.associations": {
"pages.json": "jsonc", // pages.json
"manifest.json": "jsonc" // manifest.json
},
"cSpell.words": [
"climblee",
"commitlint",
"dcloudio",
"feige",
"qrcode",
"refresherrefresh",
"safeareainsets",
"scrolltolower",
"tabbar",
"unibest",
"uvui",
"WechatMiniprogram"
],
"typescript.tsdk": "node_modules\\typescript\\lib",
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

48
.vscode/vue3.code-snippets vendored Normal file
View File

@ -0,0 +1,48 @@
{
// 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": [
"<template>",
" <view class=\"\">$2</view>",
"</template>\n",
"<script lang=\"ts\" setup>",
"//$3",
"</script>\n",
"<style lang=\"scss\" scoped>",
"//$4",
"</style>\n",
],
},
"Print unibest style": {
"scope": "vue",
"prefix": "st",
"body": ["<style lang=\"scss\" scoped>", "//", "</style>\n"],
},
"Print unibest script": {
"scope": "vue",
"prefix": "sc",
"body": ["<script lang=\"ts\" setup>", "//$3", "</script>\n"],
},
"Print unibest template": {
"scope": "vue",
"prefix": "te",
"body": ["<template>", " <view class=\"\">$1</view>", "</template>\n"],
},
}

17
App.vue Normal file
View File

@ -0,0 +1,17 @@
<script>
export default {
onLaunch() {
console.log('App Launch')
},
onShow() {
console.log('App Show')
},
onHide() {
console.log('App Hide')
},
}
</script>
<style>
/* 每个页面公共css */
</style>

21
LICENSE Normal file
View File

@ -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.

115
README.md Normal file
View File

@ -0,0 +1,115 @@
<p align="center">
<a href="https://github.com/codercup/unibest">
<img width="160" src="./static/logo.svg">
</a>
</p>
<h1 align="center">
<a href="https://github.com/codercup/unibest" target="_blank">unibest - 最好的 uniapp 跨端解决方案</a>
</h1>
<div align="center">
[![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)
</div>
`unibest` 是一个 uniapp 跨端解决方案,由 `uniapp` + `Vue3` + `Ts` + `Vite4` + `UnoCss` + `VSCode`(可选 `webstorm`) 实现。它使用了最新的前端技术栈,可以通过命令行方式运行 `web`、`小程序` 和 `App`,同时也支持 `HBuilderX` 运行,当前版本为 `HBuilderX` 运行版本。如需通过命令行运行,请安装命令行版(<a href="https://github.com/codercup/unibest" target="_blank">unibest</a>)。
`unibest` 内置了 `约定式路由`、`layout布局`、`请求封装`、`请求拦截`、`登录拦截`、`UnoCSS`、`i18n多语言` 等基础功能,提供了 `代码提示`、`自动格式化`、`统一配置`、`代码片段` 等辅助功能,让你编写 `uniapp` 拥有 `best` 体验 `unibest 的由来`)。
![](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png)
<p align="center">
<a href="https://codercup.github.io/unibest/" target="_blank">📱 在线预览</a>
<span style="margin:0 10px;">|</span>
<a href="https://codercup.github.io/unibest-docs/base/13-hbx" target="_blank">📖 阅读文档</a>
</p>
## ✨ 特性
- ⚡️ [Vue 3](https://github.com/vuejs/core), [Vite](https://github.com/vitejs/vite), [pnpm](https://pnpm.io/), [esbuild](https://github.com/evanw/esbuild) - 就是快!
- 🔥 最新语法 - `<script lang="ts" setup>` 语法
- 🎨 [UnoCSS](https://unocss.dev/) - 高性能且极具灵活性的即时原子化 CSS 引擎
- 😃 [UnoCSS Icons](https://unocss.dev/presets/icons) & [icones](https://icones.js.org/) - 海量图标供你选择
- 🍍 [pinia](https://pinia.vuejs.org/) & [pinia-plugin-persistedstate](https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/) - 全端适配的全局数据管理
- 🗂 `uni.request` 请求封装 - 一键引入,快捷使用
- 📦 [组件自动化加载](./src/components) - 可配置化的组件加载方式,轻松加载组件
- 📥 [API 自动加载](https://github.com/antfu/unplugin-auto-import) - 直接使用 Composition API 无需引入
- 🎉 `v3` Code Snippets 加快你的页面生成
- 🦾 [TypeScript](https://www.typescriptlang.org/) & [ESLint](https://eslint.org/) & [stylelint](https://stylelint.io/) - 保证代码质量
- 🌈 [husky](https://typicode.github.io/husky/) & [lint-staged](https://github.com/lint-staged/lint-staged) + [commitlint](https://commitlint.js.org/) - 保证代码提交质量
- 💡 `ES6 import` 自动排序,`css 属性` 自动排序,增强编码一致性
- 🖥 `多环境` 配置分开,想则怎么配置就怎么配置
<p align="center">
<a href="https://cn.vuejs.org/" target="_blank">
<img src="https://img.shields.io/badge/-Vue3-34495e?logo=vue.js" />
</a>
<a href="https://cn.vitejs.dev/" target="_blank">
<img src="https://img.shields.io/badge/-Vite4-646cff?logo=vite&logoColor=white" />
</a>
<a href="https://www.typescriptlang.org/zh/" target="_blank">
<img src="https://img.shields.io/badge/-TypeScript5-blue?logo=typescript&logoColor=white" />
</a>
<a href="https://eslint.org/" target="_blank">
<img src="https://img.shields.io/badge/-ESLint8-4b32c3?logo=eslint&logoColor=white" />
</a>
<a href="https://stylelint.io/" target="_blank">
<img src="https://img.shields.io/badge/-StyleLint16-4b32c3?logo=stylelint&logoColor=white" />
</a>
<a href="https://pnpm.io/" target="_blank">
<img src="https://img.shields.io/badge/-pnpm8-F69220?logo=pnpm&logoColor=white" />
</a>
<a href="https://unocss.dev/" target="_blank">
<img src="https://img.shields.io/badge/-UnoCSS-4d4d4d?logo=unocss" />
</a>
<a href="https://icones.js.org/" target="_blank">
<img src="https://img.shields.io/badge/-icones-1769aa?logo=Iconify" />
</a>
<a href="https://ext.dcloud.net.cn/plugin?id=8559" target="_blank">
<img src="https://svg.hamm.cn/badge.svg?key=Platform&value=uni-app"/>
</a>
</p>
## ⚙️ 环境
- node>=18
- pnpm>=7.30
## &#x1F4C2; 安装
```bash
# HBuilderX 模板,方便使用 uniCloud 云开发 (未来可以对接 uni-app x)
pnpm create unibest my-project -t hbx-base # hbx的base模板
pnpm create unibest my-project -t hbx-demo # hbx的demo模板包含所有的demo
# 执行 `pnpm i` 安装依赖
pnpm i
```
## 📦 使用 `HBuilderX` 打开
`HBuilderX` 运行(支持热更新)或 编译
## 📄 License
[MIT](https://opensource.org/license/mit/)
Copyright (c) 2024 菲鸽
## 贡献者
由仓库直接生成:
<a href="https://github.com/codercup/unibest-hbx/graphs/contributors">
<img src="https://contrib.rocks/image?repo=codercup/unibest-hbx" />
</a>

51
commitlint.config.cjs Normal file
View File

@ -0,0 +1,51 @@
// commitlint.config.cjs
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
// 'body-leading-blank': [2, 'always'], // 主体前有空行,默认就是 always
// 'footer-leading-blank': [2, 'always'], // 末行前有空行,默认就是 always
// 'header-max-length': [2, 'always', 108], // 首行最大长度,默认就是 always,72
// 'subject-empty': [2, 'never'], // 标题不可为空,默认就是 never
// 'type-empty': [2, 'never'], // 类型不可为空,默认就是 never
// 允许的类型
'type-enum': [
2,
'always',
[
'build', // 构造工具、外部依赖webpack、npm
'chore', // 不涉及 src、test 的其他修改(构建过程或辅助工具的变更)
'ci', // 修改项目继续集成流程TravisJenkinsGitLab CICircle等
'docs', // 文档
'feat', // 新增功能
'fix', // bug 修复
'perf', // 性能优化
'refactor', // 重构
'revert', // 回退
'style', // 代码风格(不影响代码含义)
'test', // 测试
// 下面几个是自定义新增的
'wip', // 开发中
'refine', // 小优化,没有到 refactor 的程度
],
],
},
}
// @see https://commitlint.js.org/#/reference-rules?id=type-enum
// 默认值为:
// [
// 'build',
// 'chore',
// 'ci',
// 'docs',
// 'feat',
// 'fix',
// 'perf',
// 'refactor',
// 'revert',
// 'style',
// 'test',
// ];

View File

@ -0,0 +1,11 @@
<template>
<view class="fly-content">
<view v-for="n in line" :key="n" class="h-10 leading-10 text-center">
很多内容这里是第{{ n }}
</view>
</view>
</template>
<script lang="ts" setup>
withDefaults(defineProps<{ line?: number }>(), { line: 10 })
</script>

View File

@ -0,0 +1,3 @@
<template>
<view class="text-green-500"> header </view>
</template>

View File

@ -0,0 +1,7 @@
# fly-login
点击“点击显示微信头像”按钮后,出现的半屏登录弹窗,可以在任意页面引入。
仿“掘金小册”小程序。
![掘金小册登录](screenshot.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,120 @@
<template>
<view class="fly-login" v-if="modelValue">
<view class="fly-login-mask" />
<view class="fly-login-content px-4">
<view class="font-bold h-16 leading-16">获取您的昵称头像</view>
<view
class="rounded-full bg-light-600 w-6 h-6 text-center absolute top-4 right-4"
@click="onClose"
>
<view class="i-carbon-close text-gray-700" />
</view>
<view
class="flex items-center h-16 leading-16 border-b-gray-400 border-b-solid border-[1rpx]"
>
<text class="mr-4 flex-shrink-0">头像</text>
<button
class="bg-transparent flex items-center after:b-none w-full h-12 leading-12"
open-type="chooseAvatar"
@chooseavatar="onChooseAvatar"
>
<image class="w-8 h-8 rounded-full" :src="avatarUrl"></image>
<text class="ml-auto i-carbon-chevron-right"></text>
</button>
</view>
<view
class="flex items-center h-16 leading-16 border-b-gray-400 border-b-solid border-1 mt-4"
>
<text class="mr-4 flex-shrink-0">昵称</text>
<input type="nickname" placeholder="请输入昵称" @change="onChange" @blur="onChange" />
</view>
<button
size="default"
type="default"
style="color: #fff; background-color: #1aad19; border-color: #1aad19"
class="text-center leading-12 w-40 my-4"
@click="onSubmit"
>
确定
</button>
</view>
</view>
</template>
<script lang="ts" setup>
import { useUserStore } from '@/store'
import defaultAvatarUrl from './defaultAvatar.png'
const emit = defineEmits(['update:modelValue'])
defineProps<{ modelValue: boolean }>()
const userStore = useUserStore()
const avatarUrl = ref(defaultAvatarUrl)
const nickname = ref('')
const onClose = () => {
emit('update:modelValue', false)
}
const onChooseAvatar = (e) => {
const { avatarUrl: url } = e.detail
avatarUrl.value = url
//
console.log(url)
}
const onChange = (e) => {
const { value } = e.detail
nickname.value = value
console.log(value)
}
const onSubmit = () => {
// 1
// 2
if (avatarUrl.value === defaultAvatarUrl) {
uni.showToast({
title: '请选择头像',
icon: 'none',
})
return
}
if (!nickname.value) {
uni.showToast({
title: '请填写昵称',
icon: 'none',
})
return
}
emit('update:modelValue', false)
console.log('保存用户信息')
userStore.setUserInfo({ nickname: nickname.value, avatar: avatarUrl.value })
}
</script>
<style lang="scss" scoped>
.fly-login {
position: fixed;
inset: 0;
.fly-login-mask {
position: fixed;
inset: 0;
background-color: rgb(0 0 0 / 30%);
}
.fly-login-content {
position: fixed;
right: 0;
bottom: var(--window-bottom);
left: 0;
background-color: #fff;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

View File

@ -0,0 +1,3 @@
# fly-navbar
建议本导航栏组件在设置 `"navigationStyle": "custom"` 的页面使用,目前支持微信小程序的页面滚动动画。

View File

@ -0,0 +1,71 @@
<template>
<!-- 自定义导航栏: 默认透明不可见, scroll-view 滚动到 50 时展示 -->
<view class="fly-navbar" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
<!-- 1/3多于1个页面用返回图标 -->
<navigator v-if="pages.length > 1" open-type="navigateBack" class="left-icon">
<view class="bg-gray-500/80 rounded-full w-8 h-8 flex items-center justify-center">
<button class="i-carbon-chevron-left text-white w-7 h-7"></button>
</view>
</navigator>
<!-- 2/3只有1个页面如果不是tabbar需要首页图标 -->
<!-- 这种情况一般出现在用户直接打开分享出去的详情页面或者使用redirectTo等API -->
<navigator
v-else-if="!isTabbar"
open-type="switchTab"
url="/pages/index/index"
class="left-icon"
>
<view class="bg-gray-500/80 rounded-full w-8 h-8 flex items-center justify-center">
<button class="i-carbon-home text-white w-6 h-6"></button>
</view>
</navigator>
<!-- 3/3如果当前页就是tabbar页不用去首页也就是什么图标都不需要 -->
<view class="title">{{ title || '' }}</view>
</view>
</template>
<script lang="ts" setup>
import { getIsTabbar } from '@/utils/index'
defineProps<{ title?: string }>()
//
const pages = getCurrentPages()
const isTabbar = getIsTabbar()
console.log({ isTabbar, pagesLen: pages.length })
//
const { safeAreaInsets } = uni.getSystemInfoSync()
</script>
<style lang="scss" scoped>
.fly-navbar {
position: fixed;
top: 0;
left: 0;
z-index: 9;
width: 750rpx;
color: #000;
background-color: transparent;
.left-icon {
position: absolute;
left: 0;
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
font-size: 44rpx;
color: #000;
}
.title {
display: flex;
align-items: center;
justify-content: center;
height: 44px;
font-size: 32rpx;
color: transparent;
}
}
</style>

21
env.d.ts vendored Normal file
View File

@ -0,0 +1,21 @@
/// <reference types="vite/client" />
/// <reference types="vite-svg-loader" />
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
}

18
env/.env vendored Normal file
View File

@ -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 lacaleen, 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'

6
env/.env.development vendored Normal file
View File

@ -0,0 +1,6 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE = false
# VITE_SERVER_BASEURL = 'https://ukw0y1.laf.run'

6
env/.env.production vendored Normal file
View File

@ -0,0 +1,6 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE = true
# VITE_SERVER_BASEURL = 'https://xxx.com'

6
env/.env.test vendored Normal file
View File

@ -0,0 +1,6 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE = false
# VITE_SERVER_BASEURL = 'https://xxx.com'

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

0
hooks/.gitkeep Normal file
View File

62
hooks/useNavbarWeixin.ts Normal file
View File

@ -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<any>
// 获取当前页面实例,数组最后一项
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,
}
}

23
index.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

2
interceptors/index.ts Normal file
View File

@ -0,0 +1,2 @@
export { routeInterceptor } from './route'
export { requestInterceptor } from './request'

53
interceptors/request.ts Normal file
View File

@ -0,0 +1,53 @@
/* eslint-disable no-param-reassign */
import qs from 'qs'
import { useUserStore } from '@/store'
export type CustomRequestOptions = UniApp.RequestOptions & {
query?: Record<string, any>
} & 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)
},
}

54
interceptors/route.ts Normal file
View File

@ -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)
},
}

12
layouts/default.vue Normal file
View File

@ -0,0 +1,12 @@
<template>
<view class="default-layout">
<slot />
</view>
</template>
<style lang="scss">
.default-layout {
height: 100vh;
overflow: auto;
}
</style>

5
layouts/demo.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<view class="text-center p-4">
<slot />
</view>
</template>

15
main.js Normal file
View File

@ -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,
}
}

103
manifest.config.ts Normal file
View File

@ -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: [
'<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>',
'<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>',
'<uses-permission android:name="android.permission.VIBRATE"/>',
'<uses-permission android:name="android.permission.READ_LOGS"/>',
'<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
'<uses-feature android:name="android.hardware.camera.autofocus"/>',
'<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
'<uses-permission android:name="android.permission.CAMERA"/>',
'<uses-permission android:name="android.permission.GET_ACCOUNTS"/>',
'<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
'<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>',
'<uses-permission android:name="android.permission.WAKE_LOCK"/>',
'<uses-permission android:name="android.permission.FLASHLIGHT"/>',
'<uses-feature android:name="android.hardware.camera"/>',
'<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
],
},
/* 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',
})

97
package.json Normal file
View File

@ -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"
}
}

20
pages-sub/demo.vue Normal file
View File

@ -0,0 +1,20 @@
<route lang="json5" type="page">
{
style: { navigationBarTitleText: '分包页面 标题' },
}
</route>
<template>
<view class="text-center">
<view class="m-8">http://localhost:9000/#/pages-sub/demo/index</view>
<view class="text-green-500">分包页面demo</view>
</view>
</template>
<script lang="ts" setup>
// code here
</script>
<style lang="scss" scoped>
//
</style>

45
pages.config.ts Normal file
View File

@ -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: '关于',
},
],
},
})

37
pages/index/about.vue Normal file
View File

@ -0,0 +1,37 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationBarTitleText: '关于',
},
}
</route>
<template>
<view
class="bg-white overflow-hidden pt-2 px-4"
:style="{ marginTop: safeAreaInsets?.top + 'px' }"
>
<view class="text-center text-3xl mt-4">关于页面</view>
<view class="text-center text-3xl mt-8">
鸽友们好我是
<text class="text-red-500">菲鸽</text>
</view>
<view class="text-center mt-8">
<wd-button type="primary">wot 组件库测试</wd-button>
</view>
<view class="desc">测试设计稿样式</view>
<view class="desc">设计稿是750pxcss里面全部写rpx 即可</view>
</view>
</template>
<script lang="ts" setup>
//
const { safeAreaInsets } = uni.getSystemInfoSync()
</script>
<style lang="scss" scoped>
.desc {
width: 700rpx;
}
</style>

75
pages/index/index.vue Normal file
View File

@ -0,0 +1,75 @@
<!-- 使用 type="home" 属性设置首页其他页面不需要设置默认为page推荐使用json5更强大且允许注释 -->
<route lang="json5" type="home">
{
style: {
navigationStyle: 'custom',
navigationBarTitleText: '首页',
},
}
</route>
<template>
<view
class="bg-white overflow-hidden pt-2 px-4"
:style="{ marginTop: safeAreaInsets?.top + 'px' }"
>
<view class="mt-12">
<image src="/static/logo.svg" alt="" class="w-28 h-28 block mx-auto" />
</view>
<view class="text-center text-4xl main-title-color mt-4">unibest</view>
<view class="text-center text-2xl mt-2 mb-8">最好用的 uniapp 开发模板</view>
<view class="text-center text-2xl mt-2 mb-8 text-green-400">hbx版HBuilderX)</view>
<view class="text-justify max-w-100 m-auto text-4 indent mb-2">{{ description }} </view>
<view class="mt-8 text-center">
<text class="text-green-400">在线文档</text>
<!-- #ifndef H5 -->
https://codercup.github.io/unibest-docs/
<!-- #endif -->
<!-- #ifdef H5 -->
<a href="https://codercup.github.io/unibest-docs/" target="_blank">
https://codercup.github.io/unibest-docs/
</a>
<!-- #endif -->
</view>
<view class="mt-4 text-center">
<text class="text-green-400">在线预览</text>
<!-- #ifndef H5 -->
https://codercup.github.io/unibest/
<!-- #endif -->
<!-- #ifdef H5 -->
<a href="https://codercup.github.io/unibest/" target="_blank">
https://codercup.github.io/unibest/
</a>
<!-- #endif -->
</view>
</view>
</template>
<script lang="ts" setup>
//
const { safeAreaInsets } = uni.getSystemInfoSync()
const author = ref('菲鸽')
const description = ref(
'unibest 是一个集成了多种工具和技术的 uniapp 开发模板,由 uniapp + Vue3 + Ts + Vite4 + UnoCss + UniUI + VSCode 构建,模板具有代码提示、自动格式化、统一配置、代码片段等功能,并内置了许多常用的基本组件和基本功能,让你编写 uniapp 拥有 best 体验。',
)
onLoad(() => {
console.log(author)
})
/** 激活“分享给好友” */
onShareAppMessage((options: Page.ShareAppMessageOption): Page.CustomShareContent => {
console.log('options:', options)
return {
title: 'unibest',
desc: 'unibest 演示示例',
path: '/pages/index/index?id=xxx',
}
})
</script>
<style>
.main-title-color {
color: #d14328;
}
</style>

4
service/foo.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
export interface IFooItem {
id: string
name: string
}

32
service/foo.ts Normal file
View File

@ -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<IFooItem>({
url: `/foo`,
method: 'GET',
query: { name },
})
}
/** get 请求 */
export const postFooAPI = (name: string) => {
return http<IFooItem>({
url: `/foo`,
method: 'POST',
query: { name }, // post 请求也支持 query
data: { name },
})
}
// 文件上传
export const fileUpload = (data: IUniUploadFileOptions) => {
return uniFileUpload({
url: `/foo/upload`,
method: 'POST',
...data,
})
}

47
shell/postinstall.js Normal file
View File

@ -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}已经成功创建,并写入{}`)
},
)
}

BIN
static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

33
static/logo.svg Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 113.39 113.39">
<defs>
<style>
.cls-1 {
fill: none;
}
.cls-2 {
fill: #d14328;
}
.cls-3 {
fill: #2c8d3a;
}
</style>
</defs>
<g id="_图层_1-2" data-name="图层 1">
<g>
<rect class="cls-1" width="113.39" height="113.39" />
<g>
<path class="cls-3"
d="M86.31,11.34H25.08c-8.14,0-14.74,6.6-14.74,14.74v61.23c0,8.14,6.6,14.74,14.74,14.74h61.23c.12,0,.24-.02,.37-.02-9.76-.2-17.64-8.18-17.64-17.99,0-.56,.03-1.12,.08-1.67H34.1c-1.57,0-2.83-1.27-2.83-2.83V32.43c0-.78,.63-1.42,1.42-1.42h9.17c.78,0,1.42,.63,1.42,1.42v36.52c0,.78,.63,1.42,1.42,1.42h22.02c.78,0,1.42-.63,1.42-1.42V32.43c0-.78,.63-1.42,1.42-1.42h9.17c.78,0,1.42,.63,1.42,1.42v34.99c2.13-.89,4.47-1.39,6.92-1.39,5.66,0,10.7,2.63,14.01,6.72V26.08c0-8.14-6.6-14.74-14.74-14.74Z" />
<g>
<path class="cls-2"
d="M87.04,68.03c-8.83,0-16.01,7.18-16.01,16.01s7.18,16.01,16.01,16.01,16.01-7.18,16.01-16.01-7.18-16.01-16.01-16.01Zm-.27,24.84h-7.2v-3h1.18v-10.48h4.58v2.81h1.42c.84,0,1.46-.16,1.88-.48s.62-.87,.62-1.64c0-.69-.25-1.17-.74-1.45s-1.19-.42-2.09-.42h-6.84v-3h7.2c2.38,0,4.15,.38,5.31,1.15,1.16,.77,1.74,1.93,1.74,3.48,0,1.71-.83,2.93-2.5,3.64,1.07,.4,1.87,.95,2.39,1.65s.79,1.56,.79,2.58c0,3.44-2.58,5.16-7.73,5.16Z" />
<path class="cls-2"
d="M86.49,85.17h-1.16v4.7h1.8c.81,0,1.46-.18,1.94-.55s.72-.95,.72-1.73c0-.86-.25-1.48-.74-1.85s-1.35-.56-2.56-.56Z" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
static/tabbar/example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/tabbar/exampleHL.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/tabbar/home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/tabbar/homeHL.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/tabbar/personal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

28
store/count.ts Normal file
View File

@ -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,
},
)

18
store/index.ts Normal file
View File

@ -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'

34
store/user.ts Normal file
View File

@ -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<IUserInfo>({ ...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,
},
)

41
tsconfig.json Normal file
View File

@ -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"
]
}

257
types/auto-import.d.ts vendored Normal file
View File

@ -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<typeof import('vue')['EffectScope']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onAddToFavorites: UnwrapRef<typeof import('@dcloudio/uni-app')['onAddToFavorites']>
readonly onBackPress: UnwrapRef<typeof import('@dcloudio/uni-app')['onBackPress']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onError: UnwrapRef<typeof import('@dcloudio/uni-app')['onError']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onHide: UnwrapRef<typeof import('@dcloudio/uni-app')['onHide']>
readonly onLaunch: UnwrapRef<typeof import('@dcloudio/uni-app')['onLaunch']>
readonly onLoad: UnwrapRef<typeof import('@dcloudio/uni-app')['onLoad']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onNavigationBarButtonTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']>
readonly onNavigationBarSearchInputChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']>
readonly onNavigationBarSearchInputClicked: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']>
readonly onNavigationBarSearchInputConfirmed: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']>
readonly onNavigationBarSearchInputFocusChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']>
readonly onPageNotFound: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageNotFound']>
readonly onPageScroll: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageScroll']>
readonly onPullDownRefresh: UnwrapRef<typeof import('@dcloudio/uni-app')['onPullDownRefresh']>
readonly onReachBottom: UnwrapRef<typeof import('@dcloudio/uni-app')['onReachBottom']>
readonly onReady: UnwrapRef<typeof import('@dcloudio/uni-app')['onReady']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onResize: UnwrapRef<typeof import('@dcloudio/uni-app')['onResize']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onShareAppMessage: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareAppMessage']>
readonly onShareTimeline: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareTimeline']>
readonly onShow: UnwrapRef<typeof import('@dcloudio/uni-app')['onShow']>
readonly onTabItemTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onTabItemTap']>
readonly onThemeChange: UnwrapRef<typeof import('@dcloudio/uni-app')['onThemeChange']>
readonly onUnhandledRejection: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnhandledRejection']>
readonly onUnload: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnload']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useNavbarWeixin: UnwrapRef<typeof import('../hooks/useNavbarWeixin')['default']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
}
}
declare module '@vue/runtime-core' {
interface GlobalComponents {}
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onAddToFavorites: UnwrapRef<typeof import('@dcloudio/uni-app')['onAddToFavorites']>
readonly onBackPress: UnwrapRef<typeof import('@dcloudio/uni-app')['onBackPress']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onError: UnwrapRef<typeof import('@dcloudio/uni-app')['onError']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onHide: UnwrapRef<typeof import('@dcloudio/uni-app')['onHide']>
readonly onLaunch: UnwrapRef<typeof import('@dcloudio/uni-app')['onLaunch']>
readonly onLoad: UnwrapRef<typeof import('@dcloudio/uni-app')['onLoad']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onNavigationBarButtonTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']>
readonly onNavigationBarSearchInputChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']>
readonly onNavigationBarSearchInputClicked: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']>
readonly onNavigationBarSearchInputConfirmed: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']>
readonly onNavigationBarSearchInputFocusChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']>
readonly onPageNotFound: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageNotFound']>
readonly onPageScroll: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageScroll']>
readonly onPullDownRefresh: UnwrapRef<typeof import('@dcloudio/uni-app')['onPullDownRefresh']>
readonly onReachBottom: UnwrapRef<typeof import('@dcloudio/uni-app')['onReachBottom']>
readonly onReady: UnwrapRef<typeof import('@dcloudio/uni-app')['onReady']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onResize: UnwrapRef<typeof import('@dcloudio/uni-app')['onResize']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onShareAppMessage: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareAppMessage']>
readonly onShareTimeline: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareTimeline']>
readonly onShow: UnwrapRef<typeof import('@dcloudio/uni-app')['onShow']>
readonly onTabItemTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onTabItemTap']>
readonly onThemeChange: UnwrapRef<typeof import('@dcloudio/uni-app')['onThemeChange']>
readonly onUnhandledRejection: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnhandledRejection']>
readonly onUnload: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnload']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useNavbarWeixin: UnwrapRef<typeof import('../hooks/useNavbarWeixin')['default']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
}
}

31
typing.ts Normal file
View File

@ -0,0 +1,31 @@
/* eslint-disable no-unused-vars */
/* eslint-disable @typescript-eslint/no-unused-vars */
// 全局要用的类型放到这里
type IResData<T> = {
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',
}

76
uni.scss Normal file
View File

@ -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;

96
uno.config.ts Normal file
View File

@ -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, 使用 `<button class="i-carbon-sun dark:i-carbon-moon" />`
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: `<div class="hover:(bg-gray-400 font-medium) font-(light mono)">测试 unocss</div>`
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稿pxunocss就写多少述职
* 稿100pxw-100
*
* 稿 750稿1px1rpx
* rpx是响应式的
*/

45
unocss.css Normal file
View File

@ -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;}

81
utils/http.ts Normal file
View File

@ -0,0 +1,81 @@
import { CustomRequestOptions } from '@/interceptors/request'
export const http = <T>(options: CustomRequestOptions) => {
// 1. 返回 Promise 对象
return new Promise<IResData<T>>((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<T>)
} 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<T>).msg || '请求错误',
})
reject(res)
}
},
// 响应失败
fail(err) {
uni.showToast({
icon: 'none',
title: '网络错误,换个网络试试',
})
reject(err)
},
})
})
}
// uni.uploadFile封装
export const uniFileUpload = <T>(options: CustomRequestOptions) => {
// 1. 返回 Promise 对象
return new Promise<IResData<T>>((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<T>
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)
},
})
})
}

110
utils/index.ts Normal file
View File

@ -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<string, string> = {}
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 使
* keypages 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<string, any>) => {
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]
}

118
vite.config.ts Normal file
View File

@ -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,
},
},
},
})
}