first commit
This commit is contained in:
23
src/hooks/app/equipmentName.ts
Normal file
23
src/hooks/app/equipmentName.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ref } from 'vue'
|
||||
import type { EquipmentQuery } from '@/apis/equipment/equipment'
|
||||
import { getEquipmentNameList } from '@/apis/equipment/equipment'
|
||||
import type { LabelValueState } from '@/types/global'
|
||||
|
||||
/** 设备名称列表 */
|
||||
export function equipmentName(query: EquipmentQuery, options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const equipmentNameList = ref<LabelValueState[]>([])
|
||||
|
||||
const getEquipmentName = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await getEquipmentNameList(query)
|
||||
equipmentNameList.value = res.data
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { equipmentNameList, getEquipmentName, loading }
|
||||
}
|
||||
9
src/hooks/app/index.ts
Normal file
9
src/hooks/app/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from './useMenu'
|
||||
export * from './useDept'
|
||||
export * from './useBranch'
|
||||
export * from './useRole'
|
||||
export * from './useDict'
|
||||
export * from './space'
|
||||
export * from './point'
|
||||
export * from './equipmentName'
|
||||
export * from './productName'
|
||||
22
src/hooks/app/peopleName.ts
Normal file
22
src/hooks/app/peopleName.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import type { LabelValueState } from '@/types/global'
|
||||
import { type PeopleQuery, getPeopleNameList } from '@/apis/sysPeople/people'
|
||||
|
||||
/** 设备名称列表 */
|
||||
export function peopleName(query: PeopleQuery, options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const peopleNameList = ref<LabelValueState[]>([])
|
||||
|
||||
const getPeopleName = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await getPeopleNameList(query)
|
||||
peopleNameList.value = res.data
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { peopleNameList, getPeopleName, loading }
|
||||
}
|
||||
22
src/hooks/app/point.ts
Normal file
22
src/hooks/app/point.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import type { TreeNodeData } from '@arco-design/web-vue'
|
||||
import { listPointTree } from '@/apis'
|
||||
|
||||
/** 部门模块 */
|
||||
export function point(options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const pointList = ref<TreeNodeData[]>([])
|
||||
|
||||
const getPointList = async (name?: string) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await listPointTree({ description: name })
|
||||
pointList.value = res.data
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { pointList, getPointList, loading }
|
||||
}
|
||||
22
src/hooks/app/productName.ts
Normal file
22
src/hooks/app/productName.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import { getProductNameList } from '@/apis/equipment/product'
|
||||
import type { LabelValueState } from '@/types/global'
|
||||
|
||||
/** 设备名称列表 */
|
||||
export function productName(options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const productNameList = ref<LabelValueState[]>([])
|
||||
|
||||
const getProductName = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await getProductNameList()
|
||||
productNameList.value = res.data
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { productNameList, getProductName, loading }
|
||||
}
|
||||
22
src/hooks/app/ruleName.ts
Normal file
22
src/hooks/app/ruleName.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import type { LabelValueState } from '@/types/global'
|
||||
import { getRuleNameList } from '@/apis/rule/rule'
|
||||
|
||||
/** 设备名称列表 */
|
||||
export function ruleName(options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const ruleNameList = ref<LabelValueState[]>([])
|
||||
|
||||
const getRuleName = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await getRuleNameList()
|
||||
ruleNameList.value = res.data
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { ruleNameList, getRuleName, loading }
|
||||
}
|
||||
22
src/hooks/app/space.ts
Normal file
22
src/hooks/app/space.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import type { TreeNodeData } from '@arco-design/web-vue'
|
||||
import { listSpaceTree } from '@/apis'
|
||||
|
||||
/** 部门模块 */
|
||||
export function space(options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const spaceList = ref<TreeNodeData[]>([])
|
||||
|
||||
const getSpaceList = async (name?: string) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await listSpaceTree({ description: name })
|
||||
spaceList.value = res.data
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { spaceList, getSpaceList, loading }
|
||||
}
|
||||
22
src/hooks/app/useBranch.ts
Normal file
22
src/hooks/app/useBranch.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import type { TreeNodeData } from '@arco-design/web-vue'
|
||||
import { listBranchTree } from '@/apis'
|
||||
|
||||
/** 部门模块 */
|
||||
export function useBranch(options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const branchList = ref<TreeNodeData[]>([])
|
||||
|
||||
const getBranchList = async (name?: string) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await listBranchTree({ description: name })
|
||||
branchList.value = res.data
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { branchList, getBranchList, loading }
|
||||
}
|
||||
22
src/hooks/app/useDept.ts
Normal file
22
src/hooks/app/useDept.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import type { TreeNodeData } from '@arco-design/web-vue'
|
||||
import { listDeptTree } from '@/apis'
|
||||
|
||||
/** 部门模块 */
|
||||
export function useDept(options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const deptList = ref<TreeNodeData[]>([])
|
||||
|
||||
const getDeptList = async (name?: string) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await listDeptTree({ description: name })
|
||||
deptList.value = res.data
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { deptList, getDeptList, loading }
|
||||
}
|
||||
43
src/hooks/app/useDict.ts
Normal file
43
src/hooks/app/useDict.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ref, toRefs } from 'vue'
|
||||
import { listCommonDict } from '@/apis'
|
||||
import { useDictStore } from '@/stores'
|
||||
|
||||
const pendingRequests = new Map<string, Promise<any>>()
|
||||
|
||||
export function useDict(...codes: string[]) {
|
||||
const dictStore = useDictStore()
|
||||
const dictData = ref<Record<string, App.DictItem[]>>({})
|
||||
|
||||
codes.forEach(async (code) => {
|
||||
dictData.value[code] = []
|
||||
|
||||
const cached = dictStore.getDict(code)
|
||||
if (cached) {
|
||||
dictData.value[code] = cached
|
||||
return
|
||||
}
|
||||
|
||||
if (!pendingRequests.has(code)) {
|
||||
const request = listCommonDict(code)
|
||||
.then(({ data }) => {
|
||||
dictStore.setDict(code, data)
|
||||
return data
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Failed to load dict: ${code}`, error)
|
||||
return []
|
||||
})
|
||||
.finally(() => {
|
||||
pendingRequests.delete(code)
|
||||
})
|
||||
|
||||
pendingRequests.set(code, request)
|
||||
}
|
||||
|
||||
pendingRequests.get(code)!.then((data) => {
|
||||
dictData.value[code] = data
|
||||
})
|
||||
})
|
||||
|
||||
return toRefs(dictData.value)
|
||||
}
|
||||
21
src/hooks/app/useMenu.ts
Normal file
21
src/hooks/app/useMenu.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ref } from 'vue'
|
||||
import type { TreeNodeData } from '@arco-design/web-vue'
|
||||
import { listMenuTree } from '@/apis'
|
||||
|
||||
/** 菜单模块 */
|
||||
export function useMenu(options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const menuList = ref<TreeNodeData[]>([])
|
||||
|
||||
const getMenuList = async (name?: string) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await listMenuTree({ description: name })
|
||||
menuList.value = res.data
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { menuList, getMenuList, loading }
|
||||
}
|
||||
22
src/hooks/app/useRole.ts
Normal file
22
src/hooks/app/useRole.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import { listRoleDict } from '@/apis'
|
||||
import type { LabelValueState } from '@/types/global'
|
||||
|
||||
/** 角色模块 */
|
||||
export function useRole(options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const roleList = ref<LabelValueState[]>([])
|
||||
|
||||
const getRoleList = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await listRoleDict()
|
||||
roleList.value = res.data
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { roleList, getRoleList, loading }
|
||||
}
|
||||
9
src/hooks/index.ts
Normal file
9
src/hooks/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from './modules/useLoading'
|
||||
export * from './modules/usePagination'
|
||||
export * from './modules/useRequest'
|
||||
export * from './modules/useChart'
|
||||
export * from './modules/useTable'
|
||||
export * from './modules/useDevice'
|
||||
export * from './modules/useBreakpoint'
|
||||
export * from './modules/useDownload'
|
||||
export * from './modules/useResetReactive'
|
||||
24
src/hooks/modules/useBreakpoint.ts
Normal file
24
src/hooks/modules/useBreakpoint.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { type ComputedRef, computed } from 'vue'
|
||||
import { useBreakpoints } from '@vueuse/core'
|
||||
import type { ColProps } from '@arco-design/web-vue'
|
||||
|
||||
type ColBreakpoint = Pick<ColProps, 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'>
|
||||
type Breakpoint = keyof ColBreakpoint
|
||||
|
||||
export function useBreakpoint() {
|
||||
const breakpoints = useBreakpoints({
|
||||
xs: 576, // <576
|
||||
sm: 576, // >= 576
|
||||
md: 768, // >=768
|
||||
lg: 992, // >=992
|
||||
xl: 1200, // >=1200
|
||||
xxl: 1600, // >=1600
|
||||
})
|
||||
|
||||
const arr = breakpoints.current() as ComputedRef<Breakpoint[]>
|
||||
const breakpoint = computed(() => {
|
||||
return arr.value.length ? arr.value[arr.value.length - 1] : 'xs'
|
||||
})
|
||||
|
||||
return { breakpoint }
|
||||
}
|
||||
26
src/hooks/modules/useChart.ts
Normal file
26
src/hooks/modules/useChart.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { computed } from 'vue'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { useAppStore } from '@/stores'
|
||||
|
||||
// 获取代码提示
|
||||
// 从'echarts'中导入{ SeriesOption };
|
||||
// 因为配置项太多,这提供了一个相对方便的代码提示。
|
||||
// 当使用vue时,注意反应性问题。需要保证对应的函数可以被触发,TypeScript不会报错,代码编写方便。
|
||||
|
||||
interface optionsFn {
|
||||
(isDark: boolean): EChartsOption
|
||||
}
|
||||
|
||||
export function useChart(sourceOption: optionsFn) {
|
||||
const appStore = useAppStore()
|
||||
const isDark = computed(() => appStore.theme === 'dark')
|
||||
|
||||
// echarts support https://echarts.apache.org/zh/theme-builder.html
|
||||
// 这里不使用
|
||||
// TODO 图表主题
|
||||
const chartOption = computed<EChartsOption>(() => {
|
||||
return sourceOption(isDark.value)
|
||||
})
|
||||
|
||||
return { chartOption }
|
||||
}
|
||||
36
src/hooks/modules/useComponentPaths.ts
Normal file
36
src/hooks/modules/useComponentPaths.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
|
||||
interface ComponentOption {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export const useComponentPaths = () => {
|
||||
const componentOptions = ref<ComponentOption[]>([])
|
||||
|
||||
const loadComponentPaths = async () => {
|
||||
try {
|
||||
const modules = import.meta.glob('@/views/**/index.vue')
|
||||
const paths = Object.keys(modules)
|
||||
componentOptions.value = paths.map((path) => {
|
||||
// 格式转化
|
||||
path = path.replace('/src/views/', '')
|
||||
const label = `@view/${path}`
|
||||
const value = path.split('.vue')[0]
|
||||
return { label, value }
|
||||
})
|
||||
} catch (error) {
|
||||
Message.error('加载组件路径失败')
|
||||
console.error('加载组件路径失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadComponentPaths()
|
||||
})
|
||||
|
||||
return {
|
||||
componentOptions,
|
||||
}
|
||||
}
|
||||
17
src/hooks/modules/useDevice.ts
Normal file
17
src/hooks/modules/useDevice.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { computed } from 'vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
|
||||
/**
|
||||
* 响应式布局容器固定宽度
|
||||
*
|
||||
* 大屏(>=1200px)
|
||||
* 中屏(>=992px)
|
||||
* 小屏(>=768px)
|
||||
*/
|
||||
export function useDevice() {
|
||||
const { width } = useWindowSize()
|
||||
const isDesktop = computed(() => width.value > 571)
|
||||
const isMobile = computed(() => !isDesktop.value)
|
||||
|
||||
return { isMobile, isDesktop }
|
||||
}
|
||||
49
src/hooks/modules/useDownload.ts
Normal file
49
src/hooks/modules/useDownload.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Message, Notification } from '@arco-design/web-vue'
|
||||
/**
|
||||
* @description 接收数据流生成 blob,创建链接,下载文件
|
||||
* @param {Function} api 导出表格的api方法 (必传)
|
||||
* @param {string} tempName 导出的文件名 (必传)
|
||||
* @param {object} params 导出的参数 (默认{})
|
||||
* @param {boolean} isNotify 是否有导出消息提示 (默认为 true)
|
||||
* @param {string} fileType 导出的文件格式 (默认为.xlsx)
|
||||
*/
|
||||
interface NavigatorWithMsSaveOrOpenBlob extends Navigator {
|
||||
msSaveOrOpenBlob: (blob: Blob, fileName: string) => void
|
||||
}
|
||||
export const useDownload = async (api: () => Promise<any>, isNotify = false, tempName = '', fileType = '.xlsx') => {
|
||||
try {
|
||||
const res = await api()
|
||||
if (res.headers['content-disposition']) {
|
||||
tempName = decodeURI(res.headers['content-disposition'].split(';')[1].split('=')[1])
|
||||
} else {
|
||||
tempName = tempName || new Date().getTime() + fileType
|
||||
}
|
||||
if (isNotify && !res?.code) {
|
||||
Notification.warning({
|
||||
title: '温馨提示',
|
||||
content: '如果数据庞大会导致下载缓慢哦,请您耐心等待!',
|
||||
})
|
||||
}
|
||||
if (res.status !== 200 || res.data == null || !(res.data instanceof Blob)) {
|
||||
Message.error('导出失败,请稍后再试!')
|
||||
return
|
||||
}
|
||||
const blob = new Blob([res.data])
|
||||
// 兼容 edge 不支持 createObjectURL 方法
|
||||
if ('msSaveOrOpenBlob' in (navigator as unknown as NavigatorWithMsSaveOrOpenBlob)) {
|
||||
;(window.navigator as unknown as NavigatorWithMsSaveOrOpenBlob).msSaveOrOpenBlob(blob, tempName + fileType)
|
||||
}
|
||||
const blobUrl = window.URL.createObjectURL(blob)
|
||||
const exportFile = document.createElement('a')
|
||||
exportFile.style.display = 'none'
|
||||
exportFile.download = tempName
|
||||
exportFile.href = blobUrl
|
||||
document.body.appendChild(exportFile)
|
||||
exportFile.click()
|
||||
// 去除下载对 url 的影响
|
||||
document.body.removeChild(exportFile)
|
||||
window.URL.revokeObjectURL(blobUrl)
|
||||
} catch (error) {
|
||||
// console.log(error)
|
||||
}
|
||||
}
|
||||
19
src/hooks/modules/useLoading.ts
Normal file
19
src/hooks/modules/useLoading.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export function useLoading(initValue = false) {
|
||||
const loading = ref(initValue)
|
||||
|
||||
const setLoading = (value: boolean) => {
|
||||
loading.value = value
|
||||
}
|
||||
|
||||
const toggle = () => {
|
||||
loading.value = !loading.value
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
setLoading,
|
||||
toggle,
|
||||
}
|
||||
}
|
||||
59
src/hooks/modules/usePagination.ts
Normal file
59
src/hooks/modules/usePagination.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { reactive, toRefs, watch } from 'vue'
|
||||
import { useBreakpoint } from '@/hooks'
|
||||
|
||||
type Callback = () => void
|
||||
|
||||
export interface Options {
|
||||
defaultPageSize: number
|
||||
defaultSizeOptions: number[]
|
||||
}
|
||||
|
||||
export function usePagination(callback: Callback, options: Options = { defaultPageSize: 10, defaultSizeOptions: [10, 20, 30, 40, 50] }) {
|
||||
const { breakpoint } = useBreakpoint()
|
||||
|
||||
const pagination = reactive({
|
||||
showPageSize: true,
|
||||
showTotal: true,
|
||||
current: 1,
|
||||
pageSize: options.defaultPageSize,
|
||||
pageSizeOptions: options.defaultSizeOptions,
|
||||
total: 0,
|
||||
simple: false,
|
||||
onChange: (size: number) => {
|
||||
pagination.current = size
|
||||
callback && callback()
|
||||
},
|
||||
onPageSizeChange: (size: number) => {
|
||||
pagination.current = 1
|
||||
pagination.pageSize = size
|
||||
callback && callback()
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => breakpoint.value,
|
||||
() => {
|
||||
pagination.simple = ['xs'].includes(breakpoint.value)
|
||||
pagination.showTotal = !['xs'].includes(breakpoint.value)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const changeCurrent = pagination.onChange
|
||||
const changePageSize = pagination.onPageSizeChange
|
||||
function setTotal(value: number) {
|
||||
pagination.total = value
|
||||
}
|
||||
|
||||
const { current, pageSize, total } = toRefs(pagination)
|
||||
|
||||
return {
|
||||
current,
|
||||
pageSize,
|
||||
total,
|
||||
pagination,
|
||||
changeCurrent,
|
||||
changePageSize,
|
||||
setTotal,
|
||||
}
|
||||
}
|
||||
20
src/hooks/modules/useRequest.ts
Normal file
20
src/hooks/modules/useRequest.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { type UnwrapRef, ref } from 'vue'
|
||||
import type { AxiosResponse } from 'axios'
|
||||
import { useLoading } from '@/hooks'
|
||||
|
||||
export function useRequest<T>(
|
||||
api: () => Promise<AxiosResponse<ApiRes<T>>>,
|
||||
defaultValue = [] as unknown as T,
|
||||
isLoading = true,
|
||||
) {
|
||||
const { loading, setLoading } = useLoading(isLoading)
|
||||
const response = ref<T>(defaultValue)
|
||||
api()
|
||||
.then((res) => {
|
||||
response.value = res.data as unknown as UnwrapRef<T>
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
return { loading, response }
|
||||
}
|
||||
15
src/hooks/modules/useResetReactive.ts
Normal file
15
src/hooks/modules/useResetReactive.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { reactive } from 'vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
export function useResetReactive<T extends object>(value: T) {
|
||||
const getInitValue = () => cloneDeep(value)
|
||||
|
||||
const state = reactive(getInitValue())
|
||||
|
||||
const reset = () => {
|
||||
Object.keys(state).forEach((key) => delete state[key])
|
||||
Object.assign(state, getInitValue())
|
||||
}
|
||||
|
||||
return [state, reset] as const
|
||||
}
|
||||
127
src/hooks/modules/useTable.ts
Normal file
127
src/hooks/modules/useTable.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import type { TableData, TableInstance } from '@arco-design/web-vue'
|
||||
import { Message, Modal } from '@arco-design/web-vue'
|
||||
import type { Options as paginationOptions } from './usePagination'
|
||||
import { useBreakpoint, usePagination } from '@/hooks'
|
||||
|
||||
interface Options<T, U> {
|
||||
formatResult?: (data: T[]) => U[]
|
||||
onSuccess?: () => void
|
||||
immediate?: boolean
|
||||
rowKey?: keyof T
|
||||
paginationOption?: paginationOptions
|
||||
}
|
||||
|
||||
interface PaginationParams { page: number, size: number }
|
||||
type Api<T> = (params: PaginationParams) => Promise<ApiRes<PageRes<T[]>>> | Promise<ApiRes<T[]>>
|
||||
|
||||
export function useTable<T extends U, U = T>(api: Api<T>, options?: Options<T, U>) {
|
||||
const { formatResult, onSuccess, immediate, rowKey } = options || {}
|
||||
const { pagination, setTotal } = usePagination(() => getTableData(), options?.paginationOption)
|
||||
const loading = ref(false)
|
||||
const tableData: Ref<U[]> = ref([])
|
||||
|
||||
async function getTableData() {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await api({ page: pagination.current, size: pagination.pageSize })
|
||||
const data = !Array.isArray(res.data) ? res.data.list : res.data
|
||||
tableData.value = formatResult ? formatResult(data) : data
|
||||
const total = !Array.isArray(res.data) ? res.data.total : data.length
|
||||
setTotal(total)
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
onSuccess && onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 是否立即触发
|
||||
const isImmediate = immediate ?? true
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
isImmediate && getTableData()
|
||||
|
||||
// 多选
|
||||
const selectedKeys = ref<(string | number)[]>([])
|
||||
const select: TableInstance['onSelect'] = (rowKeys) => {
|
||||
selectedKeys.value = rowKeys
|
||||
}
|
||||
|
||||
// 全选
|
||||
const selectAll: TableInstance['onSelectAll'] = (checked) => {
|
||||
const key = rowKey ?? 'id'
|
||||
const arr = (tableData.value as TableData[]).filter((i) => !(i?.disabled ?? false))
|
||||
selectedKeys.value = checked ? arr.map((i) => i[key as string]) : []
|
||||
}
|
||||
|
||||
// 查询
|
||||
const search = () => {
|
||||
selectedKeys.value = []
|
||||
pagination.onChange(1)
|
||||
}
|
||||
|
||||
// 刷新
|
||||
const refresh = () => {
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = async <T>(
|
||||
deleteApi: () => Promise<ApiRes<T>>,
|
||||
options?: { title?: string, content?: string, successTip?: string, showModal?: boolean },
|
||||
): Promise<boolean | undefined> => {
|
||||
const onDelete = async () => {
|
||||
try {
|
||||
const res = await deleteApi()
|
||||
if (res.success) {
|
||||
Message.success(options?.successTip || '删除成功')
|
||||
selectedKeys.value = []
|
||||
await getTableData()
|
||||
}
|
||||
return res.success
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
const flag = options?.showModal ?? true // 是否显示对话框
|
||||
if (!flag) {
|
||||
return onDelete()
|
||||
}
|
||||
Modal.warning({
|
||||
title: options?.title || '提示',
|
||||
content: options?.content || '是否确定删除该条数据?',
|
||||
okButtonProps: { status: 'danger' },
|
||||
hideCancel: false,
|
||||
maskClosable: false,
|
||||
onBeforeOk: onDelete,
|
||||
})
|
||||
}
|
||||
|
||||
const { breakpoint } = useBreakpoint()
|
||||
// 表格操作列在小屏下不固定在右侧
|
||||
const fixed = computed(() => !['xs', 'sm'].includes(breakpoint.value) ? 'right' : undefined)
|
||||
|
||||
return {
|
||||
/** 表格加载状态 */
|
||||
loading,
|
||||
/** 表格数据 */
|
||||
tableData,
|
||||
/** 获取表格数据 */
|
||||
getTableData,
|
||||
/** 搜索,页码会重置为1 */
|
||||
search,
|
||||
/** 分页的传参 */
|
||||
pagination,
|
||||
/** 选择的行keys */
|
||||
selectedKeys,
|
||||
/** 选择行 */
|
||||
select,
|
||||
/** 全选行 */
|
||||
selectAll,
|
||||
/** 处理删除、批量删除 */
|
||||
handleDelete,
|
||||
/** 刷新表格数据,页码会缓存 */
|
||||
refresh,
|
||||
/** 操作列在小屏场景下不固定在右侧 */
|
||||
fixed,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user