import { Rock, RockConfig, RockConfigBase, RockEvent, RockEventHandlerScript, handleComponentEvent } from "@ruiapp/move-style"; import { renderRock } from "@ruiapp/react-renderer"; import RapidFormMeta from "./RapidFormMeta"; import type { RapidFormRockConfig, RapidFormState } from "./rapid-form-types"; import { assign, each, forEach, get, mapValues, merge, pick, set, trim } from "lodash"; import { Form } from "antd"; import { useEffect, useMemo, useState } from "react"; import { parseRockExpressionFunc } from "../../utils/parse-utility"; import { generateRockConfigOfError } from "../../rock-generators/generateRockConfigOfError"; import { RapidFormSubmitOptions } from "../../types/rapid-action-types"; import { getExtensionLocaleStringResource } from "../../helpers/i18nHelper"; export default { $type: "rapidForm", onResolveState(props, state) { const [form] = Form.useForm(); return { form, }; }, async onReceiveMessage(message, state, props, rockInstance) { // TODO: refactor to write less if-else if (message.name === "submit") { const form = state.form; try { const values = await form.validateFields(); form.submit(); } catch (err) { message.framework.getRockLogger().error(props, `Failed to submit form: ${err.message}`, { error: err }); } } else if (message.name === "validateFields") { state.form.validateFields(); } else if (message.name === "setFieldsValue") { state.form.setFieldsValue(message.payload); } else if (message.name === "resetFields") { state.form.resetFields(); handleComponentEvent("onFormRefresh", message.framework, message.page as any, rockInstance._scope, props, props.onFormRefresh, [{ form: state.form }]); } else if (message.name === "refreshView") { state.form.resetFields(); handleComponentEvent("onFormRefresh", message.framework, message.page as any, rockInstance._scope, props, props.onFormRefresh, [{ form: state.form }]); } else if (message.name === "setSubmitOptions") { state.submitOptions = message.payload; } }, Renderer(context, props, state) { const { framework, page, scope, logger } = context; // 当前主要是触发 rerender const [currentFormData, setCurrentFormData] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); if (!props.items) { return renderRock({ context, rockConfig: generateRockConfigOfError(new Error(`RapidFormConfig.items are required.`)), }); } if (props.onFinish && props.onFormSubmit) { return renderRock({ context, rockConfig: generateRockConfigOfError(new Error(`"onFinish" should not be configured while "onFormSubmit" is configured.`)), }); } const dataFormItemRocks: RockConfig[] = []; if (props.items) { props.items.forEach((formItemConfig) => { (formItemConfig as any).form = state.form; let formItemRockConfig: RockConfig; if (formItemConfig.hidden) { formItemRockConfig = { $id: `${props.$id}-item-${formItemConfig.uniqueKey || formItemConfig.code}`, $type: "rapidFormItem", ...formItemConfig, }; } else { formItemRockConfig = { $id: `${props.$id}-item-${formItemConfig.uniqueKey || formItemConfig.code}-col`, $type: "antdCol", span: (24 / (props.column || 1)) * (formItemConfig.column || 1), form: state.form, children: { $id: `${props.$id}-item-${formItemConfig.uniqueKey || formItemConfig.code}`, $type: "rapidFormItem", ...formItemConfig, }, $exps: formItemConfig.$exps, }; } dataFormItemRocks.push(formItemRockConfig); }); } const formActionRocks: RockConfig[] = []; each(props.actions, (formAction, index) => { if ((formAction as RockConfig).$type) { formActionRocks.push(formAction as RockConfig); return; } page.interpreteComponentProperties(null, formAction as any, {}); const formActionRock: RockConfig = { $id: `${props.$id}-actions-${index}`, $type: "antdButton", children: { $type: "text", text: formAction.actionText, }, onClick: () => { if (formAction.submitMethod || formAction.submitUrl || formAction.fixedFields) { page.sendComponentMessage(props.$id, { name: "setSubmitOptions", payload: { submitMethod: formAction.submitMethod, submitUrl: formAction.submitUrl, fixedFields: formAction.fixedFields, successMessage: formAction.successMessage, errorMessage: formAction.errorMessage, onSuccess: formAction.onSuccess, onError: formAction.onError, } satisfies RapidFormSubmitOptions, }); } }, _hidden: formAction.hidden, }; if (formAction.actionType === "submit") { formActionRock.type = formAction.buttonType || "primary"; formActionRock.htmlType = "submit"; formActionRock.form = undefined; formActionRock.loading = isSubmitting; formActionRock.disabled = isSubmitting; } assign(formActionRock, formAction.actionProps); formActionRocks.push(formActionRock); }); const isHorizonLayout = !(props.layout === "vertical" || props.layout === "inline"); const formActionsRowConfig: RockConfig = { $id: `${props.$id}-actions`, $type: "antdRow", gutter: 24, children: [ { $id: `${props.$id}-actions-wrap-col`, $type: "antdCol", span: 24, children: [ { $id: `${props.$id}-actions-wrap-form-item`, $type: "antdFormItem", style: { textAlign: props.actionsAlign || (isHorizonLayout ? "center" : "left"), }, ...props.actionsLayout, children: formActionRocks.length == 1 ? formActionRocks : { $type: "antdSpace", size: 16, children: formActionRocks, }, }, ], }, ], }; const dataSource = props.dataSource || (props.dataSourceCode && get(scope.stores[props.dataSourceCode], "data.list[0]")); const initialValues = useMemo(() => { let values; if (dataSource || (props.dataSourceCode && !props.disabledLoadStore)) { values = { ...props.defaultFormFields, ...(props.fieldNameOfFormDataInDataSource ? get(dataSource, props.fieldNameOfFormDataInDataSource) : dataSource), }; } else { values = props.defaultFormFields; } if (typeof props.formDataAdapter === "string" && trim(props.formDataAdapter)) { const formDataAdapter = parseRockExpressionFunc(props.formDataAdapter, { data: values, form: state.form }, context); values = formDataAdapter(); } return values || {}; }, [props.defaultFormFields, props.disabledLoadStore, props.fieldNameOfFormDataInDataSource, dataSource]); useEffect(() => { state.form.setFieldsValue(initialValues); setCurrentFormData(initialValues); }, [initialValues, state.form]); const onValuesChange: RockEventHandlerScript["script"] = (event: RockEvent) => { setCurrentFormData(event.args[0]); handleComponentEvent("onValuesChange", framework, page, scope, props, props.onValuesChange, event.args); }; const formRockConfig: RockConfig = { $id: `${props.$id}-antdForm`, $type: "antdForm", form: state.form, labelCol: isHorizonLayout ? props.labelCol || { span: 8 } : null, wrapperCol: isHorizonLayout ? props.wrapperCol || { span: 16 } : null, layout: props.layout, requiredMark: props.requiredMark, initialValues, children: [ { $id: `${props.$id}-antdForm-items-row`, $type: "antdRow", gutter: 24, children: dataFormItemRocks, }, ...(formActionRocks.length ? [formActionsRowConfig] : []), ], onFinish: [ { $action: "script", script: async (event: RockEvent) => { setIsSubmitting(true); try { const submitOptions = state.submitOptions; const submitUrl = submitOptions?.submitUrl || props.submitUrl; let onSubmit = props.onSubmit || props.onFormSubmit || props.onFinish; if (!submitUrl && !onSubmit) { logger.error(props, `Failed to submit form: submitUrl or onSubmit is not configured.`); return; } let formData = Object.assign({}, omitUndefinedValues(event.args[0])); const fixedFields = omitUndefinedValues(merge({}, props.fixedFields, submitOptions?.fixedFields)); if (typeof props.beforeSubmitFormDataAdapter === "string" && trim(props.beforeSubmitFormDataAdapter)) { const adapter = parseRockExpressionFunc(props.beforeSubmitFormDataAdapter, { formData, fixedFields }, context); formData = adapter(); } let submitData: any; if (props.fieldNameOfFormDataInSubmitData) { submitData = set({}, props.fieldNameOfFormDataInSubmitData, formData); } else { submitData = formData; } submitData = merge(submitData, fixedFields); if (props.beforeSubmit) { await handleComponentEvent("beforeSubmit", event.framework, event.page as any, event.scope, event.sender, props.beforeSubmit, [ submitData, submitOptions, ]); } if (!onSubmit) { const submitMethod = submitOptions?.submitMethod || props.submitMethod || "POST"; const successMessage = submitOptions?.successMessage || props.successMessage || getExtensionLocaleStringResource(framework, "saveSuccess"); const errorMessage = submitOptions?.errorMessage || props.errorMessage || getExtensionLocaleStringResource(framework, "saveError"); const onSubmitSuccess = submitOptions?.onSuccess || props.onSubmitSuccess; const onSubmitError = submitOptions?.onError || props.onSubmitError; onSubmit = { $action: "sendHttpRequest", method: submitMethod, url: submitUrl, data: submitData, onSuccess: [ { $action: "antdToast", type: "success", content: successMessage, }, ...(onSubmitSuccess ? (Array.isArray(onSubmitSuccess) ? onSubmitSuccess : [onSubmitSuccess]) : []), ], onError: [ { $action: "antdToast", type: "error", $exps: { content: `${JSON.stringify(errorMessage)}+ " " + $event.args[0].message`, }, }, ...(onSubmitError ? (Array.isArray(onSubmitError) ? onSubmitError : [onSubmitError]) : []), ], }; } await handleComponentEvent("onSubmit", event.framework, event.page as any, event.scope, event.sender, onSubmit, [submitData, submitOptions]); } catch (error) { logger.error(props, error); } finally { setIsSubmitting(false); } }, }, ], onValuesChange: { $action: "script", script: onValuesChange, }, }; let formSectionConfig: RockConfig; if (props.dataSourceCode) { formSectionConfig = { $id: `${props.$id}-show`, $type: "show", fallback: [ { $id: `${props.$id}-spin`, $type: "antdSpin", }, ], children: [formRockConfig], $exps: { when: `$scope.stores.${props.dataSourceCode}.data`, }, }; } else { formSectionConfig = formRockConfig; } const rockConfig: RockConfig = formSectionConfig; return renderRock({ context, rockConfig }); }, ...RapidFormMeta, } as Rock; function omitUndefinedValues(data: Record) { return mapValues(data, (v) => (v === undefined ? null : v)); }