Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,403 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { defineAsyncComponent, defineComponent, h, ref } from 'vue';
|
||||
|
||||
import { ApiComponent, globalShareState } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElNotification } from 'element-plus';
|
||||
|
||||
const ElButton = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/button/index'),
|
||||
import('element-plus/es/components/button/style/css'),
|
||||
]).then(([res]) => res.ElButton),
|
||||
);
|
||||
const ElCheckbox = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/checkbox/index'),
|
||||
import('element-plus/es/components/checkbox/style/css'),
|
||||
]).then(([res]) => res.ElCheckbox),
|
||||
);
|
||||
const ElCheckboxButton = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/checkbox/index'),
|
||||
import('element-plus/es/components/checkbox-button/style/css'),
|
||||
]).then(([res]) => res.ElCheckboxButton),
|
||||
);
|
||||
const ElCheckboxGroup = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/checkbox/index'),
|
||||
import('element-plus/es/components/checkbox-group/style/css'),
|
||||
]).then(([res]) => res.ElCheckboxGroup),
|
||||
);
|
||||
const ElCascader = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/cascader/index'),
|
||||
import('element-plus/es/components/cascader/style/css'),
|
||||
]).then(([res]) => res.ElCascader),
|
||||
);
|
||||
const ElDatePicker = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/date-picker/index'),
|
||||
import('element-plus/es/components/date-picker/style/css'),
|
||||
]).then(([res]) => res.ElDatePicker),
|
||||
);
|
||||
const DeptSelector = defineAsyncComponent(() =>
|
||||
import('#/components/zq-form/dept-selector/dept-selector.vue').then(
|
||||
(res) => res.default,
|
||||
),
|
||||
);
|
||||
const PostSelector = defineAsyncComponent(() =>
|
||||
import('#/components/zq-form/post-selector/post-selector.vue').then(
|
||||
(res) => res.default,
|
||||
),
|
||||
);
|
||||
const RoleSelector = defineAsyncComponent(() =>
|
||||
import('#/components/zq-form/role-selector/role-selector.vue').then(
|
||||
(res) => res.default,
|
||||
),
|
||||
);
|
||||
const FileSelector = defineAsyncComponent(() =>
|
||||
import('#/components/zq-form/file-selector/file-selector.vue').then(
|
||||
(res) => res.default,
|
||||
),
|
||||
);
|
||||
const ImageSelector = defineAsyncComponent(() =>
|
||||
import('#/components/zq-form/image-selector/image-selector.vue').then(
|
||||
(res) => res.default,
|
||||
),
|
||||
);
|
||||
const UserSelector = defineAsyncComponent(() =>
|
||||
import('#/components/zq-form/user-selector/user-selector.vue').then(
|
||||
(res) => res.default,
|
||||
),
|
||||
);
|
||||
const JsonEditor = defineAsyncComponent(() =>
|
||||
import('#/components/json-editor/json-editor.vue').then((res) => res.default),
|
||||
);
|
||||
const ZqIconPicker = defineAsyncComponent(() =>
|
||||
import('#/components/zq-form/zq-icon-picker/zq-icon-picker.vue').then(
|
||||
(res) => res.default,
|
||||
),
|
||||
);
|
||||
const CronSelector = defineAsyncComponent(() =>
|
||||
import('#/components/zq-form/cron-selector/cron-selector.vue').then(
|
||||
(res) => res.default,
|
||||
),
|
||||
);
|
||||
const CodeEditor = defineAsyncComponent(() =>
|
||||
import('#/components/zq-form/code-editor/code-editor.vue').then(
|
||||
(res) => res.default,
|
||||
),
|
||||
);
|
||||
const ElDivider = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/divider/index'),
|
||||
import('element-plus/es/components/divider/style/css'),
|
||||
]).then(([res]) => res.ElDivider),
|
||||
);
|
||||
const ElInput = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/input/index'),
|
||||
import('element-plus/es/components/input/style/css'),
|
||||
]).then(([res]) => res.ElInput),
|
||||
);
|
||||
const ElInputNumber = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/input-number/index'),
|
||||
import('element-plus/es/components/input-number/style/css'),
|
||||
]).then(([res]) => res.ElInputNumber),
|
||||
);
|
||||
const ElRadio = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/radio/index'),
|
||||
import('element-plus/es/components/radio/style/css'),
|
||||
]).then(([res]) => res.ElRadio),
|
||||
);
|
||||
const ElRadioButton = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/radio/index'),
|
||||
import('element-plus/es/components/radio-button/style/css'),
|
||||
]).then(([res]) => res.ElRadioButton),
|
||||
);
|
||||
const ElRadioGroup = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/radio/index'),
|
||||
import('element-plus/es/components/radio-group/style/css'),
|
||||
]).then(([res]) => res.ElRadioGroup),
|
||||
);
|
||||
const ElSelectV2 = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/select-v2/index'),
|
||||
import('element-plus/es/components/select-v2/style/css'),
|
||||
]).then(([res]) => res.ElSelectV2),
|
||||
);
|
||||
const ElSpace = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/space/index'),
|
||||
import('element-plus/es/components/space/style/css'),
|
||||
]).then(([res]) => res.ElSpace),
|
||||
);
|
||||
const ElSwitch = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/switch/index'),
|
||||
import('element-plus/es/components/switch/style/css'),
|
||||
]).then(([res]) => res.ElSwitch),
|
||||
);
|
||||
const ElTimePicker = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/time-picker/index'),
|
||||
import('element-plus/es/components/time-picker/style/css'),
|
||||
]).then(([res]) => res.ElTimePicker),
|
||||
);
|
||||
const ElTreeSelect = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/tree-select/index'),
|
||||
import('element-plus/es/components/tree-select/style/css'),
|
||||
]).then(([res]) => res.ElTreeSelect),
|
||||
);
|
||||
const ElUpload = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/upload/index'),
|
||||
import('element-plus/es/components/upload/style/css'),
|
||||
]).then(([res]) => res.ElUpload),
|
||||
);
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
componentProps: Recordable<any> = {},
|
||||
) => {
|
||||
return defineComponent({
|
||||
name: component.name,
|
||||
inheritAttrs: false,
|
||||
setup: (props: any, { attrs, expose, slots }) => {
|
||||
const placeholder =
|
||||
props?.placeholder ||
|
||||
attrs?.placeholder ||
|
||||
$t(`ui.placeholder.${type}`);
|
||||
// 透传组件暴露的方法
|
||||
const innerRef = ref();
|
||||
expose(
|
||||
new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_target, key) => innerRef.value?.[key],
|
||||
has: (_target, key) => key in (innerRef.value || {}),
|
||||
},
|
||||
),
|
||||
);
|
||||
return () =>
|
||||
h(
|
||||
component,
|
||||
{ ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
|
||||
slots,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'ApiSelect'
|
||||
| 'ApiTreeSelect'
|
||||
| 'Cascader'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'CodeEditor'
|
||||
| 'CronSelector'
|
||||
| 'DatePicker'
|
||||
| 'DeptSelector'
|
||||
| 'Divider'
|
||||
| 'FileSelector'
|
||||
| 'IconPicker'
|
||||
| 'ImageSelector'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'JsonEditor'
|
||||
| 'PostSelector'
|
||||
| 'RadioGroup'
|
||||
| 'RoleSelector'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| 'UserSelector'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
ApiSelect: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
name: 'ApiSelect',
|
||||
},
|
||||
'select',
|
||||
{
|
||||
component: ElSelectV2,
|
||||
loadingSlot: 'loading',
|
||||
visibleEvent: 'onVisibleChange',
|
||||
},
|
||||
),
|
||||
ApiTreeSelect: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
name: 'ApiTreeSelect',
|
||||
},
|
||||
'select',
|
||||
{
|
||||
component: ElTreeSelect,
|
||||
props: { label: 'label', children: 'children' },
|
||||
nodeKey: 'value',
|
||||
loadingSlot: 'loading',
|
||||
optionsPropName: 'data',
|
||||
visibleEvent: 'onVisibleChange',
|
||||
},
|
||||
),
|
||||
Cascader: withDefaultPlaceholder(ElCascader, 'select'),
|
||||
Checkbox: ElCheckbox,
|
||||
CheckboxGroup: (props, { attrs, slots }) => {
|
||||
let defaultSlot;
|
||||
if (Reflect.has(slots, 'default')) {
|
||||
defaultSlot = slots.default;
|
||||
} else {
|
||||
const { options, isButton } = attrs;
|
||||
if (Array.isArray(options)) {
|
||||
defaultSlot = () =>
|
||||
options.map((option) =>
|
||||
h(isButton ? ElCheckboxButton : ElCheckbox, option),
|
||||
);
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElCheckboxGroup,
|
||||
{ ...props, ...attrs },
|
||||
{ ...slots, default: defaultSlot },
|
||||
);
|
||||
},
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs }, slots);
|
||||
},
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
DeptSelector: withDefaultPlaceholder(DeptSelector, 'select'),
|
||||
PostSelector: withDefaultPlaceholder(PostSelector, 'select'),
|
||||
RoleSelector: withDefaultPlaceholder(RoleSelector, 'select'),
|
||||
Divider: ElDivider,
|
||||
FileSelector: withDefaultPlaceholder(FileSelector, 'select'),
|
||||
IconPicker: withDefaultPlaceholder(ZqIconPicker, 'select'),
|
||||
ImageSelector: withDefaultPlaceholder(ImageSelector, 'select'),
|
||||
Input: withDefaultPlaceholder(ElInput, 'input'),
|
||||
Textarea: withDefaultPlaceholder(ElInput, 'input', { type: 'textarea' }),
|
||||
JsonEditor,
|
||||
CodeEditor,
|
||||
CronSelector,
|
||||
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
||||
RadioGroup: (props, { attrs, slots }) => {
|
||||
let defaultSlot;
|
||||
if (Reflect.has(slots, 'default')) {
|
||||
defaultSlot = slots.default;
|
||||
} else {
|
||||
const { options } = attrs;
|
||||
if (Array.isArray(options)) {
|
||||
defaultSlot = () =>
|
||||
options.map((option) =>
|
||||
h(attrs.isButton ? ElRadioButton : ElRadio, option),
|
||||
);
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElRadioGroup,
|
||||
{ ...props, ...attrs },
|
||||
{ ...slots, default: defaultSlot },
|
||||
);
|
||||
},
|
||||
Select: (props, { attrs, slots }) => {
|
||||
return h(ElSelectV2, { ...props, attrs }, slots);
|
||||
},
|
||||
Space: ElSpace,
|
||||
Switch: ElSwitch,
|
||||
TimePicker: (props, { attrs, slots }) => {
|
||||
const { name, id, isRange } = props;
|
||||
const extraProps: Recordable<any> = {};
|
||||
if (isRange) {
|
||||
if (name && !Array.isArray(name)) {
|
||||
extraProps.name = [name, `${name}_end`];
|
||||
}
|
||||
if (id && !Array.isArray(id)) {
|
||||
extraProps.id = [id, `${id}_end`];
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElTimePicker,
|
||||
{
|
||||
...props,
|
||||
...attrs,
|
||||
...extraProps,
|
||||
},
|
||||
slots,
|
||||
);
|
||||
},
|
||||
DatePicker: (props, { attrs, slots }) => {
|
||||
const { name, id, type } = props;
|
||||
const extraProps: Recordable<any> = {};
|
||||
if (type && type.includes('range')) {
|
||||
if (name && !Array.isArray(name)) {
|
||||
extraProps.name = [name, `${name}_end`];
|
||||
}
|
||||
if (id && !Array.isArray(id)) {
|
||||
extraProps.id = [id, `${id}_end`];
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElDatePicker,
|
||||
{
|
||||
...props,
|
||||
...attrs,
|
||||
...extraProps,
|
||||
},
|
||||
slots,
|
||||
);
|
||||
},
|
||||
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
|
||||
Upload: ElUpload,
|
||||
UserSelector: withDefaultPlaceholder(UserSelector, 'select'),
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
ElNotification({
|
||||
title,
|
||||
message: content,
|
||||
position: 'bottom-right',
|
||||
duration: 0,
|
||||
type: 'success',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
||||
@@ -0,0 +1,41 @@
|
||||
import type {
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
async function initSetupVbenForm() {
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
modelPropNameMap: {
|
||||
Upload: 'fileList',
|
||||
CheckboxGroup: 'model-value',
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
required: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
selectRequired: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null) {
|
||||
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { initSetupVbenForm, useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
@@ -0,0 +1,290 @@
|
||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { $t, $te } from '@vben/locales';
|
||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||
import { get, isFunction, isString } from '@vben/utils';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElImage,
|
||||
ElPopconfirm,
|
||||
ElTag,
|
||||
ElTooltip,
|
||||
} from 'element-plus';
|
||||
|
||||
import { useVbenForm } from './form';
|
||||
|
||||
setupVbenVxeTable({
|
||||
configVxeTable: (vxeUI) => {
|
||||
vxeUI.setConfig({
|
||||
grid: {
|
||||
align: 'center',
|
||||
border: false,
|
||||
columnConfig: {
|
||||
resizable: true,
|
||||
},
|
||||
minHeight: 180,
|
||||
formConfig: {
|
||||
// 全局禁用vxe-table的表单配置,使用formOptions
|
||||
enabled: false,
|
||||
},
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
response: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
list: '',
|
||||
},
|
||||
showActionMsg: true,
|
||||
showResponseMsg: false,
|
||||
},
|
||||
round: true,
|
||||
showOverflow: true,
|
||||
size: 'medium',
|
||||
} as VxeTableGridOptions,
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
||||
vxeUI.renderer.add('CellImage', {
|
||||
renderTableDefault(_renderOpts, params) {
|
||||
const { column, row } = params;
|
||||
const src = row[column.field];
|
||||
return h(ElImage, { src, previewSrcList: [src] });
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
||||
vxeUI.renderer.add('CellLink', {
|
||||
renderTableDefault(renderOpts) {
|
||||
const { props } = renderOpts;
|
||||
return h(
|
||||
ElButton,
|
||||
{ size: 'small', link: true },
|
||||
{ default: () => props?.text },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 单元格渲染: Tag
|
||||
vxeUI.renderer.add('CellTag', {
|
||||
renderTableDefault({ options, props }, { column, row }) {
|
||||
const value = get(row, column.field);
|
||||
const tagOptions = options ?? [
|
||||
{ type: 'success', label: $t('common.enabled'), value: true },
|
||||
{ type: 'danger', label: $t('common.disabled'), value: false },
|
||||
];
|
||||
const tagItem = tagOptions.find((item) => item.value === value);
|
||||
return h(
|
||||
ElTag,
|
||||
{
|
||||
type: tagItem?.type ?? 'info',
|
||||
...props,
|
||||
},
|
||||
{ default: () => tagItem?.label ?? value },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 注册表格的操作按钮渲染器
|
||||
*/
|
||||
vxeUI.renderer.add('CellOperation', {
|
||||
renderTableDefault({ attrs, options, props }, { column, row }) {
|
||||
const defaultProps = { size: 'small', link: true, ...props };
|
||||
let align = 'end';
|
||||
switch (column.align) {
|
||||
case 'center': {
|
||||
align = 'center';
|
||||
break;
|
||||
}
|
||||
case 'left': {
|
||||
align = 'start';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
align = 'end';
|
||||
break;
|
||||
}
|
||||
}
|
||||
const presets: Recordable<Recordable<any>> = {
|
||||
delete: {
|
||||
type: 'danger',
|
||||
text: $t('common.delete'),
|
||||
icon: 'ep:delete',
|
||||
},
|
||||
edit: {
|
||||
text: $t('common.edit'),
|
||||
icon: 'ep:edit',
|
||||
type: 'primary',
|
||||
},
|
||||
};
|
||||
const operations: Array<Recordable<any>> = (
|
||||
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,
|
||||
type: 'primary', // 自定义按钮默认使用主题色
|
||||
...defaultProps,
|
||||
};
|
||||
} else {
|
||||
// 对象配置的按钮,如果没有 type,也默认为 primary
|
||||
const buttonConfig = {
|
||||
...defaultProps,
|
||||
...presets[opt.code],
|
||||
...opt,
|
||||
};
|
||||
if (!buttonConfig.type && !presets[opt.code]) {
|
||||
buttonConfig.type = 'primary';
|
||||
}
|
||||
return buttonConfig;
|
||||
}
|
||||
})
|
||||
.map((opt) => {
|
||||
const optBtn: Recordable<any> = {};
|
||||
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<any>, listen = true) {
|
||||
const { icon, text, code, ...btnProps } = opt;
|
||||
const buttonType =
|
||||
btnProps.type === 'danger'
|
||||
? 'danger'
|
||||
: (btnProps.type === 'primary'
|
||||
? 'primary'
|
||||
: 'default');
|
||||
|
||||
const button = h(
|
||||
ElButton,
|
||||
{
|
||||
...btnProps,
|
||||
type: buttonType,
|
||||
size: 'small',
|
||||
link: true,
|
||||
circle: !!icon,
|
||||
onClick: listen
|
||||
? () =>
|
||||
attrs?.onClick?.({
|
||||
code,
|
||||
row,
|
||||
})
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
if (icon) {
|
||||
return h(IconifyIcon, { class: 'size-4', icon });
|
||||
}
|
||||
return text;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// 如果有图标,用 Tooltip 包装;否则直接返回按钮
|
||||
if (icon) {
|
||||
return h(
|
||||
ElTooltip,
|
||||
{
|
||||
content: text,
|
||||
placement: 'top',
|
||||
},
|
||||
{
|
||||
default: () => button,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
function renderConfirm(opt: Recordable<any>) {
|
||||
const { icon, text } = opt;
|
||||
|
||||
const button = h(
|
||||
ElButton,
|
||||
{
|
||||
type: 'danger',
|
||||
size: 'small',
|
||||
link: true,
|
||||
circle: !!icon,
|
||||
title: icon ? text : undefined,
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
if (icon) {
|
||||
return h(IconifyIcon, { class: 'size-4', icon });
|
||||
}
|
||||
return text;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return h(
|
||||
ElPopconfirm,
|
||||
{
|
||||
title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']),
|
||||
confirmButtonText: $t('common.confirm'),
|
||||
cancelButtonText: $t('common.cancel'),
|
||||
confirmButtonType: 'danger',
|
||||
onConfirm: () => {
|
||||
attrs?.onClick?.({
|
||||
code: opt.code,
|
||||
row,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
reference: () => button,
|
||||
default: () =>
|
||||
$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
|
||||
},
|
||||
useVbenForm,
|
||||
});
|
||||
|
||||
// 自定义类型定义
|
||||
export type OnActionClickParams<T = Recordable<any>> = {
|
||||
code: string;
|
||||
row: T;
|
||||
};
|
||||
|
||||
export type OnActionClickFn<T = Recordable<any>> = (
|
||||
params: OnActionClickParams<T>,
|
||||
) => void;
|
||||
|
||||
export { useVbenVxeGrid };
|
||||
|
||||
export type * from '@vben/plugins/vxe-table';
|
||||
Reference in New Issue
Block a user