新增:连接管理、数据查询等功能
This commit is contained in:
8
web/src/composables/index.ts
Normal file
8
web/src/composables/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 全局 Composables 导出
|
||||
*/
|
||||
|
||||
export * from './useLocalStorage'
|
||||
export * from './useDebounce'
|
||||
export * from './useTablePage'
|
||||
export * from './useApiError'
|
||||
61
web/src/composables/useApiError.ts
Normal file
61
web/src/composables/useApiError.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* API Error handling composable
|
||||
* 统一的 API 错误处理
|
||||
*/
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
|
||||
export interface ApiErrorState {
|
||||
hasError: boolean
|
||||
message: string
|
||||
code?: string | number
|
||||
}
|
||||
|
||||
export function useApiError() {
|
||||
const error = ref<ApiErrorState>({
|
||||
hasError: false,
|
||||
message: ''
|
||||
})
|
||||
|
||||
const setError = (err: Error | string | unknown, defaultMessage: string = '操作失败') => {
|
||||
let message = defaultMessage
|
||||
let code: string | number | undefined
|
||||
|
||||
if (err instanceof Error) {
|
||||
message = err.message || defaultMessage
|
||||
} else if (typeof err === 'string') {
|
||||
message = err
|
||||
} else if (err && typeof err === 'object' && 'message' in err) {
|
||||
message = (err as any).message || defaultMessage
|
||||
if ('code' in err) code = (err as any).code
|
||||
}
|
||||
|
||||
error.value = {
|
||||
hasError: true,
|
||||
message,
|
||||
code
|
||||
}
|
||||
|
||||
return { message, code }
|
||||
}
|
||||
|
||||
const showError = (err: Error | string | unknown, defaultMessage: string = '操作失败') => {
|
||||
const { message } = setError(err, defaultMessage)
|
||||
Message.error(message)
|
||||
}
|
||||
|
||||
const clearError = () => {
|
||||
error.value = {
|
||||
hasError: false,
|
||||
message: ''
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
error,
|
||||
setError,
|
||||
showError,
|
||||
clearError
|
||||
}
|
||||
}
|
||||
34
web/src/composables/useDebounce.ts
Normal file
34
web/src/composables/useDebounce.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Debounce composable
|
||||
* 防抖函数
|
||||
*/
|
||||
|
||||
import { ref, watch, type Ref, type ComputedRef } from 'vue'
|
||||
|
||||
export function useDebounce<T>(value: Ref<T> | ComputedRef<T>, delay: number = 300): Ref<T> {
|
||||
const debouncedValue = ref<T>(value.value) as Ref<T>
|
||||
let timeout: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
watch(value, (newValue) => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
debouncedValue.value = newValue
|
||||
}, delay)
|
||||
})
|
||||
|
||||
return debouncedValue
|
||||
}
|
||||
|
||||
export function debounceFn<T extends (...args: any[]) => any>(
|
||||
fn: T,
|
||||
delay: number = 300
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeout: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
return (...args: Parameters<T>) => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
fn(...args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
34
web/src/composables/useLocalStorage.ts
Normal file
34
web/src/composables/useLocalStorage.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* LocalStorage composable
|
||||
* 通用的 localStorage 操作
|
||||
*/
|
||||
|
||||
import { watch, type Ref } from 'vue'
|
||||
|
||||
export function useLocalStorage<T>(
|
||||
key: string,
|
||||
defaultValue: T,
|
||||
storage: Storage = localStorage
|
||||
): [Ref<T>, (value: T) => void, () => void] {
|
||||
const stored = storage.getItem(key)
|
||||
const value = ref<T>(stored ? JSON.parse(stored) : defaultValue)
|
||||
|
||||
const setValue = (newValue: T) => {
|
||||
value.value = newValue
|
||||
}
|
||||
|
||||
const clearValue = () => {
|
||||
value.value = defaultValue
|
||||
storage.removeItem(key)
|
||||
}
|
||||
|
||||
watch(value, (newValue) => {
|
||||
try {
|
||||
storage.setItem(key, JSON.stringify(newValue))
|
||||
} catch (e) {
|
||||
console.warn(`Failed to save ${key} to localStorage:`, e)
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
return [value, setValue, clearValue]
|
||||
}
|
||||
60
web/src/composables/useTablePage.ts
Normal file
60
web/src/composables/useTablePage.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Table Pagination composable
|
||||
* 表格分页逻辑
|
||||
*/
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
export interface PaginationOptions {
|
||||
pageSize?: number
|
||||
initialPage?: number
|
||||
}
|
||||
|
||||
export function useTablePage(options: PaginationOptions = {}) {
|
||||
const { pageSize = 10, initialPage = 1 } = options
|
||||
|
||||
const currentPage = ref(initialPage)
|
||||
const currentPageSize = ref(pageSize)
|
||||
|
||||
const canGoPrev = computed(() => currentPage.value > 1)
|
||||
const canGoNext = computed(() => currentPage.value * currentPageSize.value < totalItems.value)
|
||||
|
||||
const totalItems = ref(0)
|
||||
const totalPages = computed(() => Math.ceil(totalItems.value / currentPageSize.value))
|
||||
|
||||
const nextPage = () => {
|
||||
if (canGoNext.value) currentPage.value++
|
||||
}
|
||||
|
||||
const prevPage = () => {
|
||||
if (canGoPrev.value) currentPage.value--
|
||||
}
|
||||
|
||||
const goToPage = (page: number) => {
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
currentPage.value = page
|
||||
}
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
currentPage.value = initialPage
|
||||
}
|
||||
|
||||
const setTotalItems = (total: number) => {
|
||||
totalItems.value = total
|
||||
}
|
||||
|
||||
return {
|
||||
currentPage,
|
||||
currentPageSize,
|
||||
canGoPrev,
|
||||
canGoNext,
|
||||
totalItems,
|
||||
totalPages,
|
||||
nextPage,
|
||||
prevPage,
|
||||
goToPage,
|
||||
reset,
|
||||
setTotalItems
|
||||
}
|
||||
}
|
||||
78
web/src/composables/useTheme.ts
Normal file
78
web/src/composables/useTheme.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
type Theme = 'light' | 'dark'
|
||||
|
||||
const THEME_STORAGE_KEY = 'app-theme'
|
||||
|
||||
// 单例模式:全局共享主题状态
|
||||
const theme = ref<Theme>('light')
|
||||
let systemThemeListener: (() => void) | null = null
|
||||
|
||||
// 应用主题到 DOM
|
||||
const applyTheme = (newTheme: Theme) => {
|
||||
theme.value = newTheme
|
||||
if (newTheme === 'dark') {
|
||||
document.body.setAttribute('arco-theme', 'dark')
|
||||
} else {
|
||||
document.body.removeAttribute('arco-theme')
|
||||
}
|
||||
localStorage.setItem(THEME_STORAGE_KEY, newTheme)
|
||||
}
|
||||
|
||||
// 初始化主题(只调用一次)
|
||||
const initTheme = () => {
|
||||
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY) as Theme
|
||||
if (savedTheme && (savedTheme === 'light' || savedTheme === 'dark')) {
|
||||
applyTheme(savedTheme)
|
||||
} else {
|
||||
// 检测系统偏好
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
applyTheme('dark')
|
||||
} else {
|
||||
applyTheme('light')
|
||||
}
|
||||
}
|
||||
|
||||
// 监听系统主题变化
|
||||
if (window.matchMedia) {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
// 如果用户没有手动设置过主题,则跟随系统
|
||||
if (!localStorage.getItem(THEME_STORAGE_KEY)) {
|
||||
applyTheme(e.matches ? 'dark' : 'light')
|
||||
}
|
||||
}
|
||||
mediaQuery.addEventListener('change', handleChange)
|
||||
systemThemeListener = () => mediaQuery.removeEventListener('change', handleChange)
|
||||
}
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
// 切换主题
|
||||
const toggleTheme = () => {
|
||||
const newTheme: Theme = theme.value === 'light' ? 'dark' : 'light'
|
||||
applyTheme(newTheme)
|
||||
}
|
||||
|
||||
// 设置为亮色主题
|
||||
const setLightTheme = () => {
|
||||
applyTheme('light')
|
||||
}
|
||||
|
||||
// 设置为暗色主题
|
||||
const setDarkTheme = () => {
|
||||
applyTheme('dark')
|
||||
}
|
||||
|
||||
return {
|
||||
theme: computed(() => theme.value),
|
||||
isDark: computed(() => theme.value === 'dark'),
|
||||
toggleTheme,
|
||||
setLightTheme,
|
||||
setDarkTheme,
|
||||
initTheme
|
||||
}
|
||||
}
|
||||
|
||||
// 导出初始化函数(在 main.js 中使用)
|
||||
export { initTheme }
|
||||
Reference in New Issue
Block a user