我们可能会遇到表格复制的需求,也就是表格填写的时候,不是增加一行数据,而是增加一个表格。
以下是代码实现和原理分析。
代码实现
在 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低代码应用平台体验