Remove unused legacy monitor helpers
This commit is contained in:
@@ -1,531 +0,0 @@
|
||||
import type {
|
||||
DataSourceParamConfig,
|
||||
FormLifecycleHook,
|
||||
FormLifecycleMode,
|
||||
} from '#/components/form-design/store/formDesignStore';
|
||||
|
||||
import { ElMessage } from 'element-plus/es/components/message/index';
|
||||
|
||||
import { getWorkflowDetailApi, runWorkflowApi } from '#/api/ai-platform/ai-platform';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface FormLifecycleContext {
|
||||
formCode: string;
|
||||
formData: Record<string, any>;
|
||||
editId?: string;
|
||||
routeQuery?: Record<string, any>;
|
||||
savedId?: string;
|
||||
error?: any;
|
||||
payload?: { main: Record<string, any>; sub_tables?: Record<string, any> };
|
||||
response?: any;
|
||||
}
|
||||
|
||||
export interface FormLifecycleResult {
|
||||
blocked: boolean;
|
||||
}
|
||||
|
||||
export interface FormLoadPipelineOptions {
|
||||
hooks?: FormLifecycleHook[];
|
||||
mode: FormLifecycleMode;
|
||||
formCode: string;
|
||||
formData: Record<string, any>;
|
||||
editId?: string;
|
||||
routeQuery?: Record<string, any>;
|
||||
initForm: () => void;
|
||||
applyDefaults?: () => void;
|
||||
fetchDetail?: () => Promise<void>;
|
||||
onBlocked: () => void;
|
||||
}
|
||||
|
||||
const WORKFLOW_TYPES = ['data_process', 'automation'] as const;
|
||||
|
||||
const BLOCKING_EVENTS = new Set<FormLifecycleHook['event']>([
|
||||
'beforeLoad',
|
||||
'beforeSubmit',
|
||||
]);
|
||||
|
||||
function isBlockingEvent(event: FormLifecycleHook['event']) {
|
||||
return BLOCKING_EVENTS.has(event);
|
||||
}
|
||||
|
||||
export function replaceLifecycleVariables(
|
||||
template: string,
|
||||
formData: Record<string, any>,
|
||||
savedId?: string,
|
||||
): string {
|
||||
if (!template) return '';
|
||||
let result = template.replaceAll(/\{id\}/g, savedId ? String(savedId) : '{id}');
|
||||
result = result.replaceAll(/\{(\w+)\}/g, (match, key) => {
|
||||
if (key === 'id') {
|
||||
return savedId === undefined ? match : String(savedId);
|
||||
}
|
||||
return formData[key] === undefined ? match : String(formData[key]);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function createScriptHelpers(rootData: Record<string, any>) {
|
||||
const $setValue = (field: string, value: any) => {
|
||||
if (field.includes('.')) {
|
||||
const [table, subField] = field.split('.');
|
||||
if (Array.isArray(rootData[table!])) {
|
||||
rootData[table!].forEach((row: any) => {
|
||||
row[subField!] = value;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
rootData[field] = value;
|
||||
}
|
||||
};
|
||||
|
||||
const $setValues = (obj: Record<string, any>) => {
|
||||
for (const [field, value] of Object.entries(obj)) {
|
||||
$setValue(field, value);
|
||||
}
|
||||
};
|
||||
|
||||
const $getValue = (field: string) => {
|
||||
if (field.includes('.')) {
|
||||
const [table, subField] = field.split('.');
|
||||
if (Array.isArray(rootData[table!])) {
|
||||
return rootData[table!].map((row: any) => row[subField!]);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return rootData[field];
|
||||
};
|
||||
|
||||
const $getSubTable = (field: string) => rootData[field] || [];
|
||||
|
||||
const $setSubTable = (field: string, rows: any[]) => {
|
||||
rootData[field] = rows;
|
||||
};
|
||||
|
||||
const $addRow = (field: string, row: any) => {
|
||||
if (!Array.isArray(rootData[field])) {
|
||||
rootData[field] = [];
|
||||
}
|
||||
rootData[field].push({ _id: `${Date.now()}_${Math.random()}`, ...row });
|
||||
};
|
||||
|
||||
const $removeRow = (field: string, index: number) => {
|
||||
if (Array.isArray(rootData[field])) {
|
||||
rootData[field].splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const $updateRow = (field: string, index: number, data: any) => {
|
||||
if (Array.isArray(rootData[field]) && rootData[field][index]) {
|
||||
Object.assign(rootData[field][index], data);
|
||||
}
|
||||
};
|
||||
|
||||
const $clearSubTable = (field: string) => {
|
||||
rootData[field] = [];
|
||||
};
|
||||
|
||||
return {
|
||||
$setValue,
|
||||
$setValues,
|
||||
$getValue,
|
||||
$getSubTable,
|
||||
$setSubTable,
|
||||
$addRow,
|
||||
$removeRow,
|
||||
$updateRow,
|
||||
$clearSubTable,
|
||||
};
|
||||
}
|
||||
|
||||
function buildDataSourceParams(
|
||||
params: DataSourceParamConfig[] | undefined,
|
||||
formData: Record<string, any>,
|
||||
): Record<string, any> {
|
||||
const result: Record<string, any> = {};
|
||||
for (const p of params || []) {
|
||||
if (p.valueSource === 'fixed') {
|
||||
result[p.name] = p.fixedValue ?? p.default ?? '';
|
||||
} else if (p.valueSource === 'field' && p.sourceField) {
|
||||
result[p.name] = formData[p.sourceField] ?? '';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function buildWorkflowInputs(
|
||||
hook: FormLifecycleHook,
|
||||
context: FormLifecycleContext,
|
||||
): Record<string, any> {
|
||||
const inputs: Record<string, any> = {
|
||||
form_code: context.formCode,
|
||||
};
|
||||
const dataId = context.savedId || context.editId;
|
||||
if (dataId) {
|
||||
inputs.form_data_id = dataId;
|
||||
}
|
||||
|
||||
for (const mapping of hook.actionConfig.workflowInputs || []) {
|
||||
if (mapping.valueSource === 'field' && mapping.sourceField) {
|
||||
inputs[mapping.name] = context.formData[mapping.sourceField] ?? '';
|
||||
} else {
|
||||
inputs[mapping.name] = mapping.fixedValue ?? mapping.default ?? '';
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
function appendLoadScriptArgs(
|
||||
hook: FormLifecycleHook,
|
||||
context: FormLifecycleContext,
|
||||
argNames: string[],
|
||||
argValues: any[],
|
||||
) {
|
||||
if (hook.event === 'beforeLoad' || hook.event === 'afterLoadSuccess') {
|
||||
argNames.push('$editId', '$query');
|
||||
argValues.push(context.editId, context.routeQuery || {});
|
||||
}
|
||||
if (hook.event === 'afterLoadSuccess') {
|
||||
argNames.push('$savedId');
|
||||
argValues.push(context.savedId || context.editId);
|
||||
}
|
||||
if (hook.event === 'afterLoadFail' || hook.event === 'afterSubmitFail') {
|
||||
argNames.push('$error');
|
||||
argValues.push(context.error);
|
||||
}
|
||||
if (hook.event === 'afterSubmitSuccess') {
|
||||
argNames.push('$savedId', '$response');
|
||||
argValues.push(context.savedId, context.response);
|
||||
}
|
||||
}
|
||||
|
||||
async function executeScriptHook(
|
||||
hook: FormLifecycleHook,
|
||||
context: FormLifecycleContext,
|
||||
mode: FormLifecycleMode,
|
||||
): Promise<boolean | void> {
|
||||
const script = hook.actionConfig.script;
|
||||
if (!script?.trim()) return;
|
||||
|
||||
const rootData = context.formData;
|
||||
const helpers = createScriptHelpers(rootData);
|
||||
const argNames = [
|
||||
'model',
|
||||
'$root',
|
||||
'$mode',
|
||||
'$event',
|
||||
'$setValue',
|
||||
'$setValues',
|
||||
'$getValue',
|
||||
'$getSubTable',
|
||||
'$setSubTable',
|
||||
'$addRow',
|
||||
'$removeRow',
|
||||
'$updateRow',
|
||||
'$clearSubTable',
|
||||
];
|
||||
const argValues: any[] = [
|
||||
rootData,
|
||||
rootData,
|
||||
mode,
|
||||
hook.event,
|
||||
helpers.$setValue,
|
||||
helpers.$setValues,
|
||||
helpers.$getValue,
|
||||
helpers.$getSubTable,
|
||||
helpers.$setSubTable,
|
||||
helpers.$addRow,
|
||||
helpers.$removeRow,
|
||||
helpers.$updateRow,
|
||||
helpers.$clearSubTable,
|
||||
];
|
||||
|
||||
appendLoadScriptArgs(hook, context, argNames, argValues);
|
||||
|
||||
const fn = new Function(...argNames, script);
|
||||
return fn(...argValues);
|
||||
}
|
||||
|
||||
async function executeDataSourceHook(
|
||||
hook: FormLifecycleHook,
|
||||
context: FormLifecycleContext,
|
||||
mode: FormLifecycleMode,
|
||||
): Promise<any> {
|
||||
const dsCode = hook.actionConfig.dataSourceCode;
|
||||
if (!dsCode) return;
|
||||
|
||||
const params = buildDataSourceParams(
|
||||
hook.actionConfig.dataSourceParams,
|
||||
context.formData,
|
||||
);
|
||||
const response = await requestClient.get(
|
||||
`/api/core/data-source/execute/${dsCode}`,
|
||||
{ params },
|
||||
);
|
||||
|
||||
const callbackScript = hook.actionConfig.callbackScript;
|
||||
if (callbackScript?.trim()) {
|
||||
const rootData = context.formData;
|
||||
const helpers = createScriptHelpers(rootData);
|
||||
const argNames = [
|
||||
'model',
|
||||
'$root',
|
||||
'$result',
|
||||
'$mode',
|
||||
'$event',
|
||||
'$setValue',
|
||||
'$setValues',
|
||||
'$getValue',
|
||||
'$getSubTable',
|
||||
'$setSubTable',
|
||||
'$addRow',
|
||||
'$removeRow',
|
||||
'$updateRow',
|
||||
'$clearSubTable',
|
||||
];
|
||||
const argValues: any[] = [
|
||||
rootData,
|
||||
rootData,
|
||||
response,
|
||||
mode,
|
||||
hook.event,
|
||||
helpers.$setValue,
|
||||
helpers.$setValues,
|
||||
helpers.$getValue,
|
||||
helpers.$getSubTable,
|
||||
helpers.$setSubTable,
|
||||
helpers.$addRow,
|
||||
helpers.$removeRow,
|
||||
helpers.$updateRow,
|
||||
helpers.$clearSubTable,
|
||||
];
|
||||
appendLoadScriptArgs(hook, context, argNames, argValues);
|
||||
|
||||
const fn = new Function(...argNames, callbackScript);
|
||||
const callbackResult = fn(...argValues);
|
||||
if (isBlockingEvent(hook.event) && callbackResult === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
function executeRedirectHook(
|
||||
hook: FormLifecycleHook,
|
||||
context: FormLifecycleContext,
|
||||
) {
|
||||
const savedId = context.savedId || context.editId;
|
||||
const url = replaceLifecycleVariables(
|
||||
hook.actionConfig.url || '',
|
||||
context.formData,
|
||||
savedId,
|
||||
);
|
||||
if (!url) return;
|
||||
|
||||
if (hook.actionConfig.openInNewTab) {
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
function executeMessageHook(
|
||||
hook: FormLifecycleHook,
|
||||
context: FormLifecycleContext,
|
||||
) {
|
||||
const savedId = context.savedId || context.editId;
|
||||
const message = replaceLifecycleVariables(
|
||||
hook.actionConfig.message || '',
|
||||
context.formData,
|
||||
savedId,
|
||||
);
|
||||
if (!message) return;
|
||||
|
||||
const messageType = hook.actionConfig.messageType || 'success';
|
||||
ElMessage[messageType](message);
|
||||
}
|
||||
|
||||
async function executeWorkflowHook(
|
||||
hook: FormLifecycleHook,
|
||||
context: FormLifecycleContext,
|
||||
) {
|
||||
const workflowId = hook.actionConfig.workflowId;
|
||||
if (!workflowId) return;
|
||||
|
||||
const run = async () => {
|
||||
const workflow = await getWorkflowDetailApi(workflowId);
|
||||
if (
|
||||
workflow.status !== 'published' ||
|
||||
!WORKFLOW_TYPES.includes(
|
||||
workflow.workflow_type as (typeof WORKFLOW_TYPES)[number],
|
||||
)
|
||||
) {
|
||||
throw new Error('Workflow is not published or type not allowed');
|
||||
}
|
||||
|
||||
const inputs = buildWorkflowInputs(hook, context);
|
||||
const result = await runWorkflowApi(workflowId, inputs);
|
||||
if (result.status === 'failed') {
|
||||
throw new Error(result.error_message || 'Workflow execution failed');
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
if (hook.actionConfig.async) {
|
||||
run().catch((error) => {
|
||||
console.warn('[FormLifecycleHook] async workflow failed:', error);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await run();
|
||||
}
|
||||
|
||||
async function executeSingleHook(
|
||||
hook: FormLifecycleHook,
|
||||
context: FormLifecycleContext,
|
||||
mode: FormLifecycleMode,
|
||||
): Promise<{ blocked: boolean }> {
|
||||
switch (hook.actionType) {
|
||||
case 'script': {
|
||||
const result = await executeScriptHook(hook, context, mode);
|
||||
if (isBlockingEvent(hook.event) && result === false) {
|
||||
return { blocked: true };
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'dataSource': {
|
||||
const result = await executeDataSourceHook(hook, context, mode);
|
||||
if (isBlockingEvent(hook.event) && result === false) {
|
||||
return { blocked: true };
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'redirect': {
|
||||
executeRedirectHook(hook, context);
|
||||
if (hook.event === 'beforeLoad') {
|
||||
return { blocked: true };
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'message': {
|
||||
executeMessageHook(hook, context);
|
||||
break;
|
||||
}
|
||||
case 'workflow': {
|
||||
await executeWorkflowHook(hook, context);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return { blocked: false };
|
||||
}
|
||||
|
||||
function getActiveHooks(
|
||||
hooks: FormLifecycleHook[] | undefined,
|
||||
event: FormLifecycleHook['event'],
|
||||
mode: FormLifecycleMode,
|
||||
): FormLifecycleHook[] {
|
||||
return (hooks || [])
|
||||
.filter(
|
||||
(hook) =>
|
||||
hook.enabled &&
|
||||
hook.event === event &&
|
||||
Array.isArray(hook.modes) &&
|
||||
hook.modes.includes(mode),
|
||||
)
|
||||
.sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0));
|
||||
}
|
||||
|
||||
function getBlockErrorMessage(event: FormLifecycleHook['event']) {
|
||||
if (event === 'beforeLoad') {
|
||||
return '表单加载被阻止';
|
||||
}
|
||||
return '提交前校验未通过';
|
||||
}
|
||||
|
||||
export async function executeFormLifecycleHooks(
|
||||
hooks: FormLifecycleHook[] | undefined,
|
||||
event: FormLifecycleHook['event'],
|
||||
mode: FormLifecycleMode,
|
||||
context: FormLifecycleContext,
|
||||
): Promise<FormLifecycleResult> {
|
||||
const activeHooks = getActiveHooks(hooks, event, mode);
|
||||
|
||||
for (const hook of activeHooks) {
|
||||
try {
|
||||
const result = await executeSingleHook(hook, context, mode);
|
||||
if (result.blocked) {
|
||||
return { blocked: true };
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[FormLifecycleHook] ${hook.id} failed:`, error);
|
||||
if (isBlockingEvent(hook.event) && hook.blockOnError) {
|
||||
ElMessage.error(
|
||||
error instanceof Error ? error.message : getBlockErrorMessage(hook.event),
|
||||
);
|
||||
return { blocked: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { blocked: false };
|
||||
}
|
||||
|
||||
export async function runFormLoadPipeline(
|
||||
opts: FormLoadPipelineOptions,
|
||||
): Promise<{ loaded: boolean }> {
|
||||
const {
|
||||
hooks,
|
||||
mode,
|
||||
formCode,
|
||||
formData,
|
||||
editId,
|
||||
routeQuery,
|
||||
initForm,
|
||||
applyDefaults,
|
||||
fetchDetail,
|
||||
onBlocked,
|
||||
} = opts;
|
||||
|
||||
const baseContext: FormLifecycleContext = {
|
||||
formCode,
|
||||
formData,
|
||||
editId,
|
||||
routeQuery,
|
||||
};
|
||||
|
||||
const beforeResult = await executeFormLifecycleHooks(
|
||||
hooks,
|
||||
'beforeLoad',
|
||||
mode,
|
||||
baseContext,
|
||||
);
|
||||
if (beforeResult.blocked) {
|
||||
onBlocked();
|
||||
return { loaded: false };
|
||||
}
|
||||
|
||||
initForm();
|
||||
applyDefaults?.();
|
||||
|
||||
if (fetchDetail) {
|
||||
try {
|
||||
await fetchDetail();
|
||||
} catch (error) {
|
||||
await executeFormLifecycleHooks(hooks, 'afterLoadFail', mode, {
|
||||
...baseContext,
|
||||
error,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
await executeFormLifecycleHooks(hooks, 'afterLoadSuccess', mode, {
|
||||
...baseContext,
|
||||
savedId: editId,
|
||||
});
|
||||
|
||||
return { loaded: true };
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import type {
|
||||
FormItemSchema,
|
||||
ReverseAutoFillConfig,
|
||||
} from '#/components/form-design/store/formDesignStore';
|
||||
|
||||
export interface ReverseAutoFillContext {
|
||||
cfg: ReverseAutoFillConfig;
|
||||
formCode?: string;
|
||||
kind: 'form-selector' | 'select';
|
||||
valueField: string;
|
||||
}
|
||||
|
||||
/** 是否可在设计器配置关联自动填充 */
|
||||
export function canConfigureReverseAutoFill(
|
||||
item: { dataSource?: { type?: string }; type?: string } | null | undefined,
|
||||
): boolean {
|
||||
if (!item) return false;
|
||||
if (item.type === 'form-selector') return true;
|
||||
return item.type === 'select' && item.dataSource?.type === 'formData';
|
||||
}
|
||||
|
||||
/** 是否已启用关联自动填充(含未完整配置) */
|
||||
export function hasReverseAutoFillEnabled(
|
||||
item: FormItemSchema | null | undefined,
|
||||
): boolean {
|
||||
if (!item) return false;
|
||||
if (item.type === 'form-selector') {
|
||||
return !!item.formSelectorConfig?.reverseAutoFill?.enabled;
|
||||
}
|
||||
if (item.type === 'select' && item.dataSource?.type === 'formData') {
|
||||
return !!item.dataSource.reverseAutoFill?.enabled;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 读取关联自动填充配置(设计器用) */
|
||||
export function getReverseAutoFillConfig(
|
||||
item: FormItemSchema | null | undefined,
|
||||
): ReverseAutoFillConfig | undefined {
|
||||
if (!item) return undefined;
|
||||
if (item.type === 'form-selector') {
|
||||
return item.formSelectorConfig?.reverseAutoFill;
|
||||
}
|
||||
if (item.type === 'select' && item.dataSource?.type === 'formData') {
|
||||
return item.dataSource.reverseAutoFill;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** 运行时:获取已启用的关联自动填充上下文 */
|
||||
export function getReverseAutoFillContext(
|
||||
item: FormItemSchema | null | undefined,
|
||||
): ReverseAutoFillContext | null {
|
||||
if (!item) return null;
|
||||
|
||||
if (item.type === 'form-selector') {
|
||||
const cfg = item.formSelectorConfig?.reverseAutoFill;
|
||||
if (!cfg?.enabled || !cfg.sourceField) return null;
|
||||
return {
|
||||
cfg,
|
||||
formCode: item.formSelectorConfig?.formCode,
|
||||
valueField: item.formSelectorConfig?.valueField || 'id',
|
||||
kind: 'form-selector',
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === 'select' && item.dataSource?.type === 'formData') {
|
||||
const cfg = item.dataSource.reverseAutoFill;
|
||||
if (!cfg?.enabled || !cfg.sourceField) return null;
|
||||
return {
|
||||
cfg,
|
||||
formCode: item.dataSource.formCode,
|
||||
valueField: item.dataSource.formValueField || 'id',
|
||||
kind: 'select',
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function createDefaultReverseAutoFillConfig(): ReverseAutoFillConfig {
|
||||
return {
|
||||
enabled: false,
|
||||
sourceField: '',
|
||||
targetField: 'b_id',
|
||||
filterType: 'eq',
|
||||
pageSize: 500,
|
||||
clearWhenEmpty: true,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user