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 0000000..eb2e2bd Binary files /dev/null and b/src/static/icon/icon_arrow_down.png differ diff --git a/src/static/images/home_bg.png b/src/static/images/home_bg.png new file mode 100644 index 0000000..561998a Binary files /dev/null and b/src/static/images/home_bg.png differ diff --git a/src/static/tabbar/example.png b/src/static/tabbar/example.png deleted file mode 100644 index fd1e942..0000000 Binary files a/src/static/tabbar/example.png and /dev/null differ diff --git a/src/static/tabbar/exampleHL.png b/src/static/tabbar/exampleHL.png deleted file mode 100644 index 7501011..0000000 Binary files a/src/static/tabbar/exampleHL.png and /dev/null differ diff --git a/src/static/tabbar/home.png b/src/static/tabbar/home.png index 8f82e21..2fa5c0a 100644 Binary files a/src/static/tabbar/home.png and b/src/static/tabbar/home.png differ diff --git a/src/static/tabbar/homeHL.png b/src/static/tabbar/homeHL.png deleted file mode 100644 index 26d3761..0000000 Binary files a/src/static/tabbar/homeHL.png and /dev/null differ diff --git a/src/static/tabbar/home_s.png b/src/static/tabbar/home_s.png new file mode 100644 index 0000000..b58714d Binary files /dev/null and b/src/static/tabbar/home_s.png differ diff --git a/src/static/tabbar/my.png b/src/static/tabbar/my.png new file mode 100644 index 0000000..104beff Binary files /dev/null and b/src/static/tabbar/my.png differ diff --git a/src/static/tabbar/my_s.png b/src/static/tabbar/my_s.png new file mode 100644 index 0000000..85a80c3 Binary files /dev/null and b/src/static/tabbar/my_s.png differ diff --git a/src/static/tabbar/personal.png b/src/static/tabbar/personal.png deleted file mode 100644 index 0a569a2..0000000 Binary files a/src/static/tabbar/personal.png and /dev/null differ diff --git a/src/static/tabbar/personalHL.png b/src/static/tabbar/personalHL.png deleted file mode 100644 index 8c3e66e..0000000 Binary files a/src/static/tabbar/personalHL.png and /dev/null differ diff --git a/src/static/tabbar/reserve.png b/src/static/tabbar/reserve.png new file mode 100644 index 0000000..9f817e7 Binary files /dev/null and b/src/static/tabbar/reserve.png differ diff --git a/src/static/tabbar/reserve_s.png b/src/static/tabbar/reserve_s.png new file mode 100644 index 0000000..dc9c686 Binary files /dev/null and b/src/static/tabbar/reserve_s.png differ diff --git a/src/static/tabbar/scan.png b/src/static/tabbar/scan.png deleted file mode 100644 index f0f60c2..0000000 Binary files a/src/static/tabbar/scan.png and /dev/null differ diff --git a/src/store/index.ts b/src/store/index.ts index 74b1b2f..6a276cd 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -3,12 +3,12 @@ import { createPersistedState } from 'pinia-plugin-persistedstate' // 数据持 const store = createPinia() store.use( - createPersistedState({ - storage: { - getItem: uni.getStorageSync, - setItem: uni.setStorageSync, - }, - }), + createPersistedState({ + storage: { + getItem: uni.getStorageSync, + setItem: uni.setStorageSync, + }, + }), ) export default store diff --git a/src/store/user.ts b/src/store/user.ts index cec931a..78c815c 100644 --- a/src/store/user.ts +++ b/src/store/user.ts @@ -2,110 +2,110 @@ import type { IUserInfoVo } from '@/api/types/login' import { defineStore } from 'pinia' import { ref } from 'vue' import { - getUserInfo as _getUserInfo, - login as _login, - logout as _logout, - wxLogin as _wxLogin, - getWxCode, + getUserInfo as _getUserInfo, + login as _login, + logout as _logout, + wxLogin as _wxLogin, + getWxCode, } from '@/api/login' import { toast } from '@/utils/toast' // 初始化状态 const userInfoState: IUserInfoVo = { - id: 0, - username: '', - avatar: '/static/images/default-avatar.png', - token: '', + id: 0, + username: '', + avatar: '/static/images/default-avatar.png', + token: '', } export const useUserStore = defineStore( - '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 - } + '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', - }, - }) + }, + }) }