From a52154d9fcf28bcc4df330f745efbb74add711ec Mon Sep 17 00:00:00 2001 From: wangxiaowei <1121133807@qq.com> Date: Thu, 14 Aug 2025 14:30:12 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=98=E5=88=B6=E9=A6=96=E9=A1=B5=E5=92=8C?= =?UTF-8?q?=E9=85=8D=E7=BD=AEtabbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.config.ts | 264 +++++++-------- pages.config.ts | 36 +- src/hooks/useUpload.ts | 264 +++++++-------- src/http/alova.ts | 154 ++++----- src/http/http.ts | 142 ++++---- src/http/interceptor.ts | 100 +++--- src/main.ts | 17 +- src/pages.json | 38 ++- src/pages/index/index.vue | 198 +++++------ src/pages/my/my.vue | 120 +++++++ src/pages/reserve/reserve.vue | 120 +++++++ src/static/icon/icon_arrow_down.png | Bin 0 -> 260 bytes src/static/images/home_bg.png | Bin 0 -> 23580 bytes src/static/tabbar/example.png | Bin 1371 -> 0 bytes src/static/tabbar/exampleHL.png | Bin 1398 -> 0 bytes src/static/tabbar/home.png | Bin 1346 -> 799 bytes src/static/tabbar/homeHL.png | Bin 1415 -> 0 bytes src/static/tabbar/home_s.png | Bin 0 -> 1769 bytes src/static/tabbar/my.png | Bin 0 -> 1522 bytes src/static/tabbar/my_s.png | Bin 0 -> 1132 bytes src/static/tabbar/personal.png | Bin 2457 -> 0 bytes src/static/tabbar/personalHL.png | Bin 2534 -> 0 bytes src/static/tabbar/reserve.png | Bin 0 -> 656 bytes src/static/tabbar/reserve_s.png | Bin 0 -> 1659 bytes src/static/tabbar/scan.png | Bin 3962 -> 0 bytes src/store/index.ts | 12 +- src/store/user.ts | 190 +++++------ src/tabbar/config.ts | 156 +++++---- src/uni.scss | 3 + src/utils/index.ts | 246 +++++++------- src/utils/uploadFile.ts | 506 ++++++++++++++-------------- uno.config.ts | 118 +++---- vite.config.ts | 296 ++++++++-------- 33 files changed, 1617 insertions(+), 1363 deletions(-) create mode 100644 src/pages/my/my.vue create mode 100644 src/pages/reserve/reserve.vue create mode 100644 src/static/icon/icon_arrow_down.png create mode 100644 src/static/images/home_bg.png delete mode 100644 src/static/tabbar/example.png delete mode 100644 src/static/tabbar/exampleHL.png delete mode 100644 src/static/tabbar/homeHL.png create mode 100644 src/static/tabbar/home_s.png create mode 100644 src/static/tabbar/my.png create mode 100644 src/static/tabbar/my_s.png delete mode 100644 src/static/tabbar/personal.png delete mode 100644 src/static/tabbar/personalHL.png create mode 100644 src/static/tabbar/reserve.png create mode 100644 src/static/tabbar/reserve_s.png delete mode 100644 src/static/tabbar/scan.png diff --git a/manifest.config.ts b/manifest.config.ts index 6f3c7d9..edf4b5c 100644 --- a/manifest.config.ts +++ b/manifest.config.ts @@ -6,143 +6,143 @@ import { loadEnv } from 'vite' // 手动解析命令行参数获取 mode function getMode() { - const args = process.argv.slice(2) - const modeFlagIndex = args.findIndex(arg => arg === '--mode') - return modeFlagIndex !== -1 ? args[modeFlagIndex + 1] : args[0] === 'build' ? 'production' : 'development' // 默认 development + const args = process.argv.slice(2) + const modeFlagIndex = args.findIndex(arg => arg === '--mode') + return modeFlagIndex !== -1 ? args[modeFlagIndex + 1] : args[0] === 'build' ? 'production' : 'development' // 默认 development } // 获取环境变量的范例 const env = loadEnv(getMode(), path.resolve(process.cwd(), 'env')) const { - VITE_APP_TITLE, - VITE_UNI_APPID, - VITE_WX_APPID, - VITE_APP_PUBLIC_BASE, - VITE_FALLBACK_LOCALE, + VITE_APP_TITLE, + VITE_UNI_APPID, + VITE_WX_APPID, + VITE_APP_PUBLIC_BASE, + VITE_FALLBACK_LOCALE, } = env export default defineManifestConfig({ - 'name': VITE_APP_TITLE, - 'appid': VITE_UNI_APPID, - 'description': '', - 'versionName': '1.0.0', - 'versionCode': '100', - 'transformPx': false, - 'locale': VITE_FALLBACK_LOCALE, // 'zh-Hans' - 'h5': { - router: { - // base: VITE_APP_PUBLIC_BASE, - }, - }, - /* 5+App特有相关 */ - 'app-plus': { - usingComponents: true, - nvueStyleCompiler: 'uni-app', - compilerVersion: 3, - compatible: { - ignoreVersion: true, - }, - splashscreen: { - alwaysShowBeforeRender: true, - waiting: true, - autoclose: true, - delay: 0, - }, - /* 模块配置 */ - modules: {}, - /* 应用发布信息 */ - distribute: { - /* android打包配置 */ - android: { - minSdkVersion: 30, - targetSdkVersion: 30, - abiFilters: ['armeabi-v7a', 'arm64-v8a'], - permissions: [ - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - ], - }, - /* ios打包配置 */ - ios: {}, - /* SDK配置 */ - sdkConfigs: {}, - /* 图标配置 */ - icons: { - android: { - hdpi: 'static/app/icons/72x72.png', - xhdpi: 'static/app/icons/96x96.png', - xxhdpi: 'static/app/icons/144x144.png', - xxxhdpi: 'static/app/icons/192x192.png', - }, - ios: { - appstore: 'static/app/icons/1024x1024.png', - ipad: { - 'app': 'static/app/icons/76x76.png', - 'app@2x': 'static/app/icons/152x152.png', - 'notification': 'static/app/icons/20x20.png', - 'notification@2x': 'static/app/icons/40x40.png', - 'proapp@2x': 'static/app/icons/167x167.png', - 'settings': 'static/app/icons/29x29.png', - 'settings@2x': 'static/app/icons/58x58.png', - 'spotlight': 'static/app/icons/40x40.png', - 'spotlight@2x': 'static/app/icons/80x80.png', - }, - iphone: { - 'app@2x': 'static/app/icons/120x120.png', - 'app@3x': 'static/app/icons/180x180.png', - 'notification@2x': 'static/app/icons/40x40.png', - 'notification@3x': 'static/app/icons/60x60.png', - 'settings@2x': 'static/app/icons/58x58.png', - 'settings@3x': 'static/app/icons/87x87.png', - 'spotlight@2x': 'static/app/icons/80x80.png', - 'spotlight@3x': 'static/app/icons/120x120.png', - }, - }, - }, - }, - }, - /* 快应用特有相关 */ - 'quickapp': {}, - /* 小程序特有相关 */ - 'mp-weixin': { - appid: VITE_WX_APPID, - setting: { - urlCheck: false, - // 是否启用 ES6 转 ES5 - es6: true, - minified: true, - }, - optimization: { - subPackages: true, - }, - // styleIsolation: 'shared', - usingComponents: true, - // __usePrivacyCheck__: true, - }, - 'mp-alipay': { - usingComponents: true, - styleIsolation: 'shared', - }, - 'mp-baidu': { - usingComponents: true, - }, - 'mp-toutiao': { - usingComponents: true, - }, - 'uniStatistics': { - enable: false, - }, - 'vueVersion': '3', + 'name': VITE_APP_TITLE, + 'appid': VITE_UNI_APPID, + 'description': '', + 'versionName': '1.0.0', + 'versionCode': '100', + 'transformPx': false, + 'locale': VITE_FALLBACK_LOCALE, // 'zh-Hans' + 'h5': { + router: { + // base: VITE_APP_PUBLIC_BASE, + }, + }, + /* 5+App特有相关 */ + 'app-plus': { + usingComponents: true, + nvueStyleCompiler: 'uni-app', + compilerVersion: 3, + compatible: { + ignoreVersion: true, + }, + splashscreen: { + alwaysShowBeforeRender: true, + waiting: true, + autoclose: true, + delay: 0, + }, + /* 模块配置 */ + modules: {}, + /* 应用发布信息 */ + distribute: { + /* android打包配置 */ + android: { + minSdkVersion: 30, + targetSdkVersion: 30, + abiFilters: ['armeabi-v7a', 'arm64-v8a'], + permissions: [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ], + }, + /* ios打包配置 */ + ios: {}, + /* SDK配置 */ + sdkConfigs: {}, + /* 图标配置 */ + icons: { + android: { + hdpi: 'static/app/icons/72x72.png', + xhdpi: 'static/app/icons/96x96.png', + xxhdpi: 'static/app/icons/144x144.png', + xxxhdpi: 'static/app/icons/192x192.png', + }, + ios: { + appstore: 'static/app/icons/1024x1024.png', + ipad: { + 'app': 'static/app/icons/76x76.png', + 'app@2x': 'static/app/icons/152x152.png', + 'notification': 'static/app/icons/20x20.png', + 'notification@2x': 'static/app/icons/40x40.png', + 'proapp@2x': 'static/app/icons/167x167.png', + 'settings': 'static/app/icons/29x29.png', + 'settings@2x': 'static/app/icons/58x58.png', + 'spotlight': 'static/app/icons/40x40.png', + 'spotlight@2x': 'static/app/icons/80x80.png', + }, + iphone: { + 'app@2x': 'static/app/icons/120x120.png', + 'app@3x': 'static/app/icons/180x180.png', + 'notification@2x': 'static/app/icons/40x40.png', + 'notification@3x': 'static/app/icons/60x60.png', + 'settings@2x': 'static/app/icons/58x58.png', + 'settings@3x': 'static/app/icons/87x87.png', + 'spotlight@2x': 'static/app/icons/80x80.png', + 'spotlight@3x': 'static/app/icons/120x120.png', + }, + }, + }, + }, + }, + /* 快应用特有相关 */ + 'quickapp': {}, + /* 小程序特有相关 */ + 'mp-weixin': { + appid: VITE_WX_APPID, + setting: { + urlCheck: false, + // 是否启用 ES6 转 ES5 + es6: true, + minified: true, + }, + optimization: { + subPackages: true, + }, + // styleIsolation: 'shared', + usingComponents: true, + // __usePrivacyCheck__: true, + }, + 'mp-alipay': { + usingComponents: true, + styleIsolation: 'shared', + }, + 'mp-baidu': { + usingComponents: true, + }, + 'mp-toutiao': { + usingComponents: true, + }, + 'uniStatistics': { + enable: false, + }, + 'vueVersion': '3', }) diff --git a/pages.config.ts b/pages.config.ts index 1aa275e..7c4b179 100644 --- a/pages.config.ts +++ b/pages.config.ts @@ -2,22 +2,22 @@ import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages' import { tabBar } from './src/tabbar/config' export default defineUniPages({ - globalStyle: { - navigationStyle: 'default', - navigationBarTitleText: 'unibest', - navigationBarBackgroundColor: '#f8f8f8', - navigationBarTextStyle: 'black', - backgroundColor: '#FFFFFF', - }, - easycom: { - autoscan: true, - custom: { - '^fg-(.*)': '@/components/fg-$1/fg-$1.vue', - '^wd-(.*)': 'wot-design-uni/components/wd-$1/wd-$1.vue', - '^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)': - 'z-paging/components/z-paging$1/z-paging$1.vue', - }, - }, - // tabbar 的配置统一在 “./src/tabbar/config.ts” 文件中 - tabBar: tabBar as any, + globalStyle: { + navigationStyle: 'default', + navigationBarTitleText: 'unibest', + navigationBarBackgroundColor: '#f8f8f8', + navigationBarTextStyle: 'black', + backgroundColor: '#FFFFFF', + }, + easycom: { + autoscan: true, + custom: { + '^fg-(.*)': '@/components/fg-$1/fg-$1.vue', + '^wd-(.*)': 'wot-design-uni/components/wd-$1/wd-$1.vue', + '^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)': + 'z-paging/components/z-paging$1/z-paging$1.vue', + }, + }, + // tabbar 的配置统一在 “./src/tabbar/config.ts” 文件中 + tabBar: tabBar as any, }) diff --git a/src/hooks/useUpload.ts b/src/hooks/useUpload.ts index 3080d5a..e7dc6d7 100644 --- a/src/hooks/useUpload.ts +++ b/src/hooks/useUpload.ts @@ -8,153 +8,153 @@ type TImage = 'png' | 'jpg' | 'jpeg' | 'webp' | '*' type TFile = 'doc' | 'docx' | 'ppt' | 'zip' | 'xls' | 'xlsx' | 'txt' | TImage interface TOptions { - formData?: Record - maxSize?: number - accept?: T extends 'image' ? TImage[] : TFile[] - fileType?: T - success?: (params: any) => void - error?: (err: any) => void + formData?: Record + maxSize?: number + accept?: T extends 'image' ? TImage[] : TFile[] + fileType?: T + success?: (params: any) => void + error?: (err: any) => void } export default function useUpload(options: TOptions = {} as TOptions) { - const { - formData = {}, - maxSize = 5 * 1024 * 1024, - accept = ['*'], - fileType = 'image', - success, - error: onError, - } = options + const { + formData = {}, + maxSize = 5 * 1024 * 1024, + accept = ['*'], + fileType = 'image', + success, + error: onError, + } = options - const loading = ref(false) - const error = ref(null) - const data = ref(null) + const loading = ref(false) + const error = ref(null) + const data = ref(null) - const handleFileChoose = ({ tempFilePath, size }: { tempFilePath: string, size: number }) => { - if (size > maxSize) { - uni.showToast({ - title: `文件大小不能超过 ${maxSize / 1024 / 1024}MB`, - icon: 'none', - }) - return - } + const 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) + // 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 - // } + // 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 - }, - }) - } + loading.value = true + uploadFile({ + tempFilePath, + formData, + onSuccess: (res) => { + const { data: _data } = JSON.parse(res) + data.value = _data + // console.log('上传成功', res) + success?.(_data) + }, + onError: (err) => { + error.value = err + onError?.(err) + }, + onComplete: () => { + loading.value = false + }, + }) + } - const run = () => { - // 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。 - // 微信小程序在2023年10月17日之后,使用本API需要配置隐私协议 - const chooseFileOptions = { - count: 1, - success: (res: any) => { - console.log('File selected successfully:', res) - // 小程序中res:{errMsg: "chooseImage:ok", tempFiles: [{fileType: "image", size: 48976, tempFilePath: "http://tmp/5iG1WpIxTaJf3ece38692a337dc06df7eb69ecb49c6b.jpeg"}]} - // h5中res:{errMsg: "chooseImage:ok", tempFilePaths: "blob:http://localhost:9000/f74ab6b8-a14d-4cb6-a10d-fcf4511a0de5", tempFiles: [File]} - // h5的File有以下字段:{name: "girl.jpeg", size: 48976, type: "image/jpeg"} - // App中res:{errMsg: "chooseImage:ok", tempFilePaths: "file:///Users/feige/xxx/gallery/1522437259-compressed-IMG_0006.jpg", tempFiles: [File]} - // App的File有以下字段:{path: "file:///Users/feige/xxx/gallery/1522437259-compressed-IMG_0006.jpg", size: 48976} - let tempFilePath = '' - let size = 0 - // #ifdef MP-WEIXIN - tempFilePath = res.tempFiles[0].tempFilePath - size = res.tempFiles[0].size - // #endif - // #ifndef MP-WEIXIN - tempFilePath = res.tempFilePaths[0] - size = res.tempFiles[0].size - // #endif - handleFileChoose({ tempFilePath, size }) - }, - fail: (err: any) => { - console.error('File selection failed:', err) - error.value = err - onError?.(err) - }, - } + const run = () => { + // 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。 + // 微信小程序在2023年10月17日之后,使用本API需要配置隐私协议 + const chooseFileOptions = { + count: 1, + success: (res: any) => { + console.log('File selected successfully:', res) + // 小程序中res:{errMsg: "chooseImage:ok", tempFiles: [{fileType: "image", size: 48976, tempFilePath: "http://tmp/5iG1WpIxTaJf3ece38692a337dc06df7eb69ecb49c6b.jpeg"}]} + // h5中res:{errMsg: "chooseImage:ok", tempFilePaths: "blob:http://localhost:9000/f74ab6b8-a14d-4cb6-a10d-fcf4511a0de5", tempFiles: [File]} + // h5的File有以下字段:{name: "girl.jpeg", size: 48976, type: "image/jpeg"} + // App中res:{errMsg: "chooseImage:ok", tempFilePaths: "file:///Users/feige/xxx/gallery/1522437259-compressed-IMG_0006.jpg", tempFiles: [File]} + // App的File有以下字段:{path: "file:///Users/feige/xxx/gallery/1522437259-compressed-IMG_0006.jpg", size: 48976} + let tempFilePath = '' + let size = 0 + // #ifdef MP-WEIXIN + tempFilePath = res.tempFiles[0].tempFilePath + size = res.tempFiles[0].size + // #endif + // #ifndef MP-WEIXIN + tempFilePath = res.tempFilePaths[0] + size = res.tempFiles[0].size + // #endif + handleFileChoose({ tempFilePath, size }) + }, + fail: (err: any) => { + console.error('File selection failed:', err) + error.value = err + onError?.(err) + }, + } - if (fileType === 'image') { - // #ifdef MP-WEIXIN - uni.chooseMedia({ - ...chooseFileOptions, - mediaType: ['image'], - }) - // #endif + if (fileType === 'image') { + // #ifdef MP-WEIXIN + uni.chooseMedia({ + ...chooseFileOptions, + mediaType: ['image'], + }) + // #endif - // #ifndef MP-WEIXIN - uni.chooseImage(chooseFileOptions) - // #endif - } - else { - uni.chooseFile({ - ...chooseFileOptions, - type: 'all', - }) - } - } + // #ifndef MP-WEIXIN + uni.chooseImage(chooseFileOptions) + // #endif + } + else { + uni.chooseFile({ + ...chooseFileOptions, + type: 'all', + }) + } + } - return { loading, error, data, run } + return { loading, error, data, run } } async function uploadFile({ - tempFilePath, - formData, - onSuccess, - onError, - onComplete, + tempFilePath, + formData, + onSuccess, + onError, + onComplete, }: { - tempFilePath: string - formData: Record - onSuccess: (data: any) => void - onError: (err: any) => void - onComplete: () => void + tempFilePath: string + formData: Record + onSuccess: (data: any) => void + onError: (err: any) => void + onComplete: () => void }) { - uni.uploadFile({ - url: VITE_UPLOAD_BASEURL, - filePath: tempFilePath, - name: 'file', - formData, - success: (uploadFileRes) => { - try { - const data = uploadFileRes.data - onSuccess(data) - } - catch (err) { - onError(err) - } - }, - fail: (err) => { - console.error('Upload failed:', err) - onError(err) - }, - complete: onComplete, - }) + uni.uploadFile({ + url: VITE_UPLOAD_BASEURL, + filePath: tempFilePath, + name: 'file', + formData, + success: (uploadFileRes) => { + try { + const data = uploadFileRes.data + onSuccess(data) + } + catch (err) { + onError(err) + } + }, + fail: (err) => { + console.error('Upload failed:', err) + onError(err) + }, + complete: onComplete, + }) } diff --git a/src/http/alova.ts b/src/http/alova.ts index 005b54b..360ba19 100644 --- a/src/http/alova.ts +++ b/src/http/alova.ts @@ -9,103 +9,103 @@ import { ContentTypeEnum, ResultEnum, ShowMessage } from './tools/enum' // 配置动态Tag export const API_DOMAINS = { - DEFAULT: import.meta.env.VITE_SERVER_BASEURL, - SECONDARY: import.meta.env.VITE_API_SECONDARY_URL, + DEFAULT: import.meta.env.VITE_SERVER_BASEURL, + SECONDARY: import.meta.env.VITE_API_SECONDARY_URL, } /** * 创建请求实例 */ const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication< - typeof VueHook, - typeof uniappRequestAdapter + typeof VueHook, + typeof uniappRequestAdapter >({ - refreshTokenOnError: { - isExpired: (error) => { - return error.response?.status === ResultEnum.Unauthorized - }, - handler: async () => { - try { - // await authLogin(); - } - catch (error) { - // 切换到登录页 - await uni.reLaunch({ url: '/pages/common/login/index' }) - throw error - } - }, - }, + refreshTokenOnError: { + isExpired: (error) => { + return error.response?.status === ResultEnum.Unauthorized + }, + handler: async () => { + try { + // await authLogin(); + } + catch (error) { + // 切换到登录页 + await uni.reLaunch({ url: '/pages/common/login/index' }) + throw error + } + }, + }, }) /** * alova 请求实例 */ const alovaInstance = createAlova({ - baseURL: import.meta.env.VITE_APP_PROXY_PREFIX, - ...AdapterUniapp(), - timeout: 5000, - statesHook: VueHook, + baseURL: import.meta.env.VITE_APP_PROXY_PREFIX, + ...AdapterUniapp(), + timeout: 5000, + statesHook: VueHook, - beforeRequest: onAuthRequired((method) => { - // 设置默认 Content-Type - method.config.headers = { - ContentType: ContentTypeEnum.JSON, - Accept: 'application/json, text/plain, */*', - ...method.config.headers, - } + beforeRequest: onAuthRequired((method) => { + // 设置默认 Content-Type + method.config.headers = { + ContentType: ContentTypeEnum.JSON, + Accept: 'application/json, text/plain, */*', + ...method.config.headers, + } - const { config } = method - const ignoreAuth = !config.meta?.ignoreAuth - console.log('ignoreAuth===>', ignoreAuth) - // 处理认证信息 自行处理认证问题 - if (ignoreAuth) { - const token = 'getToken()' - if (!token) { - throw new Error('[请求错误]:未登录') - } - // method.config.headers.token = token; - } + const { config } = method + const ignoreAuth = !config.meta?.ignoreAuth + console.log('ignoreAuth===>', ignoreAuth) + // 处理认证信息 自行处理认证问题 + if (ignoreAuth) { + const token = 'getToken()' + if (!token) { + throw new Error('[请求错误]:未登录') + } + // method.config.headers.token = token; + } - // 处理动态域名 - if (config.meta?.domain) { - method.baseURL = config.meta.domain - console.log('当前域名', method.baseURL) - } - }), + // 处理动态域名 + if (config.meta?.domain) { + method.baseURL = config.meta.domain + console.log('当前域名', method.baseURL) + } + }), - responded: onResponseRefreshToken((response, method) => { - const { config } = method - const { requestType } = config - const { - statusCode, - data: rawData, - errMsg, - } = response as UniNamespace.RequestSuccessCallbackResult + responded: onResponseRefreshToken((response, method) => { + const { config } = method + const { requestType } = config + const { + statusCode, + data: rawData, + errMsg, + } = response as UniNamespace.RequestSuccessCallbackResult - // 处理特殊请求类型(上传/下载) - if (requestType === 'upload' || requestType === 'download') { - return response - } + // 处理特殊请求类型(上传/下载) + if (requestType === 'upload' || requestType === 'download') { + return response + } - // 处理 HTTP 状态码错误 - if (statusCode !== 200) { - const errorMessage = ShowMessage(statusCode) || `HTTP请求错误[${statusCode}]` - console.error('errorMessage===>', errorMessage) - toast.error(errorMessage) - throw new Error(`${errorMessage}:${errMsg}`) - } + // 处理 HTTP 状态码错误 + if (statusCode !== 200) { + const errorMessage = ShowMessage(statusCode) || `HTTP请求错误[${statusCode}]` + console.error('errorMessage===>', errorMessage) + toast.error(errorMessage) + throw new Error(`${errorMessage}:${errMsg}`) + } - // 处理业务逻辑错误 - const { code, message, data } = rawData as IResponse - if (code !== ResultEnum.Success) { - if (config.meta?.toast !== false) { - toast.warning(message) - } - throw new Error(`请求错误[${code}]:${message}`) - } - // 处理成功响应,返回业务数据 - return data - }), + // 处理业务逻辑错误 + const { code, message, data } = rawData as IResponse + if (code !== ResultEnum.Success) { + if (config.meta?.toast !== false) { + toast.warning(message) + } + throw new Error(`请求错误[${code}]:${message}`) + } + // 处理成功响应,返回业务数据 + return data + }), }) export const http = alovaInstance diff --git a/src/http/http.ts b/src/http/http.ts index 98c7324..846b0eb 100644 --- a/src/http/http.ts +++ b/src/http/http.ts @@ -1,47 +1,47 @@ import type { CustomRequestOptions } from '@/http/types' export function http(options: CustomRequestOptions) { - // 1. 返回 Promise 对象 - return new Promise>((resolve, reject) => { - uni.request({ - ...options, - dataType: 'json', - // #ifndef MP-WEIXIN - responseType: 'json', - // #endif - // 响应成功 - success(res) { - // 状态码 2xx,参考 axios 的设计 - if (res.statusCode >= 200 && res.statusCode < 300) { - // 2.1 提取核心数据 res.data - resolve(res.data as IResData) - } - else if (res.statusCode === 401) { - // 401错误 -> 清理用户信息,跳转到登录页 - // userStore.clearUserInfo() - // uni.navigateTo({ url: '/pages/login/login' }) - reject(res) - } - else { - // 其他错误 -> 根据后端错误信息轻提示 - !options.hideErrorToast - && uni.showToast({ - icon: 'none', - title: (res.data as IResData).msg || '请求错误', - }) - reject(res) - } - }, - // 响应失败 - fail(err) { - uni.showToast({ - icon: 'none', - title: '网络错误,换个网络试试', - }) - reject(err) - }, - }) - }) + // 1. 返回 Promise 对象 + return new Promise>((resolve, reject) => { + uni.request({ + ...options, + dataType: 'json', + // #ifndef MP-WEIXIN + responseType: 'json', + // #endif + // 响应成功 + success(res) { + // 状态码 2xx,参考 axios 的设计 + if (res.statusCode >= 200 && res.statusCode < 300) { + // 2.1 提取核心数据 res.data + resolve(res.data as IResData) + } + else if (res.statusCode === 401) { + // 401错误 -> 清理用户信息,跳转到登录页 + // userStore.clearUserInfo() + // uni.navigateTo({ url: '/pages/login/login' }) + reject(res) + } + else { + // 其他错误 -> 根据后端错误信息轻提示 + !options.hideErrorToast + && uni.showToast({ + icon: 'none', + title: (res.data as IResData).msg || '请求错误', + }) + reject(res) + } + }, + // 响应失败 + fail(err) { + uni.showToast({ + icon: 'none', + title: '网络错误,换个网络试试', + }) + reject(err) + }, + }) + }) } /** @@ -52,13 +52,13 @@ export function http(options: CustomRequestOptions) { * @returns */ export function httpGet(url: string, query?: Record, header?: Record, options?: Partial) { - return http({ - url, - query, - method: 'GET', - header, - ...options, - }) + return http({ + url, + query, + method: 'GET', + header, + ...options, + }) } /** @@ -70,40 +70,40 @@ export function httpGet(url: string, query?: Record, header?: Re * @returns */ export function httpPost(url: string, data?: Record, query?: Record, header?: Record, options?: Partial) { - return http({ - url, - query, - data, - method: 'POST', - header, - ...options, - }) + return http({ + url, + query, + data, + method: 'POST', + header, + ...options, + }) } /** * PUT 请求 */ export function httpPut(url: string, data?: Record, query?: Record, header?: Record, options?: Partial) { - return http({ - url, - data, - query, - method: 'PUT', - header, - ...options, - }) + return http({ + url, + data, + query, + method: 'PUT', + header, + ...options, + }) } /** * DELETE 请求(无请求体,仅 query) */ export function httpDelete(url: string, query?: Record, header?: Record, options?: Partial) { - return http({ - url, - query, - method: 'DELETE', - header, - ...options, - }) + return http({ + url, + query, + method: 'DELETE', + header, + ...options, + }) } http.get = httpGet diff --git a/src/http/interceptor.ts b/src/http/interceptor.ts index 357780b..c06aed1 100644 --- a/src/http/interceptor.ts +++ b/src/http/interceptor.ts @@ -9,57 +9,57 @@ const baseUrl = getEnvBaseUrl() // 拦截器配置 const httpInterceptor = { - // 拦截前触发 - invoke(options: CustomRequestOptions) { - // 接口请求支持通过 query 参数配置 queryString - if (options.query) { - const queryStr = stringifyQuery(options.query) - if (options.url.includes('?')) { - options.url += `&${queryStr}` - } - else { - options.url += `?${queryStr}` - } - } - // 非 http 开头需拼接地址 - if (!options.url.startsWith('http')) { - // #ifdef H5 - // console.log(__VITE_APP_PROXY__) - if (JSON.parse(__VITE_APP_PROXY__)) { - // 自动拼接代理前缀 - options.url = import.meta.env.VITE_APP_PROXY_PREFIX + options.url - } - else { - options.url = baseUrl + options.url - } - // #endif - // 非H5正常拼接 - // #ifndef H5 - options.url = baseUrl + options.url - // #endif - // TIPS: 如果需要对接多个后端服务,也可以在这里处理,拼接成所需要的地址 - } - // 1. 请求超时 - options.timeout = 10000 // 10s - // 2. (可选)添加小程序端请求头标识 - options.header = { - platform, // 可选,与 uniapp 定义的平台一致,告诉后台来源 - ...options.header, - } - // 3. 添加 token 请求头标识 - const userStore = useUserStore() - const { token } = userStore.userInfo as unknown as IUserInfo - if (token) { - options.header.Authorization = `Bearer ${token}` - } - }, + // 拦截前触发 + invoke(options: CustomRequestOptions) { + // 接口请求支持通过 query 参数配置 queryString + if (options.query) { + const queryStr = stringifyQuery(options.query) + if (options.url.includes('?')) { + options.url += `&${queryStr}` + } + else { + options.url += `?${queryStr}` + } + } + // 非 http 开头需拼接地址 + if (!options.url.startsWith('http')) { + // #ifdef H5 + // console.log(__VITE_APP_PROXY__) + if (JSON.parse(__VITE_APP_PROXY__)) { + // 自动拼接代理前缀 + options.url = import.meta.env.VITE_APP_PROXY_PREFIX + options.url + } + else { + options.url = baseUrl + options.url + } + // #endif + // 非H5正常拼接 + // #ifndef H5 + options.url = baseUrl + options.url + // #endif + // TIPS: 如果需要对接多个后端服务,也可以在这里处理,拼接成所需要的地址 + } + // 1. 请求超时 + options.timeout = 10000 // 10s + // 2. (可选)添加小程序端请求头标识 + options.header = { + platform, // 可选,与 uniapp 定义的平台一致,告诉后台来源 + ...options.header, + } + // 3. 添加 token 请求头标识 + const userStore = useUserStore() + const { token } = userStore.userInfo as unknown as IUserInfo + if (token) { + options.header.Authorization = `Bearer ${token}` + } + }, } export const requestInterceptor = { - install() { - // 拦截 request 请求 - uni.addInterceptor('request', httpInterceptor) - // 拦截 uploadFile 文件上传 - uni.addInterceptor('uploadFile', httpInterceptor) - }, + install() { + // 拦截 request 请求 + uni.addInterceptor('request', httpInterceptor) + // 拦截 uploadFile 文件上传 + uni.addInterceptor('uploadFile', httpInterceptor) + }, } diff --git a/src/main.ts b/src/main.ts index a357275..9562a55 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,13 +9,14 @@ import '@/style/index.scss' import 'virtual:uno.css' export function createApp() { - const app = createSSRApp(App) - app.use(store) - app.use(routeInterceptor) - app.use(requestInterceptor) - app.use(VueQueryPlugin) + const app = createSSRApp(App) + app.use(store) + app.use(routeInterceptor) + app.use(requestInterceptor) + app.use(VueQueryPlugin) + app.provide('OSS', '/src/static/') - return { - app, - } + return { + app, + } } diff --git a/src/pages.json b/src/pages.json index 86b05f7..107f0e6 100644 --- a/src/pages.json +++ b/src/pages.json @@ -15,7 +15,7 @@ } }, "tabBar": { - "custom": true, + "custom": false, "color": "#999999", "selectedColor": "#018d71", "backgroundColor": "#F8F8F8", @@ -27,15 +27,21 @@ "list": [ { "iconPath": "static/tabbar/home.png", - "selectedIconPath": "static/tabbar/homeHL.png", + "selectedIconPath": "static/tabbar/home_s.png", "pagePath": "pages/index/index", "text": "首页" }, { - "iconPath": "static/tabbar/example.png", - "selectedIconPath": "static/tabbar/exampleHL.png", - "pagePath": "pages/about/about", - "text": "关于" + "iconPath": "static/tabbar/reserve.png", + "selectedIconPath": "static/tabbar/reserve_s.png", + "pagePath": "pages/reserve/reserve", + "text": "预约" + }, + { + "iconPath": "static/tabbar/my.png", + "selectedIconPath": "static/tabbar/my_s.png", + "pagePath": "pages/my/my", + "text": "我的" } ] }, @@ -72,6 +78,24 @@ "style": { "navigationBarTitleText": "Vue Query 请求演示" } + }, + { + "path": "pages/my/my", + "type": "page", + "layout": "tabbar", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "我的" + } + }, + { + "path": "pages/reserve/reserve", + "type": "page", + "layout": "tabbar", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "预约" + } } ], "subPackages": [ @@ -89,4 +113,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index 08b1284..bbd085a 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -1,122 +1,86 @@ - -{ - "layout": "tabbar", - "style": { - // 'custom' 表示开启自定义导航栏,默认 'default' - "navigationStyle": "custom", - "navigationBarTitleText": "首页" - } -} - - - +{ + "layout": "tabbar", + "style": { + // 'custom' 表示开启自定义导航栏,默认 'default' + "navigationStyle": "custom", + "navigationBarTitleText": "首页" + } +} + + + + \ No newline at end of file diff --git a/src/pages/my/my.vue b/src/pages/my/my.vue new file mode 100644 index 0000000..47eb572 --- /dev/null +++ b/src/pages/my/my.vue @@ -0,0 +1,120 @@ + +{ + "layout": "tabbar", + "style": { + // 'custom' 表示开启自定义导航栏,默认 'default' + "navigationStyle": "custom", + "navigationBarTitleText": "我的" + } +} + + + + diff --git a/src/pages/reserve/reserve.vue b/src/pages/reserve/reserve.vue new file mode 100644 index 0000000..31cec65 --- /dev/null +++ b/src/pages/reserve/reserve.vue @@ -0,0 +1,120 @@ + +{ + "layout": "tabbar", + "style": { + // 'custom' 表示开启自定义导航栏,默认 'default' + "navigationStyle": "custom", + "navigationBarTitleText": "预约" + } +} + + + + diff --git a/src/static/icon/icon_arrow_down.png b/src/static/icon/icon_arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..eb2e2bd274aebe771fbd99cc79a56ea92dda849d GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^d_c^}!3-o#C%=sbQsDtUA+A6g2>$>7-_X#|*VotI z-w!0ay1F{Rpc4!rTnHPP1S)}Zk=a1O{N~l1KyzhEg8YIR7#Z0(1yoesJ%f_7i~1)n z*>>RU)f>0pe*E_9_v%luWWMVSXV`_4Cn{VQhdew|eD*EN#%lI&n)hU%fjr~s>gTe~ HDWM4f?iFp2 literal 0 HcmV?d00001 diff --git a/src/static/images/home_bg.png b/src/static/images/home_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..561998a34cc42737bfb9a7955f0d5e32aa5d2a25 GIT binary patch literal 23580 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 Gjr<=)V(Hlc literal 0 HcmV?d00001 diff --git a/src/static/tabbar/example.png b/src/static/tabbar/example.png deleted file mode 100644 index fd1e942bcf691d6d3711535d9419417cabd5d781..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1371 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 Eg2>i=Q~&?~ diff --git a/src/static/tabbar/home.png b/src/static/tabbar/home.png index 8f82e213c78bd38fc9e7a6eee31c05de0cd3363d..2fa5c0a02e8c400771bf3a99d8b4088c26c5531f 100644 GIT binary patch literal 799 zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!3?wz9Rv9xeFe(T5gt!8^|NsAQzyNJ+Z9p*~ zSyfd9WB|#QmKGqRxw#ogHZ?UNllArWKyG7WBUZAmt`1WQA_yemT7YaIfT)BpfNTgC zs0LyMgamR)1q3zXQ6M$v@&;fqC6omD1v4-*v2gJ42?&dbic3h!D=6#e8yH(y+c-G8 zxVpJ}_=kjsM?}ZOCnP2&r=+H3X6KibmseJ|cXW35_fMWNbN0OXi&w8%w`u#%UAy-j zK7Rc4`776M-+%o4{fCdAzy13C=kFS3gH^z&@%D6a46!)<_G+{>Q=mx0!xs)mrabYn ze5RwM6sUZ7LCUcyMnC`kUtbXD%YNC%d`Cj;+pY7zC%xbLZS%InA2%0nxNrXT?%~58 z^<{y~7oPoIa84y)HDkJ;^=o~G^~H*!6+&y?MzK!V!`JT4a!Qc<)j5W$HS^wtHf~ux zeQp4Q>J1-e*98&=+YMz;?1+|LXA*GNbYUFliZx|#KXcjO!o)ZW8- zyx<`Z ntd;10e`WgV$M^qDuVXs5YP!kN^rqFoC}i+-^>bP0l+XkKNS|~4 literal 1346 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 V*RY*zoyhZ! diff --git a/src/static/tabbar/home_s.png b/src/static/tabbar/home_s.png new file mode 100644 index 0000000000000000000000000000000000000000..b58714d35a36433127044a8d4cfff23424d79c47 GIT binary patch literal 1769 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 Y0Q$!3ce!x6=Kufz07*qoM6N<$g7YEZg#Z8m literal 0 HcmV?d00001 diff --git a/src/static/tabbar/my_s.png b/src/static/tabbar/my_s.png new file mode 100644 index 0000000000000000000000000000000000000000..85a80c327fcc33b10b6b49d8bf607ab36689a0ae GIT binary patch literal 1132 zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!3?wz9Rv9xeFqQ=Pgt!8^Ake_z(BR-O-@$E> zp7mU7mzgd=w)-4Q-+o0!NRT4jywIeC7gW?Y-wa12uZibM&3(>M`5FXSR#ad>8Mz_TF=yJ!d=l z05#5Y_MYeHHOJX|fs5BXXRmpVo^u?$<~sTS*>fGe<~e~C0NGAHK=xdqGN2kKkjlA0 zDWD0?ol=YquX3qhVAiW^#Knj4mKvn?R-aw5YHvmbX0v8{kz4L+2a003X zng(+4e4s9%t6YG(fLz~sKm^nXVuMWsv4K)RH6R2w0VoBL1&e^S15E?T0>zQ_f{g`9 zxh#7vDFY1i>XIP8UJ!*Jwt078yj0&J9~Qv2M0$- zCubLT4{sm;fZ&j@$e8$q#N_0Z^o-2xoSeME;_}Lxn!5Uyw$ARJ{{BgmCQqI+ZTjr_ zYu0bvyk+0M{rmTyK6CEkmD{)PKY#h={riufK7aZC{pauBf2Pk@O=VzU((!b046!)9 zcS>%!P@qU_|HR0MWlN@IYB{Mcduh6K$(vrC>Wyo3R9kgcsI1x0z4*tUpLPa)htJ5K z*&6lM-?)5U>9u*a?<|%62fn@dvZTuDR-J|Ao|r>nFW(k8n?x<}Yt2xQI>!`nE?rE_ z=%*3G&4PyON(s8(+OpO!Tj7#`hAR=5JT|#1l8) zj$d3Y+!c2}^1@xEv$=njA3s+9n|qdTt>WvGJgbr~6$M(p%Dq`0)cT8sd&8wGmJ4HM zy~tTrd)ndPS+xUJ#w%>3EaFZdX=>q)Yr8&us(*N2XU7R8ot+Pa&S*93b#$B%n8J61 z5W(76me9Qvz$1lqR+BU{+VmZ@bEA-M@s{zUeB%j#!rz#!L0g{67-5 z_x^srJd1xNfB)2=>osOVAI_#N-}d99kLJ>Qp*Qv|y;6Fg-)%vXdwD+pkDpIpi&+%Z zmIO7am%S9f{hRqmcATc^qwFpn=e+x0-Yl1G@Orl_$Sd~Kc56m!P4!0U;`MRoZf-mG zOMjBcgW`bTJ>elq8@}I>`TRof$SZeM_G`}nU9D7OrNVfbqrY8zzVx)o2BDP=PYaE1 zn5=Rv|Ixm0KU1}Qu->;7Y@(m(0FD${$m#=JvQt87`c;-0d!(Px;Q%OWYRCr$PoxiJPH4w*hd9TpUMz9dp?Zsy6U0)h|OF_{63q-K9a2M=E@IP?H z#!|3dh>MMdh#-QEKi0}Zun}y8Cv#0C?|3rvotfmEJh=}ZZfyt_K?JOjv^jrv`{8D$`SoG=R{QsZnZ;bKt$@-kF z&eTA1=f4(0xL82Y_A$XBF!cA)%#fx6vH(C3M*R>N`u!-z_+ZpSEagrCB&GCDwt&|y zjd0#G6`b9GRMv3M*`YmeCpZscjQ4vMswrz1AlV{j0^r2?NFxMWV zsO$^s%P0b)pYqr9xXr%QTvE9mpR_^O`PD+(1AW zG&-ZyWA*2X(Wxo6Oe9r71UN7V_chO!mhRbO2^>r4DCBMR@eB9BO z7s#d16BHW4>1`0OrSn_&jDljG)tiB<@>(RLE!;^`cYh@S^i&K?{MW_AP*%swLVb zSwMxfGKEegOGe5uMpQc5H(Ej?Dj$Rmu4(o7mGLCMeM!dHuQaSLxMD}jLsqggF0DJ3%X zR<1~09s*+FS2(FrSqUJ$aCsUa+-UA<9Wh3@UK$XKOaNJWDhfL0qtt7Cz9&95s`*5NiUe?!2?6^ zYhEB~Kf(cNIdqrS3oDLh-{gUkQrf(&E!kfMBE-?IPU}50_J9BqjGMQ$1*Dk;^*Hp( zkF@4Do68oX^Deo{u+@>)UO>1NJC`JCET;FeLL)Pwns;e|bQ!@t($t!cXC`mllC(>% zDmCZ=qGphWs;FflTL9tomHN{aUUNWz-7a0M!!i-x4TzMA z&&QoryVNDC$j_8EOn0-6>j;F~Xi)mMo23XlUFdmTd}KW^dP!FIWE2RHr)(AuXc<(%g;SV`A8d*ClPQ zZzC}0A%rhO2tQOG{F(nv_1C;LV%XeAo0hSpPKJymAdMlZgZ1-~?cwBjHIkbONR@8* zB82d)wTHp`OveSlvX7T>nE+LCQP93_QO_b8DKCgY4%le=W$oH4ZFu@vj3jGN-4b>Lil)5w@+e> zuXv$Y7QQ$j>ctgJ1HiKynj%}*vd7?3N?(N#ZY^&RNVcCHxr8h1wFJC$w4o@Bqhi}i$&fdR$ z`}QxDpCh%P$Ow8{sjzRrwUv~UEio&ql&H-?NEWb?hk{O42}w*gwU6w6Z^{<;Vn_vy z=|EWL5S0v?x8Nc`xM*wDN`RxnrS4tlG}4IId@hXg=ETl>tPKIe9gbhE80a%hk(#Dc zJ=X$f@>^|p9sd)705SCyD*<2(&mnn0RI($zrGYNJ0>x_`?8LBcl(b8vyrC0;p?P&h zkB;?}0lrP{I3OjNMwcxh3jwTzUZ+97Ts~@Af5AZmq0kH3xHo6R@~i=Ztbe!K)_two zGxD|f0#JSpAw1J67qt#pvMOCgms+n(^sbXkwK^4m!tlUf$OBtQ1x0$3+_GZuj9xo( zx4vHT6D##m%d`auvP-1Y?SBKqs1oY-tAPLxe)>tD$FdX|2-uO=^)Ug-P$cR4AjbHn zr}4Ain^Jl!gzz3yKebeqvOGYfsB6j`l@~HFga0kY__XyoW+2&05PxDaU2B6z-UqJf zsQpG4$x=GGYYzq37ffB^f}x&=FflqZKY`W~Mvibx*$OA_hS3d3mM>&0=(X}IfWIh= zyKO~v`HjA&deH0YKroCZnIzxa|Au*+-(Ji87-xY0cgWrzrfn)$dk26pDkh%^7QdtY) z?3Z;dR@41E4Gjl*>beaN--iRKtYy=$Uag)>@<}_}wTF0?vO45@3pep0!hEHCcRFIh z+%;tf`|^842!M2!5PkOMVfrQ(Qn%iy+h_;&x^8n2rQ(|dSwv#w^tU*mgM!)(#O^>A zU`XFRU$2dJcZLzfYt5+zxkHFbWO=X4LHrM753 zpBBhkBPvLBBz0ru+Q;P-mG?l8rZan7d#!-Px;ph-kQRCr$Pojs2mMHqnJSyDhpgG7OpOOI&s1Mbq$Qz9WH_X}d5Lq&0kM3*?W z_yczl4J8skActru5JEyoX#j_c0*MBRChVY@-L-dpJMY)btk;pR3Bt3pGw<_$&F308 z{EbQiW>l>?GzJbpMy3P@AO|2LlfcNDvws${m>j_?ID(=$s$^N-?8>xwS^e*F<^TP3 zc{MVbqs!=_!Q9K=ey2UczkoXjg684UqZd z+>8svw)}SnW>_HevwH^YMI!C1vM2-9zs+A2{N0~60ch7YIGAC8EG9?BnhUU?3DmWZ zw^u$x;-<3BCAz;lMK;FXRleWVva*G}1Jh*}ej?3kSCb!z&OKF{nIlEjXL5sBOFke9 ztFT}nW`PjWWKCjW;K~|%n#)@90C_w)F@<@~wCF)J4{eJa%*vMbG{*olJrDs5ZxK68 zF|R?AfytssX@PLFs2HAZq9ibyO2YlqDF-GmhHEE*VB-W^*jwa?d0To|gEjTV(m}LZlQ{xrB@C;h zfdo=hRqSvpd`Na>749#VhkHJxbgDRl_mQ+Z97v@3^$9BkL<=|tkmPN`td5ugM}eU3 zN~z7MwJ`;d;6Rb#VnXi^%8sZL2a?!bdG`<)?e8R@uLFBt7u$GYbFrHOz9My`>0d0Q~pBx4eHK@h{Hg;Ks> zI#m>~ko-WGz9IkR> z%tJB!J-uFjs^wY+oisocLw-iv@FLU}|L)W7c841P_Iqp1ubZFjzdn#w_iM3ljmSCn zkxt9Vr%p1M^;)2OONr*+RFKOueEo_ksBr;lI$_OsVBx)FQtz)wbzDHyCdCxg@YC<1 z9tnNENHAN4uVrqJ?|%Ts97AE=1b`QCw)iHd{*A@UmxjYYLgzLTj@BEq_V+3daDSE` zffw+ovzKgs_K5-eWhXPT@KyO>@o_8^&B7P5ge#{j>Hy=chN!-9Ve}ZBnNNNIFt@iK zw}rh8)8&sbZIA|ZpO6G!h-F%_fnAnS@nmA*6&Uyj4E*G-*znhDFwP>H`Sfo9c>5X< zYd?Yq%ddGa?6SpU0Mz22yAl8d}l%siR?1_rJjV~fqcv^zAl z8|CC9cAZt|4|{S|dp`Nxfcb2z_%C+0c%NHEUpehM{UBRO$%CEU9_5FyEx{+#`^LbB z0PvOp__=)Jzd204_`{7OT*Bebzk3(Ry-Esm_c2>Zv4IV1%|Pa&h6h2IK#!KQPFA7r zikRA`cfW^^E#r;j!x$Y1&q=X`r{NpQt~g3e0s+MQf-m4*2H)j$QC9F;yRq zp^?YhXdt+z_cn|ZGg7W|2;|H!O3$nH_Z?fOXYAOafq3%`ACBC%5(bR?b4Yx#hfDHY zZWl_Vb>IWTc2lWcBJ~ZOEEtljD^dtHrVhq!a%+X8ZggN+2OkJwB|a8ddxg$NjW}MQ z%otGIdJ1-Pw!}U2T$XDwqhUzLPzXO0C>U>*J>L>j8;8trr7f?h^4$daYnILZ;KgMZ>tj1|AJWJ0u@J$b7lYNmNL8;mB?{kk-{NUsAO?%nabn*bHp$NB#R{O3>cqY0X zI|h;*MD}Ipd_WpO30y!+w)sCSS$&zB`f5T4_YmoOejtqt8~W3$jg|UOufnnP5YLw3 zWph#620jdswYB)Q`E#je2D}pXabl6?BVKRt#A8?>y4VsECA7~D4x(x3@Iab34|qUF zKxJnjb`PWjrZ|cGytY8*y+wBpVmv_F^6^$P7@W~SXp3NXE{oFX<^%O*aZ*5eEd27? zcJ1yM^~!Bk8uatvR3CeOA1{!;&NPsGDqGZi>+69eL=Bv5Y;1(EAq*gi3E+yU%P{$!_6X<`$C4nwUiXvP?*7TM=PzEqdh_o6hmW5=fBE|D z`;VW$e*gLVZ)x|elMD=u7M?DSAr_~vPQ4j)$U(#p-Eb5!FD^oM2F7}x%+b8Ou?UuxzE=mCq&n*xutDeV1Af!N?1<*-Dh9&7_HB4 zUl`=IlI2*|%E+w4%m1?OHecegYwo7Fme#1^9lVQ$AD^-AH~8w}lEQIy5%af&ib_gS zp%Qmh&n!>lyf3u2e95JtzK3@F-j#bdZc=Bfm|Q2mZ${m&Z679{)6!_;PrtBTX0cK0 zfsM`DhAqqH{CyYu?fOsU4x3;pS&p)2IV={|W#=FawVbZSHySlu)5_d~)Eu3dM^-8UtK#;tHyqFA(5C%_IKbLh* G2~7ZG$tz_5 literal 0 HcmV?d00001 diff --git a/src/static/tabbar/reserve_s.png b/src/static/tabbar/reserve_s.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9c686cf67ba30a33fd2a5d6daa204146647061 GIT binary patch literal 1659 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 { - // 定义用户信息 - const userInfo = ref({ ...userInfoState }) - // 设置用户信息 - const setUserInfo = (val: IUserInfoVo) => { - console.log('设置用户信息', val) - // 若头像为空 则使用默认头像 - if (!val.avatar) { - val.avatar = userInfoState.avatar - } - else { - val.avatar = 'https://oss.laf.run/ukw0y1-site/avatar.jpg?feige' - } - userInfo.value = val - } - const setUserAvatar = (avatar: string) => { - userInfo.value.avatar = avatar - console.log('设置用户头像', avatar) - console.log('userInfo', userInfo.value) - } - // 删除用户信息 - const removeUserInfo = () => { - userInfo.value = { ...userInfoState } - uni.removeStorageSync('userInfo') - uni.removeStorageSync('token') - } - /** - * 获取用户信息 - */ - const getUserInfo = async () => { - const res = await _getUserInfo() - const userInfo = res.data - setUserInfo(userInfo) - uni.setStorageSync('userInfo', userInfo) - uni.setStorageSync('token', userInfo.token) - // TODO 这里可以增加获取用户路由的方法 根据用户的角色动态生成路由 - return res - } - /** - * 用户登录 - * @param credentials 登录参数 - * @returns R - */ - const login = async (credentials: { - username: string - password: string - code: string - uuid: string - }) => { - const res = await _login(credentials) - console.log('登录信息', res) - toast.success('登录成功') - await getUserInfo() - return res - } + 'user', + () => { + // 定义用户信息 + const userInfo = ref({ ...userInfoState }) + // 设置用户信息 + const setUserInfo = (val: IUserInfoVo) => { + console.log('设置用户信息', val) + // 若头像为空 则使用默认头像 + if (!val.avatar) { + val.avatar = userInfoState.avatar + } + else { + val.avatar = 'https://oss.laf.run/ukw0y1-site/avatar.jpg?feige' + } + userInfo.value = val + } + const setUserAvatar = (avatar: string) => { + userInfo.value.avatar = avatar + console.log('设置用户头像', avatar) + console.log('userInfo', userInfo.value) + } + // 删除用户信息 + const removeUserInfo = () => { + userInfo.value = { ...userInfoState } + uni.removeStorageSync('userInfo') + uni.removeStorageSync('token') + } + /** + * 获取用户信息 + */ + const getUserInfo = async () => { + const res = await _getUserInfo() + const userInfo = res.data + setUserInfo(userInfo) + uni.setStorageSync('userInfo', userInfo) + uni.setStorageSync('token', userInfo.token) + // TODO 这里可以增加获取用户路由的方法 根据用户的角色动态生成路由 + return res + } + /** + * 用户登录 + * @param credentials 登录参数 + * @returns R + */ + const login = async (credentials: { + username: string + password: string + code: string + uuid: string + }) => { + const res = await _login(credentials) + console.log('登录信息', res) + toast.success('登录成功') + await getUserInfo() + return res + } - /** - * 退出登录 并 删除用户信息 - */ - const logout = async () => { - _logout() - removeUserInfo() - } - /** - * 微信登录 - */ - const wxLogin = async () => { - // 获取微信小程序登录的code - const data = await getWxCode() - console.log('微信登录code', data) + /** + * 退出登录 并 删除用户信息 + */ + const logout = async () => { + _logout() + removeUserInfo() + } + /** + * 微信登录 + */ + const wxLogin = async () => { + // 获取微信小程序登录的code + const data = await getWxCode() + console.log('微信登录code', data) - const res = await _wxLogin(data) - await getUserInfo() - return res - } + const res = await _wxLogin(data) + await getUserInfo() + return res + } - return { - userInfo, - login, - wxLogin, - getUserInfo, - setUserAvatar, - logout, - } - }, - { - persist: true, - }, + return { + userInfo, + login, + wxLogin, + getUserInfo, + setUserAvatar, + logout, + } + }, + { + persist: true, + }, ) diff --git a/src/tabbar/config.ts b/src/tabbar/config.ts index 94c4020..27cddca 100644 --- a/src/tabbar/config.ts +++ b/src/tabbar/config.ts @@ -3,11 +3,11 @@ import type { TabBar } from '@uni-helper/vite-plugin-uni-pages' type NativeTabBarItem = TabBar['list'][0] type CustomTabBarItem = (Pick & { - iconType: 'uniUi' | 'uiLib' | 'unocss' | 'iconfont' | 'image' // 不建议用 image 模式,需要配置2张图 - icon: any // 其实是 string 类型,这里是为了避免 ts 报错 (tabbar/index.vue 里面 uni-icons 那行) - activeIcon?: string // 只有在 image 模式下才需要,传递的是高亮的图片(PS: 不建议用 image 模式) - badge?: number | 'dot' // badge 显示一个数字或 小红点(样式可以直接在 tabbar/index.vue 里面修改) - isBulge?: boolean // 是否是中间的鼓包tabbarItem + iconType: 'uniUi' | 'uiLib' | 'unocss' | 'iconfont' | 'image' // 不建议用 image 模式,需要配置2张图 + icon: any // 其实是 string 类型,这里是为了避免 ts 报错 (tabbar/index.vue 里面 uni-icons 那行) + activeIcon?: string // 只有在 image 模式下才需要,传递的是高亮的图片(PS: 不建议用 image 模式) + badge?: number | 'dot' // badge 显示一个数字或 小红点(样式可以直接在 tabbar/index.vue 里面修改) + isBulge?: boolean // 是否是中间的鼓包tabbarItem }) /** @@ -20,14 +20,14 @@ type CustomTabBarItem = (Pick & { * 温馨提示:本文件的任何代码更改了之后,都需要重新运行,否则 pages.json 不会更新导致配置不生效 */ export const TABBAR_MAP = { - NO_TABBAR: 0, - NATIVE_TABBAR: 1, - CUSTOM_TABBAR_WITH_CACHE: 2, - CUSTOM_TABBAR_WITHOUT_CACHE: 3, + NO_TABBAR: 0, + NATIVE_TABBAR: 1, + CUSTOM_TABBAR_WITH_CACHE: 2, + CUSTOM_TABBAR_WITHOUT_CACHE: 3, } // TODO: 1/3. 通过这里切换使用tabbar的策略 -export const selectedTabbarStrategy = TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE +export const selectedTabbarStrategy = TABBAR_MAP.NATIVE_TABBAR // TODO: 2/3. 更新下面的 tabbar 配置 // 如果是使用 NO_TABBAR(0),nativeTabbarList 和 customTabbarList 都不生效(里面的配置不用管) @@ -35,90 +35,102 @@ export const selectedTabbarStrategy = TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE // 如果是使用 CUSTOM_TABBAR(2,3),nativeTabbarList 不生效(里面的配置不用管) // pagePath 是 nativeTabbarList 和 customTabbarList 的关联点,如果没有对应上,会有问题!! export const nativeTabbarList: NativeTabBarItem[] = [ - { - iconPath: 'static/tabbar/home.png', - selectedIconPath: 'static/tabbar/homeHL.png', - pagePath: 'pages/index/index', - text: '首页', - }, - { - iconPath: 'static/tabbar/example.png', - selectedIconPath: 'static/tabbar/exampleHL.png', - pagePath: 'pages/about/about', - text: '关于', - }, + { + iconPath: 'static/tabbar/home.png', + selectedIconPath: 'static/tabbar/home_s.png', + pagePath: 'pages/index/index', + text: '首页', + }, + { + iconPath: 'static/tabbar/reserve.png', + selectedIconPath: 'static/tabbar/reserve_s.png', + pagePath: 'pages/reserve/reserve', + text: '预约', + }, + { + iconPath: 'static/tabbar/my.png', + selectedIconPath: 'static/tabbar/my_s.png', + pagePath: 'pages/my/my', + text: '我的', + }, + // { + // iconPath: 'static/tabbar/example.png', + // selectedIconPath: 'static/tabbar/exampleHL.png', + // pagePath: 'pages/about/about', + // text: '关于', + // }, ] // pagePath 是 nativeTabbarList 和 customTabbarList 的关联点,如果没有对应上,会有问题!! // 如果希望通过接口调用 customTabbarList,可以在 tabbar/index.vue 文件里面调用接口 // 本文件因为需要提前编译生成 pages.json, 接口拦截还不生效,无法正常调用接口 export const customTabbarList: CustomTabBarItem[] = [ - { - // text 和 pagePath 可以自己直接写,也可以通过索引从 nativeTabbarList 中获取 - text: '首页', - pagePath: 'pages/index/index', // pagePath 是两者的关联点 - // 本框架内置了 uniapp 官方UI库 (uni-ui)的图标库 - // 使用方式如: - // 图标列表地址:https://uniapp.dcloud.net.cn/component/uniui/uni-icons.html - iconType: 'uniUi', - icon: 'home', - // badge: 'dot', - }, - { - text: nativeTabbarList[1].text, - pagePath: nativeTabbarList[1].pagePath, // pagePath 是两者的关联点 - // 注意 unocss 图标需要如下处理:(二选一) - // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行) - // 2)配置到 unocss.config.ts 的 safelist 中 - iconType: 'unocss', - icon: 'i-carbon-code', - // badge: 10, - }, + { + // text 和 pagePath 可以自己直接写,也可以通过索引从 nativeTabbarList 中获取 + text: '首页', + pagePath: 'pages/index/index', // pagePath 是两者的关联点 + // 本框架内置了 uniapp 官方UI库 (uni-ui)的图标库 + // 使用方式如: + // 图标列表地址:https://uniapp.dcloud.net.cn/component/uniui/uni-icons.html + iconType: 'uniUi', + icon: 'home', + // badge: 'dot', + }, + { + text: nativeTabbarList[1].text, + pagePath: nativeTabbarList[1].pagePath, // pagePath 是两者的关联点 + // 注意 unocss 图标需要如下处理:(二选一) + // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行) + // 2)配置到 unocss.config.ts 的 safelist 中 + iconType: 'unocss', + icon: 'i-carbon-code', + // badge: 10, + }, - // { - // pagePath: 'pages/mine/index', - // text: '我的', - // // 注意 iconfont 图标需要额外加上 'iconfont',如下 - // iconType: 'iconfont', - // icon: 'iconfont icon-my', - // }, - // { - // pagePath: 'pages/index/index', - // text: '首页', - // // 使用 ‘image’时,需要配置 icon + iconActive 2张图片(不推荐) - // // 既然已经用了自定义tabbar了,就不建议用图片了,所以不推荐 - // iconType: 'image', - // icon: '/static/tabbar/home.png', - // iconActive: '/static/tabbar/homeHL.png', - // }, + // { + // pagePath: 'pages/mine/index', + // text: '我的', + // // 注意 iconfont 图标需要额外加上 'iconfont',如下 + // iconType: 'iconfont', + // icon: 'iconfont icon-my', + // }, + // { + // pagePath: 'pages/index/index', + // text: '首页', + // // 使用 ‘image’时,需要配置 icon + iconActive 2张图片(不推荐) + // // 既然已经用了自定义tabbar了,就不建议用图片了,所以不推荐 + // iconType: 'image', + // icon: '/static/tabbar/home.png', + // iconActive: '/static/tabbar/homeHL.png', + // }, ] // NATIVE_TABBAR(1) 和 CUSTOM_TABBAR_WITH_CACHE(2) 时,需要tabbar缓存 /** 是否启用 tabbar 缓存 */ export const tabbarCacheEnable - = [TABBAR_MAP.NATIVE_TABBAR, TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE].includes(selectedTabbarStrategy) + = [TABBAR_MAP.NATIVE_TABBAR, TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE].includes(selectedTabbarStrategy) // CUSTOM_TABBAR_WITH_CACHE(2) 和 CUSTOM_TABBAR_WITHOUT_CACHE(3) 时,启用自定义tabbar /** 是否启用自定义 tabbar */ export const customTabbarEnable - = [TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE, TABBAR_MAP.CUSTOM_TABBAR_WITHOUT_CACHE].includes(selectedTabbarStrategy) + = [TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE, TABBAR_MAP.CUSTOM_TABBAR_WITHOUT_CACHE].includes(selectedTabbarStrategy) // CUSTOM_TABBAR_WITH_CACHE(2)时,需要隐藏原生tabbar /** 是否需要隐藏原生 tabbar */ export const nativeTabbarNeedHide = selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE const _tabbar: TabBar = { - // 只有微信小程序支持 custom。App 和 H5 不生效 - custom: selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE, - color: '#999999', - selectedColor: '#018d71', - backgroundColor: '#F8F8F8', - borderStyle: 'black', - height: '50px', - fontSize: '10px', - iconWidth: '24px', - spacing: '3px', - list: nativeTabbarList as unknown as TabBar['list'], + // 只有微信小程序支持 custom。App 和 H5 不生效 + custom: selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE, + color: '#999999', + selectedColor: '#018d71', + backgroundColor: '#F8F8F8', + borderStyle: 'black', + height: '50px', + fontSize: '10px', + iconWidth: '24px', + spacing: '3px', + list: nativeTabbarList as unknown as TabBar['list'], } export const tabbarList = nativeTabbarList diff --git a/src/uni.scss b/src/uni.scss index 21b9e5f..bc750e1 100644 --- a/src/uni.scss +++ b/src/uni.scss @@ -75,3 +75,6 @@ $uni-color-subtitle: #555; // 二级标题颜色 $uni-font-size-subtitle: 18px; $uni-color-paragraph: #3f536e; // 文章段落颜色 $uni-font-size-paragraph: 15px; + +$cz-page-background: #F6F7F9; // 页面背景色 +$OSS: '/src/static/'; diff --git a/src/utils/index.ts b/src/utils/index.ts index 3aa8e52..79a9e29 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,11 +3,11 @@ import { tabbarList } from '@/tabbar/config' import { isMpWeixin } from './platform' export function getLastPage() { - // getCurrentPages() 至少有1个元素,所以不再额外判断 - // const lastPage = getCurrentPages().at(-1) - // 上面那个在低版本安卓中打包会报错,所以改用下面这个【虽然我加了 src/interceptions/prototype.ts,但依然报错】 - const pages = getCurrentPages() - return pages[pages.length - 1] + // getCurrentPages() 至少有1个元素,所以不再额外判断 + // const lastPage = getCurrentPages().at(-1) + // 上面那个在低版本安卓中打包会报错,所以改用下面这个【虽然我加了 src/interceptions/prototype.ts,但依然报错】 + const pages = getCurrentPages() + return pages[pages.length - 1] } /** @@ -16,25 +16,25 @@ export function getLastPage() { * redirectPath 如 '/pages/demo/base/route-interceptor' */ export function currRoute() { - const lastPage = getLastPage() - const currRoute = (lastPage as any).$page - // console.log('lastPage.$page:', currRoute) - // console.log('lastPage.$page.fullpath:', currRoute.fullPath) - // console.log('lastPage.$page.options:', currRoute.options) - // console.log('lastPage.options:', (lastPage as any).options) - // 经过多端测试,只有 fullPath 靠谱,其他都不靠谱 - const { fullPath } = currRoute as { fullPath: string } - // console.log(fullPath) - // eg: /pages/login/index?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor (小程序) - // eg: /pages/login/index?redirect=%2Fpages%2Froute-interceptor%2Findex%3Fname%3Dfeige%26age%3D30(h5) - return getUrlObj(fullPath) + const lastPage = getLastPage() + const currRoute = (lastPage as any).$page + // console.log('lastPage.$page:', currRoute) + // console.log('lastPage.$page.fullpath:', currRoute.fullPath) + // console.log('lastPage.$page.options:', currRoute.options) + // console.log('lastPage.options:', (lastPage as any).options) + // 经过多端测试,只有 fullPath 靠谱,其他都不靠谱 + const { fullPath } = currRoute as { fullPath: string } + // console.log(fullPath) + // eg: /pages/login/index?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor (小程序) + // eg: /pages/login/index?redirect=%2Fpages%2Froute-interceptor%2Findex%3Fname%3Dfeige%26age%3D30(h5) + return getUrlObj(fullPath) } function ensureDecodeURIComponent(url: string) { - if (url.startsWith('%')) { - return ensureDecodeURIComponent(decodeURIComponent(url)) - } - return url + if (url.startsWith('%')) { + return ensureDecodeURIComponent(decodeURIComponent(url)) + } + return url } /** * 解析 url 得到 path 和 query @@ -42,22 +42,22 @@ function ensureDecodeURIComponent(url: string) { * 输出: {path: /pages/login/index, query: {redirect: /pages/demo/base/route-interceptor}} */ export function getUrlObj(url: string) { - const [path, queryStr] = url.split('?') - // console.log(path, queryStr) + const [path, queryStr] = url.split('?') + // console.log(path, queryStr) - if (!queryStr) { - return { - path, - query: {}, - } - } - const query: Record = {} - queryStr.split('&').forEach((item) => { - const [key, value] = item.split('=') - // console.log(key, value) - query[key] = ensureDecodeURIComponent(value) // 这里需要统一 decodeURIComponent 一下,可以兼容h5和微信y - }) - return { path, query } + if (!queryStr) { + return { + path, + query: {}, + } + } + const query: Record = {} + queryStr.split('&').forEach((item) => { + const [key, value] = item.split('=') + // console.log(key, value) + query[key] = ensureDecodeURIComponent(value) // 这里需要统一 decodeURIComponent 一下,可以兼容h5和微信y + }) + return { path, query } } /** * 得到所有的需要登录的 pages,包括主包和分包的 @@ -65,49 +65,49 @@ export function getUrlObj(url: string) { * 如果没有传 key,则表示所有的 pages,如果传递了 key, 则表示通过 key 过滤 */ export function getAllPages(key = 'needLogin') { - // 这里处理主包 - const mainPages = pages - .filter(page => !key || page[key]) - .map(page => ({ - ...page, - path: `/${page.path}`, - })) + // 这里处理主包 + const mainPages = pages + .filter(page => !key || page[key]) + .map(page => ({ + ...page, + path: `/${page.path}`, + })) - // 这里处理分包 - const subPages: any[] = [] - subPackages.forEach((subPageObj) => { - // console.log(subPageObj) - const { root } = subPageObj + // 这里处理分包 + const subPages: any[] = [] + subPackages.forEach((subPageObj) => { + // console.log(subPageObj) + const { root } = subPageObj - subPageObj.pages - .filter(page => !key || page[key]) - .forEach((page: { path: string } & Record) => { - subPages.push({ - ...page, - path: `/${root}/${page.path}`, - }) - }) - }) - const result = [...mainPages, ...subPages] - // console.log(`getAllPages by ${key} result: `, result) - return result + subPageObj.pages + .filter(page => !key || page[key]) + .forEach((page: { path: string } & Record) => { + subPages.push({ + ...page, + path: `/${root}/${page.path}`, + }) + }) + }) + const result = [...mainPages, ...subPages] + // console.log(`getAllPages by ${key} result: `, result) + return result } export function isCurrentPageTabbar() { - const routeObj = currRoute() - return tabbarList.some(item => `/${item.pagePath}` === routeObj.path) + const routeObj = currRoute() + return tabbarList.some(item => `/${item.pagePath}` === routeObj.path) } export function getCurrentPageI18nKey() { - const routeObj = currRoute() - const currPage = pages.find(page => `/${page.path}` === routeObj.path) - if (!currPage) { - console.warn('路由不正确') - return '' - } - console.log(currPage) - console.log(currPage.style.navigationBarTitleText) - return currPage.style.navigationBarTitleText + const routeObj = currRoute() + const currPage = pages.find(page => `/${page.path}` === routeObj.path) + if (!currPage) { + console.warn('路由不正确') + return '' + } + console.log(currPage) + console.log(currPage.style.navigationBarTitleText) + return currPage.style.navigationBarTitleText } /** @@ -126,65 +126,75 @@ export const needLoginPages: string[] = getAllPages('needLogin').map(page => pag * 根据微信小程序当前环境,判断应该获取的 baseUrl */ export function getEnvBaseUrl() { - // 请求基准地址 - let baseUrl = import.meta.env.VITE_SERVER_BASEURL + // 请求基准地址 + let baseUrl = import.meta.env.VITE_SERVER_BASEURL - // # 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。 - const VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'https://ukw0y1.laf.run' - const VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'https://ukw0y1.laf.run' - const VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'https://ukw0y1.laf.run' + // # 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。 + const VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'https://ukw0y1.laf.run' + const VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'https://ukw0y1.laf.run' + const VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'https://ukw0y1.laf.run' - // 微信小程序端环境区分 - if (isMpWeixin) { - const { - miniProgram: { envVersion }, - } = uni.getAccountInfoSync() + // 微信小程序端环境区分 + if (isMpWeixin) { + const { + miniProgram: { envVersion }, + } = uni.getAccountInfoSync() - switch (envVersion) { - case 'develop': - baseUrl = VITE_SERVER_BASEURL__WEIXIN_DEVELOP || baseUrl - break - case 'trial': - baseUrl = VITE_SERVER_BASEURL__WEIXIN_TRIAL || baseUrl - break - case 'release': - baseUrl = VITE_SERVER_BASEURL__WEIXIN_RELEASE || baseUrl - break - } - } + switch (envVersion) { + case 'develop': + baseUrl = VITE_SERVER_BASEURL__WEIXIN_DEVELOP || baseUrl + break + case 'trial': + baseUrl = VITE_SERVER_BASEURL__WEIXIN_TRIAL || baseUrl + break + case 'release': + baseUrl = VITE_SERVER_BASEURL__WEIXIN_RELEASE || baseUrl + break + } + } - return baseUrl + return baseUrl } /** * 根据微信小程序当前环境,判断应该获取的 UPLOAD_BASEURL */ export function getEnvBaseUploadUrl() { - // 请求基准地址 - let baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL + // 请求基准地址 + let baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL - const VITE_UPLOAD_BASEURL__WEIXIN_DEVELOP = 'https://ukw0y1.laf.run/upload' - const VITE_UPLOAD_BASEURL__WEIXIN_TRIAL = 'https://ukw0y1.laf.run/upload' - const VITE_UPLOAD_BASEURL__WEIXIN_RELEASE = 'https://ukw0y1.laf.run/upload' + const VITE_UPLOAD_BASEURL__WEIXIN_DEVELOP = 'https://ukw0y1.laf.run/upload' + const VITE_UPLOAD_BASEURL__WEIXIN_TRIAL = 'https://ukw0y1.laf.run/upload' + const VITE_UPLOAD_BASEURL__WEIXIN_RELEASE = 'https://ukw0y1.laf.run/upload' - // 微信小程序端环境区分 - if (isMpWeixin) { - const { - miniProgram: { envVersion }, - } = uni.getAccountInfoSync() + // 微信小程序端环境区分 + if (isMpWeixin) { + const { + miniProgram: { envVersion }, + } = uni.getAccountInfoSync() - switch (envVersion) { - case 'develop': - baseUploadUrl = VITE_UPLOAD_BASEURL__WEIXIN_DEVELOP || baseUploadUrl - break - case 'trial': - baseUploadUrl = VITE_UPLOAD_BASEURL__WEIXIN_TRIAL || baseUploadUrl - break - case 'release': - baseUploadUrl = VITE_UPLOAD_BASEURL__WEIXIN_RELEASE || baseUploadUrl - break - } - } + switch (envVersion) { + case 'develop': + baseUploadUrl = VITE_UPLOAD_BASEURL__WEIXIN_DEVELOP || baseUploadUrl + break + case 'trial': + baseUploadUrl = VITE_UPLOAD_BASEURL__WEIXIN_TRIAL || baseUploadUrl + break + case 'release': + baseUploadUrl = VITE_UPLOAD_BASEURL__WEIXIN_RELEASE || baseUploadUrl + break + } + } - return baseUploadUrl + return baseUploadUrl } + +export function getNavBarHeight() { + const systemInfo = uni.getSystemInfoSync() + const statusBarHeight = systemInfo.statusBarHeight; // 状态栏高度(单位:px) + const titleBarHeight = 44; // 默认导航栏标题高度(iOS/Android 一般为 44px) + const navbarHeight = statusBarHeight + titleBarHeight; // 完整的导航栏高度 + + console.log("🚀 ~ getNavBarHeight ~ navbarHeight:", navbarHeight) + return navbarHeight +} \ No newline at end of file diff --git a/src/utils/uploadFile.ts b/src/utils/uploadFile.ts index 416d39c..643b053 100644 --- a/src/utils/uploadFile.ts +++ b/src/utils/uploadFile.ts @@ -20,8 +20,8 @@ import { toast } from './toast' * 上传文件的URL配置 */ export const uploadFileUrl = { - /** 用户头像上传地址 */ - USER_AVATAR: `${import.meta.env.VITE_SERVER_BASEURL}/user/avatar`, + /** 用户头像上传地址 */ + USER_AVATAR: `${import.meta.env.VITE_SERVER_BASEURL}/user/avatar`, } /** @@ -32,35 +32,35 @@ export const uploadFileUrl = { * @param options 上传选项 */ export function useFileUpload(url: string, filePath: string, formData: Record = {}, options: Omit = {}) { - return useUpload( - url, - formData, - { - ...options, - sourceType: ['album'], - sizeType: ['original'], - }, - filePath, - ) + return useUpload( + url, + formData, + { + ...options, + sourceType: ['album'], + sizeType: ['original'], + }, + filePath, + ) } export interface UploadOptions { - /** 最大可选择的图片数量,默认为1 */ - count?: number - /** 所选的图片的尺寸,original-原图,compressed-压缩图 */ - sizeType?: Array<'original' | 'compressed'> - /** 选择图片的来源,album-相册,camera-相机 */ - sourceType?: Array<'album' | 'camera'> - /** 文件大小限制,单位:MB */ - maxSize?: number // - /** 上传进度回调函数 */ - onProgress?: (progress: number) => void - /** 上传成功回调函数 */ - onSuccess?: (res: Record) => void - /** 上传失败回调函数 */ - onError?: (err: Error | UniApp.GeneralCallbackResult) => void - /** 上传完成回调函数(无论成功失败) */ - onComplete?: () => void + /** 最大可选择的图片数量,默认为1 */ + count?: number + /** 所选的图片的尺寸,original-原图,compressed-压缩图 */ + sizeType?: Array<'original' | 'compressed'> + /** 选择图片的来源,album-相册,camera-相机 */ + sourceType?: Array<'album' | 'camera'> + /** 文件大小限制,单位:MB */ + maxSize?: number // + /** 上传进度回调函数 */ + onProgress?: (progress: number) => void + /** 上传成功回调函数 */ + onSuccess?: (res: Record) => void + /** 上传失败回调函数 */ + onError?: (err: Error | UniApp.GeneralCallbackResult) => void + /** 上传完成回调函数(无论成功失败) */ + onComplete?: () => void } /** @@ -72,150 +72,150 @@ export interface UploadOptions { * @returns 上传状态和控制对象 */ export function useUpload(url: string, formData: Record = {}, options: UploadOptions = {}, - /** 直接传入文件路径,跳过选择器 */ - directFilePath?: string) { - /** 上传中状态 */ - const loading = ref(false) - /** 上传错误状态 */ - const error = ref(false) - /** 上传成功后的响应数据 */ - const data = ref() - /** 上传进度(0-100) */ - const progress = ref(0) + /** 直接传入文件路径,跳过选择器 */ + directFilePath?: string) { + /** 上传中状态 */ + const loading = ref(false) + /** 上传错误状态 */ + const error = ref(false) + /** 上传成功后的响应数据 */ + const data = ref() + /** 上传进度(0-100) */ + const progress = ref(0) - /** 解构上传选项,设置默认值 */ - const { - /** 最大可选择的图片数量 */ - count = 1, - /** 所选的图片的尺寸 */ - sizeType = ['original', 'compressed'], - /** 选择图片的来源 */ - sourceType = ['album', 'camera'], - /** 文件大小限制(MB) */ - maxSize = 10, - /** 进度回调 */ - onProgress, - /** 成功回调 */ - onSuccess, - /** 失败回调 */ - onError, - /** 完成回调 */ - onComplete, - } = options + /** 解构上传选项,设置默认值 */ + const { + /** 最大可选择的图片数量 */ + count = 1, + /** 所选的图片的尺寸 */ + sizeType = ['original', 'compressed'], + /** 选择图片的来源 */ + sourceType = ['album', 'camera'], + /** 文件大小限制(MB) */ + maxSize = 10, + /** 进度回调 */ + onProgress, + /** 成功回调 */ + onSuccess, + /** 失败回调 */ + onError, + /** 完成回调 */ + onComplete, + } = options - /** - * 检查文件大小是否超过限制 - * @param size 文件大小(字节) - * @returns 是否通过检查 - */ - const checkFileSize = (size: number) => { - const sizeInMB = size / 1024 / 1024 - if (sizeInMB > maxSize) { - toast.warning(`文件大小不能超过${maxSize}MB`) - return false - } - return true - } - /** - * 触发文件选择和上传 - * 根据平台使用不同的选择器: - * - 微信小程序使用 chooseMedia - * - 其他平台使用 chooseImage - */ - const run = () => { - if (directFilePath) { - // 直接使用传入的文件路径 - loading.value = true - progress.value = 0 - uploadFile({ - url, - tempFilePath: directFilePath, - formData, - data, - error, - loading, - progress, - onProgress, - onSuccess, - onError, - onComplete, - }) - return - } + /** + * 检查文件大小是否超过限制 + * @param size 文件大小(字节) + * @returns 是否通过检查 + */ + const checkFileSize = (size: number) => { + const sizeInMB = size / 1024 / 1024 + if (sizeInMB > maxSize) { + toast.warning(`文件大小不能超过${maxSize}MB`) + return false + } + return true + } + /** + * 触发文件选择和上传 + * 根据平台使用不同的选择器: + * - 微信小程序使用 chooseMedia + * - 其他平台使用 chooseImage + */ + const run = () => { + if (directFilePath) { + // 直接使用传入的文件路径 + loading.value = true + progress.value = 0 + uploadFile({ + url, + tempFilePath: directFilePath, + formData, + data, + error, + loading, + progress, + onProgress, + onSuccess, + onError, + onComplete, + }) + return + } - // #ifdef MP-WEIXIN - // 微信小程序环境下使用 chooseMedia API - uni.chooseMedia({ - count, - mediaType: ['image'], // 仅支持图片类型 - sourceType, - success: (res) => { - const file = res.tempFiles[0] - // 检查文件大小是否符合限制 - if (!checkFileSize(file.size)) - return + // #ifdef MP-WEIXIN + // 微信小程序环境下使用 chooseMedia API + uni.chooseMedia({ + count, + mediaType: ['image'], // 仅支持图片类型 + sourceType, + success: (res) => { + const file = res.tempFiles[0] + // 检查文件大小是否符合限制 + if (!checkFileSize(file.size)) + return - // 开始上传 - loading.value = true - progress.value = 0 - uploadFile({ - url, - tempFilePath: file.tempFilePath, - formData, - data, - error, - loading, - progress, - onProgress, - onSuccess, - onError, - onComplete, - }) - }, - fail: (err) => { - console.error('选择媒体文件失败:', err) - error.value = true - onError?.(err) - }, - }) - // #endif + // 开始上传 + loading.value = true + progress.value = 0 + uploadFile({ + url, + tempFilePath: file.tempFilePath, + formData, + data, + error, + loading, + progress, + onProgress, + onSuccess, + onError, + onComplete, + }) + }, + fail: (err) => { + console.error('选择媒体文件失败:', err) + error.value = true + onError?.(err) + }, + }) + // #endif - // #ifndef MP-WEIXIN - // 非微信小程序环境下使用 chooseImage API - uni.chooseImage({ - count, - sizeType, - sourceType, - success: (res) => { - console.log('选择图片成功:', res) + // #ifndef MP-WEIXIN + // 非微信小程序环境下使用 chooseImage API + uni.chooseImage({ + count, + sizeType, + sourceType, + success: (res) => { + console.log('选择图片成功:', res) - // 开始上传 - loading.value = true - progress.value = 0 - uploadFile({ - url, - tempFilePath: res.tempFilePaths[0], - formData, - data, - error, - loading, - progress, - onProgress, - onSuccess, - onError, - onComplete, - }) - }, - fail: (err) => { - console.error('选择图片失败:', err) - error.value = true - onError?.(err) - }, - }) - // #endif - } + // 开始上传 + loading.value = true + progress.value = 0 + uploadFile({ + url, + tempFilePath: res.tempFilePaths[0], + formData, + data, + error, + loading, + progress, + onProgress, + onSuccess, + onError, + onComplete, + }) + }, + fail: (err) => { + console.error('选择图片失败:', err) + error.value = true + onError?.(err) + }, + }) + // #endif + } - return { loading, error, data, progress, run } + return { loading, error, data, progress, run } } /** @@ -223,28 +223,28 @@ export function useUpload(url: string, formData: Record * @template T 上传成功后返回的数据类型 */ interface UploadFileOptions { - /** 上传地址 */ - url: string - /** 临时文件路径 */ - tempFilePath: string - /** 额外的表单数据 */ - formData: Record - /** 上传成功后的响应数据 */ - data: Ref - /** 上传错误状态 */ - error: Ref - /** 上传中状态 */ - loading: Ref - /** 上传进度(0-100) */ - progress: Ref - /** 上传进度回调 */ - onProgress?: (progress: number) => void - /** 上传成功回调 */ - onSuccess?: (res: Record) => void - /** 上传失败回调 */ - onError?: (err: Error | UniApp.GeneralCallbackResult) => void - /** 上传完成回调 */ - onComplete?: () => void + /** 上传地址 */ + url: string + /** 临时文件路径 */ + tempFilePath: string + /** 额外的表单数据 */ + formData: Record + /** 上传成功后的响应数据 */ + data: Ref + /** 上传错误状态 */ + error: Ref + /** 上传中状态 */ + loading: Ref + /** 上传进度(0-100) */ + progress: Ref + /** 上传进度回调 */ + onProgress?: (progress: number) => void + /** 上传成功回调 */ + onSuccess?: (res: Record) => void + /** 上传失败回调 */ + onError?: (err: Error | UniApp.GeneralCallbackResult) => void + /** 上传完成回调 */ + onComplete?: () => void } /** @@ -253,72 +253,72 @@ interface UploadFileOptions { * @param options 上传选项 */ function uploadFile({ - url, - tempFilePath, - formData, - data, - error, - loading, - progress, - onProgress, - onSuccess, - onError, - onComplete, + url, + tempFilePath, + formData, + data, + error, + loading, + progress, + onProgress, + onSuccess, + onError, + onComplete, }: UploadFileOptions) { - try { - // 创建上传任务 - const uploadTask = uni.uploadFile({ - url, - filePath: tempFilePath, - name: 'file', // 文件对应的 key - formData, - header: { - // H5环境下不需要手动设置Content-Type,让浏览器自动处理multipart格式 - // #ifndef H5 - 'Content-Type': 'multipart/form-data', - // #endif - }, - // 确保文件名称合法 - success: (uploadFileRes) => { - console.log('上传文件成功:', uploadFileRes) - try { - // 解析响应数据 - const { data: _data } = JSON.parse(uploadFileRes.data) - // 上传成功 - data.value = _data as T - onSuccess?.(_data) - } - catch (err) { - // 响应解析错误 - console.error('解析上传响应失败:', err) - error.value = true - onError?.(new Error('上传响应解析失败')) - } - }, - fail: (err) => { - // 上传请求失败 - console.error('上传文件失败:', err) - error.value = true - onError?.(err) - }, - complete: () => { - // 无论成功失败都执行 - loading.value = false - onComplete?.() - }, - }) + try { + // 创建上传任务 + const uploadTask = uni.uploadFile({ + url, + filePath: tempFilePath, + name: 'file', // 文件对应的 key + formData, + header: { + // H5环境下不需要手动设置Content-Type,让浏览器自动处理multipart格式 + // #ifndef H5 + 'Content-Type': 'multipart/form-data', + // #endif + }, + // 确保文件名称合法 + success: (uploadFileRes) => { + console.log('上传文件成功:', uploadFileRes) + try { + // 解析响应数据 + const { data: _data } = JSON.parse(uploadFileRes.data) + // 上传成功 + data.value = _data as T + onSuccess?.(_data) + } + catch (err) { + // 响应解析错误 + console.error('解析上传响应失败:', err) + error.value = true + onError?.(new Error('上传响应解析失败')) + } + }, + fail: (err) => { + // 上传请求失败 + console.error('上传文件失败:', err) + error.value = true + onError?.(err) + }, + complete: () => { + // 无论成功失败都执行 + loading.value = false + onComplete?.() + }, + }) - // 监听上传进度 - uploadTask.onProgressUpdate((res) => { - progress.value = res.progress - onProgress?.(res.progress) - }) - } - catch (err) { - // 创建上传任务失败 - console.error('创建上传任务失败:', err) - error.value = true - loading.value = false - onError?.(new Error('创建上传任务失败')) - } + // 监听上传进度 + uploadTask.onProgressUpdate((res) => { + progress.value = res.progress + onProgress?.(res.progress) + }) + } + catch (err) { + // 创建上传任务失败 + console.error('创建上传任务失败:', err) + error.value = true + loading.value = false + onError?.(new Error('创建上传任务失败')) + } } diff --git a/uno.config.ts b/uno.config.ts index 5055f45..45163a3 100644 --- a/uno.config.ts +++ b/uno.config.ts @@ -1,66 +1,66 @@ // https://www.npmjs.com/package/@uni-helper/unocss-preset-uni import { presetUni } from '@uni-helper/unocss-preset-uni' import { - defineConfig, - presetAttributify, - presetIcons, - transformerDirectives, - transformerVariantGroup, + defineConfig, + presetAttributify, + presetIcons, + transformerDirectives, + transformerVariantGroup, } from 'unocss' export default defineConfig({ - presets: [ - presetUni({ - attributify: { - // prefix: 'fg-', // 如果加前缀,则需要在代码里面使用 `fg-` 前缀,如:
- prefixedOnly: true, - }, - }), - presetIcons({ - scale: 1.2, - warn: true, - extraProperties: { - 'display': 'inline-block', - 'vertical-align': 'middle', - }, - }), - // 支持css class属性化 - presetAttributify(), - ], - transformers: [ - // 启用指令功能:主要用于支持 @apply、@screen 和 theme() 等 CSS 指令 - transformerDirectives(), - // 启用 () 分组功能 - // 支持css class组合,eg: `
测试 unocss
` - transformerVariantGroup(), - ], - shortcuts: [ - { - center: 'flex justify-center items-center', - }, - ], - // 动态图标需要在这里配置,或者写在vue页面中注释掉 - safelist: ['i-carbon-code'], - rules: [ - [ - 'p-safe', - { - padding: - 'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)', - }, - ], - ['pt-safe', { 'padding-top': 'env(safe-area-inset-top)' }], - ['pb-safe', { 'padding-bottom': 'env(safe-area-inset-bottom)' }], - ], - theme: { - colors: { - /** 主题色,用法如: text-primary */ - primary: 'var(--wot-color-theme,#0957DE)', - }, - fontSize: { - /** 提供更小号的字体,用法如:text-2xs */ - '2xs': ['20rpx', '28rpx'], - '3xs': ['18rpx', '26rpx'], - }, - }, + presets: [ + presetUni({ + attributify: { + // prefix: 'fg-', // 如果加前缀,则需要在代码里面使用 `fg-` 前缀,如:
+ prefixedOnly: true, + }, + }), + presetIcons({ + scale: 1.2, + warn: true, + extraProperties: { + 'display': 'inline-block', + 'vertical-align': 'middle', + }, + }), + // 支持css class属性化 + presetAttributify(), + ], + transformers: [ + // 启用指令功能:主要用于支持 @apply、@screen 和 theme() 等 CSS 指令 + transformerDirectives(), + // 启用 () 分组功能 + // 支持css class组合,eg: `
测试 unocss
` + transformerVariantGroup(), + ], + shortcuts: [ + { + center: 'flex justify-center items-center', + }, + ], + // 动态图标需要在这里配置,或者写在vue页面中注释掉 + safelist: ['i-carbon-code'], + rules: [ + [ + 'p-safe', + { + padding: + 'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)', + }, + ], + ['pt-safe', { 'padding-top': 'env(safe-area-inset-top)' }], + ['pb-safe', { 'padding-bottom': 'env(safe-area-inset-bottom)' }], + ], + theme: { + colors: { + /** 主题色,用法如: text-primary */ + primary: 'var(--wot-color-theme,#0957DE)', + }, + fontSize: { + /** 提供更小号的字体,用法如:text-2xs */ + '2xs': ['20rpx', '28rpx'], + '3xs': ['18rpx', '26rpx'], + }, + }, }) diff --git a/vite.config.ts b/vite.config.ts index 59e6086..1d11e9b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -25,159 +25,159 @@ import ViteRestart from 'vite-plugin-restart' // https://vitejs.dev/config/ export default ({ command, mode }) => { - // @see https://unocss.dev/ - // const UnoCSS = (await import('unocss/vite')).default - // console.log(mode === process.env.NODE_ENV) // true + // @see https://unocss.dev/ + // const UnoCSS = (await import('unocss/vite')).default + // console.log(mode === process.env.NODE_ENV) // true - // mode: 区分生产环境还是开发环境 - console.log('command, mode -> ', command, mode) - // pnpm dev:h5 时得到 => serve development - // pnpm build:h5 时得到 => build production - // pnpm dev:mp-weixin 时得到 => build development (注意区别,command为build) - // pnpm build:mp-weixin 时得到 => build production - // pnpm dev:app 时得到 => build development (注意区别,command为build) - // pnpm build:app 时得到 => build production - // dev 和 build 命令可以分别使用 .env.development 和 .env.production 的环境变量 + // mode: 区分生产环境还是开发环境 + console.log('command, mode -> ', command, mode) + // pnpm dev:h5 时得到 => serve development + // pnpm build:h5 时得到 => build production + // pnpm dev:mp-weixin 时得到 => build development (注意区别,command为build) + // pnpm build:mp-weixin 时得到 => build production + // pnpm dev:app 时得到 => build development (注意区别,command为build) + // pnpm build:app 时得到 => build production + // dev 和 build 命令可以分别使用 .env.development 和 .env.production 的环境变量 - const { UNI_PLATFORM } = process.env - console.log('UNI_PLATFORM -> ', UNI_PLATFORM) // 得到 mp-weixin, h5, app 等 + const { UNI_PLATFORM } = process.env + console.log('UNI_PLATFORM -> ', UNI_PLATFORM) // 得到 mp-weixin, h5, app 等 - const env = loadEnv(mode, path.resolve(process.cwd(), 'env')) - const { - VITE_APP_PORT, - VITE_SERVER_BASEURL, - VITE_DELETE_CONSOLE, - VITE_SHOW_SOURCEMAP, - VITE_APP_PUBLIC_BASE, - VITE_APP_PROXY, - VITE_APP_PROXY_PREFIX, - } = env - console.log('环境变量 env -> ', env) + const env = loadEnv(mode, path.resolve(process.cwd(), 'env')) + const { + VITE_APP_PORT, + VITE_SERVER_BASEURL, + VITE_DELETE_CONSOLE, + VITE_SHOW_SOURCEMAP, + VITE_APP_PUBLIC_BASE, + VITE_APP_PROXY, + VITE_APP_PROXY_PREFIX, + } = env + console.log('环境变量 env -> ', env) - return defineConfig({ - envDir: './env', // 自定义env目录 - base: VITE_APP_PUBLIC_BASE, - plugins: [ - UniPages({ - exclude: ['**/components/**/**.*'], - // homePage 通过 vue 文件的 route-block 的type="home"来设定 - // pages 目录为 src/pages,分包目录不能配置在pages目录下 - subPackages: ['src/pages-sub'], // 是个数组,可以配置多个,但是不能为pages里面的目录 - dts: 'src/types/uni-pages.d.ts', - }), - UniLayouts(), - UniPlatform(), - UniManifest(), - // UniXXX 需要在 Uni 之前引入 - { - // 临时解决 dcloudio 官方的 @dcloudio/uni-mp-compiler 出现的编译 BUG - // 参考 github issue: https://github.com/dcloudio/uni-app/issues/4952 - // 自定义插件禁用 vite:vue 插件的 devToolsEnabled,强制编译 vue 模板时 inline 为 true - name: 'fix-vite-plugin-vue', - configResolved(config) { - const plugin = config.plugins.find(p => p.name === 'vite:vue') - if (plugin && plugin.api && plugin.api.options) { - plugin.api.options.devToolsEnabled = false - } - }, - }, - UnoCSS(), - AutoImport({ - imports: ['vue', 'uni-app'], - dts: 'src/types/auto-import.d.ts', - dirs: ['src/hooks'], // 自动导入 hooks - vueTemplate: true, // default false - }), - // Optimization 插件需要 page.json 文件,故应在 UniPages 插件之后执行 - Optimization({ - enable: { - 'optimization': true, - 'async-import': true, - 'async-component': true, - }, - dts: { - base: 'src/types', - }, - logger: false, - }), + return defineConfig({ + envDir: './env', // 自定义env目录 + base: VITE_APP_PUBLIC_BASE, + plugins: [ + UniPages({ + exclude: ['**/components/**/**.*'], + // homePage 通过 vue 文件的 route-block 的type="home"来设定 + // pages 目录为 src/pages,分包目录不能配置在pages目录下 + subPackages: ['src/pages-sub'], // 是个数组,可以配置多个,但是不能为pages里面的目录 + dts: 'src/types/uni-pages.d.ts', + }), + UniLayouts(), + UniPlatform(), + UniManifest(), + // UniXXX 需要在 Uni 之前引入 + { + // 临时解决 dcloudio 官方的 @dcloudio/uni-mp-compiler 出现的编译 BUG + // 参考 github issue: https://github.com/dcloudio/uni-app/issues/4952 + // 自定义插件禁用 vite:vue 插件的 devToolsEnabled,强制编译 vue 模板时 inline 为 true + name: 'fix-vite-plugin-vue', + configResolved(config) { + const plugin = config.plugins.find(p => p.name === 'vite:vue') + if (plugin && plugin.api && plugin.api.options) { + plugin.api.options.devToolsEnabled = false + } + }, + }, + UnoCSS(), + AutoImport({ + imports: ['vue', 'uni-app'], + dts: 'src/types/auto-import.d.ts', + dirs: ['src/hooks'], // 自动导入 hooks + vueTemplate: true, // default false + }), + // Optimization 插件需要 page.json 文件,故应在 UniPages 插件之后执行 + Optimization({ + enable: { + 'optimization': true, + 'async-import': true, + 'async-component': true, + }, + dts: { + base: 'src/types', + }, + logger: false, + }), - ViteRestart({ - // 通过这个插件,在修改vite.config.js文件则不需要重新运行也生效配置 - restart: ['vite.config.js'], - }), - // h5环境增加 BUILD_TIME 和 BUILD_BRANCH - UNI_PLATFORM === 'h5' && { - name: 'html-transform', - transformIndexHtml(html) { - return html.replace('%BUILD_TIME%', dayjs().format('YYYY-MM-DD HH:mm:ss')) - }, - }, - // 打包分析插件,h5 + 生产环境才弹出 - UNI_PLATFORM === 'h5' - && mode === 'production' - && visualizer({ - filename: './node_modules/.cache/visualizer/stats.html', - open: true, - gzipSize: true, - brotliSize: true, - }), - // 只有在 app 平台时才启用 copyNativeRes 插件 - // UNI_PLATFORM === 'app' && copyNativeRes(), - Components({ - extensions: ['vue'], - deep: true, // 是否递归扫描子目录, - directoryAsNamespace: false, // 是否把目录名作为命名空间前缀,true 时组件名为 目录名+组件名, - dts: 'src/types/components.d.ts', // 自动生成的组件类型声明文件路径(用于 TypeScript 支持) - }), - Uni(), - ], - define: { - __UNI_PLATFORM__: JSON.stringify(UNI_PLATFORM), - __VITE_APP_PROXY__: JSON.stringify(VITE_APP_PROXY), - }, - css: { - postcss: { - plugins: [ - // autoprefixer({ - // // 指定目标浏览器 - // overrideBrowserslist: ['> 1%', 'last 2 versions'], - // }), - ], - }, - }, + ViteRestart({ + // 通过这个插件,在修改vite.config.js文件则不需要重新运行也生效配置 + restart: ['vite.config.js'], + }), + // h5环境增加 BUILD_TIME 和 BUILD_BRANCH + UNI_PLATFORM === 'h5' && { + name: 'html-transform', + transformIndexHtml(html) { + return html.replace('%BUILD_TIME%', dayjs().format('YYYY-MM-DD HH:mm:ss')) + }, + }, + // 打包分析插件,h5 + 生产环境才弹出 + UNI_PLATFORM === 'h5' + && mode === 'production' + && visualizer({ + filename: './node_modules/.cache/visualizer/stats.html', + open: true, + gzipSize: true, + brotliSize: true, + }), + // 只有在 app 平台时才启用 copyNativeRes 插件 + // UNI_PLATFORM === 'app' && copyNativeRes(), + Components({ + extensions: ['vue'], + deep: true, // 是否递归扫描子目录, + directoryAsNamespace: false, // 是否把目录名作为命名空间前缀,true 时组件名为 目录名+组件名, + dts: 'src/types/components.d.ts', // 自动生成的组件类型声明文件路径(用于 TypeScript 支持) + }), + Uni(), + ], + define: { + __UNI_PLATFORM__: JSON.stringify(UNI_PLATFORM), + __VITE_APP_PROXY__: JSON.stringify(VITE_APP_PROXY), + }, + css: { + postcss: { + plugins: [ + // autoprefixer({ + // // 指定目标浏览器 + // overrideBrowserslist: ['> 1%', 'last 2 versions'], + // }), + ], + }, + }, - resolve: { - alias: { - '@': path.join(process.cwd(), './src'), - '@img': path.join(process.cwd(), './src/static/images'), - }, - }, - server: { - host: '0.0.0.0', - hmr: true, - port: Number.parseInt(VITE_APP_PORT, 10), - // 仅 H5 端生效,其他端不生效(其他端走build,不走devServer) - proxy: JSON.parse(VITE_APP_PROXY) - ? { - [VITE_APP_PROXY_PREFIX]: { - target: VITE_SERVER_BASEURL, - changeOrigin: true, - rewrite: path => path.replace(new RegExp(`^${VITE_APP_PROXY_PREFIX}`), ''), - }, - } - : undefined, - }, - esbuild: { - drop: VITE_DELETE_CONSOLE === 'true' ? ['console', 'debugger'] : ['debugger'], - }, - build: { - sourcemap: false, - // 方便非h5端调试 - // sourcemap: VITE_SHOW_SOURCEMAP === 'true', // 默认是false - target: 'es6', - // 开发环境不用压缩 - minify: mode === 'development' ? false : 'esbuild', + resolve: { + alias: { + '@': path.join(process.cwd(), './src'), + '@img': path.join(process.cwd(), './src/static/images'), + }, + }, + server: { + host: '0.0.0.0', + hmr: true, + port: Number.parseInt(VITE_APP_PORT, 10), + // 仅 H5 端生效,其他端不生效(其他端走build,不走devServer) + proxy: JSON.parse(VITE_APP_PROXY) + ? { + [VITE_APP_PROXY_PREFIX]: { + target: VITE_SERVER_BASEURL, + changeOrigin: true, + rewrite: path => path.replace(new RegExp(`^${VITE_APP_PROXY_PREFIX}`), ''), + }, + } + : undefined, + }, + esbuild: { + drop: VITE_DELETE_CONSOLE === 'true' ? ['console', 'debugger'] : ['debugger'], + }, + build: { + sourcemap: false, + // 方便非h5端调试 + // sourcemap: VITE_SHOW_SOURCEMAP === 'true', // 默认是false + target: 'es6', + // 开发环境不用压缩 + minify: mode === 'development' ? false : 'esbuild', - }, - }) + }, + }) }