绘制首页和配置tabbar

This commit is contained in:
wangxiaowei
2025-08-14 14:30:12 +08:00
parent b817e31b69
commit a52154d9fc
33 changed files with 1617 additions and 1363 deletions

View File

@ -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',
})

View File

@ -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,
})

View File

@ -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,
})
}

View File

@ -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

View File

@ -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

View File

@ -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)
},
}

View File

@ -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,
}
}

View File

@ -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 @@
]
}
]
}
}

View File

@ -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
View 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>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/static/tabbar/my.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/static/tabbar/my_s.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -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

View File

@ -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,
},
)

View File

@ -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

View File

@ -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/';

View File

@ -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
}

View File

@ -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('创建上传任务失败'))
}
}

View File

@ -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'],
},
},
})

View File

@ -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',
},
})
},
})
}