我们可能会遇到表格复制的需求,也就是表格填写的时候,不是增加一行数据,而是增加一个表格。
以下是代码实现和原理分析。
代码实现
在 boot 工程的 main.ts 中加入以下代码
import {
  registerDesignerFieldWidgetCreator,
  selectorDesignerFieldWidgetCreator
} from '@oinone/kunlun-ui-designer-dependencies';
// 注册无代码组件,将表头分组的无代码组件,注册成字段表格组件
registerDesignerFieldWidgetCreator(
  { widget: 'DynamicCreateTable' },
  selectorDesignerFieldWidgetCreator({ widget: TABLE_WIDGET })!
);
DynamicCreateTableWidget 动态添加表格 ts 组件
import {
  FormO2MTableFieldWidget,
  Widget,
  DslDefinition,
  RuntimeView,
  SubmitValue,
  BaseFieldWidget,
  ModelFieldType,
  SPI,
  ViewType,
  ActiveRecord,
  uniqueKeyGenerator
} from '@oinone/kunlun-dependencies';
import { MyMetadataViewWidget } from './MyMetadataViewWidget';
import DynamicCreateTable from './DynamicCreateTable.vue';
@SPI.ClassFactory(
  BaseFieldWidget.Token({
    viewType: ViewType.Form,
    ttype: ModelFieldType.OneToMany,
    widget: 'DynamicCreateTable'
  })
)
export class DynamicCreateTableWidget extends FormO2MTableFieldWidget {
  public myMetadataViewWidget: MyMetadataViewWidget[] = [];
  @Widget.Reactive()
  public myMetadataViewWidgetLength = 0;
  @Widget.Reactive()
  public myMetadataViewWidgetKeys: string[] = [];
  protected props: Record<string, unknown> = {};
  public initialize(props) {
    super.initialize(props);
    this.props = props;
    this.setComponent(DynamicCreateTable);
    return this;
  }
  // region 创建动态表格
  @Widget.Method()
  public async createTableWidget(record: ActiveRecord) {
    const index = this.myMetadataViewWidget.length;
    const handle = uniqueKeyGenerator();
    const slotKey = `MyMetadataViewWidget_${handle}`;
    const widget = this.createWidget(
      new MyMetadataViewWidget(handle),
      slotKey, // 插槽名称
      {
        subIndex: index,
        metadataHandle: this.metadataHandle,
        rootHandle: this.rootHandle,
        automatic: true,
        internal: true,
        inline: true
      }
    );
    this.initDynamicSubview(this.props, widget);
    widget.setData(record);
    this.myMetadataViewWidgetLength++;
    this.myMetadataViewWidgetKeys.push(slotKey);
    this.myMetadataViewWidget.push(widget);
  }
  protected initDynamicSubview(props: Record<string, unknown>, widget: MyMetadataViewWidget) {
    const { currentViewDsl } = this;
    let viewDsl = currentViewDsl;
    if (!viewDsl) {
      viewDsl = this.getViewDsl(props) as DslDefinition | undefined;
      this.currentViewDsl = viewDsl;
    }
    const runtimeSubview = this.generatorRuntimeSubview(props);
    this.initRuntimeContext(widget, runtimeSubview as RuntimeView);
    this.initSubviewAfterProperties(props);
  }
  // mountedProcess 里数据已经回填,根据值动态创建表格
  protected async mountedProcess() {
    await super.mountedProcess();
    if (Array.isArray(this.value) && this.value.length > 0) {
      this.value.forEach((item) => {
        this.createTableWidget(item);
      });
    } else {
      this.createTableWidget({});
    }
  }
  // region 删除动态表格
  @Widget.Method()
  public async deleteTableWidget(index) {
    this.myMetadataViewWidget.splice(index, 1);
    this.myMetadataViewWidgetKeys.splice(index, 1);
    this.myMetadataViewWidgetLength--;
  }
  // region 数据提交
  public async submit(submitValue: SubmitValue) {
    // 拿到所有子表格的数据
    const records = this.myMetadataViewWidget.map((widget) => widget.dataSource?.[0]).filter((record) => !!record);
    const returnValue = {};
    returnValue[this.itemName] = records;
    return returnValue;
  }
}
DynamicCreateTable.vue 动态添加表格 vue 组件
<template>
  <div class="dynamic-create-table" v-bind="basicProps">
    <div class="dynamic-create-table-container">
      <oio-icon icon="oinone-tianjia2" size="24" @click="createTableWidget({})" />
    </div>
    <template v-for="(key, index) in myMetadataViewWidgetKeys" :key="key">
      <div class="dynamic-delete-table-container">
        <oio-icon icon="oinone-shanchu" size="24" @click="deleteTableWidget(index)" />
        <slot :name="key" />
      </div>
    </template>
  </div>
