- 新增活动管理相关功能,包括活动创建、编辑、删除和状态管理 - 新增 PublishStatus 枚举定义活动发布状态 - 优化字典组件,支持字符串和数字类型的字典值 - 重构业务数据存储逻辑,简化字典数据获取流程 - 新增活动详情页面,支持分组管理和队伍配置 - 集成富文本编辑器用于活动规则编辑 - 优化图片上传组件,支持小图上传模式 - 修复模板管理中的区域选择和分页问题 - 更新 TypeScript 类型定义,完善 API 接口 - 调整主题配置,新增活动状态相关颜色
347 lines
8.8 KiB
TypeScript
347 lines
8.8 KiB
TypeScript
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'
|
||
> & {
|
||
/**
|
||
* 获取列的可见性
|
||
*
|
||
* @param column
|
||
*
|
||
* @default true
|
||
*
|
||
* @returns 如果列可见返回 true,否则返回 false
|
||
*/
|
||
getColumnVisible?: (column: NaiveUI.TableColumn<ApiData>) => boolean
|
||
}
|
||
|
||
const SELECTION_KEY = '__selection__'
|
||
|
||
const EXPAND_KEY = '__expand__'
|
||
|
||
/**
|
||
* NaiveUI 表格
|
||
* @param options 表格选项
|
||
*/
|
||
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,
|
||
})
|
||
|
||
// 计算表格的总宽度,用于水平滚动
|
||
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'>
|
||
/**
|
||
* 是否显示表格的总条数
|
||
*
|
||
* @default true
|
||
*/
|
||
showTotal?: boolean
|
||
onPaginationParamsChange?: (params: PaginationParams) => void | Promise<void>
|
||
}
|
||
|
||
/**
|
||
* NaiveUI 分页表格
|
||
* @param options 表格选项
|
||
*/
|
||
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
|
||
|
||
// 针对移动端,如果系统不支持移动端,可以直接使用 `pagination`
|
||
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,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* NaiveUI 表格操作
|
||
* @param data 表格数据
|
||
* @param idKey 表格数据的主键字段
|
||
* @param getData 获取表格数据的函数
|
||
*/
|
||
export function useTableOperate<TableData>(
|
||
data: Ref<TableData[]>,
|
||
idKey: keyof TableData,
|
||
getData: () => Promise<void>,
|
||
): {
|
||
drawerVisible: Ref<boolean> /** 抽屉是否可见 */
|
||
openDrawer: () => void /** 打开抽屉 */
|
||
closeDrawer: () => void /** 关闭抽屉 */
|
||
operateType: Ref<NaiveUI.TableOperateType> /** 操作类型 */
|
||
handleAdd: () => void /** 新增操作 */
|
||
editingData: Ref<TableData | null> /** 编辑行数据 */
|
||
handleEdit: (id: TableData[keyof TableData]) => void /** 编辑操作 */
|
||
checkedRowKeys: Ref<string[]> /** 表格的选中行 keys */
|
||
onBatchDeleted: () => Promise<void> /** 批量删除操作完成后的钩子 */
|
||
onDeleted: () => Promise<void> /** 删除操作完成后的钩子 */
|
||
} {
|
||
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean()
|
||
|
||
const operateType = shallowRef<NaiveUI.TableOperateType>('add')
|
||
|
||
function handleAdd() {
|
||
operateType.value = 'add'
|
||
openDrawer()
|
||
}
|
||
|
||
/** 编辑行数据 */
|
||
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()
|
||
}
|
||
|
||
/** 表格的选中行 keys */
|
||
const checkedRowKeys = shallowRef<string[]>([])
|
||
|
||
/** 批量删除操作完成后的钩子 */
|
||
async function onBatchDeleted() {
|
||
window.$message?.success($t('common.deleteSuccess'))
|
||
|
||
checkedRowKeys.value = []
|
||
|
||
await getData()
|
||
}
|
||
|
||
/** 删除操作完成后的钩子 */
|
||
async function onDeleted() {
|
||
window.$message?.success($t('common.deleteSuccess'))
|
||
|
||
await getData()
|
||
}
|
||
|
||
return {
|
||
drawerVisible,
|
||
openDrawer,
|
||
closeDrawer,
|
||
operateType,
|
||
handleAdd,
|
||
editingData,
|
||
handleEdit,
|
||
checkedRowKeys,
|
||
onBatchDeleted,
|
||
onDeleted,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 默认的表格数据转换函数
|
||
*
|
||
* @param response 原始的 API 响应数据
|
||
* @returns 转换后的分页数据
|
||
*/
|
||
export function defaultTransform<ApiData>(
|
||
response: FlatResponseData<any, Api.Common.PaginatingQueryRecord<ApiData>>,
|
||
): PaginationData<ApiData> {
|
||
const { data, error } = response
|
||
|
||
if (!error) {
|
||
const { data: list, currentPage: pageNum, pageSize: size, total = 0 } = data
|
||
|
||
return {
|
||
data: list,
|
||
pageNum,
|
||
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)
|
||
}
|