Compare commits

..

73 Commits
main ... spa

Author SHA1 Message Date
GitHub Actions
ade652c4b1 Auto merge base into spa 2025-06-21 02:14:34 +00:00
GitHub Actions
9952611dd1 Auto merge base into spa 2025-06-21 02:12:02 +00:00
GitHub Actions
741975114c Auto merge base into spa 2025-06-21 01:26:19 +00:00
GitHub Actions
7e4e5a19ad Auto merge base into spa 2025-06-21 01:19:11 +00:00
feige996
a6aeab23e1 Merge branch 'base' into spa 2025-06-20 14:47:01 +08:00
GitHub Actions
e60d924281 Auto merge base into spa 2025-06-16 01:53:26 +00:00
GitHub Actions
48a37ab36e Auto merge base into spa 2025-06-15 14:28:47 +00:00
GitHub Actions
9664664762 Auto merge base into spa 2025-06-15 09:04:45 +00:00
GitHub Actions
9c907d3cd2 Auto merge base into spa 2025-06-15 08:46:49 +00:00
GitHub Actions
f1833571bd Auto merge base into spa 2025-06-14 04:36:19 +00:00
GitHub Actions
1fbac1d13a Auto merge base into spa 2025-06-14 04:00:34 +00:00
GitHub Actions
533f968a7f Auto merge base into spa 2025-06-14 03:13:51 +00:00
GitHub Actions
1784880890 Auto merge base into spa 2025-06-14 02:46:13 +00:00
GitHub Actions
13be95ad42 Auto merge base into spa 2025-06-13 10:34:51 +00:00
GitHub Actions
03071099a7 Auto merge base into spa 2025-06-13 10:33:45 +00:00
GitHub Actions
cd1cff5e4b Auto merge base into spa 2025-06-13 09:01:21 +00:00
GitHub Actions
9ed17c5acb Auto merge base into spa 2025-06-13 09:00:53 +00:00
GitHub Actions
5dcaf77949 Auto merge base into spa 2025-06-13 08:47:43 +00:00
GitHub Actions
b8d0133200 Auto merge base into spa 2025-06-13 08:38:56 +00:00
GitHub Actions
76f21fa406 Auto merge base into spa 2025-06-11 15:39:50 +00:00
GitHub Actions
5e79debf2c Auto merge base into spa 2025-06-10 09:14:21 +00:00
GitHub Actions
51ff0413c0 Auto merge base into spa 2025-06-06 15:46:16 +00:00
GitHub Actions
3cc8c43ff5 Auto merge base into spa 2025-06-06 14:59:38 +00:00
GitHub Actions
d56ba37c6d Auto merge base into spa 2025-06-06 14:42:49 +00:00
GitHub Actions
0700391fe2 Auto merge base into spa 2025-06-06 04:45:19 +00:00
GitHub Actions
1cc6ba0e8c Auto merge base into spa 2025-06-06 04:41:25 +00:00
GitHub Actions
b1535fdae5 Auto merge base into spa 2025-06-05 14:56:48 +00:00
GitHub Actions
6c9fb3ff51 Auto merge base into spa 2025-06-04 14:44:05 +00:00
feige996
d4d2608c46 Merge branch 'spa' of https://gitee.com/feige996/unibest into spa 2025-06-04 17:56:27 +08:00
GitHub Actions
cd52284a5b Auto merge base into spa 2025-06-04 09:41:11 +00:00
GitHub Actions
d1d00b7f75 Auto merge base into spa 2025-06-04 07:20:26 +00:00
GitHub Actions
e9f81aa347 Auto merge base into spa 2025-06-04 01:03:41 +00:00
GitHub Actions
a605313eb9 Auto merge base into spa 2025-06-04 01:03:10 +00:00
GitHub Actions
f23ff75a68 Auto merge base into spa 2025-06-04 01:00:58 +00:00
GitHub Actions
f4207824bf Auto merge base into spa 2025-06-03 12:04:11 +00:00
GitHub Actions
b240690ea8 Auto merge base into spa 2025-06-03 11:51:52 +00:00
GitHub Actions
9aacf11a63 Auto merge base into spa 2025-06-03 11:50:31 +00:00
GitHub Actions
1d35a08e11 Auto merge base into spa 2025-06-03 11:43:13 +00:00
GitHub Actions
a31ceadf7b Auto merge base into spa 2025-06-03 10:12:13 +00:00
GitHub Actions
9b0eb79074 Auto merge base into spa 2025-06-03 09:48:13 +00:00
GitHub Actions
32d9c1b64a Auto merge base into spa 2025-06-03 09:26:52 +00:00
GitHub Actions
93ad9785c6 Auto merge base into spa 2025-06-03 06:49:14 +00:00
feige996
2889a7055b fix: 更新应用ID并禁用app平台的copyNativeRes插件
更新manifest.json和.env文件中的应用ID为'__UNI__D1E5001'以匹配新配置
注释掉vite.config.ts中app平台的copyNativeRes插件调用以优化构建流程
2025-06-03 14:38:52 +08:00
feige996
381343a4cf Merge commit 'de55a88d0ea187b6a01613e10050c580d05e3758' into spa 2025-06-03 12:56:04 +08:00
GitHub Actions
c78f927bb8 Auto merge base into spa 2025-06-03 02:20:33 +00:00
GitHub Actions
d2b4d7f3c4 Auto merge base into spa 2025-06-03 02:14:09 +00:00
GitHub Actions
5603dc4aa0 Auto merge base into spa 2025-06-03 01:28:54 +00:00
GitHub Actions
5ee75213d5 Auto merge base into spa 2025-06-03 01:17:53 +00:00
GitHub Actions
0f104bedff Auto merge base into spa 2025-05-30 03:17:55 +00:00
GitHub Actions
af29c1991d Auto merge base into spa 2025-05-28 08:18:01 +00:00
GitHub Actions
1239c5e89a Auto merge base into spa 2025-05-28 07:21:17 +00:00
GitHub Actions
54e67c43c4 Auto merge base into spa 2025-05-28 07:15:44 +00:00
GitHub Actions
817b6c4a81 Auto merge base into spa 2025-05-28 03:40:46 +00:00
feige996
bfdab82d39 Merge branch 'base' into spa 2025-05-28 11:35:23 +08:00
feige996
566e5c6031 fix: 移除自定义导航栏样式并更新模板分支显示
- 移除关于页面的自定义导航栏配置及相关代码
- 将首页显示的模板分支从"base"更新为"spa"
- 清理了不再需要的导航栏组件注释代码
```

这个提交消息:
1. 使用"fix"类型,因为这些修改主要是修复/清理性质的
2. 省略了scope,因为修改涉及多个文件/功能
3. 描述简洁说明了主要修改内容
4. 在body中列出了具体的修改点,没有重复描述中的信息
5. 使用了中文并保持简洁明了
6. 遵循了50字符限制和祈使语气的要求
2025-05-28 10:08:33 +08:00
feige996
08f3f1ae9b Merge branch 'base' into spa 2025-05-28 00:59:50 +08:00
feige996
938ca363e5 Merge branch 'base' into spa 2025-05-28 00:53:33 +08:00
GitHub Actions
b7b0f04557 Auto merge base into spa 2025-05-27 08:55:54 +00:00
feige996
ba11c710c0 Merge branch 'base' into spa 2025-05-27 16:10:13 +08:00
feige996
27f23173fd feat(tabbar): 实现新的底部导航栏布局和功能
- 将原有tabbar组件重构为layout布局方式
- 新增tabbar store管理导航项状态和路由
- 为首页和关于页添加tabbar布局配置
- 移除旧的tabbar组件实现
- 添加tabbar路由跳转功能
- 更新类型定义以支持新组件
```

