初始化本地仓库
This commit is contained in:
@@ -0,0 +1,758 @@
|
||||
<template>
|
||||
<div v-if="crudSchema?.useVxeTable" class="bg-card h-full flex flex-col">
|
||||
<vxe-grid
|
||||
ref="gridRef"
|
||||
:id="crudSchemaRef?.rowKey"
|
||||
:columns="vxeTableColumns"
|
||||
:toolbar-config="{
|
||||
refresh: true, // 刷新
|
||||
print: false, // 打印
|
||||
export: false, // 导出
|
||||
custom: true, // 自定义列
|
||||
zoom: true, // 最大化最小化,
|
||||
slots: {
|
||||
buttons: 'toolbar-left',
|
||||
tools: 'toolbar-right'
|
||||
}
|
||||
}"
|
||||
:keepSource="true"
|
||||
:tree-config="
|
||||
crudSchemaRef.tree
|
||||
? {
|
||||
childrenField: crudSchemaRef.tree?.children || 'children',
|
||||
rowField: crudSchemaRef.tree?.id || 'id'
|
||||
}
|
||||
: undefined
|
||||
"
|
||||
height="auto"
|
||||
:pager-config="{
|
||||
enabled: crudSchemaRef.tree ? false : true,
|
||||
layouts: [
|
||||
'Total',
|
||||
'Sizes',
|
||||
'Home',
|
||||
'PrevJump',
|
||||
'PrevPage',
|
||||
'Number',
|
||||
'NextPage',
|
||||
'NextJump',
|
||||
'End'
|
||||
],
|
||||
pageSize: 20,
|
||||
background: true,
|
||||
pageSizes: [10, 20, 30, 50, 100, 200],
|
||||
className: 'w-full',
|
||||
size: 'mini' as const,
|
||||
align: 'center'
|
||||
}"
|
||||
:proxy-config="{
|
||||
ajax: {
|
||||
query: ({ page }, formValues) => {
|
||||
return funApi({
|
||||
[tableConfig.paginationKey.current || 'pageNum']: page.currentPage,
|
||||
[tableConfig.paginationKey.size || 'pageSize']: page.pageSize,
|
||||
...formValues
|
||||
})
|
||||
}
|
||||
},
|
||||
response: {
|
||||
result: ({ data }) => {
|
||||
const fields = tableConfig.recordFields || ['rows']
|
||||
for (const field of fields) {
|
||||
if (data[field]) {
|
||||
return data[field]
|
||||
}
|
||||
}
|
||||
return []
|
||||
},
|
||||
total: ({ data }) => {
|
||||
const fields = tableConfig.totalFields || ['recordCount']
|
||||
for (const field of fields) {
|
||||
if (data[field]) {
|
||||
return data[field]
|
||||
}
|
||||
}
|
||||
return 0
|
||||
},
|
||||
list: ({ data }) => {
|
||||
if(crudSchemaRef.tree) return data;
|
||||
const fields = tableConfig.totalFields || ['rows']
|
||||
for (const field of fields) {
|
||||
if (data[field]) {
|
||||
return data[field]
|
||||
}
|
||||
}
|
||||
return data
|
||||
},
|
||||
}
|
||||
}"
|
||||
@checkboxChange="handleCheckboxChange"
|
||||
@checkboxAll="handleCheckboxAll"
|
||||
v-bind="{ ...$attrs }"
|
||||
>
|
||||
<template #toolbar-left>
|
||||
<slot name="toolbar-actions">
|
||||
<TableAction :actions="toolbarActions" :drop-down-actions="dropDownToolbarActions">
|
||||
<template #more>
|
||||
<ElButton>
|
||||
<template #icon
|
||||
><Icon class="icon-more" icon="ant-design:down-outlined"
|
||||
/></template>
|
||||
更多操作</ElButton
|
||||
>
|
||||
</template>
|
||||
</TableAction>
|
||||
</slot>
|
||||
</template>
|
||||
<template #toolbar-right>
|
||||
<div class="pr-[7px]">
|
||||
<button
|
||||
:key="item.auth"
|
||||
:title="item.label"
|
||||
v-for="item in crudSchemaRef.toolbarConfig?.tools || []"
|
||||
class="vxe-button type--button is--circle"
|
||||
@click="handleToolbarToolClick(item.auth)"
|
||||
>
|
||||
<i class="iconfont-sys">
|
||||
<Icon :icon="item.icon" />
|
||||
</i>
|
||||
</button>
|
||||
</div>
|
||||
<slot name="toolbar-tools"></slot>
|
||||
</template>
|
||||
<template #form>
|
||||
<slot name="form">
|
||||
<div class="relative" v-if="crudSchemaRef.useSearchForm !== false">
|
||||
<SearchBar @reset="handleReset" @submit="handleSearch" />
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<template v-for="(_, slotName) in $slots" #[slotName]>
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</div>
|
||||
<template v-else>
|
||||
<SearchBar
|
||||
v-if="crudSchemaRef.useSearchForm !== false"
|
||||
@reset="handleReset"
|
||||
@submit="handleSearch"
|
||||
/>
|
||||
<ElCard class="art-table-card" shadow="never">
|
||||
<!-- 表格头部 -->
|
||||
<ArtTableHeader
|
||||
v-model:columns="columnChecks"
|
||||
@refresh="refreshData"
|
||||
@export="handleExport"
|
||||
:layout="headerLayout"
|
||||
:show-export-button="showExportButton"
|
||||
>
|
||||
<template #left>
|
||||
<slot name="toolbar-actions">
|
||||
<TableAction :actions="toolbarActions" :drop-down-actions="dropDownToolbarActions">
|
||||
<template #more>
|
||||
<ElButton>
|
||||
<template #icon
|
||||
><Icon class="icon-more" icon="ant-design:down-outlined"
|
||||
/></template>
|
||||
更多操作</ElButton
|
||||
>
|
||||
</template>
|
||||
</TableAction>
|
||||
</slot>
|
||||
</template>
|
||||
<template #tools>
|
||||
<div
|
||||
:key="item.auth"
|
||||
v-for="item in crudSchemaRef.toolbarConfig?.tools || []"
|
||||
class="button active"
|
||||
@click="handleToolbarToolClick(item.auth)"
|
||||
>
|
||||
<i class="iconfont-sys">
|
||||
<Icon :icon="item.icon" />
|
||||
</i>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<slot name="toolbar-tools"></slot>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
:rowKey="crudSchemaRef.rowKey ?? 'id'"
|
||||
:loading="loading"
|
||||
:data="crudSchemaRef.data ?? tableData"
|
||||
:columns="columns"
|
||||
:pagination="crudSchemaRef.tree ? undefined : pagination"
|
||||
@pagination:size-change="handleSizeChange"
|
||||
@pagination:current-change="handleCurrentChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
:stripe="true"
|
||||
v-bind="{ ...$attrs }"
|
||||
>
|
||||
<template v-for="(_, slotName) in $slots" #[slotName]>
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
</template>
|
||||
<DialogForm
|
||||
v-bind="{ ...crudSchemaRef.dialogProps }"
|
||||
ref="dialogFormRef"
|
||||
:crud-schema="crudSchemaRef"
|
||||
/>
|
||||
<ElDialog v-model="exportDialogVisible" title="导出数据" width="620px" destroy-on-close>
|
||||
<div class="text-sm text-gray-500 -mt-4 mb-5">选择导出字段、文件格式和数据范围</div>
|
||||
<ElForm label-position="top">
|
||||
<ElRow :gutter="16">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="文件名称">
|
||||
<ElInput v-model="exportConfig.filename" maxlength="80" show-word-limit />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="工作表名称">
|
||||
<ElInput v-model="exportConfig.sheetName" maxlength="31" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="导出格式">
|
||||
<ElSelect v-model="exportConfig.format" class="w-full">
|
||||
<ElOption label="Excel 工作簿(XLSX)" value="xlsx" />
|
||||
<ElOption label="CSV 文件" value="csv" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="选择数据">
|
||||
<ElSelect v-model="exportConfig.scope" class="w-full">
|
||||
<ElOption label="当前页数据" value="current" />
|
||||
<ElOption label="选择的数据" value="selected" />
|
||||
<ElOption label="全部筛选结果" value="all" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<div class="border border-gray-200 rounded-md px-4 py-3">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<ElCheckbox :model-value="isAllExportFieldsSelected" :indeterminate="isExportFieldsIndeterminate" @change="toggleExportFields">全选字段</ElCheckbox>
|
||||
<span class="text-xs text-gray-400">已选择 {{ selectedExportFields.length }} / {{ exportFields.length }} 个字段</span>
|
||||
</div>
|
||||
<ElCheckboxGroup v-model="selectedExportFields" class="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||
<ElCheckbox v-for="field in exportFields" :key="field.field" :label="field.field">{{ field.title }}</ElCheckbox>
|
||||
</ElCheckboxGroup>
|
||||
</div>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<ElButton @click="exportDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" :loading="exporting" @click="confirmExport">确认导出</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
<script setup lang="tsx">
|
||||
import { useSearchBar } from '@/components/search-bar'
|
||||
import { CrudInstance, CrudReadonlyState, CrudSchema } from './types'
|
||||
import request from '@/utils/http'
|
||||
import { TableAction } from '../table-action'
|
||||
import DialogForm from './dialog-form.vue'
|
||||
import { useAction } from './use-action'
|
||||
import { Icon } from '@/components/icon'
|
||||
import { downloadFile, getTreeNodeAndChildrenIds } from '@/utils/tool'
|
||||
import { useAuth, useTable } from '@/hooks'
|
||||
import { useColumns } from './use-columns'
|
||||
import { tableConfig } from '@/utils/table/tableConfig'
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
crudSchema: CrudSchema
|
||||
crudMethods?: any
|
||||
}>(),
|
||||
{
|
||||
crudSchema: () => {
|
||||
return {
|
||||
columns: [],
|
||||
api: '',
|
||||
useSearchForm: true,
|
||||
useVxeTable: false,
|
||||
searchFormSchema: {
|
||||
schema: []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit', values: any): void
|
||||
(e: 'reset'): void
|
||||
(e: 'register', methods: CrudInstance): void
|
||||
(e: 'toolbarToolClick', code: string): void
|
||||
}>()
|
||||
const { hasAuth } = useAuth()
|
||||
const crudSchemaRef = ref(props.crudSchema)
|
||||
const dialogFormRef = ref<any>(null)
|
||||
// 计算各种操作按钮配置
|
||||
const { toolbarActions, dropDownToolbarActions, tableActions, dropDownActions } = useAction(
|
||||
crudSchemaRef.value,
|
||||
dialogFormRef,
|
||||
props.crudMethods
|
||||
)
|
||||
const headerLayout = computed(() => {
|
||||
const defaultLayout = ['refresh', 'size', 'fullscreen', 'columns', 'settings'] as const
|
||||
const toolbarConfig = crudSchemaRef.value.toolbarConfig || {}
|
||||
// 过滤掉被禁用的选项
|
||||
const layout = defaultLayout.filter((item) => toolbarConfig[item] !== false)
|
||||
|
||||
return layout.join(',')
|
||||
})
|
||||
const resolveExportBaseUrl = () => {
|
||||
const crudApiExport = crudSchemaRef.value.crudApi?.export as any
|
||||
if (crudApiExport?.__baseUrl) {
|
||||
return crudApiExport.__baseUrl as string
|
||||
}
|
||||
const api = crudSchemaRef.value.api as any
|
||||
if (typeof api === 'string' && api) {
|
||||
return api
|
||||
}
|
||||
if (typeof api === 'function' && api.__baseUrl) {
|
||||
return api.__baseUrl as string
|
||||
}
|
||||
return ''
|
||||
}
|
||||
const showExportButton = computed(() => {
|
||||
if (crudSchemaRef.value.hasExport === false || crudSchemaRef.value.toolbarConfig?.export === false) {
|
||||
return false
|
||||
}
|
||||
const exportAuth = crudSchemaRef.value.exportAuth
|
||||
if (exportAuth && !hasAuth(exportAuth)) {
|
||||
return false
|
||||
}
|
||||
return !!(crudSchemaRef.value.crudApi?.export || resolveExportBaseUrl())
|
||||
})
|
||||
const funApi = computed(() => {
|
||||
const { api, requestMethod, beforeFetch, afterFetch } = props.crudSchema
|
||||
|
||||
if (!api) return api as any
|
||||
|
||||
const fun = (params: any) => {
|
||||
// 处理请求前的参数
|
||||
let processedParams = beforeFetch ? beforeFetch(params) : params
|
||||
|
||||
// 发起请求
|
||||
const apiRequest =
|
||||
typeof api === 'string'
|
||||
? request[requestMethod ?? 'post']({ url: api, params: processedParams })
|
||||
: api(processedParams)
|
||||
|
||||
// 处理响应后的结果
|
||||
return afterFetch ? apiRequest.then(afterFetch) : apiRequest
|
||||
}
|
||||
|
||||
return fun
|
||||
})
|
||||
const actionItemRecord = ref()
|
||||
const handleActionItemClick = (record: any) => {
|
||||
actionItemRecord.value = record
|
||||
}
|
||||
const [SearchBar, searchBarApi] = useSearchBar(crudSchemaRef.value.searchFormSchema || {})
|
||||
const tableObject = useTable<any>({
|
||||
core: {
|
||||
apiFn: funApi.value,
|
||||
immediate:
|
||||
(crudSchemaRef.value.immediate ?? true) && crudSchemaRef.value?.useVxeTable !== true,
|
||||
apiParams: {
|
||||
// 其他搜索参数
|
||||
},
|
||||
columnsFactory: () => {
|
||||
const { columns } = useColumns(
|
||||
crudSchemaRef,
|
||||
tableActions,
|
||||
dropDownActions,
|
||||
handleActionItemClick
|
||||
)
|
||||
return columns as any
|
||||
}
|
||||
}
|
||||
})
|
||||
const {
|
||||
data: tableData,
|
||||
columns,
|
||||
loading,
|
||||
pagination,
|
||||
columnChecks,
|
||||
getData,
|
||||
refreshData,
|
||||
handleSizeChange,
|
||||
handleCurrentChange
|
||||
} = tableObject
|
||||
const getExportFields = () => {
|
||||
if (crudSchemaRef.value.exportFields?.length) {
|
||||
return crudSchemaRef.value.exportFields
|
||||
}
|
||||
|
||||
const sourceColumns = (columnChecks.value?.length
|
||||
? columnChecks.value
|
||||
: (typeof crudSchemaRef.value.columns === 'function'
|
||||
? crudSchemaRef.value.columns()
|
||||
: crudSchemaRef.value.columns) || []) as any[]
|
||||
|
||||
return sourceColumns
|
||||
.filter((column) => {
|
||||
if (!column?.prop) {
|
||||
return false
|
||||
}
|
||||
if (column.prop === '__ACTION__') {
|
||||
return false
|
||||
}
|
||||
if (['selection', 'expand', 'index', 'globalIndex'].includes(column.type)) {
|
||||
return false
|
||||
}
|
||||
if (column.visible === false || column.checked === false) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
.map((column) => ({
|
||||
field: column.prop,
|
||||
title: column.label || column.prop
|
||||
}))
|
||||
}
|
||||
type ExportScope = 'all' | 'current' | 'selected'
|
||||
type ExportFormat = 'xlsx' | 'csv'
|
||||
interface ExportField {
|
||||
field: string
|
||||
title: string
|
||||
}
|
||||
const exportDialogVisible = ref(false)
|
||||
const exporting = ref(false)
|
||||
const exportFields = ref<ExportField[]>([])
|
||||
const selectedExportFields = ref<string[]>([])
|
||||
const exportConfig = ref<{ filename: string; sheetName: string; format: ExportFormat; scope: ExportScope }>({
|
||||
filename: '',
|
||||
sheetName: '',
|
||||
format: 'xlsx',
|
||||
scope: 'all'
|
||||
})
|
||||
const isAllExportFieldsSelected = computed(() => exportFields.value.length > 0 && selectedExportFields.value.length === exportFields.value.length)
|
||||
const isExportFieldsIndeterminate = computed(() => selectedExportFields.value.length > 0 && selectedExportFields.value.length < exportFields.value.length)
|
||||
const toggleExportFields = (checked: boolean | string | number) => {
|
||||
selectedExportFields.value = checked === true ? exportFields.value.map((field) => field.field) : []
|
||||
}
|
||||
const buildExportQuery = async () => {
|
||||
const values = crudSchemaRef.value.useSearchForm !== false ? await searchBarApi.getValues() : {}
|
||||
let query = { ...values }
|
||||
|
||||
if (crudSchemaRef.value.beforeFetch) {
|
||||
query = crudSchemaRef.value.beforeFetch({ ...query }) || query
|
||||
}
|
||||
|
||||
delete query.page
|
||||
delete query.limit
|
||||
delete query.format
|
||||
delete query.field
|
||||
delete query.order
|
||||
delete query.filters
|
||||
delete query.sorter
|
||||
delete query.sorters
|
||||
delete query.orders
|
||||
delete query.columns
|
||||
delete query.columnChecks
|
||||
|
||||
return query
|
||||
}
|
||||
const handleExport = () => {
|
||||
const fields = getExportFields() as ExportField[]
|
||||
if (!fields.length) {
|
||||
ElMessage.warning('没有可导出的字段')
|
||||
return
|
||||
}
|
||||
const exportName = crudSchemaRef.value.exportFileName || crudSchemaRef.value.dialogTitle || '数据导出'
|
||||
exportFields.value = fields
|
||||
selectedExportFields.value = fields.map((field) => field.field)
|
||||
exportConfig.value = {
|
||||
filename: exportName,
|
||||
sheetName: exportName.slice(0, 31),
|
||||
format: 'xlsx',
|
||||
scope: 'all'
|
||||
}
|
||||
exportDialogVisible.value = true
|
||||
}
|
||||
const confirmExport = async () => {
|
||||
const exportRequest = crudSchemaRef.value.crudApi?.export
|
||||
? crudSchemaRef.value.crudApi.export
|
||||
: (() => {
|
||||
const baseUrl = resolveExportBaseUrl()
|
||||
if (!baseUrl) {
|
||||
return null
|
||||
}
|
||||
return (params: any) => request.post({ url: `${baseUrl}/export`, params })
|
||||
})()
|
||||
|
||||
if (!exportRequest) {
|
||||
ElMessage.warning('当前页面暂未配置导出能力')
|
||||
return
|
||||
}
|
||||
|
||||
const fields = exportFields.value.filter((field) => selectedExportFields.value.includes(field.field))
|
||||
if (!fields.length) {
|
||||
ElMessage.warning('请至少选择一个导出字段')
|
||||
return
|
||||
}
|
||||
|
||||
const query = await buildExportQuery()
|
||||
if (exportConfig.value.scope === 'current') {
|
||||
query.__export_page = pagination.current
|
||||
query.__export_limit = pagination.size
|
||||
}
|
||||
if (exportConfig.value.scope === 'selected') {
|
||||
const selectedIds = multipleSelection.value
|
||||
.map((row: Record<string, any>) => row[crudSchemaRef.value.rowKey || 'id'])
|
||||
.filter((id: unknown) => id !== undefined && id !== null && id !== '')
|
||||
if (!selectedIds.length) {
|
||||
ElMessage.warning('请先勾选需要导出的数据')
|
||||
return
|
||||
}
|
||||
query.__export_ids = selectedIds
|
||||
}
|
||||
exporting.value = true
|
||||
try {
|
||||
const exportResult: any = await exportRequest({
|
||||
filename: exportConfig.value.filename.trim() || '数据导出',
|
||||
fileName: exportConfig.value.filename.trim() || '数据导出',
|
||||
sheetName: exportConfig.value.sheetName.trim() || '数据导出',
|
||||
isHeader: true,
|
||||
fields,
|
||||
query,
|
||||
format: exportConfig.value.format,
|
||||
scope: exportConfig.value.scope
|
||||
})
|
||||
|
||||
const downloadParams = exportResult?.params
|
||||
? { ...exportResult.params }
|
||||
: {}
|
||||
|
||||
if ((!downloadParams.file_path || !downloadParams.file) && exportResult?.url) {
|
||||
const url = new URL(exportResult.url, window.location.origin)
|
||||
downloadParams.file_path = downloadParams.file_path || url.searchParams.get('file_path') || ''
|
||||
downloadParams.file = downloadParams.file || url.searchParams.get('file') || `${exportConfig.value.filename || '数据导出'}.${exportConfig.value.format}`
|
||||
}
|
||||
|
||||
if (!downloadParams.file_path) {
|
||||
ElMessage.error('导出文件地址生成失败')
|
||||
return
|
||||
}
|
||||
|
||||
const blob = await request.get({
|
||||
url: '/export/download-excel',
|
||||
params: downloadParams,
|
||||
responseType: 'blob',
|
||||
showErrorMessage: true
|
||||
})
|
||||
|
||||
downloadFile(blob, downloadParams.file || `${exportConfig.value.filename || '数据导出'}.${exportConfig.value.format}`)
|
||||
exportDialogVisible.value = false
|
||||
ElMessage.success('导出成功')
|
||||
} finally {
|
||||
exporting.value = false
|
||||
}
|
||||
}
|
||||
const handleSearch = (values: any) => {
|
||||
//Object.assign(searchParams, values)
|
||||
emit('submit', values)
|
||||
if (crudSchemaRef.value.useVxeTable === true) {
|
||||
gridRef.value?.commitProxy('query', values)
|
||||
return
|
||||
}
|
||||
getData(values)
|
||||
}
|
||||
const handleReset = () => {
|
||||
//Object.assign(searchParams, {})
|
||||
emit('reset')
|
||||
if (crudSchemaRef.value.useVxeTable === true) {
|
||||
gridRef.value?.commitProxy('query', {})
|
||||
return
|
||||
}
|
||||
getData({})
|
||||
}
|
||||
// 删除
|
||||
const handleDelete = (row: any) => {
|
||||
const rowKey = props.crudSchema.rowKey || 'id'
|
||||
let ids = row?.[rowKey]
|
||||
? [row[rowKey]]
|
||||
: multipleSelection.value.map((item: any) => item[rowKey])
|
||||
|
||||
// 如果是树形结构,需要获取节点及其所有子节点
|
||||
if (crudSchemaRef.value.tree) {
|
||||
const idKey = crudSchemaRef.value.tree.id || 'id'
|
||||
const childrenKey = crudSchemaRef.value.tree.children || 'children'
|
||||
|
||||
// 收集所有选中节点及其子节点的ID
|
||||
const allIds: any[] = []
|
||||
const selectedRows = row?.[rowKey] ? [row] : multipleSelection.value
|
||||
|
||||
selectedRows.forEach((selectedRow: any) => {
|
||||
const nodeIds = getTreeNodeAndChildrenIds(
|
||||
tableData.value,
|
||||
selectedRow[idKey],
|
||||
idKey,
|
||||
childrenKey
|
||||
)
|
||||
allIds.push(...nodeIds)
|
||||
})
|
||||
|
||||
ids = allIds
|
||||
}
|
||||
|
||||
props.crudSchema.crudApi
|
||||
?.remove?.({
|
||||
[`${rowKey}s`]: ids
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage.success(`删除${props.crudSchema.dialogTitle}成功`)
|
||||
if (crudSchemaRef.value.useVxeTable === true) {
|
||||
props.crudMethods?.refreshRemove(ids)
|
||||
} else {
|
||||
tableObject.refreshRemove()
|
||||
}
|
||||
})
|
||||
}
|
||||
const handleToolbarToolClick = (key: any) => {
|
||||
emit('toolbarToolClick', key)
|
||||
}
|
||||
// 定义一些只读状态
|
||||
const readonlyState = reactive<CrudReadonlyState>({
|
||||
selection: []
|
||||
})
|
||||
const multipleSelection = ref<any>([])
|
||||
const handleSelectionChange = (selection: any) => {
|
||||
multipleSelection.value = selection
|
||||
readonlyState.selection = selection
|
||||
}
|
||||
const crudInstanceMethods: CrudInstance = {
|
||||
async reload(params) {
|
||||
const values = await searchBarApi.getValues()
|
||||
if (crudSchemaRef.value.useVxeTable === true) {
|
||||
return await gridRef.value?.commitProxy('query', { ...values, ...params })
|
||||
}
|
||||
return getData({
|
||||
...values,
|
||||
...params
|
||||
})
|
||||
},
|
||||
// 设置组件状态(props)
|
||||
setState(state: CrudSchema) {
|
||||
return new Promise((resolve) => {
|
||||
crudSchemaRef.value = {
|
||||
...crudSchemaRef.value,
|
||||
...state
|
||||
}
|
||||
nextTick(() => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
getState(): Promise<CrudSchema> {
|
||||
return new Promise((resolve) => {
|
||||
resolve(crudSchemaRef.value)
|
||||
})
|
||||
},
|
||||
getReadonlyState() {
|
||||
return readonlyState
|
||||
},
|
||||
getArtTableInstance() {
|
||||
return tableObject
|
||||
},
|
||||
async exportData() {
|
||||
await handleExport()
|
||||
},
|
||||
getVxeTableInstance() {
|
||||
return gridRef.value
|
||||
},
|
||||
getRowSelection() {
|
||||
return multipleSelection.value
|
||||
},
|
||||
removeByApi(row) {
|
||||
handleDelete(row)
|
||||
},
|
||||
openAddDialog() {
|
||||
dialogFormRef.value?.show({
|
||||
data: {},
|
||||
type: 'add',
|
||||
getCrudApi() {
|
||||
return props.crudMethods
|
||||
}
|
||||
})
|
||||
},
|
||||
openEditDialog() {
|
||||
dialogFormRef.value?.show({
|
||||
data: actionItemRecord.value,
|
||||
type: 'edit',
|
||||
getCrudApi() {
|
||||
return props.crudMethods
|
||||
}
|
||||
})
|
||||
},
|
||||
openViewDialog() {
|
||||
dialogFormRef.value?.show({
|
||||
data: {},
|
||||
type: 'view',
|
||||
getCrudApi() {
|
||||
return props.crudMethods
|
||||
}
|
||||
})
|
||||
},
|
||||
executeRemove() {
|
||||
handleDelete(actionItemRecord.value)
|
||||
},
|
||||
executeBatchRemove() {
|
||||
handleDelete({})
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
emit('register', crudInstanceMethods)
|
||||
})
|
||||
// vxe-table部分逻辑
|
||||
const gridRef = ref()
|
||||
const vxeTableColumns = computed<any>(() => {
|
||||
if (crudSchemaRef.value?.useVxeTable === true) {
|
||||
const { columns } = useColumns(
|
||||
crudSchemaRef,
|
||||
tableActions,
|
||||
dropDownActions,
|
||||
handleActionItemClick
|
||||
)
|
||||
return columns
|
||||
}
|
||||
return []
|
||||
})
|
||||
const handleCheckboxChange = () => {
|
||||
const records = gridRef.value.getCheckboxRecords()
|
||||
multipleSelection.value = records
|
||||
readonlyState.selection = records
|
||||
}
|
||||
const handleCheckboxAll = () => {
|
||||
const records = gridRef.value.getCheckboxRecords()
|
||||
multipleSelection.value = records
|
||||
readonlyState.selection = records
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
@reference '@styles/core/tailwind.css';
|
||||
|
||||
.button {
|
||||
@apply ml-2
|
||||
size-8
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
cursor-pointer
|
||||
rounded-md
|
||||
bg-g-300/55
|
||||
dark:bg-g-300/40
|
||||
text-g-700
|
||||
hover:bg-g-300
|
||||
md:ml-0
|
||||
md:mr-2.5;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<Dialog>
|
||||
<DetailForm v-if="viewType == 'view'" :model="record"> </DetailForm>
|
||||
<BasicForm v-else :model="formModel" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive, nextTick } from 'vue'
|
||||
import { useDetailForm, useForm } from '@/components/form'
|
||||
import { useDialog } from '@/components/dialog'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { CrudMethods, CrudSchema } from '@/components/crud'
|
||||
const props = defineProps<{
|
||||
crudSchema: CrudSchema
|
||||
}>()
|
||||
const record = ref<any>({})
|
||||
const updateFlag = ref(false)
|
||||
const formModel = reactive<any>({})
|
||||
const crudApi = ref<CrudMethods>()
|
||||
const viewType = ref('view')
|
||||
const [BasicForm, formApi] = useForm(props.crudSchema.formSchema || {})
|
||||
const [DetailForm] = useDetailForm(props.crudSchema.formSchema || {})
|
||||
const dialogTitle = computed(() => {
|
||||
return props.crudSchema.dialogTitle || '标题'
|
||||
})
|
||||
const [Dialog, dialogApi] = useDialog({
|
||||
title: dialogTitle.value,
|
||||
...props.crudSchema.dialogProps,
|
||||
destroyOnClose: true,
|
||||
draggable: true,
|
||||
modal: true,
|
||||
onConfirm: () => {
|
||||
handleSubmit()
|
||||
}
|
||||
})
|
||||
|
||||
const show = ({
|
||||
data,
|
||||
getCrudApi,
|
||||
type = 'view'
|
||||
}: {
|
||||
data: any
|
||||
getCrudApi: () => any
|
||||
type?: 'add' | 'edit' | 'view'
|
||||
}) => {
|
||||
viewType.value = type
|
||||
dialogApi.setState(props.crudSchema.dialogProps || {})
|
||||
if (type === 'view') {
|
||||
dialogApi.setState({
|
||||
footer: false,
|
||||
closeOnClickModal: true
|
||||
})
|
||||
} else {
|
||||
dialogApi.setState({
|
||||
footer: true,
|
||||
closeOnClickModal: false
|
||||
})
|
||||
}
|
||||
updateFlag.value = type === 'edit'
|
||||
record.value = data || {}
|
||||
crudApi.value = getCrudApi()
|
||||
dialogApi.open()
|
||||
if (type === 'view' || type === 'edit') {
|
||||
const detailApi = props.crudSchema.crudApi?.view
|
||||
const rowKey = props.crudSchema.rowKey || 'id'
|
||||
detailApi?.(record.value[rowKey]).then((res) => {
|
||||
record.value = {
|
||||
...record.value,
|
||||
...res
|
||||
}
|
||||
nextTick(() => {
|
||||
formApi.setValues(record.value)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
nextTick(() => {
|
||||
formApi.setValues(record.value)
|
||||
})
|
||||
}
|
||||
const viewButtonText = props.crudSchema.viewButtonText || '详情'
|
||||
const editButtonText = props.crudSchema.editButtonText || '编辑'
|
||||
const addButtonText = props.crudSchema.addButtonText || '新增'
|
||||
let title = `${dialogTitle.value}${viewButtonText}`
|
||||
if (type === 'add') {
|
||||
title = `${addButtonText}${dialogTitle.value}`
|
||||
// 这里处理一下树型结构
|
||||
if (props.crudSchema.tree) {
|
||||
const idKey = props.crudSchema.tree.id || 'id'
|
||||
const pidKey = props.crudSchema.tree.pid || 'parentId'
|
||||
record.value = {
|
||||
[pidKey]: record.value[idKey]
|
||||
}
|
||||
}
|
||||
} else if (type === 'edit') {
|
||||
title = `${editButtonText}${dialogTitle.value}`
|
||||
}
|
||||
dialogApi.setState({
|
||||
title: title
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
dialogApi.setState({ loading: true, confirmLoading: true })
|
||||
formApi
|
||||
.validate()
|
||||
.then(async () => {
|
||||
const values = await formApi.getValues()
|
||||
// 转换表单值
|
||||
let submitValues = values
|
||||
if (props.crudSchema.transformFormValues) {
|
||||
const type = updateFlag.value ? 'edit' : 'add'
|
||||
submitValues = props.crudSchema.transformFormValues(values, type)
|
||||
}
|
||||
if (updateFlag.value) {
|
||||
const rowKey = props.crudSchema.rowKey || 'id'
|
||||
const recordId = record.value?.[rowKey]
|
||||
if (recordId !== undefined && recordId !== null && recordId !== '') {
|
||||
submitValues = { ...submitValues, [rowKey]: recordId }
|
||||
}
|
||||
}
|
||||
const submitApi = updateFlag.value
|
||||
? props.crudSchema.crudApi?.edit
|
||||
: props.crudSchema.crudApi?.add
|
||||
submitApi?.(submitValues).then(() => {
|
||||
const editButtonText = props.crudSchema.editButtonText || '编辑'
|
||||
const addButtonText = props.crudSchema.addButtonText || '新增'
|
||||
ElMessage.success(
|
||||
updateFlag.value
|
||||
? `${editButtonText}${dialogTitle.value}成功`
|
||||
: `${addButtonText}${dialogTitle.value}成功`
|
||||
)
|
||||
dialogApi.close()
|
||||
if (updateFlag.value) {
|
||||
crudApi.value?.refreshUpdate()
|
||||
} else {
|
||||
crudApi.value?.refreshCreate()
|
||||
}
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
dialogApi.setState({ loading: false, confirmLoading: false })
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as BasicTable } from './crud.vue'
|
||||
export { useCrud } from './use-crud'
|
||||
export type * from './types'
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
import { MyDialogProps } from '../dialog'
|
||||
import { FormSchema } from '../form'
|
||||
import { ActionItem } from '../table-action'
|
||||
import type { VxeColumnProps } from 'vxe-table'
|
||||
|
||||
export interface CrudSchema {
|
||||
// 行id
|
||||
rowKey?: string
|
||||
// 请求接口
|
||||
api?: ((params: any) => Promise<any>) | string
|
||||
// 请求方法
|
||||
requestMethod?: 'get' | 'post'
|
||||
// 请求之前对参数进行处理
|
||||
beforeFetch?: (params: any) => any
|
||||
// 请求之后对返回值进行处理
|
||||
afterFetch?: (res: any) => any
|
||||
// 是否立即加载数据
|
||||
immediate?: boolean
|
||||
data?: any
|
||||
// 树型配置
|
||||
tree?: {
|
||||
// 树形id字段名
|
||||
id?: string
|
||||
// 树形pid字段名
|
||||
pid?: string
|
||||
// 树形children字段名
|
||||
children?: string
|
||||
}
|
||||
// 右侧工具栏
|
||||
toolbarConfig?: {
|
||||
// 是否显示刷新按钮
|
||||
refresh?: boolean
|
||||
// 是否显示大小按钮
|
||||
size?: boolean
|
||||
// 是否显示全屏按钮
|
||||
fullscreen?: boolean
|
||||
// 是否显示列设置按钮
|
||||
columns?: boolean
|
||||
// 是否显示列设置按钮
|
||||
settings?: boolean
|
||||
// 是否显示导出按钮
|
||||
export?: boolean
|
||||
// 表格右上工具栏,在左侧追加
|
||||
tools?: ActionItem[]
|
||||
}
|
||||
// 列定义
|
||||
columns?: CrudColumn[] | (() => CrudColumn[])
|
||||
// 是否显示搜索表单
|
||||
useSearchForm?: boolean
|
||||
// 搜索表单配置
|
||||
searchFormSchema?: FormSchema | (() => FormSchema)
|
||||
// crud表单配置
|
||||
formSchema?: FormSchema | (() => FormSchema)
|
||||
// 操作列-按钮定义
|
||||
tableActions?: ActionItem[]
|
||||
// 操作列-下拉菜单定义
|
||||
dropDownActions?: ActionItem[]
|
||||
// 操作列配置
|
||||
tableActionColumn?: CrudColumn
|
||||
// 左侧栏工具配置
|
||||
toolbarActions?: ActionItem[]
|
||||
// 左侧栏-下拉菜单定义
|
||||
dropDownToolbarActions?: ActionItem[]
|
||||
// 内置弹窗配置
|
||||
dialogProps?: MyDialogProps
|
||||
// 增删改查接口
|
||||
crudApi?: CrudApi
|
||||
// 是否使用crud
|
||||
useCrud?: boolean
|
||||
// 模块名
|
||||
dialogTitle?: string
|
||||
// 是否有新增
|
||||
hasAdd?: boolean
|
||||
// 新增按钮文本
|
||||
addButtonText?: string
|
||||
// 新增按钮权限编码
|
||||
addAuth?: string
|
||||
// 是否有编辑
|
||||
hasEdit?: boolean
|
||||
// 编辑按钮文本
|
||||
editButtonText?: string
|
||||
// 编辑按钮权限编码
|
||||
editAuth?: string
|
||||
// 是否有详情
|
||||
hasView?: boolean
|
||||
// 详情按钮文本
|
||||
viewButtonText?: string
|
||||
// 详情按钮权限编码
|
||||
viewAuth?: string
|
||||
// 是否有删除
|
||||
hasRemove?: boolean
|
||||
// 删除按钮文本
|
||||
removeButtonText?: string
|
||||
// 删除按钮权限编码
|
||||
removeAuth?: string
|
||||
// 是否启用默认导出
|
||||
hasExport?: boolean
|
||||
// 导出按钮权限编码
|
||||
exportAuth?: string
|
||||
// 导出文件名
|
||||
exportFileName?: string
|
||||
// 自定义导出字段
|
||||
exportFields?: Array<{
|
||||
field: string
|
||||
title: string
|
||||
format?: Record<string, any>
|
||||
}>
|
||||
// 转换表单值的函数,可用于添加隐藏字段、格式化数据等
|
||||
transformFormValues?: (values: Record<string, any>, type: 'add' | 'edit') => Record<string, any>
|
||||
// 使用vxe-table
|
||||
useVxeTable?: boolean
|
||||
}
|
||||
// 表格列配置接口
|
||||
export interface CrudColumn<T = any> {
|
||||
// 列类型
|
||||
type?: 'selection' | 'expand' | 'index' | 'globalIndex'
|
||||
// 列属性名
|
||||
prop?: string
|
||||
// 列标题
|
||||
label?: string
|
||||
// 列宽度
|
||||
width?: string | number
|
||||
// 最小列宽度
|
||||
minWidth?: string | number
|
||||
// 固定列
|
||||
fixed?: boolean | 'left' | 'right'
|
||||
// 是否可排序
|
||||
sortable?: boolean
|
||||
// 过滤器选项
|
||||
filters?: any[]
|
||||
// 过滤方法
|
||||
filterMethod?: (value: any, row: any) => boolean
|
||||
// 过滤器位置
|
||||
filterPlacement?: string
|
||||
// 是否禁用
|
||||
disabled?: boolean
|
||||
// 是否选中显示(可用于隐藏列)
|
||||
checked?: boolean
|
||||
// 自定义渲染函数
|
||||
formatter?: (row: T) => any
|
||||
// 插槽相关配置
|
||||
// 是否使用插槽渲染内容
|
||||
useSlot?: boolean
|
||||
// 插槽名称(默认为 prop 值)
|
||||
slotName?: string
|
||||
// 是否使用表头插槽
|
||||
useHeaderSlot?: boolean
|
||||
// 表头插槽名称(默认为 `${prop}-header`)
|
||||
headerSlotName?: string
|
||||
// 其他属性
|
||||
[key: string]: any
|
||||
// 列回显组件
|
||||
component?: string
|
||||
// 列回显组件属性
|
||||
componentProps?: Record<string, any>
|
||||
// vxe-table列定义
|
||||
vxeColumn?: VxeColumnProps
|
||||
}
|
||||
export interface CrudApi {
|
||||
add?: (params: any) => Promise<any>
|
||||
edit?: (params: any) => Promise<any>
|
||||
remove?: (params: any) => Promise<any>
|
||||
view?: (params: any) => Promise<any>
|
||||
export?: (params: any) => Promise<any>
|
||||
}
|
||||
// 只读配置
|
||||
export interface CrudReadonlyState {
|
||||
selection: []
|
||||
}
|
||||
// 表格基础方法,和tableRef和tableApi相同的方法
|
||||
export interface CrudBaseMethods {
|
||||
// 重新加载数据
|
||||
reload: (params?: Record<string, any>) => Promise<any>
|
||||
// 设置组件状态(props)
|
||||
setState: (state: CrudSchema) => Promise<void>
|
||||
// 获取组件状态(props)
|
||||
getState: () => Promise<CrudSchema>
|
||||
// 获取勾选行信息
|
||||
getRowSelection: () => []
|
||||
// 获取只读状态信息
|
||||
getReadonlyState: () => CrudReadonlyState
|
||||
// 获取art table实例
|
||||
getArtTableInstance: () => any
|
||||
// 执行默认导出
|
||||
exportData: () => Promise<void>
|
||||
// 获取vxe table实例
|
||||
getVxeTableInstance: () => any
|
||||
// 通过接口删除行
|
||||
removeByApi: (row: any) => void
|
||||
// 打开内置新增表单
|
||||
openAddDialog: () => void
|
||||
// 打开内置修改表单
|
||||
openEditDialog: () => void
|
||||
// 打开内置详情表单
|
||||
openViewDialog: () => void
|
||||
// 执行内置的删除方法-行删除
|
||||
executeRemove: () => void
|
||||
// 执行内置的批量删除方法
|
||||
executeBatchRemove: () => void
|
||||
}
|
||||
export type CrudInstance = CrudBaseMethods
|
||||
// 定义表格方法接口,提供更好的类型提示
|
||||
export interface CrudMethods extends CrudBaseMethods {
|
||||
// 获取crud实例
|
||||
getCrudRef(): any
|
||||
// 获取art表格实例,可通过该方法来操作原生的art-table组件
|
||||
getArtTableInstance(): any
|
||||
// 刷新策略
|
||||
/** 全量刷新:清空所有缓存,重新获取数据(适用于手动刷新按钮) */
|
||||
refreshData(): void
|
||||
/** 轻量刷新:仅清空当前搜索条件的缓存,保持分页状态(适用于定时刷新) */
|
||||
refreshSoft(): void
|
||||
/** 新增后刷新:回到第一页并清空分页缓存(适用于新增数据后) */
|
||||
refreshCreate(): void
|
||||
/** 更新后刷新:保持当前页,仅清空当前搜索缓存(适用于更新数据后) */
|
||||
refreshUpdate(): void
|
||||
/** 删除后刷新:智能处理页码,避免空页面(适用于删除数据后) */
|
||||
refreshRemove(): void
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { ActionItem } from '../table-action'
|
||||
import { CrudMethods, CrudSchema } from './types'
|
||||
|
||||
export function useAction(
|
||||
crudSchema: CrudSchema,
|
||||
dialogFormRef: Ref<any>,
|
||||
crudMethods: CrudMethods
|
||||
) {
|
||||
// 左侧栏工具,默认有新增按钮
|
||||
const toolbarActions = computed(() => {
|
||||
const actions: ActionItem[] = []
|
||||
if (crudSchema.useCrud === true && crudSchema.hasAdd !== false) {
|
||||
actions.push({
|
||||
label: crudSchema.addButtonText || '新增',
|
||||
type: 'primary',
|
||||
icon: 'ant-design:plus-outlined',
|
||||
auth: crudSchema.addAuth,
|
||||
dialogRef: dialogFormRef,
|
||||
dialogParams: {
|
||||
isUpdate: false,
|
||||
getCrudApi: () => crudMethods,
|
||||
type: 'add'
|
||||
}
|
||||
})
|
||||
}
|
||||
actions.push(...(crudSchema.toolbarActions || []))
|
||||
return actions
|
||||
})
|
||||
// 左侧栏工具-下拉菜单,默认有删除按钮
|
||||
const dropDownToolbarActions = computed(() => {
|
||||
const actions: ActionItem[] = []
|
||||
if (crudSchema.useCrud === true && crudSchema.hasRemove !== false) {
|
||||
actions.push({
|
||||
label: '批量删除',
|
||||
type: 'danger',
|
||||
icon: 'ant-design:delete-outlined',
|
||||
auth: crudSchema.removeAuth,
|
||||
ifShow: () => crudMethods.getReadonlyState().selection.length > 0,
|
||||
confirm: {
|
||||
title: '提示',
|
||||
content: '确定批量删除吗?',
|
||||
ok() {
|
||||
crudMethods.removeByApi({})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
actions.push(...(crudSchema.dropDownToolbarActions || []))
|
||||
return actions
|
||||
})
|
||||
// 行操作-默认有详情、编辑
|
||||
const tableActions = computed(() => {
|
||||
const actions: ActionItem[] = []
|
||||
if (crudSchema.useCrud === true && crudSchema.hasView !== false) {
|
||||
actions.push({
|
||||
label: crudSchema.viewButtonText || '详情',
|
||||
type: 'primary',
|
||||
link: true,
|
||||
icon: 'ant-design:eye-outlined',
|
||||
auth: crudSchema.viewAuth,
|
||||
dialogRef: dialogFormRef,
|
||||
dialogParams() {
|
||||
return {
|
||||
getCrudApi: () => crudMethods
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (crudSchema.useCrud === true && crudSchema.hasEdit !== false) {
|
||||
actions.push({
|
||||
label: crudSchema.editButtonText || '编辑',
|
||||
type: 'primary',
|
||||
link: true,
|
||||
icon: 'ant-design:edit-outlined',
|
||||
auth: crudSchema.editAuth,
|
||||
dialogRef: dialogFormRef,
|
||||
dialogParams: {
|
||||
isUpdate: true,
|
||||
type: 'edit',
|
||||
getCrudApi: () => crudMethods
|
||||
}
|
||||
})
|
||||
}
|
||||
actions.push(...(crudSchema.tableActions || []))
|
||||
return actions
|
||||
})
|
||||
// 行操作-下拉菜单默认有删除按钮,树形表格默认添加子节点按钮
|
||||
const dropDownActions = computed(() => {
|
||||
const actions: ActionItem[] = []
|
||||
if (crudSchema.useCrud === true && crudSchema.hasAdd !== false) {
|
||||
// 判断一下是否则为树结构
|
||||
if (crudSchema.tree) {
|
||||
actions.push({
|
||||
label: (crudSchema.addButtonText || '新增') + '子节点',
|
||||
icon: 'ant-design:plus-outlined',
|
||||
size: 'small',
|
||||
auth: crudSchema.addAuth,
|
||||
dialogRef: dialogFormRef,
|
||||
dialogParams: {
|
||||
isUpdate: true,
|
||||
type: 'add',
|
||||
tree: crudSchema.tree,
|
||||
getCrudApi: () => crudMethods
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
if (crudSchema.useCrud === true && crudSchema.hasRemove !== false) {
|
||||
actions.push({
|
||||
label: crudSchema.removeButtonText || '删除',
|
||||
type: 'danger',
|
||||
icon: 'ant-design:delete-outlined',
|
||||
size: 'small',
|
||||
auth: crudSchema.removeAuth,
|
||||
confirm: {
|
||||
title: '提示',
|
||||
content: '确定删除吗?',
|
||||
ok(record: any) {
|
||||
crudMethods.removeByApi(record)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
actions.push(...(crudSchema.dropDownActions || []))
|
||||
return actions
|
||||
})
|
||||
return {
|
||||
toolbarActions,
|
||||
dropDownToolbarActions,
|
||||
tableActions,
|
||||
dropDownActions
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { ActionItem, TableAction } from '../table-action'
|
||||
import { viewColumnFormatter } from '../view/component-map'
|
||||
import { CrudSchema } from './types'
|
||||
|
||||
export function useColumns(
|
||||
crudSchemaRef: Ref<CrudSchema>,
|
||||
tableActions: Ref<ActionItem[]>,
|
||||
dropDownActions: Ref<ActionItem[]>,
|
||||
handleActionItemClick: (record: any) => void
|
||||
) {
|
||||
if (crudSchemaRef.value?.useVxeTable) {
|
||||
const columns =
|
||||
(typeof crudSchemaRef.value.columns === 'function'
|
||||
? crudSchemaRef.value.columns()
|
||||
: crudSchemaRef.value.columns) || []
|
||||
const vxeTableColumns = columns.map((column) => {
|
||||
return {
|
||||
type: column.type === 'selection' ? 'checkbox' : column.type,
|
||||
field: column.prop,
|
||||
title: column.label,
|
||||
align: column.align,
|
||||
width: column.type === 'selection' ? 60 : column.width,
|
||||
showOverflow: 'tooltip',
|
||||
cellRender: {
|
||||
name: column.component,
|
||||
props: column.componentProps
|
||||
},
|
||||
...column.vxeColumn
|
||||
}
|
||||
})
|
||||
// 有操作列配置才添加操作列
|
||||
if (
|
||||
(tableActions.value && tableActions.value.length > 0) ||
|
||||
(dropDownActions.value && dropDownActions.value.length > 0)
|
||||
) {
|
||||
// @ts-nocheck 如果想忽略整个文件检查可使用此注释
|
||||
vxeTableColumns.push({
|
||||
type: 'default',
|
||||
field: '__ACTION__',
|
||||
title: '操作',
|
||||
width: 220,
|
||||
align: 'center',
|
||||
showOverflow: 'tooltip', // 显式定义以避免类型错误
|
||||
slots: {
|
||||
default: ({ row }: { row: any }) => {
|
||||
return (
|
||||
<TableAction
|
||||
onActionItemClick={handleActionItemClick}
|
||||
record={row}
|
||||
actions={tableActions.value}
|
||||
dropDownActions={dropDownActions.value}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
...crudSchemaRef.value.tableActionColumn?.vxeColumn
|
||||
} as any)
|
||||
}
|
||||
return { columns: vxeTableColumns }
|
||||
}
|
||||
const columns = (
|
||||
(typeof crudSchemaRef.value.columns === 'function'
|
||||
? crudSchemaRef.value.columns()
|
||||
: crudSchemaRef.value.columns) || []
|
||||
).map((column) => {
|
||||
const c = { ...column }
|
||||
viewColumnFormatter(column)
|
||||
c.formatter = column.formatter
|
||||
delete c.component
|
||||
delete c.componentProps
|
||||
return c
|
||||
})
|
||||
// 有操作列配置才添加操作列
|
||||
if (
|
||||
(tableActions.value && tableActions.value.length > 0) ||
|
||||
(dropDownActions.value && dropDownActions.value.length > 0)
|
||||
) {
|
||||
columns.push({
|
||||
prop: '__ACTION__',
|
||||
label: '操作',
|
||||
width: 220,
|
||||
align: 'center',
|
||||
fixed: 'right', // 固定列
|
||||
formatter: (row: unknown) => {
|
||||
return (
|
||||
<TableAction
|
||||
onActionItemClick={handleActionItemClick}
|
||||
record={row}
|
||||
actions={tableActions.value}
|
||||
dropDownActions={dropDownActions.value}
|
||||
/>
|
||||
)
|
||||
},
|
||||
...crudSchemaRef.value.tableActionColumn
|
||||
})
|
||||
}
|
||||
return {
|
||||
columns
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { CrudMethods, CrudSchema } from '.'
|
||||
import Crud from './crud.vue'
|
||||
/**
|
||||
*
|
||||
* 防止未来重构影响
|
||||
* 通过统一的 useCrud 钩子封装,即使内部实现发生重构,外部调用方也不会受到影响
|
||||
* 提供稳定的API接口,隔离底层实现细节的变化
|
||||
* @param crudSchema
|
||||
* @returns
|
||||
*/
|
||||
export function useCrud(crudSchema: CrudSchema) {
|
||||
const crudRef = ref<any>(null)
|
||||
const register = (crudInstance: any) => {
|
||||
crudRef.value = crudInstance
|
||||
}
|
||||
const crudMethods: CrudMethods = {
|
||||
// 获取表格实例
|
||||
getCrudRef() {
|
||||
return crudRef.value
|
||||
},
|
||||
// 设置组件状态(props)
|
||||
setState(state: any) {
|
||||
return crudRef.value?.setState(state)
|
||||
},
|
||||
// 获取组件状态
|
||||
getState() {
|
||||
return crudRef.value?.getState()
|
||||
},
|
||||
getRowSelection() {
|
||||
return crudRef.value?.getRowSelection()
|
||||
},
|
||||
// 刷新表格数据,保留当前表单搜索条件
|
||||
reload(params?: Record<string, any>) {
|
||||
return crudRef.value?.reload(params)
|
||||
},
|
||||
// 获取只读状态信息
|
||||
getReadonlyState() {
|
||||
return crudRef.value?.getReadonlyState() || { selection: [] }
|
||||
},
|
||||
// 通过接口删除行
|
||||
removeByApi(row) {
|
||||
return crudRef.value?.removeByApi(row)
|
||||
},
|
||||
// 获取art表格实例,可通过该方法来操作原生的art-table组件
|
||||
// 个人建议,可以在该文件上包装一层再暴露给外部使用,如上述的reload方法一样
|
||||
getArtTableInstance() {
|
||||
return crudRef.value?.getArtTableInstance()
|
||||
},
|
||||
exportData() {
|
||||
return crudRef.value?.exportData()
|
||||
},
|
||||
getVxeTableInstance() {
|
||||
return crudRef.value?.getVxeTableInstance()
|
||||
},
|
||||
// 刷新策略
|
||||
/** 全量刷新:清空所有缓存,重新获取数据(适用于手动刷新按钮) */
|
||||
refreshData() {
|
||||
if (crudSchema.useVxeTable) {
|
||||
return this.getVxeTableInstance()?.commitProxy('reload', {})
|
||||
}
|
||||
return this.getArtTableInstance()?.refreshData()
|
||||
},
|
||||
/** 轻量刷新:仅清空当前搜索条件的缓存,保持分页状态(适用于定时刷新) */
|
||||
refreshSoft() {
|
||||
if (crudSchema.useVxeTable) {
|
||||
return this.getVxeTableInstance()?.commitProxy('query', {})
|
||||
}
|
||||
return this.getArtTableInstance()?.refreshSoft()
|
||||
},
|
||||
/** 新增后刷新:回到第一页并清空分页缓存(适用于新增数据后) */
|
||||
refreshCreate() {
|
||||
if (crudSchema.useVxeTable) {
|
||||
return this.getVxeTableInstance()?.commitProxy('reload', {})
|
||||
}
|
||||
return this.getArtTableInstance()?.refreshCreate()
|
||||
},
|
||||
/** 更新后刷新:保持当前页,仅清空当前搜索缓存(适用于更新数据后) */
|
||||
refreshUpdate() {
|
||||
if (crudSchema.useVxeTable) {
|
||||
return this.getVxeTableInstance()?.commitProxy('query', {})
|
||||
}
|
||||
return this.getArtTableInstance()?.refreshUpdate()
|
||||
},
|
||||
/** 删除后刷新:智能处理页码,避免空页面(适用于删除数据后) */
|
||||
refreshRemove() {
|
||||
if (crudSchema.useVxeTable) {
|
||||
const gridApi = this.getVxeTableInstance()
|
||||
const records = gridApi.getCheckboxRecords()
|
||||
if (
|
||||
gridApi.getTableData()?.fullData?.length === 1 ||
|
||||
gridApi.getTableData()?.fullData?.length === records.length
|
||||
) {
|
||||
const { pager }: any = gridApi.getProxyInfo()
|
||||
if (pager.currentPage > 1) {
|
||||
pager.currentPage = pager.currentPage - 1
|
||||
return this.getVxeTableInstance()?.commitProxy('query', {})
|
||||
} else {
|
||||
if (pager.total >= gridApi.getTableData()?.fullData?.length) {
|
||||
return this.getVxeTableInstance()?.commitProxy('query', {})
|
||||
} else {
|
||||
return this.getVxeTableInstance()?.commitProxy('reload', {})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return this.getVxeTableInstance()?.commitProxy('query', {})
|
||||
}
|
||||
}
|
||||
return this.getArtTableInstance()?.refreshRemove()
|
||||
},
|
||||
// 打开内置新增表单
|
||||
openAddDialog() {
|
||||
return this.getCrudRef()?.openAddDialog()
|
||||
},
|
||||
// 打开内置修改表单
|
||||
openEditDialog() {
|
||||
return this.getCrudRef()?.openEditDialog()
|
||||
},
|
||||
// 打开内置详情表单
|
||||
openViewDialog() {
|
||||
return this.getCrudRef()?.openViewDialog()
|
||||
},
|
||||
// 执行内置的删除方法
|
||||
executeRemove() {
|
||||
return this.getCrudRef()?.executeRemove()
|
||||
},
|
||||
// 执行内置的批量删除方法
|
||||
executeBatchRemove() {
|
||||
return this.getCrudRef()?.executeBatchRemove()
|
||||
}
|
||||
}
|
||||
|
||||
// 创建表单组件的配置
|
||||
const CrudComponent = defineComponent({
|
||||
setup(_, { attrs, slots }) {
|
||||
return () =>
|
||||
h(
|
||||
Crud,
|
||||
{
|
||||
...attrs,
|
||||
crudSchema: crudSchema,
|
||||
onRegister: register,
|
||||
crudMethods: crudMethods
|
||||
},
|
||||
slots
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return [CrudComponent, crudMethods] as const
|
||||
}
|
||||
Reference in New Issue
Block a user