绘制首页和配置tabbar
@ -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('创建上传任务失败'))
|
||||
}
|
||||
}
|
||||
|
||||