这个提交消息:
1. 使用feat类型,因为这是新增功能
2. 添加了scope(tabbar)明确修改范围
3. 简明描述了主要变更内容
4. 使用中文符合要求
5. 保持了50字符以内的描述行
6. 在正文中列出了主要变更点而不重复描述
2025-05-27 00:22:40 +08:00
feige996
e123a5cb1b feat(store): 添加tabbar模块并导出
新增tabbar状态管理模块,包含tabbar项的状态、getters和actions
在store统一导出文件中添加tabbar模块的导出
2025-05-26 23:43:15 +08:00
feige996
cfced2e6d8 refactor: 移除tabbar相关配置和逻辑
- 删除pages.config.ts和pages.json中的tabBar配置
- 移除default.vue中与tabbar相关的逻辑代码
- 更新类型声明文件中的SwitchTabOptions接口
- 添加Tabbar组件到全局组件声明
```

这个提交消息:
1. 使用了`refactor`类型,因为这是代码重构,不改变功能但优化了代码结构
2. 简洁地描述了主要变更内容
3. 在消息体中列出了具体的修改点
4. 使用了中文并保持简洁明了
5. 遵循了50字符限制和祈使语气的要求
2025-05-26 23:40:38 +08:00
feige996
f91fff8004 Merge branch 'base' into spa 2025-05-26 23:31:35 +08:00
GitHub Actions
57addd9531 Auto merge base into spa 2025-05-22 12:02:39 +00:00
GitHub Actions
c3a26b0df9 Auto merge base into spa 2025-05-21 02:17:03 +00:00
GitHub Actions
b9ee449c5c Auto merge base into spa 2025-05-19 07:12:12 +00:00
feige996
2c404a072b chore: / 2025-05-19 14:56:47 +08:00
feige996
c115cb8ab4 Merge branch 'base' into spa 2025-05-19 14:52:56 +08:00
GitHub Actions
61264eba09 Auto merge base into spa 2025-04-22 06:47:50 +00:00
laifeipeng
1693727f56 Merge branch 'base' into spa 2025-04-22 14:45:51 +08:00
laifeipeng
fd64936e36 feat: 去掉 tabbar 2025-04-22 14:44:16 +08:00
ygytsyjj
775690c405 feat(tabbar): 测试提交 2025-01-02 17:17:24 +08:00
ygytsyjj
bff59b33b1 feat(tabbar): test 2024-12-31 15:26:28 +08:00
57 changed files with 880 additions and 2963 deletions

View File

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

2
.gitignore vendored
View File

@ -27,7 +27,7 @@ dist
docs/.vitepress/dist docs/.vitepress/dist
docs/.vitepress/cache docs/.vitepress/cache
src/types types
# lock 文件还是不要了,我主要的版本写死就好了 # lock 文件还是不要了,我主要的版本写死就好了
# pnpm-lock.yaml # pnpm-lock.yaml

34
.oxlintrc.json Normal file
View File

@ -0,0 +1,34 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"extends": ["config:recommended"],
"plugins": ["import", "typescript", "unicorn"],
"rules": {
"no-console": "off",
"no-unused-vars": "off"
},
"env": {
"es6": true
},
"globals": {
"foo": "readonly"
},
"ignorePatterns": [
"node_modules",
"dist",
"src/static/**",
"src/uni_modules/**",
"vite.config.ts",
"uno.config.ts",
"pages.config.ts",
"manifest.config.ts"
],
"settings": {},
"overrides": [
{
"files": ["*.test.ts", "*.spec.ts"],
"rules": {
"@typescript-eslint/no-explicit-any": "off"
}
}
]
}

76
.vscode/settings.json vendored
View File

@ -1,7 +1,14 @@
{ {
// 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,7 +48,6 @@
"tabbar", "tabbar",
"Toutiao", "Toutiao",
"unibest", "unibest",
"uview",
"uvui", "uvui",
"Wechat", "Wechat",
"WechatMiniprogram", "WechatMiniprogram",
@ -53,67 +59,7 @@
"explorer.fileNesting.patterns": { "explorer.fileNesting.patterns": {
"README.md": "index.html,favicon.ico,robots.txt,CHANGELOG.md", "README.md": "index.html,favicon.ico,robots.txt,CHANGELOG.md",
"pages.config.ts": "manifest.config.ts,openapi-ts-request.config.ts", "pages.config.ts": "manifest.config.ts,openapi-ts-request.config.ts",
"package.json": "tsconfig.json,pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc", "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc",
"eslint.config.mjs": ".commitlintrc.*,.prettier*,.editorconfig,.commitlint.cjs,.eslint*" ".oxlintrc.json": "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

@ -27,12 +27,12 @@
" },", " },",
"}", "}",
"</route>\n", "</route>\n",
"<script lang=\"ts\" setup>",
"//$3",
"</script>\n",
"<template>", "<template>",
" <view class=\"\">$2</view>", " <view class=\"\">$2</view>",
"</template>\n", "</template>\n",
"<script lang=\"ts\" setup>",
"//$3",
"</script>\n",
"<style lang=\"scss\" scoped>", "<style lang=\"scss\" scoped>",
"//$4", "//$4",
"</style>\n", "</style>\n",
@ -41,28 +41,16 @@
"Print unibest style": { "Print unibest style": {
"scope": "vue", "scope": "vue",
"prefix": "st", "prefix": "st",
"body": [ "body": ["<style lang=\"scss\" scoped>", "//", "</style>\n"],
"<style lang=\"scss\" scoped>",
"//",
"</style>\n"
],
}, },
"Print unibest script": { "Print unibest script": {
"scope": "vue", "scope": "vue",
"prefix": "sc", "prefix": "sc",
"body": [ "body": ["<script lang=\"ts\" setup>", "//$3", "</script>\n"],
"<script lang=\"ts\" setup>",
"//$3",
"</script>\n"
],
}, },
"Print unibest template": { "Print unibest template": {
"scope": "vue", "scope": "vue",
"prefix": "te", "prefix": "te",
"body": [ "body": ["<template>", " <view class=\"\">$1</view>", "</template>\n"],
"<template>",
" <view class=\"\">$1</view>",
"</template>\n"
],
}, },
} }

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` 然后打开微信开发者工具,导入本地文件夹,选择本项目的`dist/dev/mp-weixin` 文件。 - weixin平台`pnpm dev:mp-weixin` 然后打开微信开发者工具,导入本地文件夹,选择本项目的`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`, 打包后的文件在 `dist/build/mp-weixin`,然后通过微信开发者工具导入,并点击右上角的“上传”按钮进行上传。 - weixin平台`pnpm build:mp-weixin`, 打包后的文件在 `dist/build/mp-weixin`,然后通过微信开发者工具导入,并点击右上角的“上传”按钮进行上传。
- APP平台`pnpm build:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/build/app` 文件夹,选择发行 - APP云打包。 - APP平台`pnpm build:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/build/app` 文件夹,选择发行 - APP云打包。
## 📄 License ## 📄 License

