chore: init read-star monorepo
This commit is contained in:
21
apps/admin/src/hooks/business/auth.ts
Normal file
21
apps/admin/src/hooks/business/auth.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { useAuthStore } from '@/store/modules/auth'
|
||||
|
||||
export function useAuth() {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
function hasAuth(codes: string | string[]) {
|
||||
if (!authStore.isLogin) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof codes === 'string') {
|
||||
return authStore.userInfo.buttons.includes(codes)
|
||||
}
|
||||
|
||||
return codes.some(code => authStore.userInfo.buttons.includes(code))
|
||||
}
|
||||
|
||||
return {
|
||||
hasAuth,
|
||||
}
|
||||
}
|
||||
71
apps/admin/src/hooks/business/captcha.ts
Normal file
71
apps/admin/src/hooks/business/captcha.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { useCountDown, useLoading } from '@sa/hooks'
|
||||
import { computed } from 'vue'
|
||||
import { REG_PHONE } from '@/constants/reg'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
export function useCaptcha() {
|
||||
const { loading, startLoading, endLoading } = useLoading()
|
||||
const { count, start, stop, isCounting } = useCountDown(10)
|
||||
|
||||
const label = computed(() => {
|
||||
let text = $t('page.login.codeLogin.getCode')
|
||||
|
||||
const countingLabel = $t('page.login.codeLogin.reGetCode', { time: count.value })
|
||||
|
||||
if (loading.value) {
|
||||
text = ''
|
||||
}
|
||||
|
||||
if (isCounting.value) {
|
||||
text = countingLabel
|
||||
}
|
||||
|
||||
return text
|
||||
})
|
||||
|
||||
function isPhoneValid(phone: string) {
|
||||
if (phone.trim() === '') {
|
||||
window.$message?.error?.($t('form.phone.required'))
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (!REG_PHONE.test(phone)) {
|
||||
window.$message?.error?.($t('form.phone.invalid'))
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async function getCaptcha(phone: string) {
|
||||
const valid = isPhoneValid(phone)
|
||||
|
||||
if (!valid || loading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
startLoading()
|
||||
|
||||
// request
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 500)
|
||||
})
|
||||
|
||||
window.$message?.success?.($t('page.login.codeLogin.sendCodeSuccess'))
|
||||
|
||||
start()
|
||||
|
||||
endLoading()
|
||||
}
|
||||
|
||||
return {
|
||||
label,
|
||||
start,
|
||||
stop,
|
||||
isCounting,
|
||||
loading,
|
||||
getCaptcha,
|
||||
}
|
||||
}
|
||||
233
apps/admin/src/hooks/common/echarts.ts
Normal file
233
apps/admin/src/hooks/common/echarts.ts
Normal file
@ -0,0 +1,233 @@
|
||||
import type {
|
||||
BarSeriesOption,
|
||||
GaugeSeriesOption,
|
||||
LineSeriesOption,
|
||||
PictorialBarSeriesOption,
|
||||
PieSeriesOption,
|
||||
RadarSeriesOption,
|
||||
ScatterSeriesOption,
|
||||
} from 'echarts/charts'
|
||||
import type {
|
||||
DatasetComponentOption,
|
||||
GridComponentOption,
|
||||
LegendComponentOption,
|
||||
TitleComponentOption,
|
||||
ToolboxComponentOption,
|
||||
TooltipComponentOption,
|
||||
} from 'echarts/components'
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts'
|
||||
import {
|
||||
DatasetComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
ToolboxComponent,
|
||||
TooltipComponent,
|
||||
TransformComponent,
|
||||
} from 'echarts/components'
|
||||
import * as echarts from 'echarts/core'
|
||||
import { LabelLayout, UniversalTransition } from 'echarts/features'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { computed, effectScope, nextTick, onScopeDispose, shallowRef, watch } from 'vue'
|
||||
import { useThemeStore } from '@/store/modules/theme'
|
||||
|
||||
export type ECOption = echarts.ComposeOption<
|
||||
| BarSeriesOption
|
||||
| LineSeriesOption
|
||||
| PieSeriesOption
|
||||
| ScatterSeriesOption
|
||||
| PictorialBarSeriesOption
|
||||
| RadarSeriesOption
|
||||
| GaugeSeriesOption
|
||||
| TitleComponentOption
|
||||
| LegendComponentOption
|
||||
| TooltipComponentOption
|
||||
| GridComponentOption
|
||||
| ToolboxComponentOption
|
||||
| DatasetComponentOption
|
||||
>
|
||||
|
||||
echarts.use([
|
||||
TitleComponent,
|
||||
LegendComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
DatasetComponent,
|
||||
TransformComponent,
|
||||
ToolboxComponent,
|
||||
BarChart,
|
||||
LineChart,
|
||||
PieChart,
|
||||
ScatterChart,
|
||||
PictorialBarChart,
|
||||
RadarChart,
|
||||
GaugeChart,
|
||||
LabelLayout,
|
||||
UniversalTransition,
|
||||
CanvasRenderer,
|
||||
])
|
||||
|
||||
interface ChartHooks {
|
||||
onRender?: (chart: echarts.ECharts) => void | Promise<void>
|
||||
onUpdated?: (chart: echarts.ECharts) => void | Promise<void>
|
||||
onDestroy?: (chart: echarts.ECharts) => void | Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* use echarts
|
||||
*
|
||||
* @param optionsFactory echarts options factory function
|
||||
* @param darkMode dark mode
|
||||
*/
|
||||
export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: ChartHooks = {}) {
|
||||
const scope = effectScope()
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
const darkMode = computed(() => themeStore.darkMode)
|
||||
|
||||
const domRef = shallowRef<HTMLElement | null>(null)
|
||||
const initialSize = { width: 0, height: 0 }
|
||||
const { width, height } = useElementSize(domRef, initialSize)
|
||||
|
||||
const chart = shallowRef<echarts.ECharts | null>(null)
|
||||
const chartOptions: T = optionsFactory()
|
||||
|
||||
const {
|
||||
onRender = (instance) => {
|
||||
const textColor = darkMode.value ? 'rgb(224, 224, 224)' : 'rgb(31, 31, 31)'
|
||||
const maskColor = darkMode.value ? 'rgba(0, 0, 0, 0.4)' : 'rgba(255, 255, 255, 0.8)'
|
||||
|
||||
instance.showLoading({
|
||||
color: themeStore.themeColor,
|
||||
textColor,
|
||||
fontSize: 14,
|
||||
maskColor,
|
||||
})
|
||||
},
|
||||
onUpdated = (instance) => {
|
||||
instance.hideLoading()
|
||||
},
|
||||
onDestroy,
|
||||
} = hooks
|
||||
|
||||
/** is chart rendered */
|
||||
function isRendered() {
|
||||
return Boolean(domRef.value && chart.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* update chart options
|
||||
*
|
||||
* @param callback callback function
|
||||
*/
|
||||
async function updateOptions(callback: (opts: T, optsFactory: () => T) => ECOption = () => chartOptions) {
|
||||
const updatedOpts = callback(chartOptions, optionsFactory)
|
||||
|
||||
Object.assign(chartOptions, updatedOpts)
|
||||
|
||||
await nextTick()
|
||||
|
||||
if (!isRendered())
|
||||
return
|
||||
|
||||
if (isRendered()) {
|
||||
chart.value?.clear()
|
||||
}
|
||||
|
||||
chart.value?.setOption({ ...updatedOpts, backgroundColor: 'transparent' })
|
||||
|
||||
await onUpdated?.(chart.value!)
|
||||
}
|
||||
|
||||
function setOptions(options: T) {
|
||||
chart.value?.setOption(options)
|
||||
}
|
||||
|
||||
/** render chart */
|
||||
async function render() {
|
||||
if (isRendered())
|
||||
return
|
||||
|
||||
const chartTheme = darkMode.value ? 'dark' : 'light'
|
||||
|
||||
chart.value = echarts.init(domRef.value, chartTheme)
|
||||
|
||||
chart.value?.setOption({ ...chartOptions, backgroundColor: 'transparent' })
|
||||
|
||||
await onRender?.(chart.value!)
|
||||
}
|
||||
|
||||
/** resize chart */
|
||||
function resize() {
|
||||
chart.value?.resize()
|
||||
}
|
||||
|
||||
/** destroy chart */
|
||||
async function destroy() {
|
||||
if (!chart.value)
|
||||
return
|
||||
|
||||
await onDestroy?.(chart.value)
|
||||
chart.value?.dispose()
|
||||
chart.value = null
|
||||
}
|
||||
|
||||
/** change chart theme */
|
||||
async function changeTheme() {
|
||||
await destroy()
|
||||
await render()
|
||||
await onUpdated?.(chart.value!)
|
||||
}
|
||||
|
||||
/**
|
||||
* render chart by size
|
||||
*
|
||||
* @param w width
|
||||
* @param h height
|
||||
*/
|
||||
async function renderChartBySize(w: number, h: number) {
|
||||
initialSize.width = w
|
||||
initialSize.height = h
|
||||
|
||||
// resize chart
|
||||
if (isRendered()) {
|
||||
resize()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// render chart
|
||||
await render()
|
||||
|
||||
if (chart.value) {
|
||||
await onUpdated?.(chart.value)
|
||||
}
|
||||
}
|
||||
|
||||
scope.run(() => {
|
||||
watch(
|
||||
[width, height],
|
||||
([newWidth, newHeight]) => {
|
||||
renderChartBySize(newWidth, newHeight)
|
||||
},
|
||||
{ flush: 'post' },
|
||||
)
|
||||
|
||||
watch(darkMode, () => {
|
||||
changeTheme()
|
||||
})
|
||||
})
|
||||
|
||||
onScopeDispose(() => {
|
||||
destroy()
|
||||
scope.stop()
|
||||
})
|
||||
|
||||
return {
|
||||
domRef,
|
||||
chart,
|
||||
updateOptions,
|
||||
setOptions,
|
||||
}
|
||||
}
|
||||
97
apps/admin/src/hooks/common/form.ts
Normal file
97
apps/admin/src/hooks/common/form.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import type { FormInst } from 'naive-ui'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import { ref, toValue } from 'vue'
|
||||
import { REG_CODE_SIX, REG_EMAIL, REG_PHONE, REG_PWD, REG_USER_NAME } from '@/constants/reg'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
export function useFormRules() {
|
||||
const patternRules = {
|
||||
userName: {
|
||||
pattern: REG_USER_NAME,
|
||||
message: $t('form.userName.invalid'),
|
||||
trigger: 'change',
|
||||
},
|
||||
phone: {
|
||||
pattern: REG_PHONE,
|
||||
message: $t('form.phone.invalid'),
|
||||
trigger: 'change',
|
||||
},
|
||||
pwd: {
|
||||
pattern: REG_PWD,
|
||||
message: $t('form.pwd.invalid'),
|
||||
trigger: 'change',
|
||||
},
|
||||
code: {
|
||||
pattern: REG_CODE_SIX,
|
||||
message: $t('form.code.invalid'),
|
||||
trigger: 'change',
|
||||
},
|
||||
email: {
|
||||
pattern: REG_EMAIL,
|
||||
message: $t('form.email.invalid'),
|
||||
trigger: 'change',
|
||||
},
|
||||
} satisfies Record<string, App.Global.FormRule>
|
||||
|
||||
const formRules = {
|
||||
userName: [createRequiredRule($t('form.userName.required')), patternRules.userName],
|
||||
phone: [createRequiredRule($t('form.phone.required')), patternRules.phone],
|
||||
pwd: [createRequiredRule($t('form.pwd.required')), patternRules.pwd],
|
||||
code: [createRequiredRule($t('form.code.required')), patternRules.code],
|
||||
email: [createRequiredRule($t('form.email.required')), patternRules.email],
|
||||
} satisfies Record<string, App.Global.FormRule[]>
|
||||
|
||||
/** the default required rule */
|
||||
const defaultRequiredRule = createRequiredRule($t('form.required'))
|
||||
|
||||
function createRequiredRule(message: string): App.Global.FormRule {
|
||||
return {
|
||||
required: true,
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
/** create a rule for confirming the password */
|
||||
function createConfirmPwdRule(pwd: string | Ref<string> | ComputedRef<string>) {
|
||||
const confirmPwdRule: App.Global.FormRule[] = [
|
||||
{ required: true, message: $t('form.confirmPwd.required') },
|
||||
{
|
||||
asyncValidator: (rule, value) => {
|
||||
if (value.trim() !== '' && value !== toValue(pwd)) {
|
||||
return Promise.reject(rule.message)
|
||||
}
|
||||
return Promise.resolve()
|
||||
},
|
||||
message: $t('form.confirmPwd.invalid'),
|
||||
trigger: 'input',
|
||||
},
|
||||
]
|
||||
return confirmPwdRule
|
||||
}
|
||||
|
||||
return {
|
||||
patternRules,
|
||||
formRules,
|
||||
defaultRequiredRule,
|
||||
createRequiredRule,
|
||||
createConfirmPwdRule,
|
||||
}
|
||||
}
|
||||
|
||||
export function useNaiveForm() {
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
|
||||
async function validate() {
|
||||
await formRef.value?.validate()
|
||||
}
|
||||
|
||||
async function restoreValidation() {
|
||||
formRef.value?.restoreValidation()
|
||||
}
|
||||
|
||||
return {
|
||||
formRef,
|
||||
validate,
|
||||
restoreValidation,
|
||||
}
|
||||
}
|
||||
10
apps/admin/src/hooks/common/icon.ts
Normal file
10
apps/admin/src/hooks/common/icon.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { useSvgIconRender } from '@sa/hooks'
|
||||
import SvgIcon from '@/components/custom/svg-icon.vue'
|
||||
|
||||
export function useSvgIcon() {
|
||||
const { SvgIconVNode } = useSvgIconRender(SvgIcon)
|
||||
|
||||
return {
|
||||
SvgIconVNode,
|
||||
}
|
||||
}
|
||||
116
apps/admin/src/hooks/common/router.ts
Normal file
116
apps/admin/src/hooks/common/router.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import type { RouteKey } from '@elegant-router/types'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { router as globalRouter } from '@/router'
|
||||
|
||||
/**
|
||||
* Router push
|
||||
*
|
||||
* Jump to the specified route, it can replace function router.push
|
||||
*
|
||||
* @param inSetup Whether is in vue script setup
|
||||
*/
|
||||
export function useRouterPush(inSetup = true) {
|
||||
const router = inSetup ? useRouter() : globalRouter
|
||||
const route = globalRouter.currentRoute
|
||||
|
||||
const routerPush = router.push
|
||||
|
||||
const routerBack = router.back
|
||||
|
||||
async function routerPushByKey(key: RouteKey, options?: App.Global.RouterPushOptions) {
|
||||
const { query, params } = options || {}
|
||||
|
||||
const routeLocation: RouteLocationRaw = {
|
||||
name: key,
|
||||
}
|
||||
|
||||
if (Object.keys(query || {}).length) {
|
||||
routeLocation.query = query
|
||||
}
|
||||
|
||||
if (Object.keys(params || {}).length) {
|
||||
routeLocation.params = params
|
||||
}
|
||||
|
||||
return routerPush(routeLocation)
|
||||
}
|
||||
|
||||
function routerPushByKeyWithMetaQuery(key: RouteKey) {
|
||||
const allRoutes = router.getRoutes()
|
||||
const meta = allRoutes.find(item => item.name === key)?.meta || null
|
||||
|
||||
const query: Record<string, string> = {}
|
||||
|
||||
meta?.query?.forEach((item) => {
|
||||
query[item.key] = item.value
|
||||
})
|
||||
|
||||
return routerPushByKey(key, { query })
|
||||
}
|
||||
|
||||
async function toHome() {
|
||||
return routerPushByKey('root')
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to login page
|
||||
*
|
||||
* @param loginModule The login module
|
||||
* @param redirectUrl The redirect url, if not specified, it will be the current route fullPath
|
||||
*/
|
||||
async function toLogin(loginModule?: UnionKey.LoginModule, redirectUrl?: string) {
|
||||
const module = loginModule || 'pwd-login'
|
||||
|
||||
const options: App.Global.RouterPushOptions = {
|
||||
params: {
|
||||
module,
|
||||
},
|
||||
}
|
||||
|
||||
const redirect = redirectUrl || route.value.fullPath
|
||||
|
||||
options.query = {
|
||||
redirect,
|
||||
}
|
||||
|
||||
return routerPushByKey('login', options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle login module
|
||||
*
|
||||
* @param module
|
||||
*/
|
||||
async function toggleLoginModule(module: UnionKey.LoginModule) {
|
||||
const query = route.value.query as Record<string, string>
|
||||
|
||||
return routerPushByKey('login', { query, params: { module } })
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect from login
|
||||
*
|
||||
* @param [needRedirect] Whether to redirect after login. Default is `true`
|
||||
*/
|
||||
async function redirectFromLogin(needRedirect = true) {
|
||||
const redirect = route.value.query?.redirect as string
|
||||
|
||||
if (needRedirect && redirect) {
|
||||
await routerPush(redirect)
|
||||
}
|
||||
else {
|
||||
await toHome()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
routerPush,
|
||||
routerBack,
|
||||
routerPushByKey,
|
||||
routerPushByKeyWithMetaQuery,
|
||||
toLogin,
|
||||
toggleLoginModule,
|
||||
redirectFromLogin,
|
||||
}
|
||||
}
|
||||
315
apps/admin/src/hooks/common/table.ts
Normal file
315
apps/admin/src/hooks/common/table.ts
Normal file
@ -0,0 +1,315 @@
|
||||
import type { FlatResponseData } from '@sa/axios'
|
||||
import type { PaginationData, TableColumnCheck, UseTableOptions } from '@sa/hooks'
|
||||
import type { PaginationProps } from 'naive-ui'
|
||||
import type { Ref } from 'vue'
|
||||
import { useBoolean, useTable } from '@sa/hooks'
|
||||
import { jsonClone } from '@sa/utils'
|
||||
import { computed, effectScope, onScopeDispose, reactive, shallowRef, watch } from 'vue'
|
||||
import { $t } from '@/locales'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
|
||||
export type UseNaiveTableOptions<ResponseData, ApiData, Pagination extends boolean> = Omit<
|
||||
UseTableOptions<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, Pagination>,
|
||||
'pagination' | 'getColumnChecks' | 'getColumns'
|
||||
> & {
|
||||
/**
|
||||
* get column visible
|
||||
*
|
||||
* @param column
|
||||
*
|
||||
* @default true
|
||||
*
|
||||
* @returns true if the column is visible, false otherwise
|
||||
*/
|
||||
getColumnVisible?: (column: NaiveUI.TableColumn<ApiData>) => boolean
|
||||
}
|
||||
|
||||
const SELECTION_KEY = '__selection__'
|
||||
|
||||
const EXPAND_KEY = '__expand__'
|
||||
|
||||
export function useNaiveTable<ResponseData, ApiData>(options: UseNaiveTableOptions<ResponseData, ApiData, false>) {
|
||||
const scope = effectScope()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, false>({
|
||||
...options,
|
||||
getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
|
||||
getColumns,
|
||||
})
|
||||
|
||||
// calculate the total width of the table this is used for horizontal scrolling
|
||||
const scrollX = computed(() => {
|
||||
return result.columns.value.reduce((acc, column) => {
|
||||
return acc + Number(column.width ?? column.minWidth ?? 120)
|
||||
}, 0)
|
||||
})
|
||||
|
||||
scope.run(() => {
|
||||
watch(
|
||||
() => appStore.locale,
|
||||
() => {
|
||||
result.reloadColumns()
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
onScopeDispose(() => {
|
||||
scope.stop()
|
||||
})
|
||||
|
||||
return {
|
||||
...result,
|
||||
scrollX,
|
||||
}
|
||||
}
|
||||
|
||||
type PaginationParams = Pick<PaginationProps, 'page' | 'pageSize'>
|
||||
|
||||
type UseNaivePaginatedTableOptions<ResponseData, ApiData> = UseNaiveTableOptions<ResponseData, ApiData, true> & {
|
||||
paginationProps?: Omit<PaginationProps, 'page' | 'pageSize' | 'itemCount'>
|
||||
/**
|
||||
* whether to show the total count of the table
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
showTotal?: boolean
|
||||
onPaginationParamsChange?: (params: PaginationParams) => void | Promise<void>
|
||||
}
|
||||
|
||||
export function useNaivePaginatedTable<ResponseData, ApiData>(
|
||||
options: UseNaivePaginatedTableOptions<ResponseData, ApiData>,
|
||||
) {
|
||||
const scope = effectScope()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const isMobile = computed(() => appStore.isMobile)
|
||||
|
||||
const showTotal = computed(() => options.showTotal ?? true)
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
itemCount: 0,
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 15, 20, 25, 30],
|
||||
prefix: showTotal.value ? page => $t('datatable.itemCount', { total: page.itemCount }) : undefined,
|
||||
onUpdatePage(page) {
|
||||
pagination.page = page
|
||||
},
|
||||
onUpdatePageSize(pageSize) {
|
||||
pagination.pageSize = pageSize
|
||||
pagination.page = 1
|
||||
},
|
||||
...options.paginationProps,
|
||||
}) as PaginationProps
|
||||
|
||||
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
|
||||
const mobilePagination = computed(() => {
|
||||
const p: PaginationProps = {
|
||||
...pagination,
|
||||
pageSlot: isMobile.value ? 3 : 9,
|
||||
prefix: !isMobile.value && showTotal.value ? pagination.prefix : undefined,
|
||||
}
|
||||
|
||||
return p
|
||||
})
|
||||
|
||||
const paginationParams = computed(() => {
|
||||
const { page, pageSize } = pagination
|
||||
|
||||
return {
|
||||
page,
|
||||
pageSize,
|
||||
}
|
||||
})
|
||||
|
||||
const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, true>({
|
||||
...options,
|
||||
pagination: true,
|
||||
getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
|
||||
getColumns,
|
||||
onFetched: (data) => {
|
||||
pagination.itemCount = data.total
|
||||
pagination.pageSize = data.pageSize
|
||||
},
|
||||
})
|
||||
|
||||
async function getDataByPage(page: number = 1) {
|
||||
if (page !== pagination.page) {
|
||||
pagination.page = page
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await result.getData()
|
||||
}
|
||||
|
||||
scope.run(() => {
|
||||
watch(
|
||||
() => appStore.locale,
|
||||
() => {
|
||||
result.reloadColumns()
|
||||
},
|
||||
)
|
||||
|
||||
watch(paginationParams, async (newVal) => {
|
||||
await options.onPaginationParamsChange?.(newVal)
|
||||
|
||||
await result.getData()
|
||||
})
|
||||
})
|
||||
|
||||
onScopeDispose(() => {
|
||||
scope.stop()
|
||||
})
|
||||
|
||||
return {
|
||||
...result,
|
||||
getDataByPage,
|
||||
pagination,
|
||||
mobilePagination,
|
||||
}
|
||||
}
|
||||
|
||||
export function useTableOperate<TableData>(
|
||||
data: Ref<TableData[]>,
|
||||
idKey: keyof TableData,
|
||||
getData: () => Promise<void>,
|
||||
) {
|
||||
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean()
|
||||
|
||||
const operateType = shallowRef<NaiveUI.TableOperateType>('add')
|
||||
|
||||
function handleAdd() {
|
||||
operateType.value = 'add'
|
||||
openDrawer()
|
||||
}
|
||||
|
||||
/** the editing row data */
|
||||
const editingData = shallowRef<TableData | null>(null)
|
||||
|
||||
function handleEdit(id: TableData[keyof TableData]) {
|
||||
operateType.value = 'edit'
|
||||
const findItem = data.value.find(item => item[idKey] === id) || null
|
||||
editingData.value = jsonClone(findItem)
|
||||
|
||||
openDrawer()
|
||||
}
|
||||
|
||||
/** the checked row keys of table */
|
||||
const checkedRowKeys = shallowRef<string[]>([])
|
||||
|
||||
/** the hook after the batch delete operation is completed */
|
||||
async function onBatchDeleted() {
|
||||
window.$message?.success($t('common.deleteSuccess'))
|
||||
|
||||
checkedRowKeys.value = []
|
||||
|
||||
await getData()
|
||||
}
|
||||
|
||||
/** the hook after the delete operation is completed */
|
||||
async function onDeleted() {
|
||||
window.$message?.success($t('common.deleteSuccess'))
|
||||
|
||||
await getData()
|
||||
}
|
||||
|
||||
return {
|
||||
drawerVisible,
|
||||
openDrawer,
|
||||
closeDrawer,
|
||||
operateType,
|
||||
handleAdd,
|
||||
editingData,
|
||||
handleEdit,
|
||||
checkedRowKeys,
|
||||
onBatchDeleted,
|
||||
onDeleted,
|
||||
}
|
||||
}
|
||||
|
||||
export function defaultTransform<ApiData>(
|
||||
response: FlatResponseData<any, Api.Common.PaginatingQueryRecord<ApiData>>,
|
||||
): PaginationData<ApiData> {
|
||||
const { data, error } = response
|
||||
|
||||
if (!error) {
|
||||
const { records, current, size, total } = data
|
||||
|
||||
return {
|
||||
data: records,
|
||||
pageNum: current,
|
||||
pageSize: size,
|
||||
total,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: [],
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
}
|
||||
}
|
||||
|
||||
function getColumnChecks<Column extends NaiveUI.TableColumn<any>>(
|
||||
cols: Column[],
|
||||
getColumnVisible?: (column: Column) => boolean,
|
||||
) {
|
||||
const checks: TableColumnCheck[] = []
|
||||
|
||||
cols.forEach((column) => {
|
||||
if (isTableColumnHasKey(column)) {
|
||||
checks.push({
|
||||
key: column.key as string,
|
||||
title: column.title!,
|
||||
checked: true,
|
||||
visible: getColumnVisible?.(column) ?? true,
|
||||
})
|
||||
}
|
||||
else if (column.type === 'selection') {
|
||||
checks.push({
|
||||
key: SELECTION_KEY,
|
||||
title: $t('common.check'),
|
||||
checked: true,
|
||||
visible: getColumnVisible?.(column) ?? false,
|
||||
})
|
||||
}
|
||||
else if (column.type === 'expand') {
|
||||
checks.push({
|
||||
key: EXPAND_KEY,
|
||||
title: $t('common.expandColumn'),
|
||||
checked: true,
|
||||
visible: getColumnVisible?.(column) ?? false,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return checks
|
||||
}
|
||||
|
||||
function getColumns<Column extends NaiveUI.TableColumn<any>>(cols: Column[], checks: TableColumnCheck[]) {
|
||||
const columnMap = new Map<string, Column>()
|
||||
|
||||
cols.forEach((column) => {
|
||||
if (isTableColumnHasKey(column)) {
|
||||
columnMap.set(column.key as string, column)
|
||||
}
|
||||
else if (column.type === 'selection') {
|
||||
columnMap.set(SELECTION_KEY, column)
|
||||
}
|
||||
else if (column.type === 'expand') {
|
||||
columnMap.set(EXPAND_KEY, column)
|
||||
}
|
||||
})
|
||||
|
||||
const filteredColumns = checks.filter(item => item.checked).map(check => columnMap.get(check.key) as Column)
|
||||
|
||||
return filteredColumns
|
||||
}
|
||||
|
||||
export function isTableColumnHasKey<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
|
||||
return Boolean((column as NaiveUI.TableColumnWithKey<T>).key)
|
||||
}
|
||||
Reference in New Issue
Block a user