Merge branch 'main' into base-sard-ui

This commit is contained in:
feige996 2025-06-21 19:12:43 +08:00
commit 1272f9bd8f
58 changed files with 9649 additions and 2977 deletions

View File

@ -1,106 +1,3 @@
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
const scopes = fs
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name.replace(/s$/, ''))
// precomputed scope
const scopeComplete = execSync('git status --porcelain || true')
.toString()
.trim()
.split('\n')
.find((r) => ~r.indexOf('M src'))
?.replace(/(\/)/g, '%%')
?.match(/src%%((\w|-)*)/)?.[1]
?.replace(/s$/, '')
module.exports = { module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'], extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'subject-case': [0],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'style',
'docs',
'test',
'refactor',
'build',
'ci',
'chore',
'revert',
'wip',
'workflow',
'types',
'release',
],
],
},
prompt: {
/** @use `pnpm commit :f` */
alias: {
f: 'docs: fix typos',
r: 'docs: update README',
s: 'style: update code format',
b: 'build: bump dependencies',
c: 'chore: update config',
},
customScopesAlign: !scopeComplete ? 'top' : 'bottom',
defaultScope: scopeComplete,
scopes: [...scopes, 'mock'],
allowEmptyIssuePrefixs: false,
allowCustomIssuePrefixs: false,
// English
typesAppend: [
{ value: 'wip', name: 'wip: work in process' },
{ value: 'workflow', name: 'workflow: workflow improvements' },
{ value: 'types', name: 'types: type definition file changes' },
],
// 中英文对照版
// messages: {
// type: '选择你要提交的类型 :',
// scope: '选择一个提交范围 (可选):',
// customScope: '请输入自定义的提交范围 :',
// subject: '填写简短精炼的变更描述 :\n',
// body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n',
// breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n',
// footerPrefixsSelect: '选择关联issue前缀 (可选):',
// customFooterPrefixs: '输入自定义issue前缀 :',
// footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
// confirmCommit: '是否提交或修改commit ?',
// },
// types: [
// { value: 'feat', name: 'feat: 新增功能' },
// { value: 'fix', name: 'fix: 修复缺陷' },
// { value: 'docs', name: 'docs: 文档变更' },
// { value: 'style', name: 'style: 代码格式' },
// { value: 'refactor', name: 'refactor: 代码重构' },
// { value: 'perf', name: 'perf: 性能优化' },
// { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' },
// { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' },
// { value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
// { value: 'revert', name: 'revert: 回滚 commit' },
// { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },
// { value: 'wip', name: 'wip: 正在开发中' },
// { value: 'workflow', name: 'workflow: 工作流程改进' },
// { value: 'types', name: 'types: 类型定义文件修改' },
// ],
// emptyScopesAlias: 'empty: 不填写',
// customScopesAlias: 'custom: 自定义',
},
} }

60
.github/release.yml vendored
View File

@ -1,31 +1,31 @@
categories: categories:
- title: '🚀 新功能' - title: 🚀 新功能
labels: ['feat', 'feature'] labels: [feat, feature]
- title: '🛠️ 修复' - title: 🛠️ 修复
labels: ['fix', 'bugfix'] labels: [fix, bugfix]
- title: '💅 样式' - title: 💅 样式
labels: ['style'] labels: [style]
- title: '📄 文档' - title: 📄 文档
labels: ['docs'] labels: [docs]
- title: '⚡️ 性能' - title: ⚡️ 性能
labels: ['perf'] labels: [perf]
- title: '🧪 测试' - title: 🧪 测试
labels: ['test'] labels: [test]
- title: '♻️ 重构' - title: ♻️ 重构
labels: ['refactor'] labels: [refactor]
- title: '📦 构建' - title: 📦 构建
labels: ['build'] labels: [build]
- title: '🚨 补丁' - title: 🚨 补丁
labels: ['patch', 'hotfix'] labels: [patch, hotfix]
- title: '🌐 发布' - title: 🌐 发布
labels: ['release', 'publish'] labels: [release, publish]
- title: '🔧 流程' - title: 🔧 流程
labels: ['ci', 'cd', 'workflow'] labels: [ci, cd, workflow]
- title: '⚙️ 配置' - title: ⚙️ 配置
labels: ['config', 'chore'] labels: [config, chore]
- title: '📁 文件' - title: 📁 文件
labels: ['file'] labels: [file]
- title: '🎨 格式化' - title: 🎨 格式化
labels: ['format'] labels: [format]
- title: '🔀 其他' - title: 🔀 其他
labels: ['other', 'misc'] labels: [other, misc]

View File

@ -1,32 +1,14 @@
name: Auto Merge Base to Other Branches name: Auto Merge Main to Other Branches
on: on:
push: push:
branches: branches:
- base - main
workflow_dispatch: # 手动触发 workflow_dispatch: # 手动触发
jobs: jobs:
merge-to-main:
name: Merge base into main
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_TOKEN_AUTO_MERGE }}
- name: Merge base into main
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git checkout main
git merge base --no-ff -m "Auto merge base into main"
git push origin main
merge-to-i18n: merge-to-i18n:
name: Merge base into i18n name: Merge main into i18n
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
@ -35,46 +17,10 @@ jobs:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GH_TOKEN_AUTO_MERGE }} token: ${{ secrets.GH_TOKEN_AUTO_MERGE }}
- name: Merge base into i18n - name: Merge main into i18n
run: | run: |
git config user.name "GitHub Actions" git config user.name "GitHub Actions"
git config user.email "actions@github.com" git config user.email "actions@github.com"
git checkout i18n git checkout i18n
git merge base --no-ff -m "Auto merge base into i18n" git merge main --no-ff -m "Auto merge main into i18n"
git push origin i18n git push origin i18n
merge-to-tabbar:
name: Merge base into tabbar
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_TOKEN_AUTO_MERGE }}
- name: Merge base into tabbar
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git checkout tabbar
git merge base --no-ff -m "Auto merge base into tabbar"
git push origin tabbar
merge-to-spa:
name: Merge base into spa
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_TOKEN_AUTO_MERGE }}
- name: Merge base into spa
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git checkout spa
git merge base --no-ff -m "Auto merge base into spa"
git push origin spa

View File

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

View File

@ -10,7 +10,7 @@ module.exports = {
htmlWhitespaceSensitivity: 'ignore', htmlWhitespaceSensitivity: 'ignore',
overrides: [ overrides: [
{ {
files: '*.json', files: '*.{json,jsonc}',
options: { options: {
trailingComma: 'none', trailingComma: 'none',
}, },

View File

@ -13,6 +13,8 @@
"uni-helper.uni-ui-snippets-vscode", "uni-helper.uni-ui-snippets-vscode",
"uni-helper.uni-app-snippets-vscode", "uni-helper.uni-app-snippets-vscode",
"mrmlnc.vscode-json5", "mrmlnc.vscode-json5",
"streetsidesoftware.code-spell-checker" "streetsidesoftware.code-spell-checker",
"foxundermoon.shell-format",
"christian-kohler.path-intellisense"
] ]
} }

76
.vscode/settings.json vendored
View File

@ -1,14 +1,7 @@
{ {
// prettier // prettier
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
//
"editor.formatOnSave": true,
//
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
// stylelint // stylelint
"stylelint.validate": ["css", "scss", "vue", "html"], // package.jsonscripts "stylelint.validate": ["css", "scss", "vue", "html"], // package.jsonscripts
"stylelint.enable": true, "stylelint.enable": true,
@ -41,6 +34,7 @@
"commitlint", "commitlint",
"dcloudio", "dcloudio",
"iconfont", "iconfont",
"oxlint",
"qrcode", "qrcode",
"refresherrefresh", "refresherrefresh",
"scrolltolower", "scrolltolower",
@ -56,7 +50,69 @@
"explorer.fileNesting.enabled": true, "explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false, "explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": { "explorer.fileNesting.patterns": {
"README.md": "index.html,favicon.ico,robots.txt,CHANGELOG.md",
"pages.config.ts": "manifest.config.ts,openapi-ts-request.config.ts",
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc", "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc",
".eslintrc.cjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,.stylelintrc.*,.eslintrc-auto-import.json,.editorconfig,.commitlint.cjs" "eslint.config.mjs": "tsconfig.json,.commitlintrc.*,.prettier*,.editorconfig,.commitlint.cjs,.eslint*"
} },
//
"prettier.enable": true,
"editor.formatOnSave": true,
//
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
// Disable the default formatter, use eslint instead
// "prettier.enable": false,
// "editor.formatOnSave": false,
// Auto fix
// "editor.codeActionsOnSave": {
// "source.fixAll.eslint": "explicit",
// "source.organizeImports": "never"
// },
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true }
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"json5",
"jsonc",
"yaml",
"toml",
"xml",
"gql",
"graphql",
"astro",
"svelte",
"css",
"less",
"scss",
"pcss",
"postcss"
]
} }

View File

