lcdp

47 阅读6分钟

组件初始化

registerRef - 注册 ref, store 里存入当前组件。动态 ref 值为生成的 uuid + 组件名称。

    /* 注册 ref 实例 */
    registerRef(refId = '') {
      if (refId) {
        this.unregisterRef(refId)
      }
      if (!this.config.id) {
        this.config.id = this.config.type + uuid().split('-')[0]
      }
      if (this.designState) {
        // 覆盖新的refDesign 前判断 是否存在旧组件未销毁
        this.delFormModelKey(true)
        // 注册新的 refDesign
        this.$store.dispatch('designerStore/addRefDesign', {
          compId: this.config.id,
          ref: this
        })
      } else {
        this.$store.dispatch('designerStore/addRef', {
          compId: this.config.id,
          ref: this
        })
      }
    },

updateComponentList - 新增组件到画布组件列表

  • 合并 widgetConfig 里和组件 config 里的配置项
  • 将右侧配置数据 setting 存入 store 的 setting 中
    updateComponentsList() {
      // 新增组件到画布组件列表
      // let component = omit(this.CONFIG, ['setting'])
      const component = cloneDeep(this.CONFIG)
      this.$store.commit('designerStore/SET_SETTING', {
        compId: this.config.id,
        value: component.setting
      })
      // this.$set(this.config, 'setting', component.setting)
      if (this.designState) {
        // 合并propertiesConfig and styleConfig
        this.$set(
          this.config,
          'propertiesConfig',
          mergeObjFuc(component.propertiesConfig, this.config.propertiesConfig)
        )
        this.$set(
          this.config,
          'styleConfig',
          mergeObjFuc(component.styleConfig, this.config.styleConfig)
        )
      } else {
        !this.config.propertiesConfig &&
          this.$set(this.config, 'propertiesConfig', component.propertiesConfig)
        !this.config.styleConfig &&
          this.$set(this.config, 'styleConfig', component.styleConfig)
      }
      /* 容器组件处理 */
      // 栅格,
      if (component.type === 'grid') {
        if (!this.config.cols) {
          const cols = component.cols?.map(colConfig => {
            return {
              ...cloneDeep(colConfig),
              id: generateUuid({ prefix: colConfig.type + '-' })
            }
          })
          this.$set(this.config, 'cols', cols)
        }
      }

      // 拆分窗格
      if (component.type === 'split-pane' && !this.config.children) {
        const children = component.children?.map(colConfig =>
          cloneDeep(colConfig)
        )
        this.$set(this.config, 'children', children)
        this.config.children.map(col => {
          const colId = uuid()
          col.id = col.type + '-' + colId.split('-')[0]
        })
      }
      // tabs
      else if (component.type === 'tab' && !this.config.tabs) {
        this.$set(this.config, 'tabs', component.tabs)
        // 复制后数据源绑定对象重置
        this.$set(this.config, 'propertiesConfig', component.propertiesConfig)
      }
      // 表格
      else if (component.type === 'table' && !this.config.rows) {
        this.$set(this.config, 'rows', component.rows)
        if (this.config.type === 'table' && this.config.rows) {
          this.config.rows.map(row => {
            const rowId = uuid()
            row.id = row.type + '-' + rowId.split('-')[0]
            if (row.cols?.length) {
              row.cols.map(col => {
                const colId = uuid()
                col.id = col.type + '-' + colId.split('-')[0]
              })
            }
          })
        }
      }
      // form card dialog
      else if (
        ['form', 'card', 'dialog'].includes(component.type) &&
        !this.config.children
      ) {
        this.$set(this.config, 'children', component.children)
      }
      // // workflow
      else if (component.type === 'workflow') {
        if (!this.config.children) {
          this.$set(this.config, 'children', component.children)
        }
        if (!this.config.processData) {
          this.$set(this.config, 'processData', component.processData)
        }
      } else if (component.type === 'businessComp') {
        // this.$set(this.config, 'processData', component.processData)
      }
      // 默认
      else {
        if (!this.config.children) {
          this.$set(this.config, 'children', component.children)
        }
      }

      if (this.config.aliasLabel === undefined) {
        this.$set(this.config, 'aliasLabel', component.cnName)
      }

      if (!this.config.init) {
        this.config.init = true
        this.designerBus.$emit('components:history-record')
        // this.designerBus.$emit('components:select', this.config)
      }
      if (this.config.isDraggable) {
        this.designerBus.$emit('components:select', this.config)
        // 选中触发操作图标显示
        this.designerBus.$emit(
          `components:select-${this.config.id}`,
          this.config
        )
        this.config.isDraggable = false
      }
    },

设计器/画布、组件拖拉拽实现

