feat: harden ai agent admin workflow experience

This commit is contained in:
2026-06-22 04:50:11 +08:00
parent ae29c24529
commit 4ec846a702
385 changed files with 61461 additions and 5886 deletions
+208 -89
View File
@@ -5,84 +5,72 @@
import type { Component } from 'vue';
import type { BaseFormComponentType } from '@vben-core/form-ui';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { defineAsyncComponent, defineComponent, h, ref } from 'vue';
import { globalShareState } from '@vben-core/shared/global-state';
import { ApiComponent, globalShareState } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { ApiComponent } from '@vben/common-ui/es/api-component';
import { ElNotification } from 'element-plus';
const ElButton = defineAsyncComponent(() =>
import('element-plus/es/components/button/index').then((res) => res.ElButton),
Promise.all([
import('element-plus/es/components/button/index'),
import('element-plus/es/components/button/style/css'),
]).then(([res]) => res.ElButton),
);
const ElCheckbox = defineAsyncComponent(() =>
import('element-plus/es/components/checkbox/index').then(
(res) => res.ElCheckbox,
),
Promise.all([
import('element-plus/es/components/checkbox/index'),
import('element-plus/es/components/checkbox/style/css'),
]).then(([res]) => res.ElCheckbox),
);
const ElCheckboxButton = defineAsyncComponent(() =>
import('element-plus/es/components/checkbox/index').then(
(res) => res.ElCheckboxButton,
),
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(() =>
import('element-plus/es/components/checkbox/index').then(
(res) => res.ElCheckboxGroup,
),
Promise.all([
import('element-plus/es/components/checkbox/index'),
import('element-plus/es/components/checkbox-group/style/css'),
]).then(([res]) => res.ElCheckboxGroup),
);
const ElDivider = defineAsyncComponent(() =>
import('element-plus/es/components/divider/index').then(
(res) => res.ElDivider,
),
);
const ElInput = defineAsyncComponent(() =>
import('element-plus/es/components/input/index').then((res) => res.ElInput),
);
const ElRadio = defineAsyncComponent(() =>
import('element-plus/es/components/radio/index').then((res) => res.ElRadio),
);
const ElRadioButton = defineAsyncComponent(() =>
import('element-plus/es/components/radio/index').then(
(res) => res.ElRadioButton,
),
);
const ElRadioGroup = defineAsyncComponent(() =>
import('element-plus/es/components/radio/index').then(
(res) => res.ElRadioGroup,
),
);
const ElSpace = defineAsyncComponent(() =>
import('element-plus/es/components/space/index').then((res) => res.ElSpace),
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(() =>
import('element-plus/es/components/date-picker/index').then(
(res) => res.ElDatePicker,
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 ElInputNumber = defineAsyncComponent(() =>
import('element-plus/es/components/input-number/index').then(
(res) => res.ElInputNumber,
const PostSelector = defineAsyncComponent(() =>
import('#/components/zq-form/post-selector/post-selector.vue').then(
(res) => res.default,
),
);
const ElSelectV2 = defineAsyncComponent(() =>
import('element-plus/es/components/select-v2/index').then(
(res) => res.ElSelectV2,
),
);
const ElTreeSelect = defineAsyncComponent(() =>
import('element-plus/es/components/tree-select/index').then(
(res) => res.ElTreeSelect,
),
);
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,
@@ -93,11 +81,106 @@ const UserSelector = defineAsyncComponent(() =>
(res) => res.default,
),
);
const JsonEditor = defineAsyncComponent(() =>
import('#/components/json-editor/json-editor.vue').then((res) => res.default),
);
const FormSelector = defineAsyncComponent(() =>
import('#/components/zq-form/form-selector/form-selector.vue').then(
(res) => res.default,
),
);
const TableSelector = defineAsyncComponent(() =>
import('#/components/zq-form/table-selector/table-selector.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,
@@ -105,7 +188,7 @@ const withDefaultPlaceholder = <T extends Component>(
componentProps: Recordable<any> = {},
) => {
return defineComponent({
name: (component as any).name,
name: component.name,
inheritAttrs: false,
setup: (props: any, { attrs, expose, slots }) => {
const placeholder =
@@ -134,33 +217,41 @@ const withDefaultPlaceholder = <T extends Component>(
};
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
let initialized = false;
export type ComponentType =
| 'ApiSelect'
| 'ApiTreeSelect'
| 'Cascader'
| 'Checkbox'
| 'CheckboxGroup'
| 'CodeEditor'
| 'CronSelector'
| 'DatePicker'
| 'DeptSelector'
| 'Divider'
| 'FileSelector'
| 'FormSelector'
| 'IconPicker'
| 'ImageSelector'
| 'Input'
| 'InputNumber'
| 'JsonEditor'
| 'PostSelector'
| 'RadioGroup'
| 'RoleSelector'
| 'Select'
| 'Space'
| 'Switch'
| 'TableSelector'
| 'TaskArgsEditor'
| 'TaskKwargsEditor'
| 'Textarea'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| 'UserSelector'
| BaseFormComponentType;
function initComponentAdapter() {
if (initialized) {
return;
}
async function initComponentAdapter() {
const components: Partial<Record<ComponentType, Component>> = {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
@@ -192,26 +283,24 @@ function initComponentAdapter() {
visibleEvent: 'onVisibleChange',
},
),
Cascader: withDefaultPlaceholder(ElCascader, 'select'),
Checkbox: ElCheckbox,
CheckboxGroup: (props, { attrs, slots }) => {
const mergedProps = { ...props, ...attrs } as Recordable<any>;
let defaultSlot;
if (Reflect.has(slots, 'default')) {
defaultSlot = slots.default;
} else {
const { options, isButton } = mergedProps;
const { options, isButton } = attrs;
if (Array.isArray(options)) {
defaultSlot = () =>
options.map((option) =>
h(isButton ? ElCheckboxButton : ElCheckbox, option, () =>
option.label ?? option.value,
),
h(isButton ? ElCheckboxButton : ElCheckbox, option),
);
}
}
return h(
ElCheckboxGroup,
mergedProps,
{ ...props, ...attrs },
{ ...slots, default: defaultSlot },
);
},
@@ -223,36 +312,47 @@ function initComponentAdapter() {
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'),
FormSelector: withDefaultPlaceholder(FormSelector, 'select'),
TableSelector: withDefaultPlaceholder(TableSelector, 'select'),
IconPicker: withDefaultPlaceholder(ZqIconPicker, 'select'),
ImageSelector: withDefaultPlaceholder(ImageSelector, 'select'),
Input: withDefaultPlaceholder(ElInput, 'input'),
Textarea: withDefaultPlaceholder(ElInput, 'input', { type: 'textarea' }),
CodeEditor: withDefaultPlaceholder(ElInput, 'input', {
autosize: { minRows: 8 },
type: 'textarea',
}),
JsonEditor,
CodeEditor,
CronSelector,
TaskArgsEditor: defineAsyncComponent(() =>
import('#/views/_core/scheduler/modules/task-args-editor.vue').then(
(res) => res.default,
),
),
TaskKwargsEditor: defineAsyncComponent(() =>
import('#/views/_core/scheduler/modules/task-kwargs-editor.vue').then(
(res) => res.default,
),
),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
RadioGroup: (props, { attrs, slots }) => {
const mergedProps = { ...props, ...attrs } as Recordable<any>;
let defaultSlot;
if (Reflect.has(slots, 'default')) {
defaultSlot = slots.default;
} else {
const { options, isButton } = mergedProps;
const { options } = attrs;
if (Array.isArray(options)) {
defaultSlot = () =>
options.map((option) =>
h(isButton ? ElRadioButton : ElRadio, option, () =>
option.label ?? option.value,
),
h(attrs.isButton ? ElRadioButton : ElRadio, option),
);
}
}
return h(
ElRadioGroup,
mergedProps,
{ ...props, ...attrs },
{ ...slots, default: defaultSlot },
);
},
@@ -260,6 +360,28 @@ function initComponentAdapter() {
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> = {};
@@ -281,28 +403,25 @@ function initComponentAdapter() {
slots,
);
},
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
Upload: ElUpload,
UserSelector: withDefaultPlaceholder(UserSelector, 'select'),
};
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);
initialized = true;
// 定义全局共享状态中的消息提示
globalShareState.defineMessage({
// 复制成功消息提示
copyPreferencesSuccess: (title: string, content: string) => {
void import('element-plus/es/components/notification/index').then(
({ ElNotification }) => {
ElNotification({
title,
message: content,
position: 'bottom-right',
duration: 0,
type: 'success',
});
},
);
copyPreferencesSuccess: (title, content) => {
ElNotification({
title,
message: content,
position: 'bottom-right',
duration: 0,
type: 'success',
});
},
});
}