绘制首页和配置tabbar
@ -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: [
|
||||
'<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>',
|
||||
'<uses-permission android:name="android.permission.VIBRATE"/>',
|
||||
'<uses-permission android:name="android.permission.READ_LOGS"/>',
|
||||
'<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
|
||||
'<uses-feature android:name="android.hardware.camera.autofocus"/>',
|
||||
'<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.CAMERA"/>',
|
||||
'<uses-permission android:name="android.permission.GET_ACCOUNTS"/>',
|
||||
'<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.WAKE_LOCK"/>',
|
||||
'<uses-permission android:name="android.permission.FLASHLIGHT"/>',
|
||||
'<uses-feature android:name="android.hardware.camera"/>',
|
||||
'<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
|
||||
],
|
||||
},
|
||||
/* ios打包配置 */
|
||||
ios: {},
|
||||
/* SDK配置 */
|
||||
sdkConfigs: {},
|
||||
/* 图标配置 */
|
||||
icons: {
|
||||
android: {
|
||||
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: [
|
||||
'<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>',
|
||||
'<uses-permission android:name="android.permission.VIBRATE"/>',
|
||||
'<uses-permission android:name="android.permission.READ_LOGS"/>',
|
||||
'<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
|
||||
'<uses-feature android:name="android.hardware.camera.autofocus"/>',
|
||||
'<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.CAMERA"/>',
|
||||
'<uses-permission android:name="android.permission.GET_ACCOUNTS"/>',
|
||||
'<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.WAKE_LOCK"/>',
|
||||
'<uses-permission android:name="android.permission.FLASHLIGHT"/>',
|
||||
'<uses-feature android:name="android.hardware.camera"/>',
|
||||
'<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
|
||||
],
|
||||
},
|
||||
/* ios打包配置 */
|
||||
ios: {},
|
||||
/* SDK配置 */
|
||||
sdkConfigs: {},
|
||||
/* 图标配置 */
|
||||
icons: {
|
||||
android: {
|
||||
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',
|
||||
})
|
||||
|
||||
@ -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,
|
||||
})
|
||||
|
||||
@ -8,153 +8,153 @@ type TImage = 'png' | 'jpg' | 'jpeg' | 'webp' | '*'
|
||||
type TFile = 'doc' | 'docx' | 'ppt' | 'zip' | 'xls' | 'xlsx' | 'txt' | TImage
|
||||
|
||||
interface TOptions<T extends TfileType> {
|
||||
formData?: Record<string, any>
|
||||
maxSize?: number
|
||||
accept?: T extends 'image' ? TImage[] : TFile[]
|
||||
fileType?: T
|
||||
success?: (params: any) => void
|
||||
error?: (err: any) => void
|
||||
formData?: Record<string, any>
|
||||
maxSize?: number
|
||||
accept?: T extends 'image' ? TImage[] : TFile[]
|
||||
fileType?: T
|
||||
success?: (params: any) => void
|
||||
error?: (err: any) => void
|
||||
}
|
||||
|
||||
export default function useUpload<T extends TfileType>(options: TOptions<T> = {} as TOptions<T>) {
|
||||
const {
|
||||
formData = {},
|
||||
maxSize = 5 * 1024 * 1024,
|
||||
accept = ['*'],
|
||||
fileType = 'image',
|
||||
success,
|
||||
error: onError,
|
||||
} = options
|
||||
const {
|
||||
formData = {},
|
||||
maxSize = 5 * 1024 * 1024,
|
||||
accept = ['*'],
|
||||
fileType = 'image',
|
||||
success,
|
||||
error: onError,
|
||||
} = options
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref<Error | null>(null)
|
||||
const data = ref<any>(null)
|
||||
const loading = ref(false)
|
||||
const error = ref<Error | null>(null)
|
||||
const data = ref<any>(null)
|
||||
|
||||
const handleFileChoose = ({ tempFilePath, size }: { tempFilePath: string, size: number }) => {
|
||||
if (size > maxSize) {
|
||||
uni.showToast({
|
||||
title: `文件大小不能超过 ${maxSize / 1024 / 1024}MB`,
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
const 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<string, any>
|
||||
onSuccess: (data: any) => void
|
||||
onError: (err: any) => void
|
||||
onComplete: () => void
|
||||
tempFilePath: string
|
||||
formData: Record<string, any>
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
142
src/http/http.ts
@ -1,47 +1,47 @@
|
||||
import type { CustomRequestOptions } from '@/http/types'
|
||||
|
||||
export function http<T>(options: CustomRequestOptions) {
|
||||
// 1. 返回 Promise 对象
|
||||
return new Promise<IResData<T>>((resolve, reject) => {
|
||||
uni.request({
|
||||
...options,
|
||||
dataType: 'json',
|
||||
// #ifndef MP-WEIXIN
|
||||
responseType: 'json',
|
||||
// #endif
|
||||
// 响应成功
|
||||
success(res) {
|
||||
// 状态码 2xx,参考 axios 的设计
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
// 2.1 提取核心数据 res.data
|
||||
resolve(res.data as IResData<T>)
|
||||
}
|
||||
else if (res.statusCode === 401) {
|
||||
// 401错误 -> 清理用户信息,跳转到登录页
|
||||
// userStore.clearUserInfo()
|
||||
// uni.navigateTo({ url: '/pages/login/login' })
|
||||
reject(res)
|
||||
}
|
||||
else {
|
||||
// 其他错误 -> 根据后端错误信息轻提示
|
||||
!options.hideErrorToast
|
||||
&& uni.showToast({
|
||||
icon: 'none',
|
||||
title: (res.data as IResData<T>).msg || '请求错误',
|
||||
})
|
||||
reject(res)
|
||||
}
|
||||
},
|
||||
// 响应失败
|
||||
fail(err) {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '网络错误,换个网络试试',
|
||||
})
|
||||
reject(err)
|
||||
},
|
||||
})
|
||||
})
|
||||
// 1. 返回 Promise 对象
|
||||
return new Promise<IResData<T>>((resolve, reject) => {
|
||||
uni.request({
|
||||
...options,
|
||||
dataType: 'json',
|
||||
// #ifndef MP-WEIXIN
|
||||
responseType: 'json',
|
||||
// #endif
|
||||
// 响应成功
|
||||
success(res) {
|
||||
// 状态码 2xx,参考 axios 的设计
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
// 2.1 提取核心数据 res.data
|
||||
resolve(res.data as IResData<T>)
|
||||
}
|
||||
else if (res.statusCode === 401) {
|
||||
// 401错误 -> 清理用户信息,跳转到登录页
|
||||
// userStore.clearUserInfo()
|
||||
// uni.navigateTo({ url: '/pages/login/login' })
|
||||
reject(res)
|
||||
}
|
||||
else {
|
||||
// 其他错误 -> 根据后端错误信息轻提示
|
||||
!options.hideErrorToast
|
||||
&& uni.showToast({
|
||||
icon: 'none',
|
||||
title: (res.data as IResData<T>).msg || '请求错误',
|
||||
})
|
||||
reject(res)
|
||||
}
|
||||
},
|
||||
// 响应失败
|
||||
fail(err) {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '网络错误,换个网络试试',
|
||||
})
|
||||
reject(err)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,13 +52,13 @@ export function http<T>(options: CustomRequestOptions) {
|
||||
* @returns
|
||||
*/
|
||||
export function httpGet<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
|
||||
return http<T>({
|
||||
url,
|
||||
query,
|
||||
method: 'GET',
|
||||
header,
|
||||
...options,
|
||||
})
|
||||
return http<T>({
|
||||
url,
|
||||
query,
|
||||
method: 'GET',
|
||||
header,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,40 +70,40 @@ export function httpGet<T>(url: string, query?: Record<string, any>, header?: Re
|
||||
* @returns
|
||||
*/
|
||||
export function httpPost<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
|
||||
return http<T>({
|
||||
url,
|
||||
query,
|
||||
data,
|
||||
method: 'POST',
|
||||
header,
|
||||
...options,
|
||||
})
|
||||
return http<T>({
|
||||
url,
|
||||
query,
|
||||
data,
|
||||
method: 'POST',
|
||||
header,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
/**
|
||||
* PUT 请求
|
||||
*/
|
||||
export function httpPut<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
|
||||
return http<T>({
|
||||
url,
|
||||
data,
|
||||
query,
|
||||
method: 'PUT',
|
||||
header,
|
||||
...options,
|
||||
})
|
||||
return http<T>({
|
||||
url,
|
||||
data,
|
||||
query,
|
||||
method: 'PUT',
|
||||
header,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE 请求(无请求体,仅 query)
|
||||
*/
|
||||
export function httpDelete<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
|
||||
return http<T>({
|
||||
url,
|
||||
query,
|
||||
method: 'DELETE',
|
||||
header,
|
||||
...options,
|
||||
})
|
||||
return http<T>({
|
||||
url,
|
||||
query,
|
||||
method: 'DELETE',
|
||||
header,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
http.get = httpGet
|
||||
|
||||
@ -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)
|
||||
},
|
||||
}
|
||||
|
||||
17
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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,122 +1,86 @@
|
||||
<!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page -->
|
||||
<route lang="jsonc" type="home">
|
||||
{
|
||||
"layout": "tabbar",
|
||||
"style": {
|
||||
// 'custom' 表示开启自定义导航栏,默认 'default'
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "首页"
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: 'Home',
|
||||
})
|
||||
|
||||
// 获取屏幕边界到安全区域距离
|
||||
let safeAreaInsets
|
||||
let systemInfo
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序使用新的API
|
||||
systemInfo = uni.getWindowInfo()
|
||||
safeAreaInsets = systemInfo.safeArea
|
||||
? {
|
||||
top: systemInfo.safeArea.top,
|
||||
right: systemInfo.windowWidth - systemInfo.safeArea.right,
|
||||
bottom: systemInfo.windowHeight - systemInfo.safeArea.bottom,
|
||||
left: systemInfo.safeArea.left,
|
||||
}
|
||||
: null
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
// 其他平台继续使用uni API
|
||||
systemInfo = uni.getSystemInfoSync()
|
||||
safeAreaInsets = systemInfo.safeAreaInsets
|
||||
// #endif
|
||||
const author = ref('菲鸽')
|
||||
const description = ref(
|
||||
'unibest 是一个集成了多种工具和技术的 uniapp 开发模板,由 uniapp + Vue3 + Ts + Vite5 + UnoCss + VSCode 构建,模板具有代码提示、自动格式化、统一配置、代码片段等功能,并内置了许多常用的基本组件和基本功能,让你编写 uniapp 拥有 best 体验。',
|
||||
)
|
||||
// 测试 uni API 自动引入
|
||||
onLoad(() => {
|
||||
console.log('项目作者:', author.value)
|
||||
})
|
||||
|
||||
console.log('index')
|
||||
</script>
|
||||
<route lang="jsonc" type="home">{
|
||||
"layout": "tabbar",
|
||||
"style": {
|
||||
// 'custom' 表示开启自定义导航栏,默认 'default'
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "首页"
|
||||
}
|
||||
}</route>
|
||||
|
||||
<template>
|
||||
<view class="bg-white px-4 pt-2" :style="{ marginTop: `${safeAreaInsets?.top}px` }">
|
||||
<view class="mt-10">
|
||||
<image src="/static/logo.svg" alt="" class="mx-auto block h-28 w-28" />
|
||||
</view>
|
||||
<view class="mt-4 text-center text-4xl text-[#d14328]">
|
||||
unibest
|
||||
</view>
|
||||
<view class="mb-8 mt-2 text-center text-2xl">
|
||||
最好用的 uniapp 开发模板
|
||||
</view>
|
||||
<view class="home-bg">
|
||||
<view class="home-bg w-[100vw] fixed top-0 left-0">
|
||||
<wd-navbar safeAreaInsetTop :bordered="false" custom-style="background-color: transparent !important;">
|
||||
<template #left>
|
||||
<view class="">
|
||||
<view class="">上海</view>
|
||||
<wd-img width="14rpx" height="9rpx" :src="`${OSS}icon/icon_arrow_down.png`" />
|
||||
</view>
|
||||
</template>
|
||||
<template #title>
|
||||
<view class="search-box">
|
||||
<wd-search v-model="keyword" hide-cancel placeholder-left></wd-search>
|
||||
</view>
|
||||
</template>
|
||||
</wd-navbar>
|
||||
</view>
|
||||
|
||||
<view class="m-auto mb-2 max-w-100 text-justify indent text-4">
|
||||
{{ description }}
|
||||
</view>
|
||||
<view class="mt-4 text-center">
|
||||
作者:
|
||||
<text class="text-green-500">
|
||||
菲鸽
|
||||
</text>
|
||||
</view>
|
||||
<view class="mt-4 text-center">
|
||||
官网地址:
|
||||
<text class="text-green-500">
|
||||
https://unibest.tech
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="mt-4 text-center">
|
||||
<a href="https://unibest.tech/base/3-plugin" target="_blank" class="text-green-500">
|
||||
新手请看必看章节1:
|
||||
</a>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="mt-4 text-center">
|
||||
新手请看必看章节1:
|
||||
<text class="text-green-500">
|
||||
https://unibest.tech/base/3-plugin
|
||||
</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="mt-4 text-center">
|
||||
<a href="https://unibest.tech/base/14-faq" target="_blank" class="text-green-500">
|
||||
新手请看必看章节2:
|
||||
</a>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="mt-4 text-center">
|
||||
新手请看必看章节2:
|
||||
<text class="text-green-500">
|
||||
https://unibest.tech/base/14-faq
|
||||
</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<view class="mt-4 text-center">
|
||||
<wd-button type="primary">
|
||||
UI组件按钮
|
||||
</wd-button>
|
||||
</view>
|
||||
<view class="mt-4 text-center">
|
||||
UI组件官网:<text class="text-green-500">
|
||||
https://wot-design-uni.cn
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 内容区:自动避让粘性导航栏高度 -->
|
||||
<view class="content" :style="{ paddingTop: navbarHeight + 'px' }">
|
||||
<view v-for="(item, index) in 100" :key="index">123--{{ index }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, inject } from 'vue'
|
||||
import { getNavBarHeight } from '@/utils/index'
|
||||
|
||||
defineOptions({
|
||||
name: 'Home',
|
||||
})
|
||||
|
||||
let keyword = ref('')
|
||||
let navbarHeight = ref(0)
|
||||
let OSS = inject('OSS') // '@/static/'
|
||||
|
||||
onLoad(() => {
|
||||
navbarHeight.value = getNavBarHeight()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.home-bg {
|
||||
background-color: $cz-page-background;
|
||||
background-image: url(#{$OSS}images/home_bg.png);
|
||||
background-size: 100% auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
--wot-search-padding: 0;
|
||||
--wot-search-side-padding: 0;
|
||||
|
||||
:deep() {
|
||||
.wd-search {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.wd-search__input {
|
||||
// #ifdef MP
|
||||
padding-left: 32px !important;
|
||||
padding-right: 0 !important;
|
||||
// #endif
|
||||
|
||||
// #ifndef MP
|
||||
padding-right: 0 !important;
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
120
src/pages/my/my.vue
Normal file
@ -0,0 +1,120 @@
|
||||
<!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page -->
|
||||
<route lang="jsonc" type="page">{
|
||||
"layout": "tabbar",
|
||||
"style": {
|
||||
// 'custom' 表示开启自定义导航栏,默认 'default'
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "我的"
|
||||
}
|
||||
}</route>
|
||||
|
||||
<template>
|
||||
<view class="bg-white px-4 pt-2" :style="{ marginTop: `${safeAreaInsets?.top}px` }">
|
||||
<view class="mt-10">
|
||||
<image src="/static/logo.svg" alt="" class="mx-auto block h-28 w-28" />
|
||||
</view>
|
||||
<view class="mt-4 text-center text-4xl text-[#d14328]">
|
||||
unibest
|
||||
</view>
|
||||
<view class="mb-8 mt-2 text-center text-2xl">
|
||||
最好用的 uniapp 开发模板
|
||||
</view>
|
||||
|
||||
<view class="m-auto mb-2 max-w-100 text-justify indent text-4">
|
||||
{{ description }}
|
||||
</view>
|
||||
<view class="mt-4 text-center">
|
||||
作者:
|
||||
<text class="text-green-500">
|
||||
菲鸽
|
||||
</text>
|
||||
</view>
|
||||
<view class="mt-4 text-center">
|
||||
官网地址:
|
||||
<text class="text-green-500">
|
||||
https://unibest.tech
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="mt-4 text-center">
|
||||
<a href="https://unibest.tech/base/3-plugin" target="_blank" class="text-green-500">
|
||||
新手请看必看章节1:
|
||||
</a>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="mt-4 text-center">
|
||||
新手请看必看章节1:
|
||||
<text class="text-green-500">
|
||||
https://unibest.tech/base/3-plugin
|
||||
</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="mt-4 text-center">
|
||||
<a href="https://unibest.tech/base/14-faq" target="_blank" class="text-green-500">
|
||||
新手请看必看章节2:
|
||||
</a>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="mt-4 text-center">
|
||||
新手请看必看章节2:
|
||||
<text class="text-green-500">
|
||||
https://unibest.tech/base/14-faq
|
||||
</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<view class="mt-4 text-center">
|
||||
<wd-button type="primary">
|
||||
UI组件按钮
|
||||
</wd-button>
|
||||
</view>
|
||||
<view class="mt-4 text-center">
|
||||
UI组件官网:<text class="text-green-500">
|
||||
https://wot-design-uni.cn
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: 'Home',
|
||||
})
|
||||
|
||||
// 获取屏幕边界到安全区域距离
|
||||
let safeAreaInsets
|
||||
let systemInfo
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序使用新的API
|
||||
systemInfo = uni.getWindowInfo()
|
||||
safeAreaInsets = systemInfo.safeArea
|
||||
? {
|
||||
top: systemInfo.safeArea.top,
|
||||
right: systemInfo.windowWidth - systemInfo.safeArea.right,
|
||||
bottom: systemInfo.windowHeight - systemInfo.safeArea.bottom,
|
||||
left: systemInfo.safeArea.left,
|
||||
}
|
||||
: null
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
// 其他平台继续使用uni API
|
||||
systemInfo = uni.getSystemInfoSync()
|
||||
safeAreaInsets = systemInfo.safeAreaInsets
|
||||
// #endif
|
||||
const author = ref('菲鸽')
|
||||
const description = ref(
|
||||
'unibest 是一个集成了多种工具和技术的 uniapp 开发模板,由 uniapp + Vue3 + Ts + Vite5 + UnoCss + VSCode 构建,模板具有代码提示、自动格式化、统一配置、代码片段等功能,并内置了许多常用的基本组件和基本功能,让你编写 uniapp 拥有 best 体验。',
|
||||
)
|
||||
// 测试 uni API 自动引入
|
||||
onLoad(() => {
|
||||
console.log('项目作者:', author.value)
|
||||
})
|
||||
|
||||
console.log('index')
|
||||
</script>
|
||||
120
src/pages/reserve/reserve.vue
Normal file
@ -0,0 +1,120 @@
|
||||
<!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page -->
|
||||
<route lang="jsonc" type="page">{
|
||||
"layout": "tabbar",
|
||||
"style": {
|
||||
// 'custom' 表示开启自定义导航栏,默认 'default'
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "预约"
|
||||
}
|
||||
}</route>
|
||||
|
||||
<template>
|
||||
<view class="bg-white px-4 pt-2" :style="{ marginTop: `${safeAreaInsets?.top}px` }">
|
||||
<view class="mt-10">
|
||||
<image src="/static/logo.svg" alt="" class="mx-auto block h-28 w-28" />
|
||||
</view>
|
||||
<view class="mt-4 text-center text-4xl text-[#d14328]">
|
||||
unibest
|
||||
</view>
|
||||
<view class="mb-8 mt-2 text-center text-2xl">
|
||||
最好用的 uniapp 开发模板
|
||||
</view>
|
||||
|
||||
<view class="m-auto mb-2 max-w-100 text-justify indent text-4">
|
||||
{{ description }}
|
||||
</view>
|
||||
<view class="mt-4 text-center">
|
||||
作者:
|
||||
<text class="text-green-500">
|
||||
菲鸽
|
||||
</text>
|
||||
</view>
|
||||
<view class="mt-4 text-center">
|
||||
官网地址:
|
||||
<text class="text-green-500">
|
||||
https://unibest.tech
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="mt-4 text-center">
|
||||
<a href="https://unibest.tech/base/3-plugin" target="_blank" class="text-green-500">
|
||||
新手请看必看章节1:
|
||||
</a>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="mt-4 text-center">
|
||||
新手请看必看章节1:
|
||||
<text class="text-green-500">
|
||||
https://unibest.tech/base/3-plugin
|
||||
</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="mt-4 text-center">
|
||||
<a href="https://unibest.tech/base/14-faq" target="_blank" class="text-green-500">
|
||||
新手请看必看章节2:
|
||||
</a>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="mt-4 text-center">
|
||||
新手请看必看章节2:
|
||||
<text class="text-green-500">
|
||||
https://unibest.tech/base/14-faq
|
||||
</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<view class="mt-4 text-center">
|
||||
<wd-button type="primary">
|
||||
UI组件按钮
|
||||
</wd-button>
|
||||
</view>
|
||||
<view class="mt-4 text-center">
|
||||
UI组件官网:<text class="text-green-500">
|
||||
https://wot-design-uni.cn
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: 'Home',
|
||||
})
|
||||
|
||||
// 获取屏幕边界到安全区域距离
|
||||
let safeAreaInsets
|
||||
let systemInfo
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序使用新的API
|
||||
systemInfo = uni.getWindowInfo()
|
||||
safeAreaInsets = systemInfo.safeArea
|
||||
? {
|
||||
top: systemInfo.safeArea.top,
|
||||
right: systemInfo.windowWidth - systemInfo.safeArea.right,
|
||||
bottom: systemInfo.windowHeight - systemInfo.safeArea.bottom,
|
||||
left: systemInfo.safeArea.left,
|
||||
}
|
||||
: null
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
// 其他平台继续使用uni API
|
||||
systemInfo = uni.getSystemInfoSync()
|
||||
safeAreaInsets = systemInfo.safeAreaInsets
|
||||
// #endif
|
||||
const author = ref('菲鸽')
|
||||
const description = ref(
|
||||
'unibest 是一个集成了多种工具和技术的 uniapp 开发模板,由 uniapp + Vue3 + Ts + Vite5 + UnoCss + VSCode 构建,模板具有代码提示、自动格式化、统一配置、代码片段等功能,并内置了许多常用的基本组件和基本功能,让你编写 uniapp 拥有 best 体验。',
|
||||
)
|
||||
// 测试 uni API 自动引入
|
||||
onLoad(() => {
|
||||
console.log('项目作者:', author.value)
|
||||
})
|
||||
|
||||
console.log('index')
|
||||
</script>
|
||||
BIN
src/static/icon/icon_arrow_down.png
Normal file
|
After Width: | Height: | Size: 260 B |
BIN
src/static/images/home_bg.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 799 B |
|
Before Width: | Height: | Size: 1.4 KiB |
BIN
src/static/tabbar/home_s.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/static/tabbar/my.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/static/tabbar/my_s.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
BIN
src/static/tabbar/reserve.png
Normal file
|
After Width: | Height: | Size: 656 B |
BIN
src/static/tabbar/reserve_s.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
@ -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
|
||||
|
||||
@ -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<IUserInfoVo>({ ...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<IUserLogin>
|
||||
*/
|
||||
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<IUserInfoVo>({ ...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<IUserLogin>
|
||||
*/
|
||||
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,
|
||||
},
|
||||
)
|
||||
|
||||
@ -3,11 +3,11 @@ import type { TabBar } from '@uni-helper/vite-plugin-uni-pages'
|
||||
type NativeTabBarItem = TabBar['list'][0]
|
||||
|
||||
type CustomTabBarItem = (Pick<NativeTabBarItem, 'text' | 'pagePath'> & {
|
||||
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<NativeTabBarItem, 'text' | 'pagePath'> & {
|
||||
* 温馨提示:本文件的任何代码更改了之后,都需要重新运行,否则 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)的图标库
|
||||
// 使用方式如:<uni-icons type="home" size="30"/>
|
||||
// 图标列表地址: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)的图标库
|
||||
// 使用方式如:<uni-icons type="home" size="30"/>
|
||||
// 图标列表地址: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
|
||||
|
||||
@ -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/';
|
||||
|
||||
@ -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<string, string> = {}
|
||||
queryStr.split('&').forEach((item) => {
|
||||
const [key, value] = item.split('=')
|
||||
// console.log(key, value)
|
||||
query[key] = ensureDecodeURIComponent(value) // 这里需要统一 decodeURIComponent 一下,可以兼容h5和微信y
|
||||
})
|
||||
return { path, query }
|
||||
if (!queryStr) {
|
||||
return {
|
||||
path,
|
||||
query: {},
|
||||
}
|
||||
}
|
||||
const query: Record<string, string> = {}
|
||||
queryStr.split('&').forEach((item) => {
|
||||
const [key, value] = item.split('=')
|
||||
// console.log(key, value)
|
||||
query[key] = ensureDecodeURIComponent(value) // 这里需要统一 decodeURIComponent 一下,可以兼容h5和微信y
|
||||
})
|
||||
return { path, query }
|
||||
}
|
||||
/**
|
||||
* 得到所有的需要登录的 pages,包括主包和分包的
|
||||
@ -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<string, any>) => {
|
||||
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<string, any>) => {
|
||||
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
|
||||
}
|
||||
@ -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<T = string>(url: string, filePath: string, formData: Record<string, any> = {}, options: Omit<UploadOptions, 'sourceType' | 'sizeType' | 'count'> = {}) {
|
||||
return useUpload<T>(
|
||||
url,
|
||||
formData,
|
||||
{
|
||||
...options,
|
||||
sourceType: ['album'],
|
||||
sizeType: ['original'],
|
||||
},
|
||||
filePath,
|
||||
)
|
||||
return useUpload<T>(
|
||||
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<string, any>) => 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<string, any>) => void
|
||||
/** 上传失败回调函数 */
|
||||
onError?: (err: Error | UniApp.GeneralCallbackResult) => void
|
||||
/** 上传完成回调函数(无论成功失败) */
|
||||
onComplete?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,150 +72,150 @@ export interface UploadOptions {
|
||||
* @returns 上传状态和控制对象
|
||||
*/
|
||||
export function useUpload<T = string>(url: string, formData: Record<string, any> = {}, options: UploadOptions = {},
|
||||
/** 直接传入文件路径,跳过选择器 */
|
||||
directFilePath?: string) {
|
||||
/** 上传中状态 */
|
||||
const loading = ref(false)
|
||||
/** 上传错误状态 */
|
||||
const error = ref(false)
|
||||
/** 上传成功后的响应数据 */
|
||||
const data = ref<T>()
|
||||
/** 上传进度(0-100) */
|
||||
const progress = ref(0)
|
||||
/** 直接传入文件路径,跳过选择器 */
|
||||
directFilePath?: string) {
|
||||
/** 上传中状态 */
|
||||
const loading = ref(false)
|
||||
/** 上传错误状态 */
|
||||
const error = ref(false)
|
||||
/** 上传成功后的响应数据 */
|
||||
const data = ref<T>()
|
||||
/** 上传进度(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<T>({
|
||||
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<T>({
|
||||
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<T>({
|
||||
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<T>({
|
||||
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<T>({
|
||||
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<T>({
|
||||
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<T = string>(url: string, formData: Record<string, any>
|
||||
* @template T 上传成功后返回的数据类型
|
||||
*/
|
||||
interface UploadFileOptions<T> {
|
||||
/** 上传地址 */
|
||||
url: string
|
||||
/** 临时文件路径 */
|
||||
tempFilePath: string
|
||||
/** 额外的表单数据 */
|
||||
formData: Record<string, any>
|
||||
/** 上传成功后的响应数据 */
|
||||
data: Ref<T | undefined>
|
||||
/** 上传错误状态 */
|
||||
error: Ref<boolean>
|
||||
/** 上传中状态 */
|
||||
loading: Ref<boolean>
|
||||
/** 上传进度(0-100) */
|
||||
progress: Ref<number>
|
||||
/** 上传进度回调 */
|
||||
onProgress?: (progress: number) => void
|
||||
/** 上传成功回调 */
|
||||
onSuccess?: (res: Record<string, any>) => void
|
||||
/** 上传失败回调 */
|
||||
onError?: (err: Error | UniApp.GeneralCallbackResult) => void
|
||||
/** 上传完成回调 */
|
||||
onComplete?: () => void
|
||||
/** 上传地址 */
|
||||
url: string
|
||||
/** 临时文件路径 */
|
||||
tempFilePath: string
|
||||
/** 额外的表单数据 */
|
||||
formData: Record<string, any>
|
||||
/** 上传成功后的响应数据 */
|
||||
data: Ref<T | undefined>
|
||||
/** 上传错误状态 */
|
||||
error: Ref<boolean>
|
||||
/** 上传中状态 */
|
||||
loading: Ref<boolean>
|
||||
/** 上传进度(0-100) */
|
||||
progress: Ref<number>
|
||||
/** 上传进度回调 */
|
||||
onProgress?: (progress: number) => void
|
||||
/** 上传成功回调 */
|
||||
onSuccess?: (res: Record<string, any>) => void
|
||||
/** 上传失败回调 */
|
||||
onError?: (err: Error | UniApp.GeneralCallbackResult) => void
|
||||
/** 上传完成回调 */
|
||||
onComplete?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
@ -253,72 +253,72 @@ interface UploadFileOptions<T> {
|
||||
* @param options 上传选项
|
||||
*/
|
||||
function uploadFile<T>({
|
||||
url,
|
||||
tempFilePath,
|
||||
formData,
|
||||
data,
|
||||
error,
|
||||
loading,
|
||||
progress,
|
||||
onProgress,
|
||||
onSuccess,
|
||||
onError,
|
||||
onComplete,
|
||||
url,
|
||||
tempFilePath,
|
||||
formData,
|
||||
data,
|
||||
error,
|
||||
loading,
|
||||
progress,
|
||||
onProgress,
|
||||
onSuccess,
|
||||
onError,
|
||||
onComplete,
|
||||
}: UploadFileOptions<T>) {
|
||||
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('创建上传任务失败'))
|
||||
}
|
||||
}
|
||||
|
||||
118
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-` 前缀,如:<div fg-border="1px solid #000"></div>
|
||||
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: `<div class="hover:(bg-gray-400 font-medium) font-(light mono)">测试 unocss</div>`
|
||||
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-` 前缀,如:<div fg-border="1px solid #000"></div>
|
||||
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: `<div class="hover:(bg-gray-400 font-medium) font-(light mono)">测试 unocss</div>`
|
||||
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'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
296
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',
|
||||
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||