widgetConfig.js 文件配置好各类型组件的属性,包括是否可拖动、组件名称、图标、属性配置(基本属性、高级属性等)以及一些特有的专属属性等。通过 vue-draggable 的 group 属性(如果一个页面有多个拖拽区域,通过设置group名称可以实现多个区域之间相互拖拽 ,同一名称的group之间可以互相拖拽,或者是一个对象{ name: “…”, pull: [true, false, ‘clone’, array , function], put: [true, false, array , function] } )。配置 list 属性通过对象的引用特性克隆组件的数据至画布的 vuex 里的页面大 json pageConfig 里的 componentsList。页面 pageConfig 记录了 formModel (如果表单数据没有绑定对象则保存在此)、pageEvents(事件)、globaleVars(全局变量)、varConfig(变量对于关联关系)。通过接口获取页面信息并解析反写到画布中。

变量模块功能

  • 设置变量 /packages/pages/designer/sidebarPanel/bindPanel/components/attrItem.vue
  1. 打开变量配置面板 组装 varConfig 并入 settings 里的属性 item。循环组件列表,拼出 path 路径
varConfig: {
  uuid: '', // 由于前置脚本会有多个,所以唯一约束不是变量标识,在每个变量被添加的时候会自动创建 uuid 作为唯一标识,后续获取变量信息和变量值都会通过 uuid。uuid 是左侧变量池里 varList 里的字段。
  name: '', // 绑定字段名,
  varCode: '', // 变量编码,用于获取真实值
  path: '', // 绑定变量路径
  frontScript: '' // 前置脚本,
  rearScript: '' // 后置脚本'
}

  1. 右侧属性值 checkbox 列表。

settings.proties.properties 对象转数组并且过滤出 isBindVar: true 的 item。

  1. 右侧属性值 checkbox 选中

同步更新 settings 里属性 varConfig 的对应值且 selected 设置为 true,同时同步更新到 store 里的当前组件 setting 值。

  1. 变量绑定面板左侧变量池列表项高亮实现

从store中组件的变量集合找出所选组件的关联变量集合数组和所选的属性名称做字段 name 匹配,如果匹配上给 varList 传入对应变量的 uuid 和 varList 的 row 进行匹配设置高亮和设置当前行

  1. 变量绑定面板中间组件列表绿点高亮实现 遍历当前组件的所有属性, 如果 store 里 settings 里对应属性的 varConfig 变量属性 varConfig.selected, 设置为高亮绿色

  2. 变量绑定面板右侧属性设置变量值勾选逻辑 组件 props 接收组件 obj 的varConfig,选中后修改 varConfig 的 selected 为 true

  3. 变量真实值渲染步骤

  • 初始化变量信息: 将变量信息更新到最新 无论来源哪里以及几个 只有第一次调用的时候会发请求

  • 获取 varList 后

this.varMap = new Map(_varList)

  • 循环 getVarConfigList 渲染每个组件的实际值 renderHandle

  • renderHandle 里执行 doVariable

  • doVariable 先判断 varList 是否存在 uuid 后执行前置脚本。

  • 前置脚本的返回结果先从前置脚本值 Map 中取,防止多次请求。varValueMap

(1)根据当前页面的变量列表 varList 提取出 varCodes,传递 varCodes 和 type 给接口获取列表和 varList 进行去重比较合并,确保当前页面是最新的变量列表。new Map(varList) 存入变量缓冲池 varDep; (2)改造,varConfig 的数据结构为组件 id 和 变量列表的关联关系 compId: 变量 List,改造为 uuid: [{ compId, path, rearScript, varCode }] 赋值为 target; (3)for in 遍历 target,执行 doVariable()获取变量数据并在每个遍历执行前置脚本,拿到变量前置脚本返回值存入 varValueMap(uuid: value) (4)前置脚本的返回值带入后置脚本的入参 (5)执行后置脚本、根据 compId、path、value 通过通用方法 setComProp 给对应的属性赋值。

  // 执行代码片段
  async exec(script, params) {
    try {
      const code = CodeScript.getCode(script, params)
      const fn = CodeScript.createAsyncFunction(
        code, // 代码
        'params', // 对象,会被解构,代码中可直接使用对象内属性名
        'CodeScript' // 类静态方法&属性
      )
      return await fn.call(this, params, CodeScript)
    } catch (error) {
      console.error('[CodeScript -> exec]:', error.message)
      throw error
    }
  }
  
   static createAsyncFunction(code, ...args) {
    const AsyncFunction = async function () {}.constructor
    return new AsyncFunction(...args, code)
  }

撤销前进功能

  • store 里维护 historyList 和 historyIndex。
  • 深度监听 componentsList。首次加载组件时重置 historyIndex 为 0。当 componentsList 发生变化时,historyIndex +1,同时将当前组件列表 clone 一份,并添加到 historyList。当撤销时,historyIndex -1,并获取对应历史组件列表,替换当前组件列表。当 historyIndex <= 1, 后退按钮 disabled,当 historyIndex === historyList.length, 前进按钮 disabled。

