diff --git a/web/apps/web-ele/src/api/core/websocket.ts b/web/apps/web-ele/src/api/core/websocket.ts index 7dced11..8151d68 100644 --- a/web/apps/web-ele/src/api/core/websocket.ts +++ b/web/apps/web-ele/src/api/core/websocket.ts @@ -39,46 +39,6 @@ export namespace WebSocketApi { heartbeatInterval?: number; } - /** 监控数据类型 */ - export interface MonitorMessage { - type: - | 'get_overview' - | 'get_realtime' - | 'set_interval' - | 'start_monitor' - | 'stop_monitor' - | 'test_connection'; - interval?: number; - timestamp?: string; - } - - /** 服务器监控数据 */ - export interface ServerMonitorData { - basic_info?: any; - cpu_info?: any; - memory_info?: any; - disk_info?: any; - network_info?: any; - process_info?: any; - system_load?: any; - boot_time?: any; - users_info?: any; - timestamp?: string; - } - - /** Redis监控数据 */ - export interface RedisMonitorData { - connection_id?: string; - connection_name?: string; - status?: string; - info?: any; - memory?: any; - stats?: any; - keyspace?: any[]; - clients?: any[]; - slow_log?: any[]; - timestamp?: string; - } } /** @@ -430,7 +390,7 @@ export function createWebSocket( } /** - * 创建测试WebSocket连接 + * Create test WebSocket connection */ export function createTestWebSocket( callbacks?: WebSocketApi.WebSocketCallbacks, @@ -439,7 +399,7 @@ export function createTestWebSocket( } /** - * 创建通知WebSocket连接 + * Create notification WebSocket connection */ export function createNotificationWebSocket( callbacks?: WebSocketApi.WebSocketCallbacks, @@ -448,197 +408,10 @@ export function createNotificationWebSocket( } /** - * 创建服务器监控WebSocket连接 - */ -export function createServerMonitorWebSocket( - callbacks?: WebSocketApi.WebSocketCallbacks, -): WebSocketManager { - return createWebSocket('/ws/server-monitor/', callbacks); -} - -/** - * 创建Redis监控WebSocket连接 - */ -export function createRedisMonitorWebSocket( - callbacks?: WebSocketApi.WebSocketCallbacks, -): WebSocketManager { - return createWebSocket('/ws/redis-monitor/', callbacks); -} - -/** - * 创建数据库监控WebSocket连接 - */ -export function createDatabaseMonitorWebSocket( - callbacks?: WebSocketApi.WebSocketCallbacks, -): WebSocketManager { - return createWebSocket('/ws/database-monitor/', callbacks); -} - -/** - * 监控WebSocket管理器类 - */ -export class MonitorWebSocketManager extends WebSocketManager { - /** - * 获取概览信息 - */ - getOverview(): boolean { - return this.send({ - type: 'get_overview', - timestamp: new Date().toISOString(), - }); - } - - /** - * 获取实时统计 - */ - getRealtime(): boolean { - return this.send({ - type: 'get_realtime', - timestamp: new Date().toISOString(), - }); - } - - /** - * 开始监控 - */ - startMonitoring(): boolean { - return this.send({ - type: 'start_monitor', - timestamp: new Date().toISOString(), - }); - } - - /** - * 停止监控 - */ - stopMonitoring(): boolean { - return this.send({ - type: 'stop_monitor', - timestamp: new Date().toISOString(), - }); - } - - /** - * 测试连接(适用于Redis监控) - */ - testConnection(): boolean { - return this.send({ - type: 'test_connection', - timestamp: new Date().toISOString(), - }); - } -} - -/** - * 创建服务器监控WebSocket管理器 - */ -export function createServerMonitorManager( - callbacks?: WebSocketApi.WebSocketCallbacks, -): MonitorWebSocketManager { - const wsManager = createServerMonitorWebSocket(callbacks); - // 创建增强版管理器 - return Object.setPrototypeOf(wsManager, MonitorWebSocketManager.prototype); -} - -/** - * 创建Redis监控WebSocket管理器 - */ -export function createRedisMonitorManager( - callbacks?: WebSocketApi.WebSocketCallbacks, -): MonitorWebSocketManager { - const wsManager = createRedisMonitorWebSocket(callbacks); - // 创建增强版管理器 - return Object.setPrototypeOf(wsManager, MonitorWebSocketManager.prototype); -} - -/** - * 数据库监控WebSocket管理器类 - */ -export class DatabaseMonitorWebSocketManager extends WebSocketManager { - /** - * 获取数据库配置列表 - */ - getConfigs(): boolean { - return this.send({ - type: 'get_configs', - timestamp: new Date().toISOString(), - }); - } - - /** - * 获取概览信息 - */ - getOverview(dbName: string): boolean { - return this.send({ - type: 'get_overview', - db_name: dbName, - timestamp: new Date().toISOString(), - }); - } - - /** - * 获取实时统计 - */ - getRealtime(dbName: string): boolean { - return this.send({ - type: 'get_realtime', - db_name: dbName, - timestamp: new Date().toISOString(), - }); - } - - /** - * 开始监控 - */ - startMonitoring(dbName: string): boolean { - return this.send({ - type: 'start_monitor', - db_name: dbName, - timestamp: new Date().toISOString(), - }); - } - - /** - * 停止监控 - */ - stopMonitoring(): boolean { - return this.send({ - type: 'stop_monitor', - timestamp: new Date().toISOString(), - }); - } - - /** - * 测试连接 - */ - testConnection(dbName: string): boolean { - return this.send({ - type: 'test_connection', - db_name: dbName, - timestamp: new Date().toISOString(), - }); - } -} - -/** - * 创建聊天WebSocket连接 + * Create chat WebSocket connection */ export function createChatWebSocket( callbacks?: WebSocketApi.WebSocketCallbacks, ): WebSocketManager { return createWebSocket('/ws/chat/', callbacks); } - -/** - * 创建数据库监控WebSocket管理器 - */ -export function createDatabaseMonitorManager( - callbacks?: WebSocketApi.WebSocketCallbacks, -): DatabaseMonitorWebSocketManager { - const wsManager = createDatabaseMonitorWebSocket(callbacks); - // 创建增强版管理器 - return Object.setPrototypeOf( - wsManager, - DatabaseMonitorWebSocketManager.prototype, - ); -} diff --git a/web/apps/web-ele/src/utils/form-lifecycle-hooks.ts b/web/apps/web-ele/src/utils/form-lifecycle-hooks.ts deleted file mode 100644 index 93b0b20..0000000 --- a/web/apps/web-ele/src/utils/form-lifecycle-hooks.ts +++ /dev/null @@ -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; - editId?: string; - routeQuery?: Record; - savedId?: string; - error?: any; - payload?: { main: Record; sub_tables?: Record }; - response?: any; -} - -export interface FormLifecycleResult { - blocked: boolean; -} - -export interface FormLoadPipelineOptions { - hooks?: FormLifecycleHook[]; - mode: FormLifecycleMode; - formCode: string; - formData: Record; - editId?: string; - routeQuery?: Record; - initForm: () => void; - applyDefaults?: () => void; - fetchDetail?: () => Promise; - onBlocked: () => void; -} - -const WORKFLOW_TYPES = ['data_process', 'automation'] as const; - -const BLOCKING_EVENTS = new Set([ - 'beforeLoad', - 'beforeSubmit', -]); - -function isBlockingEvent(event: FormLifecycleHook['event']) { - return BLOCKING_EVENTS.has(event); -} - -export function replaceLifecycleVariables( - template: string, - formData: Record, - 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) { - 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) => { - 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, -): Record { - const result: Record = {}; - 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 { - const inputs: Record = { - 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 { - 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 { - 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 { - 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 }; -} diff --git a/web/apps/web-ele/src/utils/reverse-auto-fill.ts b/web/apps/web-ele/src/utils/reverse-auto-fill.ts deleted file mode 100644 index c49df48..0000000 --- a/web/apps/web-ele/src/utils/reverse-auto-fill.ts +++ /dev/null @@ -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, - }; -}