View File

@ -1,22 +0,0 @@
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,7 +1,6 @@
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'
// 获取环境变量的范例 // 获取环境变量的范例
@ -15,14 +14,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,
}, },
@ -83,14 +82,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: {
@ -108,7 +107,7 @@ export default defineManifestConfig({
}, },
}, },
/* 快应用特有相关 */ /* 快应用特有相关 */
'quickapp': {}, quickapp: {},
/* 小程序特有相关 */ /* 小程序特有相关 */
'mp-weixin': { 'mp-weixin': {
appid: VITE_WX_APPID, appid: VITE_WX_APPID,
@ -131,8 +130,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": "3.1.0", "version": "2.14.0",
"description": "unibest - 最好的 uniapp 开发模板", "description": "unibest - 最好的 uniapp 开发模板",
"update-time": "2025-06-21", "update-time": "2025-06-17",
"author": { "author": {
"name": "feige996", "name": "feige996",
"zhName": "菲鸽", "zhName": "菲鸽",
@ -11,8 +11,8 @@
"github": "https://github.com/feige996", "github": "https://github.com/feige996",
"gitee": "https://gitee.com/feige996" "gitee": "https://gitee.com/feige996"
}, },
"license": "MIT",
"homepage": "https://unibest.tech", "homepage": "https://unibest.tech",
"license": "MIT",
"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-old": "https://github.com/codercup/unibest", "repository-old": "https://github.com/codercup/unibest",
@ -72,8 +72,21 @@
"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": "oxlint",
"lint:fix": "eslint --fix" "lint-fix": "oxlint --fix"
},
"lint-staged": {
"**/*.{html,cjs,json,md,scss,css,txt}": [
"prettier --write --cache"
],
"**/*.{js,jsx,ts,tsx,vue,mjs,cjs,mts,cts}": [
"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",
@ -98,12 +111,12 @@
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"pinia": "2.0.36", "pinia": "2.0.36",
"pinia-plugin-persistedstate": "3.2.1", "pinia-plugin-persistedstate": "3.2.1",
"qs": "6.5.3",
"vue": "^3.4.21", "vue": "^3.4.21",
"wot-design-uni": "^1.9.1", "wot-design-uni": "^1.9.1",
"z-paging": "2.8.7" "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",
@ -111,13 +124,12 @@
"@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.20.2", "@esbuild/darwin-arm64": "0.25.5",
"@esbuild/darwin-x64": "0.20.2", "@esbuild/darwin-x64": "0.25.5",
"@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",
@ -126,15 +138,14 @@
"@uni-helper/vite-plugin-uni-pages": "0.2.28", "@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": "1.0.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",
@ -148,11 +159,5 @@
"vite": "5.2.8", "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,5 +1,4 @@
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: {
@ -18,6 +17,5 @@ export default defineUniPages({
'z-paging/components/z-paging$1/z-paging$1.vue', 'z-paging/components/z-paging$1/z-paging$1.vue',
}, },
}, },
// tabbar 的配置统一在 “./src/layouts/fg-tabbar/tabbarList.ts” 文件中 // 如果不需要tabBar可以注释掉这个配置或者直接删除
tabBar: tabBar as any,
}) })

