import snippets from './snippets'; import { uuid } from '../_utils/utils'; import { createFormGroup, createFormItemSchema, formSchema2PisellFieldsOptions, fields2ChildrenSchemas, createSubmitButton, getDataSourceVariables, getVariable, getCurrentValueVariables, setBatchVariables, } from './utils'; import { MetaType } from '../_utils/type'; const dataSourceForm: MetaType = { snippets, componentName: 'DataSourceForm', title: '表单', category: '无代码表单', group: '无代码组件', docUrl: '', screenshot: '', devMode: 'proCode', npm: { package: '@pisell/materials', version: '1.0.1', exportName: 'DataSourceForm', main: 'src/index.tsx', destructuring: true, subName: '', }, generalProps: [ { type: 'group', title: { type: 'i18n', 'en-US': 'Info', 'zh-CN': '信息', }, display: 'accordion', items: [ { name: 'title', title: { label: { type: 'i18n', 'en-US': 'Title', 'zh-CN': '标题', }, }, // display: 'block', setter: 'PisellI18nSetter', }, { name: 'description', title: { label: { type: 'i18n', 'en-US': 'Description', 'zh-CN': '描述', }, }, // display: 'block', setter: 'PisellI18nSetter', }, ], }, { type: 'group', title: { type: 'i18n', 'en-US': 'Data', 'zh-CN': '数据', }, display: 'accordion', items: [ { name: 'dataSource', title: { type: 'i18n', 'en-US': 'Source', 'zh-CN': '数据源', }, extraProps: { setValue(target: any, value: any) { // 如果value为空,则清空表单 if (!value.item) { target.getProps().setPropValue('title', ''); setBatchVariables(target, [ { label: 'currentFields', value: [], }, { label: 'dataSourceTitle', value: '', }, { label: 'dataSourceValue', value: '', }, ]); target.node.children.importSchema([]); return target.getProps().setPropValue('dataSource', ''); } // 如果重新选择选择数据源,则清空表单 if (value.isSelect) { target.getProps().setPropValue('title', ''); target.node.children.importSchema([]); // 检查是否fields包含group信息 const hasGroupInfo = value.item.fields?.some((field: any) => field._groupId); let formGroupSchemas: any[] = []; if (hasGroupInfo) { // 按group分组 const groupedFields = new Map(); value.item.fields.forEach((field: any) => { const groupId = field._groupId; if (!groupedFields.has(groupId)) { groupedFields.set(groupId, { groupTitle: field._groupTitle, groupDescription: field._groupDescription, items: [] }); } // 移除group标记,避免污染 const { _groupId, _groupTitle, _groupDescription, ...cleanField } = field; groupedFields.get(groupId)!.items.push(cleanField); }); // 为每个group创建一个FormGroup groupedFields.forEach(({ groupTitle, groupDescription, items }) => { const childrenSchemas = fields2ChildrenSchemas( items, target.getProps().getPropValue('renderMode') ); formGroupSchemas.push( createFormGroup(childrenSchemas, groupTitle, groupDescription) ); }); } else { // 原有逻辑:没有group信息时,创建单个FormGroup let childrenSchemas = fields2ChildrenSchemas( value.item.fields, target.getProps().getPropValue('renderMode') ); formGroupSchemas = [createFormGroup(childrenSchemas)]; } if (target.node.children.isEmptyNode) { target.node.children.importSchema([ ...formGroupSchemas, createSubmitButton(), ]); } // 如果Title是空:选择Source后,自动把Source的名称填写到Title里 if ( !target.node.getProps().getPropValue('title') && value.title ) { target.getProps().setPropValue('title', value.title); } } const dataSource = value.isVariable ? value.value : value.item; // 清理fields中的group标记 const cleanFields = value.item.fields?.map((field: any) => { const { _groupId, _groupTitle, ...cleanField } = field; return cleanField; }) || value.item.fields; setBatchVariables(target, [ { label: 'currentFields', value: cleanFields, }, { label: 'dataSourceTitle', value: value.title, }, { label: 'dataSourceValue', value: dataSource, }, ]); return target.getProps().setPropValue('dataSource', dataSource); }, }, setter: { componentName: 'PisellDataSourceSetter', props: (target: any) => { return { filters: { tables: 'all', variables: {}, }, mode: target.getProps().getPropValue('renderMode'), variables: getDataSourceVariables(target), }; }, }, }, { name: 'scene', title: { label: { type: 'i18n', 'en-US': 'Scene', 'zh-CN': '场景', }, tip: { type: 'i18n', 'en-US': 'Only support: template, profile, instance', 'zh-CN': '仅支持: template, profile, instance', } }, defaultValue: { "type": "JSExpression", "value": "this.props?.scene", }, condition(target: any) { return !!target.getProps().getPropValue('dataSource')?.fromDeviceSettingMeta; }, setter: 'StringSetter', supportVariable: true, }, { name: 'sceneRequestId', title: { label: { type: 'i18n', 'en-US': 'Scene request id', 'zh-CN': '场景请求 ID', }, }, defaultValue: { "type": "JSExpression", "value": "this.props?.sceneRequestId" }, condition(target: any) { return !!target.getProps().getPropValue('dataSource')?.fromDeviceSettingMeta; }, setter: 'StringSetter', supportVariable: true, }, { title: '字段', display: 'entry', type: 'group', name: '_fields1', items: [ { title: { type: 'i18n', 'en-US': 'Fields', 'zh-CN': '字段', }, name: '_fields', display: 'block', setter: { componentName: 'PisellFieldSetter', props: (target: any) => { const formSchema = target.node.schema; const allFields = getVariable(target, 'currentFields'); const options = formSchema2PisellFieldsOptions( formSchema, allFields ); return { options, }; }, }, extraProps: { setValue(target: any, value: any) { const { operate } = value; const { mode, field, group } = operate; if (value.operate.mode === 'hidden') { target.node.document.removeNode(field); } else if (value.operate.mode === 'show') { // 需要追加的元素容器 优先顺序 1.当前表单最后一个分组 -> 2.当前表单 let appendContainer = target.node; let lastGroup: any; target.node.children.forEach((item: any) => { if (item.componentName === 'FormGroup') { lastGroup = item; } }); appendContainer = lastGroup || appendContainer; // 全部字段 const allFields = getVariable(target, 'currentFields'); const nocobaseField = allFields.find( (item: any) => item.name === field ); if (nocobaseField) { const appendSchema = createFormItemSchema( nocobaseField, target.getProps().getPropValue('renderMode') ); const appendNode = target.node.document.createNode(appendSchema); appendContainer.children.insert(appendNode); } } // 插入schema后 有概率不生效,所以这里手动rerender // setTimeout(() => { // target.node.document.project.simulatorHost.rerender(); // }); return {}; }, }, }, ], }, { name: 'currentValue', title: { type: 'i18n', 'en-US': 'Acquire data from', 'zh-CN': '从以下位置获取数据', }, condition(target) { return ['edit', 'view'].includes( target.getProps().getPropValue('renderMode') ); }, extraProps: { setValue(target, value) { if (!value.value) { return target.getProps().setPropValue('currentValue', ''); } return target .getProps() .setPropValue('currentValue', value.value); }, }, setter: { componentName: 'PisellDataSourceSetter', props: (target) => { return { filters: { variables: {}, }, variables: getCurrentValueVariables(target), }; }, }, }, { type: 'group', name: '_extraParams', title: '额外参数', display: 'entry', items: [ { name: 'extraParams.list', title: { type: 'i18n', 'en-US': 'List Params', 'zh-CN': '列表参数', }, setter: 'StringSetter', }, { name: 'extraParams.get', title: { type: 'i18n', 'en-US': 'Get Params', 'zh-CN': '获取参数', }, setter: 'JsonSetter', }, { name: 'extraParams.update', title: { type: 'i18n', 'en-US': 'Update Params', 'zh-CN': '更新参数', }, setter: 'JsonSetter', }, // { // name: 'extraParams.create', // title: { // type: 'i18n', // 'en-US': 'Create Params', // 'zh-CN': '创建参数', // }, // setter: 'StringSetter' // }, // { // name: 'extraParams.destroy', // title: { // type: 'i18n', // 'en-US': 'Destroy Params', // 'zh-CN': '删除参数', // }, // setter: 'StringSetter' // } ], }, ], }, { type: 'group', title: { type: 'i18n', 'en-US': 'Variables', 'zh-CN': '变量', }, display: 'accordion', items: [], }, { type: 'group', title: { type: 'i18n', 'en-US': 'Appearance', 'zh-CN': '外观', }, display: 'accordion', items: [ { name: 'showTitle', title: { label: { type: 'i18n', 'en-US': 'Show form title', 'zh-CN': '显示表单标题', }, }, defaultValue: true, setter: 'BoolSetter', }, { name: 'showDescription', title: { label: { type: 'i18n', 'en-US': 'Show description', 'zh-CN': '显示表单描述', }, }, defaultValue: true, setter: 'BoolSetter', }, { name: 'groupInfoPosition', title: { label: { type: 'i18n', 'en-US': 'Group info position', 'zh-CN': '分组信息位置', }, }, setter: [ { componentName: 'RadioGroupSetter', props: { options: [ { title: 'Top', value: 'top', }, { title: 'Side', value: 'side', }, ], }, }, { componentName: 'VariableSetter', }, ], }, { name: 'layout', title: { label: { type: 'i18n', 'en-US': 'Field label position', 'zh-CN': '字段标签位置', }, }, setter: [ { componentName: 'RadioGroupSetter', props: { options: [ { title: 'Top', value: 'vertical', }, { title: 'Side', value: 'horizontal', }, ], }, }, { componentName: 'VariableSetter', }, ], extraProps: { setValue(target: any, value: any) { if (value === 'vertical') { target.getProps().setPropValue('labelCol', { span: 24, }); target.getProps().setPropValue('wrapperCol', { span: 24, }); } else if (value === 'horizontal') { target.getProps().setPropValue('labelCol', { flex: '142px', }); target.getProps().setPropValue('wrapperCol', { flex: '1', }); } return target.getProps().setPropValue('layout', value); }, }, }, { name: 'size', title: { label: { type: 'i18n', 'en-US': 'Size', 'zh-CN': '尺寸', }, }, defaultValue: 'default', setter: { componentName: 'RadioGroupSetter', props: { options: [ { title: '小', value: 'small', }, { title: '中', value: 'middle', }, { title: '大', value: 'large', }, ], }, }, }, ], }, { type: 'group', title: { type: 'i18n', 'en-US': 'Interaction behavior', 'zh-CN': '交互行为', }, display: 'accordion', items: [ { name: 'leaveConfirmConfig', title: { label: { type: 'i18n', 'en-US': 'Unsaved warning', 'zh-CN': '未保存离开提示', }, }, setter: { componentName: 'ObjectSetter', props: { config: { items: [ { name: 'enable', title: { type: 'i18n', 'en-US': 'Enable', 'zh-CN': '启用', }, setter: 'BoolSetter', isRequired: true, }, { name: 'icon', title: { type: 'i18n', 'en-US': 'Icon', 'zh-CN': '图标', }, setter: [ { componentName: 'SlotSetter', initialValue: { type: 'JSSlot', value: [ { componentName: 'Icon', props: { type: 'SmileOutlined', size: 20, rotate: 0, spin: false, }, }, ], }, defaultValue: null, }, ], }, { name: 'title', title: { type: 'i18n', 'en-US': 'Title', 'zh-CN': '标题', }, setter: ['PisellI18nSetter', 'VariableSetter'], defaultValue: { type: 'i18n', en: 'Unsaved changes', 'zh-CN': '未保存的更改', 'zh-HK': '未保存的更改', }, }, { name: 'content', title: { type: 'i18n', 'en-US': 'Helper text', 'zh-CN': '辅助文字', }, setter: ['PisellI18nSetter', 'VariableSetter'], defaultValue: { type: 'i18n', en: 'You have unsaved changes. Leave anyway?', 'zh-CN': '您有未保存的更改,确定要离开?', 'zh-HK': '您有未保存的更改,確定要離開?', }, }, { name: 'cancelText', title: { type: 'i18n', 'en-US': 'Cancel button text', 'zh-CN': '取消按钮文字', }, setter: ['PisellI18nSetter', 'VariableSetter'], defaultValue: { type: 'i18n', en: 'Cancel', 'zh-CN': '取消', 'zh-HK': '取消', }, }, { name: 'okText', title: { type: 'i18n', 'en-US': 'OK button text', 'zh-CN': '确定按钮文字', }, setter: ['PisellI18nSetter', 'VariableSetter'], defaultValue: { type: 'i18n', en: 'Leave', 'zh-CN': '离开', 'zh-HK': '離開', }, }, ], }, columns: 1, forceInline: 1, mode: 'popup', }, }, }, ], }, ], configure: { props: [ { name: 'ref', title: { label: 'ref', tip: "ref | 通过 this.$('xxx') 获取到组件实例", }, defaultValue: () => { return `form_${uuid()}`; }, setter: 'StringSetter', supportVariable: true, }, { name: 'values', title: { label: '表单数据源', tip: '表单数据源' }, setter: 'JsonSetter', supportVariable: true, }, { name: 'colon', title: { label: '展示冒号', tip: '' }, defaultValue: true, setter: 'BoolSetter', supportVariable: true, }, { name: 'hideRequiredMark', title: { label: '隐藏必填标记', tip: '隐藏必填标记' }, defaultValue: false, setter: 'BoolSetter', supportVariable: true, }, { type: 'group', title: { type: 'i18n', 'en-US': 'Functions', 'zh-CN': '函数配置', }, display: 'accordion', items: [ { name: 'customSubmit', title: { label: { type: 'i18n', 'en-US': 'Custom Submit', 'zh-CN': '自定义提交', }, }, setter: { componentName: 'FunctionSetter', props: { template: 'customSubmit(values,${extParams}){\n// 自定义提交逻辑\nreturn values}', }, }, }, { name: 'beforeSubmit', title: { label: { type: 'i18n', 'en-US': 'Before Submit', 'zh-CN': '提交前校验', }, }, setter: { componentName: 'FunctionSetter', props: { template: 'beforeSubmit(values,${extParams}){\n// 提交前校验逻辑\n// 返回 true 继续提交,返回 false 阻止提交\nreturn true}', }, }, }, { name: 'formatSubmitValues', title: { label: { type: 'i18n', 'en-US': 'Format Submit Values', 'zh-CN': '提交前格式化数据', }, }, setter: { componentName: 'FunctionSetter', props: { template: 'formatSubmitValues(values,${extParams}){\n// 格式化数据\nreturn values}', }, }, }, { name: 'formatInitialValues', title: { label: { type: 'i18n', 'en-US': 'Format Initial Values', 'zh-CN': '获取详情时格式化数据', }, }, setter: { componentName: 'FunctionSetter', props: { template: 'formatInitialValues(values,${extParams}){\n// 格式化数据\nreturn values}', }, }, }, ], }, { type: 'group', title: '布局', display: 'accordion', items: [ { name: 'labelCol', title: '标签栅格布局设置', display: 'inline', setter: { componentName: 'ObjectSetter', props: { config: { items: [ { name: 'span', title: '宽度', setter: { componentName: 'NumberSetter', props: { min: 0, max: 24, }, }, }, { name: 'offset', title: '偏移', setter: { componentName: 'NumberSetter', props: { min: 0, max: 24, }, }, }, ], }, extraSetter: [ { name: 'span', title: '宽度', setter: { componentName: 'NumberSetter', props: { min: 0, max: 24, }, }, }, ], }, }, description: 'label 标签布局,同 `` 组件,设置 span offset 值,如 {span: 8, offset: 16},该项仅在垂直表单有效', }, { name: 'wrapperCol', title: '内容栅格布局设置', display: 'inline', setter: { componentName: 'ObjectSetter', props: { config: { items: [ { name: 'span', title: '宽度', setter: { componentName: 'NumberSetter', props: { min: 0, max: 24, }, }, }, { name: 'offset', title: '偏移', setter: { componentName: 'NumberSetter', props: { min: 0, max: 24, }, }, }, ], }, }, }, description: '需要为输入控件设置布局样式时,使用该属性,用法同 labelCol', }, ], }, { name: 'labelAlign', title: { label: '标签对齐', tip: 'label 标签的文本对齐方式', }, setter: { componentName: 'RadioGroupSetter', props: { options: [ { title: '左', value: 'left', }, { title: '右', value: 'right', }, ], }, }, defaultValue: 'right', }, { name: 'layout', title: { label: '表单布局', tip: '表单布局' }, setter: { componentName: 'RadioGroupSetter', props: { options: [ { title: '水平', value: 'horizontal', }, { title: '垂直', value: 'vertical', }, { title: '行内', value: 'inline', }, ], }, }, defaultValue: 'horizontal', }, { name: 'name', title: { label: '表单名称', tip: '表单名称,会作为表单字段 `id` 前缀使用', }, setter: 'StringSetter', supportVariable: true, }, { name: 'preserve', title: { label: '删除时保留值', tip: '当字段被删除时保留字段值', }, defaultValue: true, setter: 'BoolSetter', supportVariable: true, }, { name: 'scrollToFirstError', title: { label: '滚至错误', tip: '提交失败自动滚动到第一个错误字段', }, defaultValue: true, setter: 'BoolSetter', supportVariable: true, }, { name: 'size', title: { label: '字段组件尺寸', tip: '设置字段组件的尺寸(仅限 antd 组件)', }, setter: { componentName: 'RadioGroupSetter', props: { options: [ { title: '大', value: 'large', }, { title: '中', value: 'middle', }, { title: '小', value: 'small', }, ], }, }, defaultValue: 'middle', }, { name: 'validateMessages', title: { label: '验证提示模板', tip: '验证提示模板' }, setter: 'JsonSetter', }, { name: 'validateTrigger', title: { label: '校验时机', tip: '所有字段校验触发时机' }, setter: { componentName: 'RadioGroupSetter', props: { options: [ { title: '当前值变化时', value: 'onChange', }, { title: '失去焦点时', value: 'onBlur', }, ], }, }, }, { name: 'onFinish', title: { label: '提交表单且数据验证成功后回调事件', tip: '提交表单且数据验证成功后回调事件', }, }, { name: 'onFinishFailed', title: { label: '提交表单且数据验证失败后回调事件', tip: '提交表单且数据验证失败后回调事件', }, }, { name: 'onFieldsChange', title: { label: '字段更新时触发回调事件', tip: '字段更新时触发回调事件', }, }, { name: 'onValuesChange', title: { label: '字段值更新时触发回调事件', tip: '字段值更新时触发回调事件', }, }, { name: 'onDataSourceFinish', title: { label: '数据源提交成功', tip: '数据源提交成功', }, }, ], component: { isContainer: true }, supports: { style: true, events: [ { name: 'onFinish', template: "onFinish(values,${extParams}){\n// 提交表单且数据验证成功后回调事件\nconsole.log('onFinish',values);}", }, { name: 'onFinishFailed', template: "onFinishFailed({values,errorFields,outOfDate},${extParams}){\n// 提交表单且数据验证失败后回调事件\nconsole.log('onFinishFailed',values, errorFields, outOfDate);}", }, { name: 'onFieldsChange', template: "onFieldsChange(changedFields,allFields,${extParams}){\n// 字段更新时触发回调事件\nconsole.log('onFieldsChange',changedFields,allFields);}", }, { name: 'onValuesChange', template: "onValuesChange(changedValues,allValues,${extParams}){\n// 字段值更新时触发回调事件\nconsole.log('onValuesChange',changedValues,allValues);}", }, { name: 'onDataSourceFinish', template: "onDataSourceFinish(values,result,${extParams}){\n// 数据源提交成功\nconsole.log('onDataSourceFinish',values,result);}", }, { name: 'onDataSourceLoaded', template: "onDataSourceLoaded(values,rawResult,${extParams}){\n// 数据源加载完成\nconsole.log('onDataSourceLoaded',values,rawResult);}", } ], }, advanced: { callbacks: { onNodeAdd: (dragment, currentNode) => { const comps = [ 'Input', 'Select', 'Radio', 'Checkbox', 'Switch', 'Upload', 'Datepicker', 'Rate', 'Transfer', ]; if ( !dragment || !dragment.componentMeta || !dragment.componentMeta.npm || !dragment.componentMeta.npm.package || dragment.componentMeta.npm.package.indexOf( '@alilc/antd-lowcode-materials' ) === -1 || comps.every((comp) => dragment.componentName.indexOf(comp) === -1) ) { return; } // 为目标元素包裹一层P const layoutPNode = currentNode?.document?.createNode({ componentName: 'Form.Item', props: { label: '表单项: ', }, children: [(dragment as any).exportSchema()], }); // 当前dragment还未添加入node子节点,需要setTimeout处理 setTimeout(() => { (currentNode?.replaceChild as any)( dragment, (layoutPNode as any).exportSchema(), // 避免生成新的 nodeId { reserveSchemaNodeId: true } ); }, 1); }, }, }, }, }; export default dataSourceForm;