diff --git a/apps/web-antd/src/adapter/vxe-table.ts b/apps/web-antd/src/adapter/vxe-table.ts index b1a78b3b0..a9b26d542 100644 --- a/apps/web-antd/src/adapter/vxe-table.ts +++ b/apps/web-antd/src/adapter/vxe-table.ts @@ -1,12 +1,18 @@ import { h } from 'vue'; +import { IconifyIcon } from '@vben/icons'; +import { $te } from '@vben/locales'; import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; +import { isFunction, isString } from '@vben/utils'; -import { Button, Image, Tag } from 'ant-design-vue'; +import { Button, Image, Popconfirm, Switch, Tag } from 'ant-design-vue'; import { getDictObj } from '#/utils/dict'; import { useVbenForm } from './form'; +import type { Recordable } from '@vben/types'; + +import { $t } from '#/locales'; setupVbenVxeTable({ configVxeTable: (vxeUI) => { @@ -100,6 +106,167 @@ setupVbenVxeTable({ }, }); + // 表格配置项可以用 cellRender: { name: 'CellSwitch', props: { beforeChange: () => {} } }, + // add by 芋艿:from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L97-L123 + vxeUI.renderer.add('CellSwitch', { + renderTableDefault({ attrs, props }, { column, row }) { + const loadingKey = `__loading_${column.field}`; + const finallyProps = { + checkedChildren: $t('common.enabled'), + checkedValue: 1, + unCheckedChildren: $t('common.disabled'), + unCheckedValue: 0, + ...props, + checked: row[column.field], + loading: row[loadingKey] ?? false, + 'onUpdate:checked': onChange, + }; + async function onChange(newVal: any) { + row[loadingKey] = true; + try { + const result = await attrs?.beforeChange?.(newVal, row); + if (result !== false) { + row[column.field] = newVal; + } + } finally { + row[loadingKey] = false; + } + } + return h(Switch, finallyProps); + }, + }); + + // 注册表格的操作按钮渲染器 cellRender: { name: 'CellOperation', options: ['edit', 'delete'] } + // add by 芋艿:from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L125-L255 + vxeUI.renderer.add('CellOperation', { + renderTableDefault({ attrs, options, props }, { column, row }) { + const defaultProps = { size: 'small', type: 'link', ...props }; + let align = 'end'; + switch (column.align) { + case 'center': { + align = 'center'; + break; + } + case 'left': { + align = 'start'; + break; + } + default: { + align = 'end'; + break; + } + } + const presets: Recordable> = { + delete: { + danger: true, + text: $t('common.delete'), + }, + edit: { + text: $t('common.edit'), + }, + }; + const operations: Array> = ( + options || ['edit', 'delete'] + ) + .map((opt) => { + if (isString(opt)) { + return presets[opt] + ? { code: opt, ...presets[opt], ...defaultProps } + : { + code: opt, + text: $te(`common.${opt}`) ? $t(`common.${opt}`) : opt, + ...defaultProps, + }; + } else { + return { ...defaultProps, ...presets[opt.code], ...opt }; + } + }) + .map((opt) => { + const optBtn: Recordable = {}; + Object.keys(opt).forEach((key) => { + optBtn[key] = isFunction(opt[key]) ? opt[key](row) : opt[key]; + }); + return optBtn; + }) + .filter((opt) => opt.show !== false); + + function renderBtn(opt: Recordable, listen = true) { + return h( + Button, + { + ...props, + ...opt, + icon: undefined, + onClick: listen + ? () => + attrs?.onClick?.({ + code: opt.code, + row, + }) + : undefined, + }, + { + default: () => { + const content = []; + if (opt.icon) { + content.push( + h(IconifyIcon, { class: 'size-5', icon: opt.icon }), + ); + } + content.push(opt.text); + return content; + }, + }, + ); + } + + function renderConfirm(opt: Recordable) { + return h( + Popconfirm, + { + getPopupContainer(el) { + return el.closest('tbody') || document.body; + }, + placement: 'topLeft', + title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']), + ...props, + ...opt, + icon: undefined, + onConfirm: () => { + attrs?.onClick?.({ + code: opt.code, + row, + }); + }, + }, + { + default: () => renderBtn({ ...opt }, false), + description: () => + h( + 'div', + { class: 'truncate' }, + $t('ui.actionMessage.deleteConfirm', [ + row[attrs?.nameField || 'name'], + ]), + ), + }, + ); + } + + const btns = operations.map((opt) => + opt.code === 'delete' ? renderConfirm(opt) : renderBtn(opt), + ); + return h( + 'div', + { + class: 'flex table-operations', + style: { justifyContent: align }, + }, + btns, + ); + }, + }); + // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 // vxeUI.formats.add }, @@ -107,5 +274,12 @@ setupVbenVxeTable({ }); export { useVbenVxeGrid }; - +// add by 芋艿:from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L264-L270 +export type OnActionClickParams> = { + code: string; + row: T; +}; +export type OnActionClickFn> = ( + params: OnActionClickParams, +) => void; export type * from '@vben/plugins/vxe-table';