feat: 完成情绪博物馆项目重构和功能增强 - 新增日记评论和帖子功能 - 重构前端架构,优化用户体验 - 完善WebSocket通信机制 - 更新项目文档和部署配置
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* 认证相关组合式函数
|
||||
*/
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import AuthService from '@/services/auth'
|
||||
import type { LoginRequest, RegisterRequest, CaptchaResponse } from '@/types/auth'
|
||||
|
||||
/**
|
||||
* 使用认证功能
|
||||
*/
|
||||
export const useAuth = () => {
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
|
||||
// 验证码相关
|
||||
const captchaData = ref<CaptchaResponse | null>(null)
|
||||
const captchaImage = computed(() =>
|
||||
captchaData.value ? `data:image/png;base64,${captchaData.value.captchaImage}` : ''
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*/
|
||||
const getCaptcha = async () => {
|
||||
try {
|
||||
const response = await AuthService.getCaptcha()
|
||||
captchaData.value = response
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('获取验证码失败:', error)
|
||||
ElMessage.error('获取验证码失败')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新验证码
|
||||
*/
|
||||
const refreshCaptcha = async () => {
|
||||
return getCaptcha()
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
const login = async (loginData: LoginRequest) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const success = await authStore.login(loginData)
|
||||
|
||||
if (success) {
|
||||
ElMessage.success('登录成功')
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error: any) {
|
||||
console.error('登录失败:', error)
|
||||
ElMessage.error(error.message || '登录失败')
|
||||
return false
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
const register = async (registerData: RegisterRequest) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const success = await authStore.register(registerData)
|
||||
|
||||
if (success) {
|
||||
ElMessage.success('注册成功')
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error: any) {
|
||||
console.error('注册失败:', error)
|
||||
ElMessage.error(error.message || '注册失败')
|
||||
return false
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出
|
||||
*/
|
||||
const logout = async () => {
|
||||
try {
|
||||
await authStore.logout()
|
||||
router.push('/login')
|
||||
} catch (error) {
|
||||
console.error('登出失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查账号是否存在
|
||||
*/
|
||||
const checkAccountExists = async (account: string) => {
|
||||
if (!account || !/^[a-zA-Z0-9_]{4,20}$/.test(account)) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
return await AuthService.checkAccountExists(account)
|
||||
} catch (error) {
|
||||
console.error('检查账号失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查邮箱是否存在
|
||||
*/
|
||||
const checkEmailExists = async (email: string) => {
|
||||
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
return await AuthService.checkEmailExists(email)
|
||||
} catch (error) {
|
||||
console.error('检查邮箱失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查手机号是否存在
|
||||
*/
|
||||
const checkPhoneExists = async (phone: string) => {
|
||||
if (!phone || !/^1[3-9]\d{9}$/.test(phone)) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
return await AuthService.checkPhoneExists(phone)
|
||||
} catch (error) {
|
||||
console.error('检查手机号失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
loading,
|
||||
captchaData,
|
||||
captchaImage,
|
||||
|
||||
// 计算属性
|
||||
isLoggedIn: authStore.isLoggedIn,
|
||||
userInfo: authStore.userInfo,
|
||||
|
||||
// 方法
|
||||
getCaptcha,
|
||||
refreshCaptcha,
|
||||
login,
|
||||
register,
|
||||
logout,
|
||||
checkAccountExists,
|
||||
checkEmailExists,
|
||||
checkPhoneExists
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用表单验证
|
||||
*/
|
||||
export const useFormValidation = () => {
|
||||
/**
|
||||
* 账号验证规则
|
||||
*/
|
||||
const validateAccount = (rule: any, value: string, callback: any) => {
|
||||
if (!value) {
|
||||
callback(new Error('请输入账号'))
|
||||
return
|
||||
}
|
||||
if (!/^[a-zA-Z0-9_]{4,20}$/.test(value)) {
|
||||
callback(new Error('账号只能包含字母、数字和下划线,长度4-20位'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码验证规则
|
||||
*/
|
||||
const validatePassword = (rule: any, value: string, callback: any) => {
|
||||
if (!value) {
|
||||
callback(new Error('请输入密码'))
|
||||
return
|
||||
}
|
||||
if (value.length < 6 || value.length > 20) {
|
||||
callback(new Error('密码长度必须在6-20位之间'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认密码验证规则
|
||||
*/
|
||||
const validateConfirmPassword = (password: string) => {
|
||||
return (rule: any, value: string, callback: any) => {
|
||||
if (!value) {
|
||||
callback(new Error('请再次输入密码'))
|
||||
return
|
||||
}
|
||||
if (value !== password) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮箱验证规则
|
||||
*/
|
||||
const validateEmail = (rule: any, value: string, callback: any) => {
|
||||
if (!value) {
|
||||
callback(new Error('请输入邮箱地址'))
|
||||
return
|
||||
}
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!emailRegex.test(value)) {
|
||||
callback(new Error('请输入正确的邮箱格式'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号验证规则
|
||||
*/
|
||||
const validatePhone = (rule: any, value: string, callback: any) => {
|
||||
if (value && !/^1[3-9]\d{9}$/.test(value)) {
|
||||
callback(new Error('请输入正确的手机号格式'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
return {
|
||||
validateAccount,
|
||||
validatePassword,
|
||||
validateConfirmPassword,
|
||||
validateEmail,
|
||||
validatePhone
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* 表单验证组合式函数
|
||||
*/
|
||||
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
/**
|
||||
* 使用表单验证
|
||||
*/
|
||||
export const useFormValidation = <T extends Record<string, any>>(
|
||||
initialData: T,
|
||||
rules: FormRules
|
||||
) => {
|
||||
const formRef = ref<FormInstance>()
|
||||
const formData = reactive<T>({ ...initialData })
|
||||
const errors = ref<Record<string, string>>({})
|
||||
const isValidating = ref(false)
|
||||
|
||||
/**
|
||||
* 验证整个表单
|
||||
*/
|
||||
const validateForm = async (): Promise<boolean> => {
|
||||
if (!formRef.value) return false
|
||||
|
||||
try {
|
||||
isValidating.value = true
|
||||
await formRef.value.validate()
|
||||
errors.value = {}
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
return false
|
||||
} finally {
|
||||
isValidating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证指定字段
|
||||
*/
|
||||
const validateField = async (field: keyof T): Promise<boolean> => {
|
||||
if (!formRef.value) return false
|
||||
|
||||
try {
|
||||
await formRef.value.validateField(field as string)
|
||||
delete errors.value[field as string]
|
||||
return true
|
||||
} catch (error) {
|
||||
errors.value[field as string] = error as string
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除验证结果
|
||||
*/
|
||||
const clearValidation = (fields?: (keyof T)[]) => {
|
||||
if (!formRef.value) return
|
||||
|
||||
if (fields) {
|
||||
formRef.value.clearValidate(fields as string[])
|
||||
fields.forEach(field => {
|
||||
delete errors.value[field as string]
|
||||
})
|
||||
} else {
|
||||
formRef.value.clearValidate()
|
||||
errors.value = {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置表单
|
||||
*/
|
||||
const resetForm = () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
formRef.value.resetFields()
|
||||
Object.assign(formData, initialData)
|
||||
errors.value = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段错误
|
||||
*/
|
||||
const setFieldError = (field: keyof T, message: string) => {
|
||||
errors.value[field as string] = message
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除字段错误
|
||||
*/
|
||||
const clearFieldError = (field: keyof T) => {
|
||||
delete errors.value[field as string]
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段错误
|
||||
*/
|
||||
const getFieldError = (field: keyof T) => {
|
||||
return errors.value[field as string]
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查表单是否有错误
|
||||
*/
|
||||
const hasErrors = computed(() => {
|
||||
return Object.keys(errors.value).length > 0
|
||||
})
|
||||
|
||||
/**
|
||||
* 检查表单是否有效
|
||||
*/
|
||||
const isValid = computed(() => {
|
||||
return !hasErrors.value && !isValidating.value
|
||||
})
|
||||
|
||||
return {
|
||||
formRef,
|
||||
formData,
|
||||
errors: computed(() => errors.value),
|
||||
isValidating: computed(() => isValidating.value),
|
||||
hasErrors,
|
||||
isValid,
|
||||
validateForm,
|
||||
validateField,
|
||||
clearValidation,
|
||||
resetForm,
|
||||
setFieldError,
|
||||
clearFieldError,
|
||||
getFieldError
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 常用验证规则
|
||||
*/
|
||||
export const validationRules = {
|
||||
/**
|
||||
* 必填验证
|
||||
*/
|
||||
required: (message = '此字段为必填项') => ({
|
||||
required: true,
|
||||
message,
|
||||
trigger: 'blur'
|
||||
}),
|
||||
|
||||
/**
|
||||
* 邮箱验证
|
||||
*/
|
||||
email: (message = '请输入正确的邮箱格式') => ({
|
||||
type: 'email' as const,
|
||||
message,
|
||||
trigger: 'blur'
|
||||
}),
|
||||
|
||||
/**
|
||||
* 手机号验证
|
||||
*/
|
||||
phone: (message = '请输入正确的手机号格式') => ({
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message,
|
||||
trigger: 'blur'
|
||||
}),
|
||||
|
||||
/**
|
||||
* 长度验证
|
||||
*/
|
||||
length: (min: number, max: number, message?: string) => ({
|
||||
min,
|
||||
max,
|
||||
message: message || `长度必须在${min}-${max}位之间`,
|
||||
trigger: 'blur'
|
||||
}),
|
||||
|
||||
/**
|
||||
* 最小长度验证
|
||||
*/
|
||||
minLength: (min: number, message?: string) => ({
|
||||
min,
|
||||
message: message || `长度不能少于${min}位`,
|
||||
trigger: 'blur'
|
||||
}),
|
||||
|
||||
/**
|
||||
* 最大长度验证
|
||||
*/
|
||||
maxLength: (max: number, message?: string) => ({
|
||||
max,
|
||||
message: message || `长度不能超过${max}位`,
|
||||
trigger: 'blur'
|
||||
}),
|
||||
|
||||
/**
|
||||
* 正则验证
|
||||
*/
|
||||
pattern: (pattern: RegExp, message: string) => ({
|
||||
pattern,
|
||||
message,
|
||||
trigger: 'blur'
|
||||
}),
|
||||
|
||||
/**
|
||||
* 自定义验证
|
||||
*/
|
||||
custom: (validator: (rule: any, value: any, callback: any) => void) => ({
|
||||
validator,
|
||||
trigger: 'blur'
|
||||
}),
|
||||
|
||||
/**
|
||||
* 账号验证(字母数字下划线)
|
||||
*/
|
||||
account: (message = '账号只能包含字母、数字和下划线,长度4-20位') => ({
|
||||
pattern: /^[a-zA-Z0-9_]{4,20}$/,
|
||||
message,
|
||||
trigger: 'blur'
|
||||
}),
|
||||
|
||||
/**
|
||||
* 密码验证
|
||||
*/
|
||||
password: (min = 6, max = 20, message?: string) => ({
|
||||
min,
|
||||
max,
|
||||
message: message || `密码长度必须在${min}-${max}位之间`,
|
||||
trigger: 'blur'
|
||||
}),
|
||||
|
||||
/**
|
||||
* 确认密码验证
|
||||
*/
|
||||
confirmPassword: (passwordField: string, message = '两次输入的密码不一致') => ({
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (!value) {
|
||||
callback(new Error('请再次输入密码'))
|
||||
return
|
||||
}
|
||||
// 这里需要访问表单数据,在实际使用时需要传入表单数据
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用实时验证
|
||||
*/
|
||||
export const useRealtimeValidation = <T extends Record<string, any>>(
|
||||
formData: T,
|
||||
rules: FormRules
|
||||
) => {
|
||||
const errors = ref<Record<string, string>>({})
|
||||
const validFields = ref<Set<string>>(new Set())
|
||||
|
||||
/**
|
||||
* 验证单个字段
|
||||
*/
|
||||
const validateField = async (field: keyof T, value: any) => {
|
||||
const fieldRules = rules[field as string]
|
||||
if (!fieldRules) return true
|
||||
|
||||
try {
|
||||
// 这里简化处理,实际应该使用async-validator
|
||||
const ruleArray = Array.isArray(fieldRules) ? fieldRules : [fieldRules]
|
||||
|
||||
for (const rule of ruleArray) {
|
||||
if (rule.required && (!value || value === '')) {
|
||||
throw new Error(rule.message || '此字段为必填项')
|
||||
}
|
||||
|
||||
if (rule.min && value && value.length < rule.min) {
|
||||
throw new Error(rule.message || `长度不能少于${rule.min}位`)
|
||||
}
|
||||
|
||||
if (rule.max && value && value.length > rule.max) {
|
||||
throw new Error(rule.message || `长度不能超过${rule.max}位`)
|
||||
}
|
||||
|
||||
if (rule.pattern && value && !rule.pattern.test(value)) {
|
||||
throw new Error(rule.message || '格式不正确')
|
||||
}
|
||||
}
|
||||
|
||||
delete errors.value[field as string]
|
||||
validFields.value.add(field as string)
|
||||
return true
|
||||
} catch (error: any) {
|
||||
errors.value[field as string] = error.message
|
||||
validFields.value.delete(field as string)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查所有字段是否有效
|
||||
*/
|
||||
const isAllValid = computed(() => {
|
||||
const requiredFields = Object.keys(rules)
|
||||
return requiredFields.every(field => validFields.value.has(field)) &&
|
||||
Object.keys(errors.value).length === 0
|
||||
})
|
||||
|
||||
return {
|
||||
errors: computed(() => errors.value),
|
||||
validFields: computed(() => validFields.value),
|
||||
isAllValid,
|
||||
validateField
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/**
|
||||
* 加载状态管理组合式函数
|
||||
*/
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
import { ElLoading } from 'element-plus'
|
||||
import type { LoadingInstance } from 'element-plus/es/components/loading/src/loading'
|
||||
|
||||
// 全局加载状态
|
||||
const globalLoading = ref(false)
|
||||
const loadingCount = ref(0)
|
||||
|
||||
/**
|
||||
* 使用加载状态
|
||||
*/
|
||||
export const useLoading = (initialState = false) => {
|
||||
const loading = ref(initialState)
|
||||
|
||||
/**
|
||||
* 设置加载状态
|
||||
*/
|
||||
const setLoading = (state: boolean) => {
|
||||
loading.value = state
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始加载
|
||||
*/
|
||||
const startLoading = () => {
|
||||
loading.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束加载
|
||||
*/
|
||||
const stopLoading = () => {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步操作包装器
|
||||
*/
|
||||
const withLoading = async <T>(fn: () => Promise<T>): Promise<T> => {
|
||||
try {
|
||||
startLoading()
|
||||
return await fn()
|
||||
} finally {
|
||||
stopLoading()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loading: computed(() => loading.value),
|
||||
setLoading,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
withLoading
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用全局加载状态
|
||||
*/
|
||||
export const useGlobalLoading = () => {
|
||||
/**
|
||||
* 增加加载计数
|
||||
*/
|
||||
const addLoading = () => {
|
||||
loadingCount.value++
|
||||
globalLoading.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 减少加载计数
|
||||
*/
|
||||
const removeLoading = () => {
|
||||
loadingCount.value = Math.max(0, loadingCount.value - 1)
|
||||
if (loadingCount.value === 0) {
|
||||
globalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置加载状态
|
||||
*/
|
||||
const resetLoading = () => {
|
||||
loadingCount.value = 0
|
||||
globalLoading.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局异步操作包装器
|
||||
*/
|
||||
const withGlobalLoading = async <T>(fn: () => Promise<T>): Promise<T> => {
|
||||
try {
|
||||
addLoading()
|
||||
return await fn()
|
||||
} finally {
|
||||
removeLoading()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
globalLoading: computed(() => globalLoading.value),
|
||||
loadingCount: computed(() => loadingCount.value),
|
||||
addLoading,
|
||||
removeLoading,
|
||||
resetLoading,
|
||||
withGlobalLoading
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用页面加载遮罩
|
||||
*/
|
||||
export const usePageLoading = () => {
|
||||
let loadingInstance: LoadingInstance | null = null
|
||||
|
||||
/**
|
||||
* 显示页面加载遮罩
|
||||
*/
|
||||
const showPageLoading = (options?: {
|
||||
text?: string
|
||||
background?: string
|
||||
target?: string | HTMLElement
|
||||
}) => {
|
||||
const {
|
||||
text = '加载中...',
|
||||
background = 'rgba(0, 0, 0, 0.7)',
|
||||
target = 'body'
|
||||
} = options || {}
|
||||
|
||||
loadingInstance = ElLoading.service({
|
||||
lock: true,
|
||||
text,
|
||||
background,
|
||||
target
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏页面加载遮罩
|
||||
*/
|
||||
const hidePageLoading = () => {
|
||||
if (loadingInstance) {
|
||||
loadingInstance.close()
|
||||
loadingInstance = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面异步操作包装器
|
||||
*/
|
||||
const withPageLoading = async <T>(
|
||||
fn: () => Promise<T>,
|
||||
options?: {
|
||||
text?: string
|
||||
background?: string
|
||||
target?: string | HTMLElement
|
||||
}
|
||||
): Promise<T> => {
|
||||
try {
|
||||
showPageLoading(options)
|
||||
return await fn()
|
||||
} finally {
|
||||
hidePageLoading()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
showPageLoading,
|
||||
hidePageLoading,
|
||||
withPageLoading
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用按钮加载状态
|
||||
*/
|
||||
export const useButtonLoading = () => {
|
||||
const buttonLoadings = ref<Record<string, boolean>>({})
|
||||
|
||||
/**
|
||||
* 设置按钮加载状态
|
||||
*/
|
||||
const setButtonLoading = (key: string, loading: boolean) => {
|
||||
buttonLoadings.value[key] = loading
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取按钮加载状态
|
||||
*/
|
||||
const getButtonLoading = (key: string) => {
|
||||
return computed(() => buttonLoadings.value[key] || false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 按钮异步操作包装器
|
||||
*/
|
||||
const withButtonLoading = async <T>(
|
||||
key: string,
|
||||
fn: () => Promise<T>
|
||||
): Promise<T> => {
|
||||
try {
|
||||
setButtonLoading(key, true)
|
||||
return await fn()
|
||||
} finally {
|
||||
setButtonLoading(key, false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
buttonLoadings: computed(() => buttonLoadings.value),
|
||||
setButtonLoading,
|
||||
getButtonLoading,
|
||||
withButtonLoading
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用延迟加载
|
||||
*/
|
||||
export const useDelayedLoading = (delay = 300) => {
|
||||
const loading = ref(false)
|
||||
const actualLoading = ref(false)
|
||||
let timer: NodeJS.Timeout | null = null
|
||||
|
||||
/**
|
||||
* 设置加载状态(带延迟)
|
||||
*/
|
||||
const setLoading = (state: boolean) => {
|
||||
if (state) {
|
||||
// 立即设置实际加载状态
|
||||
actualLoading.value = true
|
||||
|
||||
// 延迟显示加载UI
|
||||
timer = setTimeout(() => {
|
||||
loading.value = true
|
||||
}, delay)
|
||||
} else {
|
||||
// 立即隐藏加载UI
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
loading.value = false
|
||||
actualLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟异步操作包装器
|
||||
*/
|
||||
const withDelayedLoading = async <T>(fn: () => Promise<T>): Promise<T> => {
|
||||
try {
|
||||
setLoading(true)
|
||||
return await fn()
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loading: computed(() => loading.value),
|
||||
actualLoading: computed(() => actualLoading.value),
|
||||
setLoading,
|
||||
withDelayedLoading
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user