使用XRender(4)_动态设置配置项

95 阅读7分钟

1.当选中的画布组件变化时 onCanvasSelect

1.1 需求:因为在实际操作过程中,拖拽后的组件id是必须存在且不能重复,这个随机id值不能作为和后端交互的标识,需要自定义一个组件,用于存放后端的标识。这个id只能在前端当作唯一key。

通过这个api,可以获取当前选中组件的schema值,通过设置状态保存当前选中的唯一标识id,为了后续拿值作准备

      const onCanvasSelect = (schema) => {
      
        // 获取当前选中的组件唯一ID
        // console.log(schema.$id?.split('/')[1], 'onCanvasSelect', schema);
        
        // 保存当前选中的标识
        setCurrentSelect(schema.$id?.split('/')[1]);
      };

2.当表单schema变化时 onSchemaChange

2.1 需求:侧边通过自定义初始配置,添加了下拉选择表单属性,每当拖拽一个组件,都需要选择一个属性值,并且每个属性值只能使用一次。例如,下拉选项3个属性值,已经使用一个,那么当选中当前组件时,只会展示剩余的两个。

2.2 逻辑实现:

1.根据保存的标识,通过schema值,获取到当前操作组件的属性。

2.获取已使用的属性值,判断当选择的数据库发生改变时,可以调接口,自定义配置schema,例如:添加额外属性配置,正则表达式限制。

3.需要判断当前的表属性是否存在,并且改变时才要重新set,不然会造成不管修改了什么属性页面都会重新set的bug

4.封装公共的方法,根据全部属性和使用属性,找出剩余的属性并重新设置

5.深度遍历判断两个对象是否相等

2.3 代码实现

     const onSchemaChange = (value) => {
        // console.log(value, 'onSchemaChange');
        let transSchema = JSON.parse(JSON.stringify(mySchema));
        // 判断当前选择的值是否为空
        if (JSON.stringify(value) != '{}') {
          let transSelect = value.properties[currentSelect];
          // console.log(transSchema, 'transSchema', value, 'transSelect');

          // 获取已使用属性
          let useProperty = [];
          for (let key in value.properties) {
            let target = value.properties[key];
            if (target.weightProperty) {
              useProperty.push(target.weightProperty);
            }
          }
          // console.log(useProperty,'useProperty')

          // 判断是否选择对应的库
          if (selectValue) {
            // 判断当前点击的组件是否为上一个组件,判断当前选择的库是否为新组件,就组件中是否存在属性,并且判断当前组件选择的库是否发生改变
            // console.log(transSchema, transSelect);
            if (
              JSON.stringify(transSchema) == '{}' ||
              !transSchema.properties[currentSelect] ||
              (transSelect &&
                !deepEqual(transSchema.properties[currentSelect], transSelect))
            ) {
              // 判断当选择的数据库发生改变时,可以调接口,自定义配置schema,例如:添加额外属性配置,正则表达式限制
              console.log('存在即证明');
              // 需要判断当前的表属性是否存在,并且改变时才要重新set,不然会造成不管修改了什么属性页面都会重新set的bug
              if (
                transSelect?.weightProperty &&
                (JSON.stringify(transSchema) == '{}' ||
                  transSelect?.weightProperty !=
                    transSchema.properties[currentSelect]?.weightProperty)
              ) {
                properList.length != 0 &&
                  properList.forEach((m) => {
                    if (transSelect.weightProperty == m.id) {
                      // 设置回显时需要的数据
                      value.properties[currentSelect].title = m.attrDescribe;
                      value.properties[currentSelect].formId = m.attrName;

                      // 根据返回的类型,添加限制条件
                      if (m.attrType == 'varchar') {
                        value.properties[currentSelect].max = 65535;
                        value.properties[currentSelect].min = 1;
                        value.properties[currentSelect].required = true;
                      } else if (m.attrType == 'float') {
                        value.properties[currentSelect].min = 1;
                        let rules = [
                          { required: true, message: '不能为空!' },
                          {
                            pattern: '^[0-9]{0,9}[.][0-9]{1,9}$',
                            message: '只能输入float类型!',
                          },
                        ];
                        value.properties[currentSelect].rules = rules;
                        value.properties[currentSelect].required = true;
                      } else if (m.attrType == 'int') {
                        value.properties[currentSelect].min = 1;
                        value.properties[currentSelect].max = 5;
                        let rules = [
                          { required: true, message: '不能为空!' },
                          {
                            pattern: '^[0-9]{0,5}$',
                            message: '只能输入int类型!',
                          },
                        ];
                        value.properties[currentSelect].rules = rules;
                        value.properties[currentSelect].required = true;
                      }
                      setMySchema(value);
                    }
                  });
              }
              actionSettting(useProperty);
            }
          }
        }
      };
      // 封装公共的方法,根据全部属性和使用属性,找出剩余的属性并重新设置
      const actionSettting = (useProperty) => {
        let transProperList = JSON.parse(JSON.stringify(properList)).filter(
          (m) => !useProperty.includes(m.id),
        );
        let transEnum = transProperList.map((m) => m.id);
        let transEnumName = transProperList.map((m) => m.attrDescribe);
        setFormSetting({
          weightProperty: {
            title: '关联数据表',
            type: 'string',
            enum: transEnum,
            enumNames: transEnumName,
            weight: 'select',
          },
          ...commonSettings,
        });
      };
      // 深度遍历判断两个对象是否相等
      function deepEqual(object1, object2) {
        let keys1 = Object.keys(object1);
        let keys2 = Object.keys(object2);

        if (keys1.length !== keys2.length) {
          return false;
        }

        for (let index = 0; index < keys1.length; index++) {
          let val1 = object1[keys1[index]];
          let val2 = object2[keys2[index]];
          let areObjects = isObject(val1) && isObject(val2);
          if (
            (areObjects && !deepEqual(val1, val2)) ||
            (!areObjects && val1 !== val2)
          ) {
            return false;
          }
        }

        return true;
      }

      function isObject(object) {
        return object != null && typeof object === 'object';
      }