</template>
<script lang="ts">
import { OioIcon, PropRecordHelper } from '@oinone/kunlun-dependencies';
import { computed, defineComponent, PropType } from 'vue';
export default defineComponent({
  name: 'DynamicCreateTable',
  inheritAttrs: false,
  components: { OioIcon },
  props: {
    myMetadataViewWidgetLength: {
      type: Number
    },
    myMetadataViewWidgetKeys: {
      type: Array as PropType<string[]>
    },
    createTableWidget: {
      type: Function,
      default: () => {}
    },
    deleteTableWidget: {
      type: Function,
      default: () => {}
    }
  },
  setup(props, context) {
    const basicProps = computed(() => {
      return PropRecordHelper.collectionBasicProps(context.attrs, [`inline-table`]);
    });
    return {
      basicProps
    };
  }
});
</script>
<style lang="scss">
.dynamic-create-table {
  display: flex;
  flex-direction: column;
  gap: 24px;
  > .dynamic-create-table-container {
    display: flex;
    justify-content: flex-end;
    > .oio-icon {
      cursor: pointer;
    }
  }
  > .dynamic-delete-table-container {
    position: relative;
    > .oio-icon {
      position: absolute;
      z-index: 1;
      right: 0;
      top: -12px;
      cursor: pointer;
    }
  }
  &.inline-table .oio-default-table-view {
    min-height: unset;
  }
}
</style>
MyMetadataViewWidget 数据隔离组件
import { ActiveRecord, ActiveRecords, CallChaining, MetadataViewWidget, Widget } from '@oinone/kunlun-dependencies';
export class MyMetadataViewWidget extends MetadataViewWidget {
  @Widget.Provide()
  public mountedCallChaining: CallChaining | undefined;
  @Widget.Provide()
  @Widget.Reactive()
  public dataSource: ActiveRecord[] = [];
  @Widget.Method()
  @Widget.Provide()
  public reloadDataSource(records: ActiveRecords | undefined) {
    if (Array.isArray(records)) {
      this.dataSource = records;
    } else {
      this.dataSource = [records || {}];
    }
  }
  @Widget.Provide()
  @Widget.Reactive()
  public activeRecords: ActiveRecord[] = [];
  @Widget.Method()
  @Widget.Provide()
  public reloadActiveRecords(records: ActiveRecords | undefined) {
    if (Array.isArray(records)) {
      this.activeRecords = records;
    } else {
      this.activeRecords = [records || {}];
    }
  }
  @Widget.Reactive()
  @Widget.Provide()
  public rootData: ActiveRecord[] | undefined;
  @Widget.Method()
  @Widget.Provide()
  public reloadRootData(records: ActiveRecords | undefined) {
    if (Array.isArray(records)) {
      this.rootData = records;
    } else {
      this.rootData = [records || {}];
    }
  }
  public initialize(props): this {
    this.mountedCallChaining = props.mountedCallChaining;
    this.subIndex = props.subIndex;
    super.initialize(props);
    return this;
  }
  protected mounted() {
    this.mountedCallChaining?.syncCall();
  }
  public getFormData() {
    return this.activeRecords?.[0];
  }
  public setData(data: Record<string, unknown>) {
    if (data) {
      this.reloadDataSource(data);
      this.reloadActiveRecords(data);
      this.reloadRootData(data);
    }
  }
  /**
   * 当前子路径索引
   */
  @Widget.Reactive()
  protected subIndex: string | number | undefined;
  /**
   * 上级路径
   */
  @Widget.Reactive()
  @Widget.Inject('path')
  protected parentPath: string | undefined;
  /**
   * 完整路径
   */
  @Widget.Reactive()
  @Widget.Provide()
  public get path() {
    const { parentPath, subIndex } = this;
    let path = parentPath || '';
    return `${path}.metadata[${subIndex || ''}]`;
  }
}
原理分析
参考 https://doc.oinone.top/frontend/view-api/21426.html
Oinone社区 作者:银时原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/components/21429.html
访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验