右侧配置项渲染。

根据 settings.properties 里的 basic 的 item.type 里判定使用什么渲染器。除一些比较特殊的比如 dataSource、buttonEditor 等,其他使用 renderBasic 渲染器。小弹窗展示 inner 配置项也在 renderBasic 渲染器中的 formItemWrapper 实现。

  valueConfig: {
    name: 'valueConfig',
    label: '值配置',
    type: 'additional',
    default: false,
    options: [
      {
        label: '', // 已配置
        class: '', // text-success
        icon: '', // el-icon-success
        value: true
      },
      {
        label: '', // 未配置
        class: '', // text-info
        icon: '', // el-icon-error
        value: false
      }
    ],
    isAdditional: {
      show: true,
      name: 'valueConfig',
      type: 'outer',
      children: []
    }
  },
  {
    name: 'defaultValue',
    label: i18n.t('dg.defaultValue'),
    type: 'input',
    default: '',
    isRequired: false,
    isBindVar: true,
    isRefresh: false,
    additional: 'valueConfig',
    _linkage: {
      show: [`(val1) => val1 === false `, 'basic.numberRuleSwitch']
    },
    _linkage: {
      show: [
        `(val1, val2, val3) => ((val1 === true || val2 === true) && (val3 === false)) || (val3 === true && val1 === true )`,
        'basic.readonly',
        'basic.disabled',
        'basic.numberRuleSwitch'
      ]
    },
  },

右侧配置项的显隐关系

通过 settings.properties 里的 _linkage 属性控制。

    isShow() {
      if (this.fieldConfig._linkage && this.fieldConfig._linkage.show) {
        const [condition, ...path] = this.fieldConfig._linkage.show
        return eval(condition)(...this.getValue(path))
      }
      return true
    },

    (val1, val2, val3) => (val1 === true || val2 === true) && val3 === false
    [true, false, false]

    // condition 条件字符串函数代码

保存页面参数


{
    "Content": "{\"pageEvents\":{},\"componentsList\":[{\"name\":\"base-input\",\"type\":\"input\",\"icon\":\"text-field\",\"cnName\":\"单行输入\",\"aliasLabel\":\"单行输入\",\"formItemFlag\":true,\"init\":true,\"isDraggable\":false,\"id\":\"input73a33cae\",\"propertiesConfig\":{\"basic\":{\"name\":\"\",\"label\":\"input\",\"labelAlign\":\"right\",\"valueConfig\":true,\"type\":\"text\",\"defaultValue\":\"fff\",\"placeholder\":\"\",\"displayAsText\":false,\"span\":24,\"size\":null,\"labelWidth\":null,\"labelHidden\":false,\"numberRuleSwitch\":false,\"numberRule\":{},\"readonly\":false,\"disabled\":false,\"hidden\":false,\"clearable\":true,\"showPassword\":false,\"required\":false,\"requiredHint\":\"\",\"validation\":\"\",\"validationHint\":\"\",\"ruleList\":[],\"componentDesc\":\"\",\"formatterCode\":\"\"},\"advanced\":{\"minLength\":0,\"maxLength\":128,\"showWordLimit\":false,\"prefixIcon\":\"\",\"suffixIcon\":\"\",\"appendButton\":false,\"appendButtonDisabled\":false,\"buttonIcon\":\"el-icon-search\"}},\"styleConfig\":{\"layout\":{\"widthUnit\":\"px\",\"heightUnit\":\"px\",\"padding\":{},\"margin\":{},\"opacity\":0},\"text\":{}},\"parentId\":\"\",\"parentPath\":\"\"}],\"formModel\":{},\"compData\":{\"input73a33cae\":{\"eventData\":{},\"i18nData\":{\"label\":\"\",\"componentDesc\":\"\",\"placeholder\":\"\",\"requiredHint\":\"\"},\"relationData\":{},\"logicData\":{}}},\"varList\":[],\"varConfigData\":{},\"globalVars\":[]}",
    "extraConfig": {
        "dataSourceUsage": [],
        "buttonInfos": [],
        "flowUsages": [],
        "businessComponents": []
    }
}

target lib 打包

打包设计器和渲染器

使用


"zjark-lcdp": "http://172.22.156.40:4873/zjark-lcdp/-/zjark-lcdp-1.8.32.tgz"

// 工单系统路由,内容防止的是低代码展示组件,根据 route.query 上的 pageCode 来获取接口信息进行渲染

    {
      path: '/designerEditor',
      name: 'designerEditor',
      component: () => import('@pkg/pages/designer/index.vue'),
      meta: {
        title: '低代码设计器',
        hidden: true
      }
    },