2254
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import type { ICaptcha, IUpdateInfo, IUpdatePassword, IUserInfoVo, IUserLogin } from './types/login' import { 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 function getCode() { export const getCode = () => {
return http.get<ICaptcha>('/user/getCode') return http.get<ICaptcha>('/user/getCode')
} }
@ -23,35 +23,35 @@ export function getCode() {
* *
* @param loginForm * @param loginForm
*/ */
export function login(loginForm: ILoginForm) { export const login = (loginForm: ILoginForm) => {
return http.post<IUserLogin>('/user/login', loginForm) return http.post<IUserLogin>('/user/login', loginForm)
} }
/** /**
* *
*/ */
export function getUserInfo() { export const getUserInfo = () => {
return http.get<IUserInfoVo>('/user/info') return http.get<IUserInfoVo>('/user/info')
} }
/** /**
* 退 * 退
*/ */
export function logout() { export const logout = () => {
return http.get<void>('/user/logout') return http.get<void>('/user/logout')
} }
/** /**
* *
*/ */
export function updateInfo(data: IUpdateInfo) { export const updateInfo = (data: IUpdateInfo) => {
return http.post('/user/updateInfo', data) return http.post('/user/updateInfo', data)
} }
/** /**
* *
*/ */
export function updateUserPassword(data: IUpdatePassword) { export const updateUserPassword = (data: IUpdatePassword) => {
return http.post('/user/updatePassword', data) return http.post('/user/updatePassword', data)
} }
@ -59,12 +59,12 @@ export function updateUserPassword(data: IUpdatePassword) {
* *
* @returns Promise (code) * @returns Promise (code)
*/ */
export function getWxCode() { export const 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 function getWxCode() {
* @param params code * @param params code
* @returns Promise * @returns Promise
*/ */
export function wxLogin(data: { code: string }) { export const 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 interface IUserInfoVo { export type IUserInfoVo = {
id: number id: number
username: string username: string
avatar: string avatar: string
@ -11,7 +11,7 @@ export interface IUserInfoVo {
/** /**
* *
*/ */
export interface IUserLogin { export type IUserLogin = {
id: string id: string
username: string username: string
token: string token: string
@ -20,7 +20,7 @@ export interface IUserLogin {
/** /**
* *
*/ */
export interface ICaptcha { export type ICaptcha = {
captchaEnabled: boolean captchaEnabled: boolean
uuid: string uuid: string
image: string image: string
@ -28,7 +28,7 @@ export interface ICaptcha {
/** /**
* *
*/ */
export interface IUploadSuccessInfo { export type IUploadSuccessInfo = {
fileId: number fileId: number
originalName: string originalName: string
fileName: string fileName: string
@ -41,7 +41,7 @@ export interface IUploadSuccessInfo {
/** /**
* *
*/ */
export interface IUpdateInfo { export type IUpdateInfo = {
id: number id: number
name: string name: string
sex: string sex: string
@ -49,7 +49,7 @@ export interface IUpdateInfo {
/** /**
* *
*/ */
export interface IUpdatePassword { export type IUpdatePassword = {
id: number id: number
oldPassword: string oldPassword: string
newPassword: string newPassword: string

7
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 type { DefineComponent } from 'vue' import { 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
} }
@ -29,6 +29,3 @@ interface ImportMetaEnv {
interface ImportMeta { interface ImportMeta {
readonly env: ImportMetaEnv readonly env: ImportMetaEnv
} }
declare const __VITE_APP_PROXY__: 'true' | 'false'
declare const __UNI_PLATFORM__: 'app' | 'h5' | 'mp-alipay' | 'mp-baidu' | 'mp-kuaishou' | 'mp-lark' | 'mp-qq' | 'mp-tiktok' | 'mp-weixin' | 'mp-xiaochengxu'

View File

@ -1,10 +1,10 @@
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { useUserStore } from '@/store'
import { needLoginPages as _needLoginPages, getNeedLoginPages } from '@/utils' import { needLoginPages as _needLoginPages, getNeedLoginPages } from '@/utils'
import { useUserStore } from '@/store'
const loginRoute = import.meta.env.VITE_LOGIN_URL const loginRoute = import.meta.env.VITE_LOGIN_URL
const isDev = import.meta.env.DEV const isDev = import.meta.env.DEV
function isLogined() { const isLogined = () => {
const userStore = useUserStore() const userStore = useUserStore()
return !!userStore.userInfo.username return !!userStore.userInfo.username
} }
@ -20,8 +20,7 @@ export function usePageAuth() {
let needLoginPages: string[] = [] let needLoginPages: string[] = []
if (isDev) { if (isDev) {
needLoginPages = getNeedLoginPages() needLoginPages = getNeedLoginPages()
} } else {
else {
needLoginPages = _needLoginPages needLoginPages = _needLoginPages
} }

View File

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

View File

@ -7,7 +7,7 @@ type TfileType = 'image' | 'file'
type TImage = 'png' | 'jpg' | 'jpeg' | 'webp' | '*' type TImage = 'png' | 'jpg' | 'jpeg' | 'webp' | '*'
type TFile = 'doc' | 'docx' | 'ppt' | 'zip' | 'xls' | 'xlsx' | 'txt' | TImage type TFile = 'doc' | 'docx' | 'ppt' | 'zip' | 'xls' | 'xlsx' | 'txt' | TImage
interface TOptions<T extends TfileType> { type TOptions<T extends TfileType> = {
formData?: Record<string, any> formData?: Record<string, any>
maxSize?: number maxSize?: number
accept?: T extends 'image' ? TImage[] : TFile[] accept?: T extends 'image' ? TImage[] : TFile[]
@ -30,46 +30,6 @@ export default function useUpload<T extends TfileType>(options: TOptions<T> = {}
const error = ref<Error | null>(null) const error = ref<Error | null>(null)
const data = ref<any>(null) 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 = () => {
// 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。 // 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。
// 微信小程序在2023年10月17日之后使用本API需要配置隐私协议 // 微信小程序在2023年10月17日之后使用本API需要配置隐私协议
@ -77,19 +37,17 @@ export default function useUpload<T extends TfileType>(options: TOptions<T> = {}
count: 1, count: 1,
success: (res: any) => { success: (res: any) => {
console.log('File selected successfully:', res) console.log('File selected successfully:', res)
// 小程序中res:{errMsg: "chooseImage:ok", tempFiles: [{fileType: "image", size: 48976, tempFilePath: "http://tmp/5iG1WpIxTaJf3ece38692a337dc06df7eb69ecb49c6b.jpeg"}]}
// h5中res:{errMsg: "chooseImage:ok", tempFilePaths: "blob:http://localhost:9000/f74ab6b8-a14d-4cb6-a10d-fcf4511a0de5", tempFiles: [File]} // h5中res:{errMsg: "chooseImage:ok", tempFilePaths: "blob:http://localhost:9000/f74ab6b8-a14d-4cb6-a10d-fcf4511a0de5", tempFiles: [File]}
// h5的File有以下字段{name: "girl.jpeg", size: 48976, type: "image/jpeg"} // 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]} // 小程序中res:{errMsg: "chooseImage:ok", tempFiles: [{fileType: "image", size: 48976, tempFilePath: "http://tmp/5iG1WpIxTaJf3ece38692a337dc06df7eb69ecb49c6b.jpeg"}]}
// App的File有以下字段{path: "file:///Users/feige/xxx/gallery/1522437259-compressed-IMG_0006.jpg", size: 48976}
let tempFilePath = '' let tempFilePath = ''
let size = 0 let size = 0
// #ifdef MP-WEIXIN // #ifdef H5
tempFilePath = res.tempFiles[0].tempFilePath tempFilePath = res.tempFilePaths[0]
size = res.tempFiles[0].size size = res.tempFiles[0].size
// #endif // #endif
// #ifndef MP-WEIXIN // #ifdef MP-WEIXIN
tempFilePath = res.tempFilePaths[0] tempFilePath = res.tempFiles[0].tempFilePath
size = res.tempFiles[0].size size = res.tempFiles[0].size
// #endif // #endif
handleFileChoose({ tempFilePath, size }) handleFileChoose({ tempFilePath, size })
@ -112,8 +70,7 @@ export default function useUpload<T extends TfileType>(options: TOptions<T> = {}
// #ifndef MP-WEIXIN // #ifndef MP-WEIXIN
uni.chooseImage(chooseFileOptions) uni.chooseImage(chooseFileOptions)
// #endif // #endif
} } else {
else {
uni.chooseFile({ uni.chooseFile({
...chooseFileOptions, ...chooseFileOptions,
type: 'all', type: 'all',
@ -121,6 +78,46 @@ export default function useUpload<T extends TfileType>(options: TOptions<T> = {}
} }
} }
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: 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
},
})
}
return { loading, error, data, run } return { loading, error, data, run }
} }
@ -146,8 +143,7 @@ async function uploadFile({
try { try {
const data = uploadFileRes.data const data = uploadFileRes.data
onSuccess(data) onSuccess(data)
} } catch (err) {
catch (err) {
onError(err) onError(err)
} }
}, },

View File

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

View File

@ -2,11 +2,10 @@ 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) if (index < 0) return this[this.length + index]
return this[this.length + index] if (index >= this.length) return undefined
if (index >= this.length)
return undefined
return this[index] return this[index]
} }
} }

View File

@ -1,7 +1,8 @@
/* eslint-disable no-param-reassign */
import qs from 'qs'
import { useUserStore } from '@/store' import { useUserStore } from '@/store'
import { getEnvBaseUrl } from '@/utils'
import { platform } from '@/utils/platform' import { platform } from '@/utils/platform'
import { stringifyQuery } from '@/utils/queryString' import { getEnvBaseUrl } from '@/utils'
export type CustomRequestOptions = UniApp.RequestOptions & { export type CustomRequestOptions = UniApp.RequestOptions & {
query?: Record<string, any> query?: Record<string, any>
@ -18,11 +19,10 @@ const httpInterceptor = {
invoke(options: CustomRequestOptions) { invoke(options: CustomRequestOptions) {
// 接口请求支持通过 query 参数配置 queryString // 接口请求支持通过 query 参数配置 queryString
if (options.query) { if (options.query) {
const queryStr = stringifyQuery(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,8 +33,7 @@ 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, getLastPage, getNeedLoginPages } from '@/utils' import { needLoginPages as _needLoginPages, getNeedLoginPages, getLastPage } from '@/utils'
// TODO Check // TODO Check
const loginRoute = import.meta.env.VITE_LOGIN_URL const loginRoute = import.meta.env.VITE_LOGIN_URL
function isLogined() { const isLogined = () => {
const userStore = useUserStore() const userStore = useUserStore()
return !!userStore.userInfo.username return !!userStore.userInfo.username
} }
@ -37,8 +37,7 @@ 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,3 +1,11 @@
<template>
<wd-config-provider :themeVars="themeVars">
<slot />
<wd-toast />
<wd-message-box />
</wd-config-provider>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import type { ConfigProviderThemeVars } from 'wot-design-uni' import type { ConfigProviderThemeVars } from 'wot-design-uni'
@ -7,11 +15,3 @@ const themeVars: ConfigProviderThemeVars = {
// buttonPrimaryColor: '#07c160', // buttonPrimaryColor: '#07c160',
} }
</script> </script>
<template>
<wd-config-provider :theme-vars="themeVars">
<slot />
<wd-toast />
<wd-message-box />
</wd-config-provider>
</template>

View File

@ -1,3 +1,11 @@
<template>
<wd-config-provider :themeVars="themeVars">
<slot />
<wd-toast />
<wd-message-box />
</wd-config-provider>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import type { ConfigProviderThemeVars } from 'wot-design-uni' import type { ConfigProviderThemeVars } from 'wot-design-uni'
@ -7,11 +15,3 @@ const themeVars: ConfigProviderThemeVars = {
// buttonPrimaryColor: '#07c160', // buttonPrimaryColor: '#07c160',
} }
</script> </script>
<template>
<wd-config-provider :theme-vars="themeVars">
<slot />
<wd-toast />
<wd-message-box />
</wd-config-provider>
</template>

View File

@ -1,67 +0,0 @@
<script setup lang="ts">
import { tabbarStore } from './tabbar'
// 'i-carbon-code',
import { tabbarList as _tabBarList, cacheTabbarEnable, selectedTabbarStrategy } from './tabbarList'
// @ts-expect-error
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
// @ts-expect-error
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 === 'uiLib'" :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

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

View File

@ -1,11 +0,0 @@
/**
* 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

@ -1,65 +0,0 @@
/**
* tabbar tabbar.md
* 0: 'NATIVE_TABBAR' `完全原生 tabbar`
* 2: 'FULL_CUSTOM_TABBAR' `全自定义 tabbar`
* 1: 'HALF_CUSTOM_TABBAR' `半自定义 tabbar`
* 3: 'NO_TABBAR' `无 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: 'uiLib',
},
{
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

View File

@ -1,19 +1,60 @@
<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> <template>
<wd-config-provider :theme-vars="themeVars"> <wd-config-provider :themeVars="themeVars">
<slot /> <wd-notify />
<FgTabbar />
<wd-toast /> <wd-toast />
<wd-message-box /> <wd-message-box />
<!-- <privacy-popup></privacy-popup> -->
<slot></slot>
<wd-tabbar
fixed
:model-value="tabbarStore.getActive.name"
@change="handleChange"
bordered
safeAreaInsetBottom
placeholder
>
<wd-tabbar-item
v-for="(item, index) in tabbarStore.getTabbarItems"
:key="index"
:name="item.name"
:value="tabbarStore.getTabbarItemValue(item.name)"
:title="item.title"
:icon="item.icon"
></wd-tabbar-item>
</wd-tabbar>
</wd-config-provider> </wd-config-provider>
</template> </template>
<script lang="ts">
export default {
options: {
addGlobalClass: true,
virtualHost: true,
styleIsolation: 'shared',
},
}
</script>
<script lang="ts" setup>
import { useTabbarStore } from '@/store/tabbar'
import { ConfigProviderThemeVars } from 'wot-design-uni'
const tabbarStore = useTabbarStore()
const themeVars = reactive<ConfigProviderThemeVars>({
colorTheme: '#fa4126',
tabsNavLineBgColor: 'red',
})
function handleChange({ value }) {
tabbarStore.setTabbarItemActive(value)
uni.navigateTo({
url: tabbarStore.getTabbarItemRoute(value),
})
}
onShow(() => {
// #ifdef APP-PLUS
uni.hideTabBar()
// #endif
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,11 +1,11 @@
import '@/style/index.scss'
import { VueQueryPlugin } from '@tanstack/vue-query' import { VueQueryPlugin } from '@tanstack/vue-query'
import 'virtual: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

@ -7,21 +7,17 @@
} }
</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

@ -14,34 +14,6 @@
"^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$1.vue" "^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$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": "首页",
"icon": "home",
"iconType": "uiLib"
},
{
"iconPath": "static/tabbar/example.png",
"selectedIconPath": "static/tabbar/exampleHL.png",
"pagePath": "pages/about/about",
"text": "关于",
"icon": "i-carbon-code",
"iconType": "unocss"
}
]
},
"pages": [ "pages": [
{ {
"path": "pages/index/index", "path": "pages/index/index",
@ -62,4 +34,4 @@
} }
], ],
"subPackages": [] "subPackages": []
} }

View File

@ -3,38 +3,49 @@
layout: 'tabbar', layout: 'tabbar',
style: { style: {
navigationBarTitleText: '关于', navigationBarTitleText: '关于',
// navigationStyle: 'custom', //
}, },
} }
</route> </route>
<template>
<view>
<!-- <fg-navbar>关于</fg-navbar> -->
<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>
<!-- <button @click="toSubPage()">去分包</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()
const toSubPage = () => {
uni.navigateTo({
url: '/pages-sub/demo/index',
})
}
// vue .ts // vue .ts
// const testOxlint = (name: string) => { // const testOxlint = (name: string) => {
// console.log('oxlint') // console.log('oxlint')
// } // }
// testOxlint('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,9 +7,36 @@
} }
</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 -->
<wd-button @click="run" class="my-6">发送请求</wd-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>
<wd-button type="error" @click="reset" class="my-6" :disabled="!data">重置数据</wd-button>
</view>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import type { IFooItem } from '@/service/index/foo' import { getFooAPI, postFooAPI, 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'
@ -34,51 +61,7 @@ const { loading, error, data, run } = useRequest<IFooItem>(() => getFooAPI('菲
// refetch, // refetch,
// } = useQuery(findPetsByStatusQueryOptions({ params: { status: ['available'] } })) // } = useQuery(findPetsByStatusQueryOptions({ params: { status: ['available'] } }))
function reset() { const 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 -->
<wd-button class="my-6" @click="run">
发送请求
</wd-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>
<wd-button type="error" class="my-6" :disabled="!data" @click="reset">
重置数据
</wd-button>
</view>
</template>

View File

@ -7,32 +7,24 @@
} }
</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">
<wd-button @click="run"> <wd-button @click="run">选择图片并上传</wd-button>
选择图片并上传 <view v-if="loading" class="text-blue h-10">上传...</view>
</wd-button>
<view v-if="loading" class="h-10 text-blue">
上传...
</view>
<template v-else> <template v-else>
<view class="m-2"> <view class="m-2">上传后返回的接口数据</view>
上传后返回的接口数据 <view class="m-2">{{ data }}</view>
</view> <view class="h-80 w-full">
<view class="m-2"> <image v-if="data" :src="data.url" mode="scaleToFill" />
{{ 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()
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
// //
</style> </style>

View File

@ -3,12 +3,33 @@
{ {
layout: 'tabbar', 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 text-[#d14328] 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">spa</text>
</view>
</view>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import PLATFORM from '@/utils/platform' import PLATFORM from '@/utils/platform'
@ -41,55 +62,10 @@ safeAreaInsets = systemInfo.safeAreaInsets
// #endif // #endif
const author = ref('菲鸽') const author = ref('菲鸽')
const description = ref( const description = ref(
'unibest 是一个集成了多种工具和技术的 uniapp 开发模板,由 uniapp + Vue3 + Ts + Vite5 + UnoCss + VSCode 构建,模板具有代码提示、自动格式化、统一配置、代码片段等功能,并内置了许多常用的基本组件和基本功能,让你编写 uniapp 拥有 best 体验。', 'unibest 是一个集成了多种工具和技术的 uniapp 开发模板,由 uniapp + Vue3 + Ts + Vite6 + UnoCss + VSCode 构建,模板具有代码提示、自动格式化、统一配置、代码片段等功能,并内置了许多常用的基本组件和基本功能,让你编写 uniapp 拥有 best 体验。',
) )
// uni API // uni API
onLoad(() => { onLoad(() => {
console.log('项目作者:', author.value) console.log('项目作者:', author.value)
}) })
console.log('index')
</script> </script>
<template>
<view class="bg-white px-4 pt-2" :style="{ marginTop: `${safeAreaInsets?.top}px` }">
<view class="mt-10">
<image src="/static/logo.svg" alt="" class="mx-auto block h-28 w-28" />
</view>
<view class="mt-4 text-center text-4xl text-[#d14328]">
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-4 text-center">
作者
<text class="text-green-500">
菲鸽
</text>
</view>
<view class="mt-4 text-center">
官网地址
<text class="text-green-500">
https://unibest.tech
</text>
</view>
<view class="mt-6 h-1px bg-#eee" />
<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
</text>
</view>
</view>
</template>

View File

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

@ -15,3 +15,4 @@ export default store
// 模块统一导出 // 模块统一导出
export * from './user' export * from './user'
export * from './tabbar'

71
src/store/tabbar.ts Normal file
View File

@ -0,0 +1,71 @@
import { defineStore } from 'pinia'
export interface TabbarItem {
name: string
value: number | null
active: boolean
route: string
title: string
icon: string
}
export const useTabbarStore = defineStore('tabbar', {
state: (): { tabbarItems: TabbarItem[] } => ({
tabbarItems: [
{
name: 'index',
value: null,
active: true,
route: '/pages/index/index',
title: '首页',
icon: 'home',
},
{
name: 'about',
value: null,
active: false,
route: '/pages/about/about',
title: '关于',
icon: 'user',
},
],
}),
getters: {
getTabbarItems: (state) => {
return state.tabbarItems
},
getActive: (state) => {
const item = state.tabbarItems.find((item) => item.active)
return item || state.tabbarItems[0]
},
getTabbarItemValue: (state) => {
return (name: string) => {
const item = state.tabbarItems.find((item) => item.name === name)
return item && item.value ? item.value : null
}
},
getTabbarItemRoute: (state) => {
return (name: string) => {
const item = state.tabbarItems.find((item) => item.name === name)
return (item && item.route) ?? null
}
},
},
actions: {
setTabbarItem(name: string, value: number) {
const tabbarItem = this.tabbarItems.find((item) => item.name === name)
if (tabbarItem) {
tabbarItem.value = value
}
},
setTabbarItemActive(name: string) {
this.tabbarItems.forEach((item) => {
if (item.name === name) {
item.active = true
} else {
item.active = false
}
})
},
},
})

View File

@ -1,14 +1,14 @@
import type { IUserInfoVo } from '@/api/types/login'
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { import {
getUserInfo as _getUserInfo,
login as _login, login as _login,
logout as _logout, getUserInfo as _getUserInfo,
wxLogin as _wxLogin, wxLogin as _wxLogin,
logout as _logout,
getWxCode, getWxCode,
} from '@/api/login' } from '@/api/login'
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { toast } from '@/utils/toast' import { toast } from '@/utils/toast'
import { IUserInfoVo } from '@/api/login.typings'
// 初始化状态 // 初始化状态
const userInfoState: IUserInfoVo = { const userInfoState: IUserInfoVo = {
@ -29,8 +29,7 @@ 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
@ -46,18 +45,6 @@ export const useUserStore = defineStore(
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
@ -75,7 +62,18 @@ export const useUserStore = defineStore(
await getUserInfo() await getUserInfo()
return res 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
}
/** /**
* 退 * 退
*/ */

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 {
interface IResData<T> { type IResData<T> = {
code: number code: number
msg: string msg: string
data: T data: T
} }
// uni.uploadFile文件上传参数 // uni.uploadFile文件上传参数
interface IUniUploadFileOptions { type 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
} }
interface IUserInfo { type 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 interface IUniUploadFileOptions { export type IUniUploadFileOptions = {
file?: File file?: File
files?: UniApp.UploadFileOptionFiles[] files?: UniApp.UploadFileOptionFiles[]
filePath?: string filePath?: string

View File

@ -1,6 +1,6 @@
import type { CustomRequestOptions } from '@/interceptors/request' import { CustomRequestOptions } from '@/interceptors/request'
export function http<T>(options: CustomRequestOptions) { export const 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,20 +15,18 @@ export function 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 || '请求错误',
}) })
reject(res) reject(res)
} }
}, },
@ -51,7 +49,12 @@ export function http<T>(options: CustomRequestOptions) {
* @param header json格式 * @param header json格式
* @returns * @returns
*/ */
export function httpGet<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) { export const httpGet = <T>(
url: string,
query?: Record<string, any>,
header?: Record<string, any>,
options?: Partial<CustomRequestOptions>,
) => {
return http<T>({ return http<T>({
url, url,
query, query,
@ -69,7 +72,13 @@ export function httpGet<T>(url: string, query?: Record<string, any>, header?: Re
* @param header json格式 * @param header json格式
* @returns * @returns
*/ */
export function httpPost<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) { export const httpPost = <T>(
url: string,
data?: Record<string, any>,
query?: Record<string, any>,
header?: Record<string, any>,
options?: Partial<CustomRequestOptions>,
) => {
return http<T>({ return http<T>({
url, url,
query, query,
@ -82,7 +91,13 @@ export function httpPost<T>(url: string, data?: Record<string, any>, query?: Rec
/** /**
* PUT * PUT
*/ */
export function httpPut<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) { export const httpPut = <T>(
url: string,
data?: Record<string, any>,
query?: Record<string, any>,
header?: Record<string, any>,
options?: Partial<CustomRequestOptions>,
) => {
return http<T>({ return http<T>({
url, url,
data, data,
@ -96,7 +111,12 @@ export function httpPut<T>(url: string, data?: Record<string, any>, query?: Reco
/** /**
* DELETE query * DELETE query
*/ */
export function httpDelete<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) { export const httpDelete = <T>(
url: string,
query?: Record<string, any>,
header?: Record<string, any>,
options?: Partial<CustomRequestOptions>,
) => {
return http<T>({ return http<T>({
url, url,
query, query,

View File

@ -1,7 +1,9 @@
import { pages, subPackages } from '@/pages.json' import pagesConfig from '@/pages.json'
import { isMpWeixin } from './platform' import { isMpWeixin } from './platform'
export function getLastPage() { const { pages, subPackages, tabBar = { list: [] } } = { ...pagesConfig }
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但依然报错】
@ -9,12 +11,46 @@ export function 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 function currRoute() { export const 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)
@ -29,7 +65,7 @@ export function currRoute() {
return getUrlObj(fullPath) return getUrlObj(fullPath)
} }
function ensureDecodeURIComponent(url: string) { const ensureDecodeURIComponent = (url: string) => {
if (url.startsWith('%')) { if (url.startsWith('%')) {
return ensureDecodeURIComponent(decodeURIComponent(url)) return ensureDecodeURIComponent(decodeURIComponent(url))
} }
@ -40,7 +76,7 @@ function 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 function getUrlObj(url: string) { export const getUrlObj = (url: string) => {
const [path, queryStr] = url.split('?') const [path, queryStr] = url.split('?')
// console.log(path, queryStr) // console.log(path, queryStr)
@ -63,11 +99,11 @@ export function getUrlObj(url: string) {
* key needLogin, route-block 使 * key needLogin, route-block 使
* key pages key, key * key pages key, key
*/ */
export function getAllPages(key = 'needLogin') { export const getAllPages = (key = 'needLogin') => {
// 这里处理主包 // 这里处理主包
const mainPages = pages const mainPages = pages
.filter(page => !key || page[key]) .filter((page) => !key || page[key])
.map(page => ({ .map((page) => ({
...page, ...page,
path: `/${page.path}`, path: `/${page.path}`,
})) }))
@ -79,7 +115,7 @@ export function 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,
@ -96,18 +132,18 @@ export function 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 function getEnvBaseUrl() { export const getEnvBaseUrl = () => {
// 请求基准地址 // 请求基准地址
let baseUrl = import.meta.env.VITE_SERVER_BASEURL let baseUrl = import.meta.env.VITE_SERVER_BASEURL
@ -136,7 +172,7 @@ export function getEnvBaseUrl() {
/** /**
* UPLOAD_BASEURL * UPLOAD_BASEURL
*/ */
export function getEnvBaseUploadUrl() { export const getEnvBaseUploadUrl = () => {
// 请求基准地址 // 请求基准地址
let baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL let baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL

View File

@ -1,29 +0,0 @@
/**
* URL查询字符串 qs
*
* @param obj
* @returns
*/
export function stringifyQuery(obj: Record<string, any>): string {
if (!obj || typeof obj !== 'object' || Array.isArray(obj))
return ''
return Object.entries(obj)
.filter(([_, value]) => value !== undefined && value !== null)
.map(([key, value]) => {
// 对键进行编码
const encodedKey = encodeURIComponent(key)
// 处理数组类型
if (Array.isArray(value)) {
return value
.filter(item => item !== undefined && item !== null)
.map(item => `${encodedKey}=${encodeURIComponent(item)}`)
.join('&')
}
// 处理基本类型
return `${encodedKey}=${encodeURIComponent(value)}`
})
.join('&')
}

View File

@ -1,11 +1,11 @@
import type { CustomRequestOptions } from '@/interceptors/request' import { CustomRequestOptions } from '@/interceptors/request'
/** /**
* 请求方法: 主要是对 uni.request openapi-ts-request request * 请求方法: 主要是对 uni.request openapi-ts-request request
* @param options * @param options
* @returns Promise * @returns Promise
*/ */
function http<T>(options: CustomRequestOptions) { const 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,20 +20,18 @@ function 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 || '请求错误',
}) })
reject(res) reject(res)
} }
}, },

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,7 +31,12 @@ export const uploadFileUrl = {
* @param formData * @param formData
* @param options * @param options
*/ */
export function useFileUpload<T = string>(url: string, filePath: string, formData: Record<string, any> = {}, options: Omit<UploadOptions, 'sourceType' | 'sizeType' | 'count'> = {}) { export const useFileUpload = <T = string>(
url: string,
filePath: string,
formData: Record<string, any> = {},
options: Omit<UploadOptions, 'sourceType' | 'sizeType' | 'count'> = {},
) => {
return useUpload<T>( return useUpload<T>(
url, url,
formData, formData,
@ -71,9 +76,13 @@ export interface UploadOptions {
* @param options * @param options
* @returns * @returns
*/ */
export function useUpload<T = string>(url: string, formData: Record<string, any> = {}, options: UploadOptions = {}, export const useUpload = <T = string>(
url: string,
formData: Record<string, any> = {},
options: UploadOptions = {},
/** 直接传入文件路径,跳过选择器 */ /** 直接传入文件路径,跳过选择器 */
directFilePath?: string) { directFilePath?: string,
) => {
/** 上传中状态 */ /** 上传中状态 */
const loading = ref(false) const loading = ref(false)
/** 上传错误状态 */ /** 上传错误状态 */
@ -152,8 +161,7 @@ export function useUpload<T = string>(url: string, formData: Record<string, any>
success: (res) => { success: (res) => {
const file = res.tempFiles[0] const file = res.tempFiles[0]
// 检查文件大小是否符合限制 // 检查文件大小是否符合限制
if (!checkFileSize(file.size)) if (!checkFileSize(file.size)) return
return
// 开始上传 // 开始上传
loading.value = true loading.value = true
@ -287,8 +295,7 @@ function uploadFile<T>({
// 上传成功 // 上传成功
data.value = _data as T data.value = _data as T
onSuccess?.(_data) onSuccess?.(_data)
} } catch (err) {
catch (err) {
// 响应解析错误 // 响应解析错误
console.error('解析上传响应失败:', err) console.error('解析上传响应失败:', err)
error.value = true error.value = true
@ -313,8 +320,7 @@ 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,15 +1,21 @@
{ {
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"lib": ["esnext", "dom"], "skipLibCheck": true,
"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/*"] "@img/*": ["./src/static/*"]
}, },
"resolveJsonModule": true, "outDir": "dist",
"lib": ["esnext", "dom"],
"types": [ "types": [
"@dcloudio/types", "@dcloudio/types",
"@uni-helper/uni-types", "@uni-helper/uni-types",
@ -17,17 +23,12 @@
"wot-design-uni/global.d.ts", "wot-design-uni/global.d.ts",
"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,6 +37,5 @@
"src/**/*.jsx", "src/**/*.jsx",
"src/**/*.vue", "src/**/*.vue",
"src/**/*.json" "src/**/*.json"
], ]
"exclude": ["node_modules"]
} }

View File

@ -2,8 +2,8 @@
import { presetUni } from '@uni-helper/unocss-preset-uni' import { presetUni } from '@uni-helper/unocss-preset-uni'
import { import {
defineConfig, defineConfig,
presetAttributify,
presetIcons, presetIcons,
presetAttributify,
transformerDirectives, transformerDirectives,
transformerVariantGroup, transformerVariantGroup,
} from 'unocss' } from 'unocss'
@ -20,7 +20,7 @@ export default defineConfig({
scale: 1.2, scale: 1.2,
warn: true, warn: true,
extraProperties: { extraProperties: {
'display': 'inline-block', display: 'inline-block',
'vertical-align': 'middle', 'vertical-align': 'middle',
}, },
}), }),
@ -39,7 +39,6 @@ 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,6 +1,5 @@
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')
@ -32,8 +31,7 @@ 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

@ -1,16 +1,14 @@
// src/plugins/updatePackageJson.ts // src/plugins/updatePackageJson.ts
import type { Plugin } from 'vite' import { Plugin } from 'vite'
import fs from 'node:fs/promises' import fs from 'fs/promises'
import path from 'node:path' import path from 'path'
import process from 'node:process'
function updatePackageJson(): Plugin { const updatePackageJson = (): Plugin => {
return { return {
name: 'update-package-json', name: 'update-package-json',
async buildStart() { async buildStart() {
// 只在生产环境构建时执行 // 只在生产环境构建时执行
if (process.env.NODE_ENV !== 'production') if (process.env.NODE_ENV !== 'production') return
return
const packageJsonPath = path.resolve(process.cwd(), 'package.json') const packageJsonPath = path.resolve(process.cwd(), 'package.json')
@ -23,11 +21,10 @@ function updatePackageJson(): Plugin {
packageJson['update-time'] = new Date().toISOString().split('T')[0] // YYYY-MM-DD packageJson['update-time'] = new Date().toISOString().split('T')[0] // YYYY-MM-DD
// 写回文件(保持 2 空格缩进) // 写回文件(保持 2 空格缩进)
await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf-8') await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8')
console.log(`[update-package-json] 更新时间戳: ${packageJson['update-time']}`) console.log(`[update-package-json] 更新时间戳: ${packageJson['update-time']}`)
} } catch (error) {
catch (error) {
console.error('[update-package-json] 插件执行失败:', error) console.error('[update-package-json] 插件执行失败:', error)
} }
}, },

View File

@ -1,27 +1,27 @@
import path from 'node:path'
import process from 'node:process'
import Uni from '@dcloudio/vite-plugin-uni' import Uni from '@dcloudio/vite-plugin-uni'
import Components from '@uni-helper/vite-plugin-uni-components' import dayjs from 'dayjs'
// @see https://uni-helper.js.org/vite-plugin-uni-layouts import path from 'node:path'
import UniLayouts from '@uni-helper/vite-plugin-uni-layouts' import { defineConfig, loadEnv } from 'vite'
// @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 // @see https://uni-helper.js.org/vite-plugin-uni-pages
import UniPages from '@uni-helper/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 // @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 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 }) => {
@ -75,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
} }
@ -91,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,
}, },
@ -113,14 +113,14 @@ 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({
@ -163,7 +163,7 @@ 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,