3.删除事件 canDelete

3.1 遇到的问题:当删除组件时,表单schema会监听到变化,捕获到的当前标识为undefined,或者自动跳转到当前拖拽的第一个组件标识,所以造成页面无法重新actionSettting

3.2 解决方法: 在删除的api里监听到事件

3.3 代码

      // 删除操作,因为在删除时,表单的onSchemaChange方法setFormSetting时页面不会重新渲染,只能在删除内操作
      const canDelete = () => {
        let transSchema = JSON.parse(JSON.stringify(mySchema));
        if (transSchema) {
          let useProperty = [];
          for (let key in transSchema.properties) {
            let target = transSchema.properties[key];
            // 因为这里是删除操作,当选中的组件id和当前删除的id不一致时,才可以添加
            if (
              target.weightProperty &&
              target.weightProperty !=
                transSchema.properties[currentSelect]?.weightProperty
            ) {
              useProperty.push(target.weightProperty);
            }
          }
          actionSettting(useProperty);
        }
        return true;
      };

整个代码,可以copy测试

    import { useEffect, useState, useCallback, useRef } from 'react';
    import styles from './index.less';
    import { history, useSelector, useDispatch } from 'umi';
    import { Button, Modal, Table, Select, Spin, message } from 'antd';
    import { ExportOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
    import commonSettings from './baseConfig/commonSettings';
    import settings from './baseConfig/settings';
    import globalSettings from './baseConfig/globalSettings';
    import Generator from 'fr-generator';

    const UserSystem = ({ location }) => {
      const { id } = location.query;
      const dispatch = useDispatch();
      const genRef = useRef();

      // 控制遮罩层
      const [loading, setLoading] = useState(true);

      // 下拉选择
      const [selectValue, setSelectValue] = useState('');

      // 退出弹窗控制
      const [outTip, setOutTip] = useState(false);

      // 保存、发布弹窗控制
      const [actionTip, setActionTip] = useState(false);
      const [status, setStatus] = useState('');

      // 处理后的table数据
      const [tableData, setTableData] = useState([]);

      // 保存设置后的schema
      const [mySchema, setMySchema] = useState({});

      // 当前选中的组件
      const [currentSelect, setCurrentSelect] = useState(undefined);

      // 所有数据库列表
      const unIssueList = useSelector((state) => state.userSystem.unIssueList);

      // 表单配置
      const [formSetting, setFormSetting] = useState({});

      // 保存返回的数据表
      const [properList, setProperList] = useState([]);

      // 获取数据库列表
      useEffect(() => {
        dispatch({
          type: 'userSystem/unIssueList',
          payload: {
            issue: 0,
          },
        }).then((res) => {
          if (id && res && res.status === 20000) {
            setSelectValue(String(id));
          }
        });
      }, []);

      // 监听选择的数据库,调数据表接口,重新设置配置项
      useEffect(() => {
        // console.log(selectValue, 'selectValue');
        if (selectValue) {
          let dataBase = JSON.parse(JSON.stringify(unIssueList));
          let modelId = dataBase.filter((m) => m.id == selectValue);
          setLoading(false);
          dispatch({
            type: 'userSystem/propertiesList',
            payload: {
              modelId: modelId[0].tableId,
            },
          }).then((res) => {
            if (res && res.status === 20000) {
              // 保存返回表数据源
              setProperList(res.data);

              // 将返回的数据,保存至schema格式
              let transEnum = res.data.map((m) => m.id);
              let transEnumName = res.data.map((m) => m.attrDescribe);
              setFormSetting({
                weightProperty: {
                  title: '关联数据表',
                  type: 'string',
                  enum: transEnum,
                  enumNames: transEnumName,
                  weight: 'select',
                },
                ...commonSettings,
              });
            }
          });
        }
      }, [selectValue]);

      // 将保存schema,强制设置到页面
      useEffect(() => {
        if (JSON.stringify(mySchema) != '{}') {
          genRef.current.setValue({
            labelWidth: 120,
            displayType: 'row',
            ...mySchema,
          });
        }
      }, [mySchema]);

      // 深度遍历判断两个对象是否相等
      function deepEqual(object1, object2) {
        let keys1 = Object.keys(object1);
        let keys2 = Object.keys(object2);

        if (keys1.length !== keys2.length) {
          return false;
        }

        for (let index = 0; index < keys1.length; index++) {
          let val1 = object1[keys1[index]];
          let val2 = object2[keys2[index]];
          let areObjects = isObject(val1) && isObject(val2);
          if (
            (areObjects && !deepEqual(val1, val2)) ||
            (!areObjects && val1 !== val2)
          ) {
            return false;
          }
        }

        return true;
      }

      function isObject(object) {
        return object != null && typeof object === 'object';
      }

      // 数据库列表下拉
      const selectChange = (value) => {
        setSelectValue(value);
        if (!value) {
          setLoading(true);
          setFormSetting({
            ...commonSettings,
          });
        }
        if (!value || value != selectValue) {
          genRef.current.setValue({});
        }
      };

      const onSchemaChange = (value) => {
        // console.log(value, 'onSchemaChange');
        let transSchema = JSON.parse(JSON.stringify(mySchema));
        // 判断当前选择的值是否为空
        if (JSON.stringify(value) != '{}') {
          let transSelect = value.properties[currentSelect];
          // console.log(transSchema, 'transSchema', value, 'transSelect');

          // 获取已使用属性
          let useProperty = [];
          for (let key in value.properties) {
            let target = value.properties[key];
            if (target.weightProperty) {
              useProperty.push(target.weightProperty);
            }
          }
          // console.log(useProperty,'useProperty')

          // 判断是否选择对应的库
          if (selectValue) {
            // 判断当前点击的组件是否为上一个组件,判断当前选择的库是否为新组件,就组件中是否存在属性,并且判断当前组件选择的库是否发生改变
            // console.log(transSchema, transSelect);
            if (
              JSON.stringify(transSchema) == '{}' ||
              !transSchema.properties[currentSelect] ||
              (transSelect &&
                !deepEqual(transSchema.properties[currentSelect], transSelect))
            ) {
              // 判断当选择的数据库发生改变时,可以调接口,自定义配置schema,例如:添加额外属性配置,正则表达式限制
              console.log('存在即证明');
              // 需要判断当前的表属性是否存在,并且改变时才要重新set,不然会造成不管修改了什么属性页面都会重新set的bug
              if (
                transSelect?.weightProperty &&
                (JSON.stringify(transSchema) == '{}' ||
                  transSelect?.weightProperty !=
                    transSchema.properties[currentSelect]?.weightProperty)
              ) {
                properList.length != 0 &&
                  properList.forEach((m) => {
                    if (transSelect.weightProperty == m.id) {
                      // 设置回显时需要的数据
                      value.properties[currentSelect].title = m.attrDescribe;
                      value.properties[currentSelect].formId = m.attrName;

                      // 根据返回的类型,添加限制条件
                      if (m.attrType == 'varchar') {
                        value.properties[currentSelect].max = 65535;
                        value.properties[currentSelect].min = 1;
                        value.properties[currentSelect].required = true;
                      } else if (m.attrType == 'float') {
                        value.properties[currentSelect].min = 1;
                        let rules = [
                          { required: true, message: '不能为空!' },
                          {
                            pattern: '^[0-9]{0,9}[.][0-9]{1,9}$',
                            message: '只能输入float类型!',
                          },
                        ];
                        value.properties[currentSelect].rules = rules;
                        value.properties[currentSelect].required = true;
                      } else if (m.attrType == 'int') {
                        value.properties[currentSelect].min = 1;
                        value.properties[currentSelect].max = 5;
                        let rules = [
                          { required: true, message: '不能为空!' },
                          {
                            pattern: '^[0-9]{0,5}$',
                            message: '只能输入int类型!',
                          },
                        ];
                        value.properties[currentSelect].rules = rules;
                        value.properties[currentSelect].required = true;
                      }
                      setMySchema(value);
                    }
                  });
              }
              actionSettting(useProperty);
            }
          }
        }
      };

      const onCanvasSelect = (schema) => {
        // 获取当前选中的组件唯一ID
        // console.log(schema.$id?.split('/')[1], 'onCanvasSelect', schema);
        setCurrentSelect(schema.$id?.split('/')[1]);
      };

      // 封装公共的方法,根据全部属性和使用属性,找出剩余的属性并重新设置
      const actionSettting = (useProperty) => {
        let transProperList = JSON.parse(JSON.stringify(properList)).filter(
          (m) => !useProperty.includes(m.id),
        );
        let transEnum = transProperList.map((m) => m.id);
        let transEnumName = transProperList.map((m) => m.attrDescribe);
        setFormSetting({
          weightProperty: {
            title: '关联数据表',
            type: 'string',
            enum: transEnum,
            enumNames: transEnumName,
            weight: 'select',
          },
          ...commonSettings,
        });
      };

      // 删除操作,因为在删除时,表单的onSchemaChange方法setFormSetting时页面不会重新渲染,只能在删除内操作
      const canDelete = () => {
        let transSchema = JSON.parse(JSON.stringify(mySchema));
        if (transSchema) {
          let useProperty = [];
          for (let key in transSchema.properties) {
            let target = transSchema.properties[key];
            // 因为这里是删除操作,当选中的组件id和当前删除的id不一致时,才可以添加
            if (
              target.weightProperty &&
              target.weightProperty !=
                transSchema.properties[currentSelect]?.weightProperty
            ) {
              useProperty.push(target.weightProperty);
            }
          }
          actionSettting(useProperty);
        }
        return true;
      };

      const columns = [
        {
          title: '序号',
          dataIndex: 'order',
          key: 'order',
          ellipsis: true,
        },
        {
          title: '控件标题',
          dataIndex: 'title',
          key: 'title',
          ellipsis: true,
        },
        {
          title: '关联数据表名',
          dataIndex: 'titleProperty',
          key: 'titleProperty',
          ellipsis: true,
        },
      ];

      // 保存、发布前需要处理schema数据,设置弹窗列表数据源
      const actionClick = (val) => {
        // 根据输出的schema 取出table信息
        let value = genRef.current && genRef.current.getValue();
        let transProperList = JSON.parse(JSON.stringify(properList));
        let transData = [];
        let num = 1;
        for (let key in value.properties) {
          let target = value.properties[key];
          // console.log(key, value.properties[key]);
          // 展示列表前进行校验,关联表是否选择
          if (!target?.weightProperty) {
            message.error('请选择关联表');
            return false;
          }
          let middleData = {};
          middleData.order = num;
          middleData.title = transProperList.filter(
            (m) => m.id == target.weightProperty,
          )[0].attrDescribe;
          middleData.titleProperty = target.formId;
          middleData.weightProperty = target.weightProperty;
          transData.push(middleData);
          num++;
        }
        // console.log(transData, 'transData');
        setTableData(transData);
        setStatus(val);
        setActionTip(true);
      };

      // 保存、发布按钮
      const modalAction = () => {
        // status === 'save' ? '保存' : '发布'
        let issue = status === 'save' ? 0 : 1;
        let schema = genRef.current && genRef.current.getValue();
        dispatch({
          type: 'userSystem/saveFormDesign',
          payload: {
            formId: selectValue,
            properties: tableData.map((m) => m.weightProperty),
            issue: issue,
            schema: JSON.stringify(schema),
          },
        }).then((res) => {
          let tip = status === 'save' ? '保存' : '发布';
          if (res && res.status === 20000) {
            message
              .success(`${tip}成功`)
              .then(() => history.push('/designCenter/formManage'));
          } else {
            message.error(`${tip}失败`);
          }
          setActionTip(false);
        });
      };

      // 退出按钮
      const outClick = (val) => {
        if (val === 'modalOk') {
          // console.log('保存并退出');
          // 退出前进行校验,关联表是否选择
          let value = genRef.current && genRef.current.getValue();
          for (let key in value.properties) {
            let target = value.properties[key];
            if (!target?.weightProperty) {
              message.error('请选择关联表');
              return false;
            }
          }
          dispatch({
            type: 'userSystem/saveFormDesign',
            payload: {
              formId: selectValue,
              properties: tableData.map((m) => m.weightProperty),
              issue: 0,
              schema: JSON.stringify(value),
            },
          }).then((res) => {
            if (res && res.status === 20000) {
              message
                .success('保存成功')
                .then(() => history.push('/designCenter/formManage'));
            } else {
              message.error('保存失败');
            }
            setActionTip(false);
          });
        } else if (val === 'modalOut') {
          // console.log('不保存');
          history.push('/designCenter/formManage');
        } else {
          history.push('/designCenter/formManage');
          // console.log('取消');
        }
        setOutTip(false);
      };

      // 退出按钮
      const backOut = () => {
        setOutTip(true);
      };

      return (
        <div className={styles.formDesing}>
          <div className={styles.topContent}>
            <div style={{ fontSize: '20px' }}>编辑表单</div>
            <div className={styles.rightContent}>
              <Button type="primary" onClick={() => actionClick('save')}>
                保存
              </Button>
              <Button type="primary" onClick={() => actionClick('publish')}>
                发布
              </Button>
              <ExportOutlined
                style={{ fontSize: '20px', color: '#00B4ED', cursor: 'pointer' }}
                onClick={backOut}
              />
            </div>
          </div>
          <div className={styles.selectContent}>
            <span>数据模型:</span>
            <Select
              allowClear
              style={{ width: '200px' }}
              value={selectValue}
              onChange={selectChange}
            >
              {Array.isArray(unIssueList) &&
                unIssueList.length > 0 &&
                unIssueList.map((item, index) => {
                  return (
                    <Select.Option value={item.id} key={index}>
                      {item.formName}
                    </Select.Option>
                  );
                })}
            </Select>
          </div>
          <Spin spinning={loading} indicator={<span>请选择关联数据模型!</span>}>
            <div style={{ height: '80vh' }}>
              <Generator
                ref={genRef}
                onCanvasSelect={onCanvasSelect} // 当选中的画布组件变化时
                onSchemaChange={onSchemaChange} // 当表单schema变化时
                extraButtons={[true, true, false, true]} // 前四项为插件默认按钮是否显示,之后才可以配置自定义按钮
                controlButtons={[true, false]} // 前两项为插件默认按钮是否显示,之后才可以配置自定义按钮
                canDelete={canDelete} // 删除事件
                hideId={true} // 不展示默认ID
                commonSettings={formSetting} // 右侧组件通用配置
                settings={settings} // 左侧基础组件配置
                globalSettings={globalSettings} // 右侧表单配置
              />
            </div>
          </Spin>
          <Modal
            title="信息提示"
            open={outTip}
            onCancel={() => setOutTip(false)}
            footer={[
              <Button
                key="modalOk"
                type="primary"
                onClick={() => outClick('modalOk')}
              >
                保存并退出
              </Button>,
              <Button
                key="modalOut"
                type="primary"
                onClick={() => outClick('modalOut')}
              >
                不保存
              </Button>,
              <Button key="modalCancel" onClick={() => outClick('modalCancel')}>
                取消
              </Button>,
            ]}
          >
            <div style={{ fontSize: '16px', margin: '10px' }}>
              <ExclamationCircleOutlined
                style={{ color: '#FFA96D', marginRight: '6px' }}
              />
              <span>表单数据暂未保存</span>
            </div>
            <div style={{ marginLeft: '10px' }}>请确认是否退出</div>
          </Modal>
          <Modal
            title={status === 'save' ? '保存表单' : '发布表单'}
            width="800px"
            open={actionTip}
            onCancel={() => setActionTip(false)}
            footer={[
              <Button key="modalAction" type="primary" onClick={modalAction}>
                {status === 'save' ? '保存' : '发布'}
              </Button>,
              <Button
                key="cancel"
                type="primary"
                onClick={() => setActionTip(false)}
              >
                取消
              </Button>,
            ]}
          >
            <div style={{ fontSize: '14px', margin: '10px' }}>
              <div style={{ marginBottom: '20px' }}>
                <span>表单名称:</span>
                <span>test1</span>
              </div>
              <div style={{ marginBottom: '20px' }}>
                <span>关联数据模型:</span>
                <span>test_table</span>
              </div>
              <Table columns={columns} dataSource={tableData} pagination={false} />
            </div>
          </Modal>
        </div>
      );
    };

    export default UserSystem;