@ -70,13 +70,13 @@
## 📦 运行(支持热更新) ## 📦 运行(支持热更新)
- web平台 `pnpm dev:h5`, 然后打开 [http://localhost:9000/](http://localhost:9000/)。 - web平台 `pnpm dev:h5`, 然后打开 [http://localhost:9000/](http://localhost:9000/)。
- weixin平台`pnpm dev:mp-weixin` 然后打开微信开发者工具,导入本地文件夹,选择本项目的`dist/dev/mp-weixin` 文件。 - weixin平台`pnpm dev:mp` 然后打开微信开发者工具,导入本地文件夹,选择本项目的`dist/dev/mp-weixin` 文件。
- APP平台`pnpm dev:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/dev/app` 文件夹,选择运行到模拟器(开发时优先使用),或者运行的安卓/ios基座。 - APP平台`pnpm dev:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/dev/app` 文件夹,选择运行到模拟器(开发时优先使用),或者运行的安卓/ios基座。
## 🔗 发布 ## 🔗 发布
- web平台 `pnpm build:h5`,打包后的文件在 `dist/build/h5`可以放到web服务器如nginx运行。如果最终不是放在根目录可以在 `manifest.config.ts` 文件的 `h5.router.base` 属性进行修改。 - web平台 `pnpm build:h5`,打包后的文件在 `dist/build/h5`可以放到web服务器如nginx运行。如果最终不是放在根目录可以在 `manifest.config.ts` 文件的 `h5.router.base` 属性进行修改。
- weixin平台`pnpm build:mp-weixin`, 打包后的文件在 `dist/build/mp-weixin`,然后通过微信开发者工具导入,并点击右上角的“上传”按钮进行上传。 - weixin平台`pnpm build:mp`, 打包后的文件在 `dist/build/mp-weixin`,然后通过微信开发者工具导入,并点击右上角的“上传”按钮进行上传。
- APP平台`pnpm build:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/build/app` 文件夹,选择发行 - APP云打包。 - APP平台`pnpm build:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/build/app` 文件夹,选择发行 - APP云打包。
## 📄 License ## 📄 License

5
env/.env vendored
View File

@ -1,12 +1,15 @@
VITE_APP_TITLE = 'unibest' VITE_APP_TITLE = 'unibest'
VITE_APP_PORT = 9000 VITE_APP_PORT = 9000
VITE_UNI_APPID = 'H57F2ACE4' VITE_UNI_APPID = '__UNI__D1E5001'
VITE_WX_APPID = 'wxa2abb91f64032a2b' VITE_WX_APPID = 'wxa2abb91f64032a2b'
# h5部署网站的base配置到 manifest.config.ts 里的 h5.router.base # h5部署网站的base配置到 manifest.config.ts 里的 h5.router.base
VITE_APP_PUBLIC_BASE=/ VITE_APP_PUBLIC_BASE=/
# 登录页面
VITE_LOGIN_URL = '/pages/login/index'
VITE_SERVER_BASEURL = 'https://ukw0y1.laf.run' VITE_SERVER_BASEURL = 'https://ukw0y1.laf.run'
VITE_UPLOAD_BASEURL = 'https://ukw0y1.laf.run/upload' VITE_UPLOAD_BASEURL = 'https://ukw0y1.laf.run/upload'

22
eslint.config.mjs Normal file
View File

@ -0,0 +1,22 @@
import uniHelper from '@uni-helper/eslint-config'
export default uniHelper({
unocss: true,
vue: true,
markdown: false,
ignores: [
'src/uni_modules/',
'dist',
],
rules: {
'no-console': 'off',
'no-unused-vars': 'off',
'vue/no-unused-refs': 'off',
'unused-imports/no-unused-vars': 'off',
'eslint-comments/no-unlimited-disable': 'off',
'jsdoc/check-param-names': 'off',
'jsdoc/require-returns-description': 'off',
'ts/no-empty-object-type': 'off',
'no-extend-native': 'off',
},
})

View File

@ -1,6 +1,7 @@
import path from 'node:path'
import process from 'node:process'
// manifest.config.ts // manifest.config.ts
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest' import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
import path from 'node:path'
import { loadEnv } from 'vite' import { loadEnv } from 'vite'
// 获取环境变量的范例 // 获取环境变量的范例
@ -14,14 +15,14 @@ const {
} = env } = env
export default defineManifestConfig({ export default defineManifestConfig({
name: VITE_APP_TITLE, 'name': VITE_APP_TITLE,
appid: VITE_UNI_APPID, 'appid': VITE_UNI_APPID,
description: '', 'description': '',
versionName: '1.0.0', 'versionName': '1.0.0',
versionCode: '100', 'versionCode': '100',
transformPx: false, 'transformPx': false,
locale: VITE_FALLBACK_LOCALE, // 'zh-Hans' 'locale': VITE_FALLBACK_LOCALE, // 'zh-Hans'
h5: { 'h5': {
router: { router: {
base: VITE_APP_PUBLIC_BASE, base: VITE_APP_PUBLIC_BASE,
}, },
@ -82,14 +83,14 @@ export default defineManifestConfig({
ios: { ios: {
appstore: 'static/app/icons/1024x1024.png', appstore: 'static/app/icons/1024x1024.png',
ipad: { ipad: {
app: 'static/app/icons/76x76.png', 'app': 'static/app/icons/76x76.png',
'app@2x': 'static/app/icons/152x152.png', 'app@2x': 'static/app/icons/152x152.png',
notification: 'static/app/icons/20x20.png', 'notification': 'static/app/icons/20x20.png',
'notification@2x': 'static/app/icons/40x40.png', 'notification@2x': 'static/app/icons/40x40.png',
'proapp@2x': 'static/app/icons/167x167.png', 'proapp@2x': 'static/app/icons/167x167.png',
settings: 'static/app/icons/29x29.png', 'settings': 'static/app/icons/29x29.png',
'settings@2x': 'static/app/icons/58x58.png', 'settings@2x': 'static/app/icons/58x58.png',
spotlight: 'static/app/icons/40x40.png', 'spotlight': 'static/app/icons/40x40.png',
'spotlight@2x': 'static/app/icons/80x80.png', 'spotlight@2x': 'static/app/icons/80x80.png',
}, },
iphone: { iphone: {
@ -107,12 +108,15 @@ export default defineManifestConfig({
}, },
}, },
/* 快应用特有相关 */ /* 快应用特有相关 */
quickapp: {}, 'quickapp': {},
/* 小程序特有相关 */ /* 小程序特有相关 */
'mp-weixin': { 'mp-weixin': {
appid: VITE_WX_APPID, appid: VITE_WX_APPID,
setting: { setting: {
urlCheck: false, urlCheck: false,
// 是否启用 ES6 转 ES5
es6: true,
minified: true,
}, },
usingComponents: true, usingComponents: true,
// __usePrivacyCheck__: true, // __usePrivacyCheck__: true,
@ -127,8 +131,8 @@ export default defineManifestConfig({
'mp-toutiao': { 'mp-toutiao': {
usingComponents: true, usingComponents: true,
}, },
uniStatistics: { 'uniStatistics': {
enable: false, enable: false,
}, },
vueVersion: '3', 'vueVersion': '3',
}) })

View File

@ -1,9 +1,9 @@
{ {
"name": "unibest", "name": "unibest",
"type": "commonjs", "type": "commonjs",
"version": "2.11.1", "version": "3.0.0",
"description": "unibest - 最好的 uniapp 开发模板", "description": "unibest - 最好的 uniapp 开发模板",
"update-time": "2025-05-28", "update-time": "2025-06-21",
"author": { "author": {
"name": "feige996", "name": "feige996",
"zhName": "菲鸽", "zhName": "菲鸽",
@ -12,13 +12,14 @@
"gitee": "https://gitee.com/feige996" "gitee": "https://gitee.com/feige996"
}, },
"license": "MIT", "license": "MIT",
"homepage": "https://unibest.tech",
"repository": "https://github.com/feige996/unibest", "repository": "https://github.com/feige996/unibest",
"repository-gitee": "https://gitee.com/feige996/unibest", "repository-gitee": "https://gitee.com/feige996/unibest",
"repository-deprecated": "https://github.com/codercup/unibest", "repository-old": "https://github.com/codercup/unibest",
"bugs": { "bugs": {
"url": "https://github.com/feige996/unibest/issues" "url": "https://github.com/feige996/unibest/issues",
"url-old": "https://github.com/codercup/unibest/issues"
}, },
"homepage": "https://feige996.github.io/unibest/",
"engines": { "engines": {
"node": ">=18", "node": ">=18",
"pnpm": ">=7.30" "pnpm": ">=7.30"
@ -70,20 +71,9 @@
"build:quickapp-webview-union": "uni build -p quickapp-webview-union", "build:quickapp-webview-union": "uni build -p quickapp-webview-union",
"type-check": "vue-tsc --noEmit", "type-check": "vue-tsc --noEmit",
"openapi-ts-request": "openapi-ts", "openapi-ts-request": "openapi-ts",
"prepare": "git init && husky" "prepare": "git init && husky",
}, "lint": "eslint",
"lint-staged": { "lint:fix": "eslint --fix"
"**/*.{vue,html,cjs,json,md,scss,css,txt}": [
"prettier --write --cache"
],
"**/*.{js,ts}": [
"oxlint --fix",
"prettier --write --cache"
],
"!**/{node_modules,dist}/**": []
},
"resolutions": {
"bin-wrapper": "npm:bin-wrapper-china"
}, },
"dependencies": { "dependencies": {
"@dcloudio/uni-app": "3.0.0-4060620250520001", "@dcloudio/uni-app": "3.0.0-4060620250520001",
@ -110,10 +100,11 @@
"pinia-plugin-persistedstate": "3.2.1", "pinia-plugin-persistedstate": "3.2.1",
"qs": "6.5.3", "qs": "6.5.3",
"sard-uniapp": "^1.17.1", "sard-uniapp": "^1.17.1",
"vue": "^3.5.15", "vue": "^3.4.21",
"z-paging": "^2.8.4" "z-paging": "2.8.7"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^4.15.0",
"@commitlint/cli": "^19.8.1", "@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1", "@commitlint/config-conventional": "^19.8.1",
"@dcloudio/types": "^3.4.8", "@dcloudio/types": "^3.4.8",
@ -121,39 +112,48 @@
"@dcloudio/uni-cli-shared": "3.0.0-4060620250520001", "@dcloudio/uni-cli-shared": "3.0.0-4060620250520001",
"@dcloudio/uni-stacktracey": "3.0.0-4060620250520001", "@dcloudio/uni-stacktracey": "3.0.0-4060620250520001",
"@dcloudio/vite-plugin-uni": "3.0.0-4060620250520001", "@dcloudio/vite-plugin-uni": "3.0.0-4060620250520001",
"@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.25.5", "@esbuild/darwin-x64": "0.20.2",
"@iconify-json/carbon": "^1.2.4", "@iconify-json/carbon": "^1.2.4",
"@rollup/rollup-darwin-x64": "^4.28.0", "@rollup/rollup-darwin-x64": "^4.28.0",
"@types/node": "^20.17.9", "@types/node": "^20.17.9",
"@types/wechat-miniprogram": "^3.4.8", "@types/wechat-miniprogram": "^3.4.8",
"@uni-helper/eslint-config": "^0.4.0",
"@uni-helper/uni-types": "1.0.0-alpha.3", "@uni-helper/uni-types": "1.0.0-alpha.3",
"@uni-helper/unocss-preset-uni": "^0.2.11", "@uni-helper/unocss-preset-uni": "^0.2.11",
"@uni-helper/vite-plugin-uni-components": "^0.2.0", "@uni-helper/vite-plugin-uni-components": "0.2.0",
"@uni-helper/vite-plugin-uni-layouts": "^0.1.10", "@uni-helper/vite-plugin-uni-layouts": "0.1.10",
"@uni-helper/vite-plugin-uni-manifest": "^0.2.8", "@uni-helper/vite-plugin-uni-manifest": "0.2.8",
"@uni-helper/vite-plugin-uni-pages": "0.2.20", "@uni-helper/vite-plugin-uni-pages": "0.2.28",
"@uni-helper/vite-plugin-uni-platform": "^0.0.4", "@uni-helper/vite-plugin-uni-platform": "0.0.4",
"@uni-ku/bundle-optimizer": "^1.3.3", "@uni-ku/bundle-optimizer": "^1.3.3",
"@unocss/eslint-plugin": "^66.2.3",
"@unocss/preset-legacy-compat": "^0.59.4", "@unocss/preset-legacy-compat": "^0.59.4",
"@vue/runtime-core": "^3.4.21", "@vue/runtime-core": "^3.4.21",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.29.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^15.2.10", "lint-staged": "^15.2.10",
"openapi-ts-request": "^1.1.2", "openapi-ts-request": "^1.1.2",
"oxlint": "^0.1.0",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"postcss-html": "^1.7.0", "postcss-html": "^1.7.0",
"postcss-scss": "^4.0.9", "postcss-scss": "^4.0.9",
"prettier": "^3.5.3",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"sass": "1.77.8", "sass": "1.77.8",
"terser": "^5.36.0", "terser": "^5.36.0",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"unocss": "65.4.2", "unocss": "65.4.2",
"unplugin-auto-import": "^0.17.8", "unplugin-auto-import": "^0.17.8",
"vite": "6.3.5", "vite": "5.2.8",
"vite-plugin-restart": "^0.4.2", "vite-plugin-restart": "^0.4.2",
"vue-tsc": "^2.2.10" "vue-tsc": "^2.2.10"
},
"resolutions": {
"bin-wrapper": "npm:bin-wrapper-china"
},
"lint-staged": {
"*": "eslint --fix"
} }
} }

View File

@ -1,4 +1,5 @@
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages' import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
import { tabBar } from './src/layouts/fg-tabbar/tabbarList'
export default defineUniPages({ export default defineUniPages({
globalStyle: { globalStyle: {
@ -17,29 +18,6 @@ export default defineUniPages({
'z-paging/components/z-paging$1/z-paging$1.vue', 'z-paging/components/z-paging$1/z-paging$1.vue',
}, },
}, },
// 如果不需要tabBar推荐使用 spa 模板。pnpm create xxx -t spa // tabbar 的配置统一在 “./src/layouts/fg-tabbar/tabbarList.ts” 文件中
tabBar: { tabBar: tabBar as any,
color: '#999999',
selectedColor: '#018d71',
backgroundColor: '#F8F8F8',
borderStyle: 'black',
height: '50px',
fontSize: '10px',
iconWidth: '24px',
spacing: '3px',
list: [
{
iconPath: 'static/tabbar/home.png',
selectedIconPath: 'static/tabbar/homeHL.png',
pagePath: 'pages/index/index',
text: '首页',
},
{
iconPath: 'static/tabbar/example.png',
selectedIconPath: 'static/tabbar/exampleHL.png',
pagePath: 'pages/about/about',
text: '关于',
},
],
},
}) })

View File

@ -0,0 +1,13 @@
diff --git a/dist/uni-h5.es.js b/dist/uni-h5.es.js
index 7421bad97d94ad34a3d4d94292a9ee9071430662..19c6071ee4036ceb8d1cfa09030e471c002d2cda 100644
--- a/dist/uni-h5.es.js
+++ b/dist/uni-h5.es.js
@@ -23410,7 +23410,7 @@ function useShowTabBar(emit2) {
const tabBar2 = useTabBar();
const showTabBar2 = computed(() => route.meta.isTabBar && tabBar2.shown);
updateCssVar({
- "--tab-bar-height": tabBar2.height
+ "--tab-bar-height": tabBar2?.height || 0
});
return showTabBar2;
}

9591
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
patchedDependencies:
'@dcloudio/uni-h5': patches/@dcloudio__uni-h5.patch

View File

@ -2,8 +2,7 @@
// # 在升级完后,会自动添加很多无用依赖,这需要删除以减小依赖包体积 // # 在升级完后,会自动添加很多无用依赖,这需要删除以减小依赖包体积
// # 只需要执行下面的命令即可 // # 只需要执行下面的命令即可
// eslint-disable-next-line @typescript-eslint/no-var-requires const { exec } = require('node:child_process')
const { exec } = require('child_process')
// 定义要执行的命令 // 定义要执行的命令
const dependencies = [ const dependencies = [

View File

@ -1,7 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app' import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
import { usePageAuth } from '@/hooks/usePageAuth'
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only' import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
usePageAuth()
onLaunch(() => { onLaunch(() => {
console.log('App Launch') console.log('App Launch')
}) })

View File

@ -1,4 +1,4 @@
import { ICaptcha, IUpdateInfo, IUpdatePassword, IUserInfoVo, IUserLogin } from './login.typings' import type { ICaptcha, IUpdateInfo, IUpdatePassword, IUserInfoVo, IUserLogin } from './login.typings'
import { http } from '@/utils/http' import { http } from '@/utils/http'
/** /**
@ -15,7 +15,7 @@ export interface ILoginForm {
* *
* @returns ICaptcha * @returns ICaptcha
*/ */
export const getCode = () => { export function getCode() {
return http.get<ICaptcha>('/user/getCode') return http.get<ICaptcha>('/user/getCode')
} }
@ -23,35 +23,35 @@ export const getCode = () => {
* *
* @param loginForm * @param loginForm
*/ */
export const login = (loginForm: ILoginForm) => { export function login(loginForm: ILoginForm) {
return http.post<IUserLogin>('/user/login', loginForm) return http.post<IUserLogin>('/user/login', loginForm)
} }
/** /**
* *
*/ */
export const getUserInfo = () => { export function getUserInfo() {
return http.get<IUserInfoVo>('/user/info') return http.get<IUserInfoVo>('/user/info')
} }
/** /**
* 退 * 退
*/ */
export const logout = () => { export function logout() {
return http.get<void>('/user/logout') return http.get<void>('/user/logout')
} }
/** /**
* *
*/ */
export const updateInfo = (data: IUpdateInfo) => { export function updateInfo(data: IUpdateInfo) {
return http.post('/user/updateInfo', data) return http.post('/user/updateInfo', data)
} }
/** /**
* *
*/ */
export const updateUserPassword = (data: IUpdatePassword) => { export function updateUserPassword(data: IUpdatePassword) {
return http.post('/user/updatePassword', data) return http.post('/user/updatePassword', data)
} }
@ -59,12 +59,12 @@ export const updateUserPassword = (data: IUpdatePassword) => {
* *
* @returns Promise (code) * @returns Promise (code)
*/ */
export const getWxCode = () => { export function getWxCode() {
return new Promise<UniApp.LoginRes>((resolve, reject) => { return new Promise<UniApp.LoginRes>((resolve, reject) => {
uni.login({ uni.login({
provider: 'weixin', provider: 'weixin',
success: (res) => resolve(res), success: res => resolve(res),
fail: (err) => reject(new Error(err)), fail: err => reject(new Error(err)),
}) })
}) })
} }
@ -78,6 +78,6 @@ export const getWxCode = () => {
* @param params code * @param params code
* @returns Promise * @returns Promise
*/ */
export const wxLogin = (data: { code: string }) => { export function wxLogin(data: { code: string }) {
return http.post<IUserLogin>('/user/wxLogin', data) return http.post<IUserLogin>('/user/wxLogin', data)
} }

View File

@ -1,7 +1,7 @@
/** /**
* *
*/ */
export type IUserInfoVo = { export interface IUserInfoVo {
id: number id: number
username: string username: string
avatar: string avatar: string
@ -11,7 +11,7 @@ export type IUserInfoVo = {
/** /**
* *
*/ */
export type IUserLogin = { export interface IUserLogin {
id: string id: string
username: string username: string
token: string token: string
@ -20,7 +20,7 @@ export type IUserLogin = {
/** /**
* *
*/ */
export type ICaptcha = { export interface ICaptcha {
captchaEnabled: boolean captchaEnabled: boolean
uuid: string uuid: string
image: string image: string
@ -28,7 +28,7 @@ export type ICaptcha = {
/** /**
* *
*/ */
export type IUploadSuccessInfo = { export interface IUploadSuccessInfo {
fileId: number fileId: number
originalName: string originalName: string
fileName: string fileName: string
@ -41,7 +41,7 @@ export type IUploadSuccessInfo = {
/** /**
* *
*/ */
export type IUpdateInfo = { export interface IUpdateInfo {
id: number id: number
name: string name: string
sex: string sex: string
@ -49,7 +49,7 @@ export type IUpdateInfo = {
/** /**
* *
*/ */
export type IUpdatePassword = { export interface IUpdatePassword {
id: number id: number
oldPassword: string oldPassword: string
newPassword: string newPassword: string

4
src/env.d.ts vendored
View File

@ -2,8 +2,8 @@
/// <reference types="vite-svg-loader" /> /// <reference types="vite-svg-loader" />
declare module '*.vue' { declare module '*.vue' {
import { DefineComponent } from 'vue' import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any> const component: DefineComponent<{}, {}, any>
export default component export default component
} }

50
src/hooks/usePageAuth.ts Normal file
View File

@ -0,0 +1,50 @@
import { onLoad } from '@dcloudio/uni-app'
import { useUserStore } from '@/store'
import { needLoginPages as _needLoginPages, getNeedLoginPages } from '@/utils'
const loginRoute = import.meta.env.VITE_LOGIN_URL
const isDev = import.meta.env.DEV
function isLogined() {
const userStore = useUserStore()
return !!userStore.userInfo.username
}
// 检查当前页面是否需要登录
export function usePageAuth() {
onLoad((options) => {
// 获取当前页面路径
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const currentPath = `/${currentPage.route}`
// 获取需要登录的页面列表
let needLoginPages: string[] = []
if (isDev) {
needLoginPages = getNeedLoginPages()
}
else {
needLoginPages = _needLoginPages
}
// 检查当前页面是否需要登录
const isNeedLogin = needLoginPages.includes(currentPath)
if (!isNeedLogin) {
return
}
const hasLogin = isLogined()
if (hasLogin) {
return true
}
// 构建重定向URL
const queryString = Object.entries(options || {})
.map(([key, value]) => `${key}=${encodeURIComponent(String(value))}`)
.join('&')
const currentFullPath = queryString ? `${currentPath}?${queryString}` : currentPath
const redirectRoute = `${loginRoute}?redirect=${encodeURIComponent(currentFullPath)}`
// 重定向到登录页
uni.redirectTo({ url: redirectRoute })
})
}

View File

@ -1,6 +1,6 @@
import { UnwrapRef } from 'vue' import type { UnwrapRef } from 'vue'
type IUseRequestOptions<T> = { interface IUseRequestOptions<T> {
/** 是否立即执行 */ /** 是否立即执行 */
immediate?: boolean immediate?: boolean
/** 初始化数据 */ /** 初始化数据 */

View File

@ -1,69 +1,160 @@
// TODO: 别忘加更改环境变量的 VITE_UPLOAD_BASEURL 地址。 import { ref } from 'vue'
import { getEnvBaseUploadUrl } from '@/utils' import { getEnvBaseUploadUrl } from '@/utils'
const VITE_UPLOAD_BASEURL = `${getEnvBaseUploadUrl()}` const VITE_UPLOAD_BASEURL = `${getEnvBaseUploadUrl()}`
/** type TfileType = 'image' | 'file'
* useUpload type TImage = 'png' | 'jpg' | 'jpeg' | 'webp' | '*'
* @param formData {name: '菲鸽'} type TFile = 'doc' | 'docx' | 'ppt' | 'zip' | 'xls' | 'xlsx' | 'txt' | TImage
* @returns {loading, error, data, run}
*/ interface TOptions<T extends TfileType> {
export default function useUpload<T = string>(formData: Record<string, any> = {}) { formData?: Record<string, any>
maxSize?: number
accept?: T extends 'image' ? TImage[] : TFile[]
fileType?: T
success?: (params: any) => void
error?: (err: any) => void
}
export default function useUpload<T extends TfileType>(options: TOptions<T> = {} as TOptions<T>) {
const {
formData = {},
maxSize = 5 * 1024 * 1024,
accept = ['*'],
fileType = 'image',
success,
error: onError,
} = options
const loading = ref(false) const loading = ref(false)
const error = ref(false) const error = ref<Error | null>(null)
const data = ref<T>() const data = ref<any>(null)
const handleFileChoose = ({ tempFilePath, size }: { tempFilePath: string, size: number }) => {
if (size > maxSize) {
uni.showToast({
title: `文件大小不能超过 ${maxSize / 1024 / 1024}MB`,
icon: 'none',
})
return
}
// const fileExtension = file?.tempFiles?.name?.split('.').pop()?.toLowerCase()
// const isTypeValid = accept.some((type) => type === '*' || type.toLowerCase() === fileExtension)
// if (!isTypeValid) {
// uni.showToast({
// title: `仅支持 ${accept.join(', ')} 格式的文件`,
// icon: 'none',
// })
// return
// }
loading.value = true
uploadFile({
tempFilePath,
formData,
onSuccess: (res) => {
const { data: _data } = JSON.parse(res)
data.value = _data
// console.log('上传成功', res)
success?.(_data)
},
onError: (err) => {
error.value = err
onError?.(err)
},
onComplete: () => {
loading.value = false
},
})
}
const run = () => { const run = () => {
// #ifdef MP-WEIXIN
// 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。 // 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。
// 微信小程序在2023年10月17日之后使用本API需要配置隐私协议 // 微信小程序在2023年10月17日之后使用本API需要配置隐私协议
uni.chooseMedia({ const chooseFileOptions = {
count: 1, count: 1,
mediaType: ['image'], success: (res: any) => {
success: (res) => { console.log('File selected successfully:', res)
loading.value = true // 小程序中res:{errMsg: "chooseImage:ok", tempFiles: [{fileType: "image", size: 48976, tempFilePath: "http://tmp/5iG1WpIxTaJf3ece38692a337dc06df7eb69ecb49c6b.jpeg"}]}
const tempFilePath = res.tempFiles[0].tempFilePath // h5中res:{errMsg: "chooseImage:ok", tempFilePaths: "blob:http://localhost:9000/f74ab6b8-a14d-4cb6-a10d-fcf4511a0de5", tempFiles: [File]}
uploadFile<T>({ tempFilePath, formData, data, error, loading }) // h5的File有以下字段{name: "girl.jpeg", size: 48976, type: "image/jpeg"}
}, // App中res:{errMsg: "chooseImage:ok", tempFilePaths: "file:///Users/feige/xxx/gallery/1522437259-compressed-IMG_0006.jpg", tempFiles: [File]}
fail: (err) => { // App的File有以下字段{path: "file:///Users/feige/xxx/gallery/1522437259-compressed-IMG_0006.jpg", size: 48976}
console.error('uni.chooseMedia err->', err) let tempFilePath = ''
error.value = true let size = 0
}, // #ifdef MP-WEIXIN
}) tempFilePath = res.tempFiles[0].tempFilePath
size = res.tempFiles[0].size
// #endif // #endif
// #ifndef MP-WEIXIN // #ifndef MP-WEIXIN
uni.chooseImage({ tempFilePath = res.tempFilePaths[0]
count: 1, size = res.tempFiles[0].size
success: (res) => { // #endif
loading.value = true handleFileChoose({ tempFilePath, size })
const tempFilePath = res.tempFilePaths[0]
uploadFile<T>({ tempFilePath, formData, data, error, loading })
}, },
fail: (err) => { fail: (err: any) => {
console.error('uni.chooseImage err->', err) console.error('File selection failed:', err)
error.value = true error.value = err
onError?.(err)
}, },
}
if (fileType === 'image') {
// #ifdef MP-WEIXIN
uni.chooseMedia({
...chooseFileOptions,
mediaType: ['image'],
}) })
// #endif // #endif
// #ifndef MP-WEIXIN
uni.chooseImage(chooseFileOptions)
// #endif
}
else {
uni.chooseFile({
...chooseFileOptions,
type: 'all',
})
}
} }
return { loading, error, data, run } return { loading, error, data, run }
} }
function uploadFile<T>({ tempFilePath, formData, data, error, loading }) { async function uploadFile({
tempFilePath,
formData,
onSuccess,
onError,
onComplete,
}: {
tempFilePath: string
formData: Record<string, any>
onSuccess: (data: any) => void
onError: (err: any) => void
onComplete: () => void
}) {
uni.uploadFile({ uni.uploadFile({
url: VITE_UPLOAD_BASEURL, url: VITE_UPLOAD_BASEURL,
filePath: tempFilePath, filePath: tempFilePath,
name: 'file', name: 'file',
formData, formData,
success: (uploadFileRes) => { success: (uploadFileRes) => {
data.value = uploadFileRes.data as T try {
const data = uploadFileRes.data
onSuccess(data)
}
catch (err) {
onError(err)
}
}, },
fail: (err) => { fail: (err) => {
console.error('uni.uploadFile err->', err) console.error('Upload failed:', err)
error.value = true onError(err)
},
complete: () => {
loading.value = false
}, },
complete: onComplete,
}) })
} }

View File

@ -1,3 +1,3 @@
export { routeInterceptor } from './route'
export { requestInterceptor } from './request'
export { prototypeInterceptor } from './prototype' export { prototypeInterceptor } from './prototype'
export { requestInterceptor } from './request'
export { routeInterceptor } from './route'

View File

@ -2,10 +2,11 @@ export const prototypeInterceptor = {
install() { install() {
// 解决低版本手机不识别 array.at() 导致运行报错的问题 // 解决低版本手机不识别 array.at() 导致运行报错的问题
if (typeof Array.prototype.at !== 'function') { if (typeof Array.prototype.at !== 'function') {
// eslint-disable-next-line no-extend-native
Array.prototype.at = function (index: number) { Array.prototype.at = function (index: number) {
if (index < 0) return this[this.length + index] if (index < 0)
if (index >= this.length) return undefined return this[this.length + index]
if (index >= this.length)
return undefined
return this[index] return this[index]
} }
} }

View File

@ -1,8 +1,7 @@
/* eslint-disable no-param-reassign */
import qs from 'qs' import qs from 'qs'
import { useUserStore } from '@/store' import { useUserStore } from '@/store'
import { platform } from '@/utils/platform'
import { getEnvBaseUrl } from '@/utils' import { getEnvBaseUrl } from '@/utils'
import { platform } from '@/utils/platform'
export type CustomRequestOptions = UniApp.RequestOptions & { export type CustomRequestOptions = UniApp.RequestOptions & {
query?: Record<string, any> query?: Record<string, any>
@ -22,7 +21,8 @@ const httpInterceptor = {
const queryStr = qs.stringify(options.query) const queryStr = qs.stringify(options.query)
if (options.url.includes('?')) { if (options.url.includes('?')) {
options.url += `&${queryStr}` options.url += `&${queryStr}`
} else { }
else {
options.url += `?${queryStr}` options.url += `?${queryStr}`
} }
} }
@ -33,7 +33,8 @@ const httpInterceptor = {
if (JSON.parse(__VITE_APP_PROXY__)) { if (JSON.parse(__VITE_APP_PROXY__)) {
// 自动拼接代理前缀 // 自动拼接代理前缀
options.url = import.meta.env.VITE_APP_PROXY_PREFIX + options.url options.url = import.meta.env.VITE_APP_PROXY_PREFIX + options.url
} else { }
else {
options.url = baseUrl + options.url options.url = baseUrl + options.url
} }
// #endif // #endif

View File

@ -5,12 +5,12 @@
* 便使 * 便使
*/ */
import { useUserStore } from '@/store' import { useUserStore } from '@/store'
import { needLoginPages as _needLoginPages, getNeedLoginPages, getLastPage } from '@/utils' import { needLoginPages as _needLoginPages, getLastPage, getNeedLoginPages } from '@/utils'
// TODO Check // TODO Check
const loginRoute = '/pages/login/index' const loginRoute = import.meta.env.VITE_LOGIN_URL
const isLogined = () => { function isLogined() {
const userStore = useUserStore() const userStore = useUserStore()
return !!userStore.userInfo.username return !!userStore.userInfo.username
} }
@ -37,7 +37,8 @@ const navigateToInterceptor = {
// 为了防止开发时出现BUG这里每次都获取一下。生产环境可以移到函数外性能更好 // 为了防止开发时出现BUG这里每次都获取一下。生产环境可以移到函数外性能更好
if (isDev) { if (isDev) {
needLoginPages = getNeedLoginPages() needLoginPages = getNeedLoginPages()
} else { }
else {
needLoginPages = _needLoginPages needLoginPages = _needLoginPages
} }
const isNeedLogin = needLoginPages.includes(path) const isNeedLogin = needLoginPages.includes(path)

View File

@ -1,7 +1,7 @@
<script lang="ts" setup></script>
<template> <template>
<div> <div>
<slot /> <slot />
</div> </div>
</template> </template>
<script lang="ts" setup></script>

View File

@ -1,7 +1,7 @@
<script lang="ts" setup></script>
<template> <template>
<div> <div>
<slot /> <slot />
</div> </div>
</template> </template>
<script lang="ts" setup></script>

View File

@ -0,0 +1,68 @@
<script setup lang="ts">
import { tabbarStore } from './tabbar'
// 'i-carbon-code',
import { tabbarList as _tabBarList, cacheTabbarEnable, selectedTabbarStrategy } from './tabbarList'
const customTabbarEnable = selectedTabbarStrategy === 1 || selectedTabbarStrategy === 2
/** tabbarList 里面的 path 从 pages.config.ts 得到 */
const tabbarList = _tabBarList.map(item => ({ ...item, path: `/${item.pagePath}` }))
function selectTabBar({ value: index }: { value: number }) {
const url = tabbarList[index].path
tabbarStore.setCurIdx(index)
if (cacheTabbarEnable) {
uni.switchTab({ url })
}
else {
uni.navigateTo({ url })
}
}
onLoad(() => {
// tabBar 2 tabBar
const hideRedundantTabbarEnable = selectedTabbarStrategy === 1
hideRedundantTabbarEnable
&& uni.hideTabBar({
fail(err) {
console.log('hideTabBar fail: ', err)
},
success(res) {
console.log('hideTabBar success: ', res)
},
})
})
</script>
<template>
<wd-tabbar
v-if="customTabbarEnable"
v-model="tabbarStore.curIdx"
bordered safeareainsetbottom placeholder fixed
@change="selectTabBar"
>
<block v-for="(item, idx) in tabbarList" :key="item.path">
<wd-tabbar-item
v-if="item.iconType === 'wot'"
:title="item.text"
:icon="item.icon"
/>
<wd-tabbar-item
v-else-if="item.iconType === 'unocss' || item.iconType === 'iconfont'"
:title="item.text"
>
<template #icon>
<view
h-40rpx
w-40rpx
:class="[item.icon, idx === tabbarStore.curIdx ? 'is-active' : 'is-inactive']"
/>
</template>
</wd-tabbar-item>
<wd-tabbar-item v-else-if="item.iconType === 'local'" :title="item.text">
<template #icon>
<image :src="item.icon" h-40rpx w-40rpx />
</template>
</wd-tabbar-item>
</block>
</wd-tabbar>
</template>

View File

@ -0,0 +1,16 @@
# tabbar 说明
tabbar 分为4种情况
- 完全原生tabbar使用 switchTab 切换 tabbartabbar页面有缓存。
- 优势原生自带的tabbar最先渲染有缓存。
- 劣势只能使用2组图片来切换选中和非选中状态修改颜色只能重新换图片或者用iconfont
- 半自定义tabbar使用 switchTab 切换 tabbartabbar页面有缓存。使用了第三方UI库的 tabbar 组件,并隐藏了原生 tabbar 的显示。
- 优势:可以随意配置自己想要的 svg icon切换字体颜色方便。有缓存。可以实现各种花里胡哨的动效等。
- 劣势首次点击tababr会闪烁。
- 全自定义tabbar使用 navigateTo 切换tabbartabbar页面无缓存。使用了第三方UI库的 tabbar 组件。
- 优势:可以随意配置自己想要的 svg icon切换字体颜色方便。可以实现各种花里胡哨的动效等。
- 劣势首次点击tababr会闪烁无缓存。
- 无tabbar只有一个页面入口底部无tabbar显示常用语临时活动页。
> 注意:花里胡哨的效果需要自己实现,本模版不提供。

View File

@ -0,0 +1,11 @@
/**
* tabbar storageSync tabbar
* 使reactive简单状态 pinia
*/
export const tabbarStore = reactive({
curIdx: uni.getStorageSync('app-tabbar-index') || 0,
setCurIdx(idx: number) {
this.curIdx = idx
uni.setStorageSync('app-tabbar-index', idx)
},
})

View File

@ -0,0 +1,65 @@
/**
* tabbar tabbar.md
* 0: 'NATIVE_TABBAR'
* 2: 'FULL_CUSTOM_TABBAR'
* 1: 'HALF_CUSTOM_TABBAR'
* 3: 'NO_TABBAR'
*
* pages.json
*/
// TODO通过这里切换使用tabbar的策略
export const selectedTabbarStrategy = 0
// 0 和 1 时需要tabbar缓存
export const cacheTabbarEnable = selectedTabbarStrategy < 2
// selectedTabbarStrategy==0 时,需要填 iconPath 和 selectedIconPath
// selectedTabbarStrategy==1 or 2 时,需要填 icon 和 iconType
// selectedTabbarStrategy==3 时tabbarList 不生效
export const tabbarList = [
{
iconPath: 'static/tabbar/home.png',
selectedIconPath: 'static/tabbar/homeHL.png',
pagePath: 'pages/index/index',
text: '首页',
icon: 'home',
iconType: 'wot',
},
{
iconPath: 'static/tabbar/example.png',
selectedIconPath: 'static/tabbar/exampleHL.png',
pagePath: 'pages/about/about',
text: '关于',
icon: 'i-carbon-code',
// 注意 unocss 的图标需要在 页面上引入一下,或者配置到 unocss.config.ts 的 safelist 中
iconType: 'unocss',
},
// {
// pagePath: 'pages/my/index',
// text: '我的',
// icon: '/static/logo.svg',
// iconType: 'local',
// },
// {
// pagePath: 'pages/mine/index',
// text: '我的',
// icon: 'iconfont icon-my',
// iconType: 'iconfont',
// },
]
const _tabbar = {
color: '#999999',
selectedColor: '#018d71',
backgroundColor: '#F8F8F8',
borderStyle: 'black',
height: '50px',
fontSize: '10px',
iconWidth: '24px',
spacing: '3px',
list: tabbarList,
}
// 0和1 需要显示底部的tabbar的各种配置以利用缓存
export const tabBar = cacheTabbarEnable ? _tabbar : undefined

19
src/layouts/tabbar.vue Normal file
View File

@ -0,0 +1,19 @@
<script lang="ts" setup>
import type { ConfigProviderThemeVars } from 'wot-design-uni'
import FgTabbar from './fg-tabbar/fg-tabbar.vue'
const themeVars: ConfigProviderThemeVars = {
// colorTheme: 'red',
// buttonPrimaryBgColor: '#07c160',
// buttonPrimaryColor: '#07c160',
}
</script>
<template>
<wd-config-provider :theme-vars="themeVars">
<slot />
<FgTabbar />
<wd-toast />
<wd-message-box />
</wd-config-provider>
</template>

View File

@ -1,11 +1,11 @@
import '@/style/index.scss'
import { VueQueryPlugin } from '@tanstack/vue-query' import { VueQueryPlugin } from '@tanstack/vue-query'
import 'uno.css'
import { createSSRApp } from 'vue' import { createSSRApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import { prototypeInterceptor, requestInterceptor, routeInterceptor } from './interceptors' import { prototypeInterceptor, requestInterceptor, routeInterceptor } from './interceptors'
import store from './store' import store from './store'
import '@/style/index.scss'
import 'virtual:uno.css'
export function createApp() { export function createApp() {
const app = createSSRApp(App) const app = createSSRApp(App)

View File

@ -1,6 +1,6 @@
{ {
"name": "unibest", "name": "unibest",
"appid": "H57F2ACE4", "appid": "__UNI__D1E5001",
"description": "", "description": "",
"versionName": "1.0.0", "versionName": "1.0.0",
"versionCode": "100", "versionCode": "100",
@ -85,7 +85,9 @@
"mp-weixin": { "mp-weixin": {
"appid": "wxa2abb91f64032a2b", "appid": "wxa2abb91f64032a2b",
"setting": { "setting": {
"urlCheck": false "urlCheck": false,
"es6": true,
"minified": true
}, },
"usingComponents": true "usingComponents": true
}, },

View File

@ -1,20 +1,27 @@
<route lang="json5" type="page"> <route lang="json5" type="page">
{ {
style: { navigationBarTitleText: '分包页面 标题' }, style: {
navigationStyle: 'default',
navigationBarTitleText: '分包页面 标题',
},
} }
</route> </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> <script lang="ts" setup>
// code here // code here
</script> </script>
<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>
<style lang="scss" scoped> <style lang="scss" scoped>
// //
</style> </style>

View File

@ -28,13 +28,17 @@
"iconPath": "static/tabbar/home.png", "iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/homeHL.png", "selectedIconPath": "static/tabbar/homeHL.png",
"pagePath": "pages/index/index", "pagePath": "pages/index/index",
"text": "首页" "text": "首页",
"icon": "home",
"iconType": "wot"
}, },
{ {
"iconPath": "static/tabbar/example.png", "iconPath": "static/tabbar/example.png",
"selectedIconPath": "static/tabbar/exampleHL.png", "selectedIconPath": "static/tabbar/exampleHL.png",
"pagePath": "pages/about/about", "pagePath": "pages/about/about",
"text": "关于" "text": "关于",
"icon": "i-carbon-code",
"iconType": "unocss"
} }
] ]
}, },
@ -42,6 +46,7 @@
{ {
"path": "pages/index/index", "path": "pages/index/index",
"type": "home", "type": "home",
"layout": "tabbar",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom",
"navigationBarTitleText": "首页" "navigationBarTitleText": "首页"
@ -50,6 +55,7 @@
{ {
"path": "pages/about/about", "path": "pages/about/about",
"type": "page", "type": "page",
"layout": "tabbar",
"style": { "style": {
"navigationBarTitleText": "关于" "navigationBarTitleText": "关于"
} }

View File

@ -1,38 +1,40 @@
<route lang="json5"> <route lang="json5">
{ {
layout: 'tabbar',
style: { style: {
navigationBarTitleText: '关于', navigationBarTitleText: '关于',
// navigationStyle: 'custom', //
}, },
} }
</route> </route>
<template>
<view>
<view
class="bg-white overflow-hidden pt-2 px-4"
:style="{ marginTop: safeAreaInsets?.top + 'px' }"
>
<view class="text-center text-3xl mt-8">
鸽友们好我是
<text class="text-red-500">菲鸽</text>
</view>
<sar-button>按钮</sar-button>
<view class="test-css">测试 scss 样式</view>
<RequestComp />
<UploadComp />
</view>
</view>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import RequestComp from './components/request.vue' import RequestComp from './components/request.vue'
import UploadComp from './components/upload.vue' import UploadComp from './components/upload.vue'
// //
const { safeAreaInsets } = uni.getSystemInfoSync() const { safeAreaInsets } = uni.getSystemInfoSync()
// vue .ts
// const testOxlint = (name: string) => {
// console.log('oxlint')
// }
// testOxlint('oxlint')
console.log('about')
</script> </script>
<template>
<view>
<view class="mt-8 text-center text-3xl">
鸽友们好我是
<text class="text-red-500">
菲鸽
</text>
</view>
<RequestComp />
<UploadComp />
</view>
</template>
<style lang="scss" scoped> <style lang="scss" scoped>
.test-css { .test-css {
// 16rpx=>0.5rem // 16rpx=>0.5rem

View File

@ -7,36 +7,9 @@
} }
</route> </route>
<template>
<view class="p-6 text-center">
<view class="my-2">使用的是 laf 云后台</view>
<view class="text-green-400">我的推荐码可以获得佣金</view>
<!-- #ifdef H5 -->
<view class="my-2">
<a class="my-2" :href="recommendUrl" target="_blank">{{ recommendUrl }}</a>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="my-2 text-left text-sm">{{ recommendUrl }}</view>
<!-- #endif -->
<!-- http://localhost:9000/#/pages/index/request -->
<sar-button @click="run" class="my-6">发送请求</sar-button>
<view class="h-16">
<view v-if="loading">loading...</view>
<block v-else>
<view class="text-xl">请求数据如下</view>
<view class="text-green leading-8">{{ JSON.stringify(data) }}</view>
</block>
</view>
<sar-button theme="danger" @click="reset" class="my-6" :disabled="!data">重置数据</sar-button>
</view>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { getFooAPI, postFooAPI, IFooItem } from '@/service/index/foo' import type { IFooItem } from '@/service/index/foo'
import { getFooAPI } from '@/service/index/foo'
// import { findPetsByStatusQueryOptions } from '@/service/app' // import { findPetsByStatusQueryOptions } from '@/service/app'
// import { useQuery } from '@tanstack/vue-query' // import { useQuery } from '@tanstack/vue-query'
@ -61,7 +34,51 @@ const { loading, error, data, run } = useRequest<IFooItem>(() => getFooAPI('菲
// refetch, // refetch,
// } = useQuery(findPetsByStatusQueryOptions({ params: { status: ['available'] } })) // } = useQuery(findPetsByStatusQueryOptions({ params: { status: ['available'] } }))
const reset = () => { function reset() {
data.value = initialData data.value = initialData
} }
</script> </script>
<template>
<view class="p-6 text-center">
<view class="my-2">
使用的是 laf 云后台
</view>
<view class="text-green-400">
我的推荐码可以获得佣金
</view>
<!-- #ifdef H5 -->
<view class="my-2">
<a class="my-2" :href="recommendUrl" target="_blank">{{ recommendUrl }}</a>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="my-2 text-left text-sm">
{{ recommendUrl }}
</view>
<!-- #endif -->
<!-- http://localhost:9000/#/pages/index/request -->
<sar-button class="my-6" @click="run">
发送请求
</sar-button>
<view class="h-16">
<view v-if="loading">
loading...
</view>
<block v-else>
<view class="text-xl">
请求数据如下
</view>
<view class="text-green leading-8">
{{ JSON.stringify(data) }}
</view>
</block>
</view>
<sar-button theme="danger" class="my-6" :disabled="!data" @click="reset">
重置数据
</sar-button>
</view>
</template>

View File

@ -7,24 +7,32 @@
} }
</route> </route>
<script lang="ts" setup>
const { loading, data, run } = useUpload()
</script>
<template> <template>
<view class="p-4 text-center"> <view class="p-4 text-center">
<sar-button @click="run">选择图片并上传</sar-button> <sar-button @click="run">
<view v-if="loading" class="text-blue h-10">上传...</view> 选择图片并上传
</sar-button>
<view v-if="loading" class="h-10 text-blue">
上传...
</view>
<template v-else> <template v-else>
<view class="m-2">上传后返回的接口数据</view> <view class="m-2">
<view class="m-2">{{ data }}</view> 上传后返回的接口数据
<view class="h-80 w-full"> </view>
<image v-if="data" :src="data || data" mode="scaleToFill" /> <view class="m-2">
{{ data }}
</view>
<view v-if="data" class="h-80 w-full">
<image :src="data.url" mode="scaleToFill" />
</view> </view>
</template> </template>
</view> </view>
</template> </template>
<script lang="ts" setup>
const { loading, data, run } = useUpload({ user: '菲鸽' })
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
// //
</style> </style>

View File

@ -1,34 +1,14 @@
<!-- 使用 type="home" 属性设置首页其他页面不需要设置默认为page推荐使用json5更强大且允许注释 --> <!-- 使用 type="home" 属性设置首页其他页面不需要设置默认为page推荐使用json5更强大且允许注释 -->
<route lang="json5" type="home"> <route lang="json5" type="home">
{ {
layout: 'tabbar',
style: { style: {
// 'custom' 'default'
navigationStyle: 'custom', navigationStyle: 'custom',
navigationBarTitleText: '首页', navigationBarTitleText: '首页',
}, },
} }
</route> </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-justify max-w-100 m-auto text-4 indent mb-2">{{ description }}</view>
<view class="text-center mt-8">
当前平台是
<text class="text-green-500">{{ PLATFORM.platform }}</text>
</view>
<view class="text-center mt-4">
模板分支是
<text class="text-green-500">base sard-ui</text>
</view>
</view>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import PLATFORM from '@/utils/platform' import PLATFORM from '@/utils/platform'
@ -38,19 +18,68 @@ defineOptions({
}) })
// //
const { safeAreaInsets } = uni.getSystemInfoSync() let safeAreaInsets
let systemInfo
// #ifdef MP-WEIXIN
// 使API
systemInfo = uni.getWindowInfo()
safeAreaInsets = systemInfo.safeArea
? {
top: systemInfo.safeArea.top,
right: systemInfo.windowWidth - systemInfo.safeArea.right,
bottom: systemInfo.windowHeight - systemInfo.safeArea.bottom,
left: systemInfo.safeArea.left,
}
: null
// #endif
// #ifndef MP-WEIXIN
// 使uni API
systemInfo = uni.getSystemInfoSync()
safeAreaInsets = systemInfo.safeAreaInsets
// #endif
const author = ref('菲鸽') const author = ref('菲鸽')
const description = ref( const description = ref(
'unibest 是一个集成了多种工具和技术的 uniapp 开发模板,由 uniapp + Vue3 + Ts + Vite6 + UnoCss + VSCode 构建,模板具有代码提示、自动格式化、统一配置、代码片段等功能,并内置了许多常用的基本组件和基本功能,让你编写 uniapp 拥有 best 体验。', 'unibest 是一个集成了多种工具和技术的 uniapp 开发模板,由 uniapp + Vue3 + Ts + Vite5 + UnoCss + VSCode 构建,模板具有代码提示、自动格式化、统一配置、代码片段等功能,并内置了许多常用的基本组件和基本功能,让你编写 uniapp 拥有 best 体验。',
) )
// uni API // uni API
onLoad(() => { onLoad(() => {
console.log('项目作者:', author.value) console.log('项目作者:', author.value)
}) })
console.log('index')
</script> </script>
<style> <template>
.main-title-color { <view
color: #d14328; class="overflow-hidden bg-white px-4 pt-2"
} :style="{ marginTop: `${safeAreaInsets?.top}px` }"
</style> >
<view class="mt-12">
<image src="/static/logo.svg" alt="" class="mx-auto block h-28 w-28" />
</view>
<view class="main-title-color mt-4 text-center text-4xl">
unibest
</view>
<view class="mb-8 mt-2 text-center text-2xl">
最好用的 uniapp 开发模板
</view>
<view class="m-auto mb-2 max-w-100 text-justify indent text-4">
{{ description }}
</view>
<view class="mt-8 text-center">
当前平台是
<text class="text-green-500">
{{ PLATFORM.platform }}
</text>
</view>
<view class="mt-4 text-center">
模板分支是
<text class="text-green-500">
base sard-ui
</text>
</view>
</view>
</template>

View File

@ -1,27 +1,28 @@
import { http } from '@/utils/http' import { http } from '@/utils/http'
export interface IFooItem { export interface IFooItem {
id: string id: string
name: string name: string
} }
/** GET 请求 */ /** GET 请求 */
export const getFooAPI = (name: string) => { export function getFooAPI(name: string) {
return http.get<IFooItem>('/foo', { name }) return http.get<IFooItem>('/foo', { name })
} }
/** GET 请求;支持 传递 header 的范例 */ /** GET 请求;支持 传递 header 的范例 */
export const getFooAPI2 = (name: string) => { export function getFooAPI2(name: string) {
return http.get<IFooItem>('/foo', { name }, { 'Content-Type-100': '100' }) return http.get<IFooItem>('/foo', { name }, { 'Content-Type-100': '100' })
} }
/** POST 请求 */ /** POST 请求 */
export const postFooAPI = (name: string) => { export function postFooAPI(name: string) {
return http.post<IFooItem>('/foo', { name }) return http.post<IFooItem>('/foo', { name })
} }
/** POST 请求;需要传递 query 参数的范例微信小程序经常有同时需要query参数和body参数的场景 */ /** POST 请求;需要传递 query 参数的范例微信小程序经常有同时需要query参数和body参数的场景 */
export const postFooAPI2 = (name: string) => { export function postFooAPI2(name: string) {
return http.post<IFooItem>('/foo', { name }) return http.post<IFooItem>('/foo', { name })
} }
/** POST 请求;支持 传递 header 的范例 */ /** POST 请求;支持 传递 header 的范例 */
export const postFooAPI3 = (name: string) => { export function postFooAPI3(name: string) {
return http.post<IFooItem>('/foo', { name }, { name }, { 'Content-Type-100': '100' }) return http.post<IFooItem>('/foo', { name }, { name }, { 'Content-Type-100': '100' })
} }

View File

@ -1,14 +1,14 @@
import { import type { IUserInfoVo } from '@/api/login.typings'
login as _login,
getUserInfo as _getUserInfo,
wxLogin as _wxLogin,
logout as _logout,
getWxCode,
} from '@/api/login'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
import {
getUserInfo as _getUserInfo,
login as _login,
logout as _logout,
wxLogin as _wxLogin,
getWxCode,
} from '@/api/login'
import { toast } from '@/utils/toast' import { toast } from '@/utils/toast'
import { IUserInfoVo } from '@/api/login.typings'
// 初始化状态 // 初始化状态
const userInfoState: IUserInfoVo = { const userInfoState: IUserInfoVo = {
@ -29,17 +29,35 @@ export const useUserStore = defineStore(
// 若头像为空 则使用默认头像 // 若头像为空 则使用默认头像
if (!val.avatar) { if (!val.avatar) {
val.avatar = userInfoState.avatar val.avatar = userInfoState.avatar
} else { }
else {
val.avatar = 'https://oss.laf.run/ukw0y1-site/avatar.jpg?feige' val.avatar = 'https://oss.laf.run/ukw0y1-site/avatar.jpg?feige'
} }
userInfo.value = val userInfo.value = val
} }
const setUserAvatar = (avatar: string) => {
userInfo.value.avatar = avatar
console.log('设置用户头像', avatar)
console.log('userInfo', userInfo.value)
}
// 删除用户信息 // 删除用户信息
const removeUserInfo = () => { const removeUserInfo = () => {
userInfo.value = { ...userInfoState } userInfo.value = { ...userInfoState }
uni.removeStorageSync('userInfo') uni.removeStorageSync('userInfo')
uni.removeStorageSync('token') uni.removeStorageSync('token')
} }
/**
*
*/
const getUserInfo = async () => {
const res = await _getUserInfo()
const userInfo = res.data
setUserInfo(userInfo)
uni.setStorageSync('userInfo', userInfo)
uni.setStorageSync('token', userInfo.token)
// TODO 这里可以增加获取用户路由的方法 根据用户的角色动态生成路由
return res
}
/** /**
* *
* @param credentials * @param credentials
@ -54,21 +72,10 @@ export const useUserStore = defineStore(
const res = await _login(credentials) const res = await _login(credentials)
console.log('登录信息', res) console.log('登录信息', res)
toast.success('登录成功') toast.success('登录成功')
getUserInfo() await getUserInfo()
return res
}
/**
*
*/
const getUserInfo = async () => {
const res = await _getUserInfo()
const userInfo = res.data
setUserInfo(userInfo)
uni.setStorageSync('userInfo', userInfo)
uni.setStorageSync('token', userInfo.token)
// TODO 这里可以增加获取用户路由的方法 根据用户的角色动态生成路由
return res return res
} }
/** /**
* 退 * 退
*/ */
@ -85,7 +92,7 @@ export const useUserStore = defineStore(
console.log('微信登录code', data) console.log('微信登录code', data)
const res = await _wxLogin(data) const res = await _wxLogin(data)
getUserInfo() await getUserInfo()
return res return res
} }
@ -94,6 +101,7 @@ export const useUserStore = defineStore(
login, login,
wxLogin, wxLogin,
getUserInfo, getUserInfo,
setUserAvatar,
logout, logout,
} }
}, },

View File

@ -1,4 +1,4 @@
// @import './iconfont.css'; @import './iconfont.css';
.test { .test {
// 可以通过 @apply 多个样式封装整体样式 // 可以通过 @apply 多个样式封装整体样式

6
src/typings.d.ts vendored
View File

@ -1,14 +1,14 @@
// 全局要用的类型放到这里 // 全局要用的类型放到这里
declare global { declare global {
type IResData<T> = { interface IResData<T> {
code: number code: number
msg: string msg: string
data: T data: T
} }
// uni.uploadFile文件上传参数 // uni.uploadFile文件上传参数
type IUniUploadFileOptions = { interface IUniUploadFileOptions {
file?: File file?: File
files?: UniApp.UploadFileOptionFiles[] files?: UniApp.UploadFileOptionFiles[]
filePath?: string filePath?: string
@ -16,7 +16,7 @@ declare global {
formData?: any formData?: any
} }
type IUserInfo = { interface IUserInfo {
nickname?: string nickname?: string
avatar?: string avatar?: string
/** 微信的 openid非微信没有这个字段 */ /** 微信的 openid非微信没有这个字段 */

View File

@ -6,7 +6,7 @@ export enum TestEnum {
} }
// uni.uploadFile文件上传参数 // uni.uploadFile文件上传参数
export type IUniUploadFileOptions = { export interface IUniUploadFileOptions {
file?: File file?: File
files?: UniApp.UploadFileOptionFiles[] files?: UniApp.UploadFileOptionFiles[]
filePath?: string filePath?: string

View File

@ -1,6 +1,6 @@
import { CustomRequestOptions } from '@/interceptors/request' import type { CustomRequestOptions } from '@/interceptors/request'
export const http = <T>(options: CustomRequestOptions) => { export function http<T>(options: CustomRequestOptions) {
// 1. 返回 Promise 对象 // 1. 返回 Promise 对象
return new Promise<IResData<T>>((resolve, reject) => { return new Promise<IResData<T>>((resolve, reject) => {
uni.request({ uni.request({
@ -15,15 +15,17 @@ export const http = <T>(options: CustomRequestOptions) => {
if (res.statusCode >= 200 && res.statusCode < 300) { if (res.statusCode >= 200 && res.statusCode < 300) {
// 2.1 提取核心数据 res.data // 2.1 提取核心数据 res.data
resolve(res.data as IResData<T>) resolve(res.data as IResData<T>)
} else if (res.statusCode === 401) { }
else if (res.statusCode === 401) {
// 401错误 -> 清理用户信息,跳转到登录页 // 401错误 -> 清理用户信息,跳转到登录页
// userStore.clearUserInfo() // userStore.clearUserInfo()
// uni.navigateTo({ url: '/pages/login/login' }) // uni.navigateTo({ url: '/pages/login/login' })
reject(res) reject(res)
} else { }
else {
// 其他错误 -> 根据后端错误信息轻提示 // 其他错误 -> 根据后端错误信息轻提示
!options.hideErrorToast && !options.hideErrorToast
uni.showToast({ && uni.showToast({
icon: 'none', icon: 'none',
title: (res.data as IResData<T>).msg || '请求错误', title: (res.data as IResData<T>).msg || '请求错误',
}) })
@ -49,16 +51,13 @@ export const http = <T>(options: CustomRequestOptions) => {
* @param header json格式 * @param header json格式
* @returns * @returns
*/ */
export const httpGet = <T>( export function httpGet<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
url: string,
query?: Record<string, any>,
header?: Record<string, any>,
) => {
return http<T>({ return http<T>({
url, url,
query, query,
method: 'GET', method: 'GET',
header, header,
...options,
}) })
} }
@ -70,51 +69,40 @@ export const httpGet = <T>(
* @param header json格式 * @param header json格式
* @returns * @returns
*/ */
export const httpPost = <T>( export function httpPost<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
url: string,
data?: Record<string, any>,
query?: Record<string, any>,
header?: Record<string, any>,
) => {
return http<T>({ return http<T>({
url, url,
query, query,
data, data,
method: 'POST', method: 'POST',
header, header,
...options,
}) })
} }
/** /**
* PUT * PUT
*/ */
export const httpPut = <T>( export function httpPut<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
url: string,
data?: Record<string, any>,
query?: Record<string, any>,
header?: Record<string, any>,
) => {
return http<T>({ return http<T>({
url, url,
data, data,
query, query,
method: 'PUT', method: 'PUT',
header, header,
...options,
}) })
} }
/** /**
* DELETE query * DELETE query
*/ */
export const httpDelete = <T>( export function httpDelete<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
url: string,
query?: Record<string, any>,
header?: Record<string, any>,
) => {
return http<T>({ return http<T>({
url, url,
query, query,
method: 'DELETE', method: 'DELETE',
header, header,
...options,
}) })
} }

View File

@ -1,9 +1,7 @@
import pagesConfig from '@/pages.json' import { pages, subPackages } from '@/pages.json'
import { isMpWeixin } from './platform' import { isMpWeixin } from './platform'
const { pages, subPackages, tabBar = { list: [] } } = { ...pagesConfig } export function getLastPage() {
export const getLastPage = () => {
// getCurrentPages() 至少有1个元素所以不再额外判断 // getCurrentPages() 至少有1个元素所以不再额外判断
// const lastPage = getCurrentPages().at(-1) // const lastPage = getCurrentPages().at(-1)
// 上面那个在低版本安卓中打包会报错,所以改用下面这个【虽然我加了 src/interceptions/prototype.ts但依然报错】 // 上面那个在低版本安卓中打包会报错,所以改用下面这个【虽然我加了 src/interceptions/prototype.ts但依然报错】
@ -11,46 +9,12 @@ export const getLastPage = () => {
return pages[pages.length - 1] return pages[pages.length - 1]
} }
export const tabBarList = tabBar?.list || []
/** 判断当前页面是否是 tabbar 页 */
export const getIsTabbar = () => {
try {
const lastPage = getLastPage()
const currPath = lastPage?.route
return Boolean(tabBar?.list?.some((item) => item.pagePath === currPath))
} catch {
return false
}
}
/**
* tabbar
* @param path
* @returns true: tabbar false: tabbar
*/
export const isTableBar = (path: string) => {
if (!tabBar) {
return false
}
if (!tabBar.list.length) {
// 通常有 tabBar 的话list 不能有空且至少有2个元素这里其实不用处理
return false
}
// 这里需要处理一下 path因为 tabBar 中的 pagePath 是不带 /pages 前缀的
if (path.startsWith('/')) {
path = path.substring(1)
}
return !!tabBar.list.find((e) => e.pagePath === path)
}
/** /**
* path redirectPath * path redirectPath
* path '/pages/login/index' * path '/pages/login/index'
* redirectPath '/pages/demo/base/route-interceptor' * redirectPath '/pages/demo/base/route-interceptor'
*/ */
export const currRoute = () => { export function currRoute() {
const lastPage = getLastPage() const lastPage = getLastPage()
const currRoute = (lastPage as any).$page const currRoute = (lastPage as any).$page
// console.log('lastPage.$page:', currRoute) // console.log('lastPage.$page:', currRoute)
@ -65,7 +29,7 @@ export const currRoute = () => {
return getUrlObj(fullPath) return getUrlObj(fullPath)
} }
const ensureDecodeURIComponent = (url: string) => { function ensureDecodeURIComponent(url: string) {
if (url.startsWith('%')) { if (url.startsWith('%')) {
return ensureDecodeURIComponent(decodeURIComponent(url)) return ensureDecodeURIComponent(decodeURIComponent(url))
} }
@ -76,7 +40,7 @@ const ensureDecodeURIComponent = (url: string) => {
* url: /pages/login/index?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor * url: /pages/login/index?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor
* : {path: /pages/login/index, query: {redirect: /pages/demo/base/route-interceptor}} * : {path: /pages/login/index, query: {redirect: /pages/demo/base/route-interceptor}}
*/ */
export const getUrlObj = (url: string) => { export function getUrlObj(url: string) {
const [path, queryStr] = url.split('?') const [path, queryStr] = url.split('?')
// console.log(path, queryStr) // console.log(path, queryStr)
@ -99,16 +63,15 @@ export const getUrlObj = (url: string) => {
* key needLogin, route-block 使 * key needLogin, route-block 使
* key pages key, key * key pages key, key
*/ */
export const getAllPages = (key = 'needLogin') => { export function getAllPages(key = 'needLogin') {
// 这里处理主包 // 这里处理主包
const mainPages = [ const mainPages = pages
...pages .filter(page => !key || page[key])
.filter((page) => !key || page[key]) .map(page => ({
.map((page) => ({
...page, ...page,
path: `/${page.path}`, path: `/${page.path}`,
})), }))
]
// 这里处理分包 // 这里处理分包
const subPages: any[] = [] const subPages: any[] = []
subPackages.forEach((subPageObj) => { subPackages.forEach((subPageObj) => {
@ -116,7 +79,7 @@ export const getAllPages = (key = 'needLogin') => {
const { root } = subPageObj const { root } = subPageObj
subPageObj.pages subPageObj.pages
.filter((page) => !key || page[key]) .filter(page => !key || page[key])
.forEach((page: { path: string } & Record<string, any>) => { .forEach((page: { path: string } & Record<string, any>) => {
subPages.push({ subPages.push({
...page, ...page,
@ -133,18 +96,18 @@ export const getAllPages = (key = 'needLogin') => {
* pages * pages
* path * path
*/ */
export const getNeedLoginPages = (): string[] => getAllPages('needLogin').map((page) => page.path) export const getNeedLoginPages = (): string[] => getAllPages('needLogin').map(page => page.path)
/** /**
* pages * pages
* path * path
*/ */
export const needLoginPages: string[] = getAllPages('needLogin').map((page) => page.path) export const needLoginPages: string[] = getAllPages('needLogin').map(page => page.path)
/** /**
* baseUrl * baseUrl
*/ */
export const getEnvBaseUrl = () => { export function getEnvBaseUrl() {
// 请求基准地址 // 请求基准地址
let baseUrl = import.meta.env.VITE_SERVER_BASEURL let baseUrl = import.meta.env.VITE_SERVER_BASEURL
@ -173,7 +136,7 @@ export const getEnvBaseUrl = () => {
/** /**
* UPLOAD_BASEURL * UPLOAD_BASEURL
*/ */
export const getEnvBaseUploadUrl = () => { export function getEnvBaseUploadUrl() {
// 请求基准地址 // 请求基准地址
let baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL let baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL

View File

@ -1,11 +1,11 @@
import { CustomRequestOptions } from '@/interceptors/request' import type { CustomRequestOptions } from '@/interceptors/request'
/** /**
* 请求方法: 主要是对 uni.request openapi-ts-request request * 请求方法: 主要是对 uni.request openapi-ts-request request
* @param options * @param options
* @returns Promise * @returns Promise
*/ */
const http = <T>(options: CustomRequestOptions) => { function http<T>(options: CustomRequestOptions) {
// 1. 返回 Promise 对象 // 1. 返回 Promise 对象
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
uni.request({ uni.request({
@ -20,15 +20,17 @@ const http = <T>(options: CustomRequestOptions) => {
if (res.statusCode >= 200 && res.statusCode < 300) { if (res.statusCode >= 200 && res.statusCode < 300) {
// 2.1 提取核心数据 res.data // 2.1 提取核心数据 res.data
resolve(res.data as T) resolve(res.data as T)
} else if (res.statusCode === 401) { }
else if (res.statusCode === 401) {
// 401错误 -> 清理用户信息,跳转到登录页 // 401错误 -> 清理用户信息,跳转到登录页
// userStore.clearUserInfo() // userStore.clearUserInfo()
// uni.navigateTo({ url: '/pages/login/login' }) // uni.navigateTo({ url: '/pages/login/login' })
reject(res) reject(res)
} else { }
else {
// 其他错误 -> 根据后端错误信息轻提示 // 其他错误 -> 根据后端错误信息轻提示
!options.hideErrorToast && !options.hideErrorToast
uni.showToast({ && uni.showToast({
icon: 'none', icon: 'none',
title: (res.data as T & { msg?: string })?.msg || '请求错误', title: (res.data as T & { msg?: string })?.msg || '请求错误',
}) })

View File

@ -21,8 +21,8 @@ export function showToast(options: ToastOptions | string) {
position: 'middle', position: 'middle',
message: '', message: '',
} }
const mergedOptions = const mergedOptions
typeof options === 'string' = typeof options === 'string'
? { ...defaultOptions, message: options } ? { ...defaultOptions, message: options }
: { ...defaultOptions, ...options } : { ...defaultOptions, ...options }
// 映射position到uniapp支持的格式 // 映射position到uniapp支持的格式

View File

@ -21,7 +21,7 @@ import { toast } from './toast'
*/ */
export const uploadFileUrl = { export const uploadFileUrl = {
/** 用户头像上传地址 */ /** 用户头像上传地址 */
USER_AVATAR: import.meta.env.VITE_SERVER_BASEURL + '/user/avatar', USER_AVATAR: `${import.meta.env.VITE_SERVER_BASEURL}/user/avatar`,
} }
/** /**
@ -31,12 +31,7 @@ export const uploadFileUrl = {
* @param formData * @param formData
* @param options * @param options
*/ */
export const useFileUpload = <T = string>( export function useFileUpload<T = string>(url: string, filePath: string, formData: Record<string, any> = {}, options: Omit<UploadOptions, 'sourceType' | 'sizeType' | 'count'> = {}) {
url: string,
filePath: string,
formData: Record<string, any> = {},
options: Omit<UploadOptions, 'sourceType' | 'sizeType' | 'count'> = {},
) => {
return useUpload<T>( return useUpload<T>(
url, url,
formData, formData,
@ -61,7 +56,7 @@ export interface UploadOptions {
/** 上传进度回调函数 */ /** 上传进度回调函数 */
onProgress?: (progress: number) => void onProgress?: (progress: number) => void
/** 上传成功回调函数 */ /** 上传成功回调函数 */
onSuccess?: (res: UniApp.UploadFileSuccessCallbackResult) => void onSuccess?: (res: Record<string, any>) => void
/** 上传失败回调函数 */ /** 上传失败回调函数 */
onError?: (err: Error | UniApp.GeneralCallbackResult) => void onError?: (err: Error | UniApp.GeneralCallbackResult) => void
/** 上传完成回调函数(无论成功失败) */ /** 上传完成回调函数(无论成功失败) */
@ -76,13 +71,9 @@ export interface UploadOptions {
* @param options * @param options
* @returns * @returns
*/ */
export const useUpload = <T = string>( export function useUpload<T = string>(url: string, formData: Record<string, any> = {}, options: UploadOptions = {},
url: string,
formData: Record<string, any> = {},
options: UploadOptions = {},
/** 直接传入文件路径,跳过选择器 */ /** 直接传入文件路径,跳过选择器 */
directFilePath?: string, directFilePath?: string) {
) => {
/** 上传中状态 */ /** 上传中状态 */
const loading = ref(false) const loading = ref(false)
/** 上传错误状态 */ /** 上传错误状态 */
@ -161,7 +152,8 @@ export const useUpload = <T = string>(
success: (res) => { success: (res) => {
const file = res.tempFiles[0] const file = res.tempFiles[0]
// 检查文件大小是否符合限制 // 检查文件大小是否符合限制
if (!checkFileSize(file.size)) return if (!checkFileSize(file.size))
return
// 开始上传 // 开始上传
loading.value = true loading.value = true
@ -248,7 +240,7 @@ interface UploadFileOptions<T> {
/** 上传进度回调 */ /** 上传进度回调 */
onProgress?: (progress: number) => void onProgress?: (progress: number) => void
/** 上传成功回调 */ /** 上传成功回调 */
onSuccess?: (res: UniApp.UploadFileSuccessCallbackResult) => void onSuccess?: (res: Record<string, any>) => void
/** 上传失败回调 */ /** 上传失败回调 */
onError?: (err: Error | UniApp.GeneralCallbackResult) => void onError?: (err: Error | UniApp.GeneralCallbackResult) => void
/** 上传完成回调 */ /** 上传完成回调 */
@ -288,20 +280,15 @@ function uploadFile<T>({
}, },
// 确保文件名称合法 // 确保文件名称合法
success: (uploadFileRes) => { success: (uploadFileRes) => {
console.log('上传文件成功:', uploadFileRes)
try { try {
// 解析响应数据 // 解析响应数据
const result = JSON.parse(uploadFileRes.data) const { data: _data } = JSON.parse(uploadFileRes.data)
if (result.code === 1) {
// 上传成功 // 上传成功
data.value = result.data as T data.value = _data as T
onSuccess?.(uploadFileRes) onSuccess?.(_data)
} else {
// 业务错误
const err = new Error(result.message || '上传失败')
error.value = true
onError?.(err)
} }
} catch (err) { catch (err) {
// 响应解析错误 // 响应解析错误
console.error('解析上传响应失败:', err) console.error('解析上传响应失败:', err)
error.value = true error.value = true
@ -326,7 +313,8 @@ function uploadFile<T>({
progress.value = res.progress progress.value = res.progress
onProgress?.(res.progress) onProgress?.(res.progress)
}) })
} catch (err) { }
catch (err) {
// 创建上传任务失败 // 创建上传任务失败
console.error('创建上传任务失败:', err) console.error('创建上传任务失败:', err)
error.value = true error.value = true

View File

@ -1,20 +1,15 @@
{ {
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"skipLibCheck": true, "lib": ["esnext", "dom"],
"baseUrl": ".",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "Node",
"resolveJsonModule": true,
"noImplicitThis": true,
"allowSyntheticDefaultImports": true,
"allowJs": true,
"sourceMap": true,
"baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"],
"@img/*": ["./src/static/*"]
}, },
"outDir": "dist", "resolveJsonModule": true,
"lib": ["esnext", "dom"],
"types": [ "types": [
"@dcloudio/types", "@dcloudio/types",
"@uni-helper/uni-types", "@uni-helper/uni-types",
@ -22,12 +17,17 @@
"sard-uniapp/global", "sard-uniapp/global",
"z-paging/types", "z-paging/types",
"./src/typings.d.ts" "./src/typings.d.ts"
] ],
"allowJs": true,
"noImplicitThis": true,
"outDir": "dist",
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
}, },
"vueCompilerOptions": { "vueCompilerOptions": {
"plugins": ["@uni-helper/uni-types/volar-plugin"] "plugins": ["@uni-helper/uni-types/volar-plugin"]
}, },
"exclude": ["node_modules"],
"include": [ "include": [
"src/**/*.ts", "src/**/*.ts",
"src/**/*.js", "src/**/*.js",
@ -36,5 +36,6 @@
"src/**/*.jsx", "src/**/*.jsx",
"src/**/*.vue", "src/**/*.vue",
"src/**/*.json" "src/**/*.json"
] ],
"exclude": ["node_modules"]
} }

View File

@ -1,20 +1,26 @@
// https://www.npmjs.com/package/@uni-helper/unocss-preset-uni
import { presetUni } from '@uni-helper/unocss-preset-uni' import { presetUni } from '@uni-helper/unocss-preset-uni'
import { import {
defineConfig, defineConfig,
presetIcons,
presetAttributify, presetAttributify,
presetIcons,
transformerDirectives, transformerDirectives,
transformerVariantGroup, transformerVariantGroup,
} from 'unocss' } from 'unocss'
export default defineConfig({ export default defineConfig({
presets: [ presets: [
presetUni(), presetUni({
attributify: {
// prefix: 'fg-', // 如果加前缀,则需要在代码里面使用 `fg-` 前缀,如:<div fg-border="1px solid #000"></div>
prefixedOnly: true,
},
}),
presetIcons({ presetIcons({
scale: 1.2, scale: 1.2,
warn: true, warn: true,
extraProperties: { extraProperties: {
display: 'inline-block', 'display': 'inline-block',
'vertical-align': 'middle', 'vertical-align': 'middle',
}, },
}), }),
@ -33,6 +39,7 @@ export default defineConfig({
center: 'flex justify-center items-center', center: 'flex justify-center items-center',
}, },
], ],
safelist: [],
rules: [ rules: [
[ [
'p-safe', 'p-safe',

View File

@ -1,5 +1,6 @@
import path from 'node:path'
import process from 'node:process'
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path'
export function copyNativeRes() { export function copyNativeRes() {
const waitPath = path.resolve(__dirname, '../src/nativeResources') const waitPath = path.resolve(__dirname, '../src/nativeResources')
@ -31,7 +32,8 @@ export function copyNativeRes() {
console.log( console.log(
`[copyNativeRes] 成功将 nativeResources 目录中的资源移动到构建目录:${buildPath}`, `[copyNativeRes] 成功将 nativeResources 目录中的资源移动到构建目录:${buildPath}`,
) )
} catch (error) { }
catch (error) {
console.error(`[copyNativeRes] 复制资源失败:`, error) console.error(`[copyNativeRes] 复制资源失败:`, error)
} }
}, },

View File

@ -0,0 +1,37 @@
// src/plugins/updatePackageJson.ts
import type { Plugin } from 'vite'
import fs from 'node:fs/promises'
import path from 'node:path'
import process from 'node:process'
function updatePackageJson(): Plugin {
return {
name: 'update-package-json',
async buildStart() {
// 只在生产环境构建时执行
if (process.env.NODE_ENV !== 'production')
return
const packageJsonPath = path.resolve(process.cwd(), 'package.json')
try {
// 读取并解析 package.json
const content = await fs.readFile(packageJsonPath, 'utf-8')
const packageJson = JSON.parse(content)
// 更新时间戳(使用 ISO 格式或自定义格式)
packageJson['update-time'] = new Date().toISOString().split('T')[0] // YYYY-MM-DD
// 写回文件(保持 2 空格缩进)
await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf-8')
console.log(`[update-package-json] 更新时间戳: ${packageJson['update-time']}`)
}
catch (error) {
console.error('[update-package-json] 插件执行失败:', error)
}
},
}
}
export default updatePackageJson

View File

@ -1,26 +1,27 @@
import Uni from '@dcloudio/vite-plugin-uni'
import dayjs from 'dayjs'
import path from 'node:path' import path from 'node:path'
import { defineConfig, loadEnv } from 'vite' import process from 'node:process'
// @see https://uni-helper.js.org/vite-plugin-uni-pages import Uni from '@dcloudio/vite-plugin-uni'
import UniPages from '@uni-helper/vite-plugin-uni-pages' import Components from '@uni-helper/vite-plugin-uni-components'
// @see https://uni-helper.js.org/vite-plugin-uni-layouts // @see https://uni-helper.js.org/vite-plugin-uni-layouts
import UniLayouts from '@uni-helper/vite-plugin-uni-layouts' import UniLayouts from '@uni-helper/vite-plugin-uni-layouts'
// @see https://github.com/uni-helper/vite-plugin-uni-manifest
import UniManifest from '@uni-helper/vite-plugin-uni-manifest'
// @see https://uni-helper.js.org/vite-plugin-uni-pages
import UniPages from '@uni-helper/vite-plugin-uni-pages'
// @see https://github.com/uni-helper/vite-plugin-uni-platform // @see https://github.com/uni-helper/vite-plugin-uni-platform
// 需要与 @uni-helper/vite-plugin-uni-pages 插件一起使用 // 需要与 @uni-helper/vite-plugin-uni-pages 插件一起使用
import UniPlatform from '@uni-helper/vite-plugin-uni-platform' 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://github.com/uni-ku/bundle-optimizer * @see https://github.com/uni-ku/bundle-optimizer
*/ */
import Optimization from '@uni-ku/bundle-optimizer' import Optimization from '@uni-ku/bundle-optimizer'
import dayjs from 'dayjs'
import { visualizer } from 'rollup-plugin-visualizer' import { visualizer } from 'rollup-plugin-visualizer'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import { defineConfig, loadEnv } from 'vite'
import ViteRestart from 'vite-plugin-restart' import ViteRestart from 'vite-plugin-restart'
import { copyNativeRes } from './vite-plugins/copyNativeRes' import updatePackageJson from './vite-plugins/updatePackageJson'
import Components from '@uni-helper/vite-plugin-uni-components'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default async ({ command, mode }) => { export default async ({ command, mode }) => {
@ -74,7 +75,7 @@ export default async ({ command, mode }) => {
// 自定义插件禁用 vite:vue 插件的 devToolsEnabled强制编译 vue 模板时 inline 为 true // 自定义插件禁用 vite:vue 插件的 devToolsEnabled强制编译 vue 模板时 inline 为 true
name: 'fix-vite-plugin-vue', name: 'fix-vite-plugin-vue',
configResolved(config) { configResolved(config) {
const plugin = config.plugins.find((p) => p.name === 'vite:vue') const plugin = config.plugins.find(p => p.name === 'vite:vue')
if (plugin && plugin.api && plugin.api.options) { if (plugin && plugin.api && plugin.api.options) {
plugin.api.options.devToolsEnabled = false plugin.api.options.devToolsEnabled = false
} }
@ -90,7 +91,7 @@ export default async ({ command, mode }) => {
// Optimization 插件需要 page.json 文件,故应在 UniPages 插件之后执行 // Optimization 插件需要 page.json 文件,故应在 UniPages 插件之后执行
Optimization({ Optimization({
enable: { enable: {
optimization: true, 'optimization': true,
'async-import': true, 'async-import': true,
'async-component': true, 'async-component': true,
}, },
@ -112,16 +113,16 @@ export default async ({ command, mode }) => {
}, },
}, },
// 打包分析插件h5 + 生产环境才弹出 // 打包分析插件h5 + 生产环境才弹出
UNI_PLATFORM === 'h5' && UNI_PLATFORM === 'h5'
mode === 'production' && && mode === 'production'
visualizer({ && visualizer({
filename: './node_modules/.cache/visualizer/stats.html', filename: './node_modules/.cache/visualizer/stats.html',
open: true, open: true,
gzipSize: true, gzipSize: true,
brotliSize: true, brotliSize: true,
}), }),
// 只有在 app 平台时才启用 copyNativeRes 插件 // 只有在 app 平台时才启用 copyNativeRes 插件
UNI_PLATFORM === 'app' && copyNativeRes(), // UNI_PLATFORM === 'app' && copyNativeRes(),
Components({ Components({
extensions: ['vue'], extensions: ['vue'],
deep: true, // 是否递归扫描子目录, deep: true, // 是否递归扫描子目录,
@ -129,6 +130,7 @@ export default async ({ command, mode }) => {
dts: 'src/types/components.d.ts', // 自动生成的组件类型声明文件路径(用于 TypeScript 支持) dts: 'src/types/components.d.ts', // 自动生成的组件类型声明文件路径(用于 TypeScript 支持)
}), }),
Uni(), Uni(),
updatePackageJson(),
], ],
define: { define: {
__UNI_PLATFORM__: JSON.stringify(UNI_PLATFORM), __UNI_PLATFORM__: JSON.stringify(UNI_PLATFORM),
@ -161,14 +163,15 @@ export default async ({ command, mode }) => {
[VITE_APP_PROXY_PREFIX]: { [VITE_APP_PROXY_PREFIX]: {
target: VITE_SERVER_BASEURL, target: VITE_SERVER_BASEURL,
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(new RegExp(`^${VITE_APP_PROXY_PREFIX}`), ''), rewrite: path => path.replace(new RegExp(`^${VITE_APP_PROXY_PREFIX}`), ''),
}, },
} }
: undefined, : undefined,
}, },
build: { build: {
sourcemap: false,
// 方便非h5端调试 // 方便非h5端调试
sourcemap: VITE_SHOW_SOURCEMAP === 'true', // 默认是false // sourcemap: VITE_SHOW_SOURCEMAP === 'true', // 默认是false
target: 'es6', target: 'es6',
// 开发环境不用压缩 // 开发环境不用压缩
minify: mode === 'development' ? false : 'terser', minify: mode === 'development' ? false : 'terser',