表格新增空行或者按照数据如何复制行

场景

描述

新增按钮点击后表格出现空行,不带业务数据,需要有行内编辑

如何实现

第一步

在layout目录下新增copyTable组件,组件代码如下

import { BaseElementWidget, SPI, TableWidget, Widget } from '@kunlun/dependencies';
import { OioNotification } from '@kunlun/vue-ui-antd';

@SPI.ClassFactory(BaseElementWidget.Token({ widget: 'copy-table-row' }))
export class CopyTableWidget extends TableWidget {
    @Widget.BehaviorSubContext(Symbol("$$TABLE_COPY_CB"), {})
    private tableCopySub;

    @Widget.BehaviorSubContext(Symbol("$$TABLE_DELETE_CB"))
    private tableDeleteSub;

    @Widget.Reactive()
    @Widget.Provide()
    protected get editorMode(): any {
        return 'manual'
    }

    public async copyRowData(row,currentRow) {
        // 获取vxetable 实例
        const tableRef = this.getTableInstance()!.getOrigin();
        if (tableRef) {
            // 有复制未保存数据,如何处理?
            const insertData = tableRef.getInsertRecords();
            if(insertData.length > 0){
                OioNotification.warning("警告","请检查未保存数据!")
                return;
            }

            const { row: newRow } = await tableRef.insertAt(row,currentRow)
            // 插入一条数据并触发校验, 其中字段名称可以替换
            await tableRef.setEditCell(newRow, 'city')
        }
    }

    public async deleteRowData(row) {
        // 获取vxetable 实例
        const tableRef = this.getTableInstance()!.getOrigin();
        if (tableRef) {
            // 有复制未保存数据,如何处理?
            console.log(row, 'remove row')
            tableRef.remove(row)
            // 插入一条数据并触发校验
        }
    }

    async mounted() {
        super.mounted();
        this.tableCopySub.subject.next({copyCb: (row,currentRow) => this.copyRowData(row,currentRow)})
        this.tableDeleteSub.subject.next({deleteCb: (row) => this.deleteRowData(row)})
    }
}

第二步

在action目录下覆盖新增按钮或者复制行按钮;代码如下

import {ActionWidget, ClickResult, ReturnPromise, SPI, Widget} from "@kunlun/dependencies";

@SPI.ClassFactory(
  ActionWidget.Token({
    model: 'resource.k2.Model0000001211', // 替换对应模型
    name: 'uiView57c25f66fac9439089d590a4ac47f027' // 替换对应action的name
  })
)
export class CopyRow extends ActionWidget{
  @Widget.BehaviorSubContext(Symbol("$$TABLE_COPY_CB"))
  private tableCopySub;

  private tableCopyCb;

  @Widget.Method()
  public clickAction(): ReturnPromise<ClickResult> {
    // 按照某一条数据复制行, 按钮在行内
    // let data = JSON.parse(JSON.stringify(this.activeRecords?.[0]));
    // 复制行删除id
    // if(data) {
    //   delete data.id
    //   delete  data['_X_ROW_KEY']
    // }
    // console.log(data, 'datatatatat')
    // this.tableCopyCb(data,this.activeRecords?.[0])

    // 全局新增,不带默认数据
    this.tableCopyCb({},null)
  }

  mounted() {
    super.mounted()
    this.tableCopySub.subscribe((value) => {
      if(value) {
        // debugger
        this.tableCopyCb = value.copyCb
      }
    })
  }
}

第三步

替换对应的表格layout

// 替换第二个入参的模型和动作
const registerGlobalTableLayout = () => {
    return registerLayout(`<view type="TABLE">
    <pack widget="group">
        <view type="SEARCH">
            <element widget="search" slot="search" slotSupport="field" />
        </view>
    </pack>
    <element widget="actionBar" slot="actionBar" slotSupport="action">
        <xslot name="actions" slotSupport="action" />
    </element>
    <pack widget="group" slot="tableGroup">
        <element widget="copy-table-row" slot="table" slotSupport="field">
            <element widget="expandColumn" slot="expandRow" />
            <xslot name="fields" slotSupport="field" />
            <element widget="rowActions" slot="rowActions" slotSupport="action" />
        </element>
    </pack>
</view>`, { viewType: ViewType.Table, model: 'resource.k2.Model0000001211' }) 
}

registerGlobalTableLayout()

补充

  1. 新增空行后的动作可以根据行内数据配置显隐,比如有无id配置是编辑还是保存
  2. 新增后怎么开启行内编辑?可以进入界面设计器选中表格字段,开启行内编辑,新增行后会默认有行内编辑

Oinone社区 作者:王海明原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/14743.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

Like (0)
王海明's avatar王海明数式管理员
Previous 2024年7月12日 pm3:34
Next 2024年7月15日 pm3:59

相关推荐

  • 「前端」关闭源码依赖

    问题背景 在 5.x 版本的 oinone 前端架构中,我们开放了部分源码,但是导致以下性能问题: 1.构建耗时增加​​:Webpack 需要编译源码文件 2.​​加载性能下降​​:页面需加载全部编译产物 3.​​冷启动缓慢​​:开发服务器启动时间延长 以下方案通过关闭源码依赖。 操作步骤 1. 添加路径修正脚本 1: 在当前项目工程根目录中的scripts目录添加patch-package-entry.js脚本,内容如下: const fs = require('fs'); const path = require('path'); const targetPackages = [ '@kunlun+vue-admin-base', '@kunlun+vue-admin-layout', '@kunlun+vue-router', '@kunlun+vue-ui', '@kunlun+vue-ui-antd', '@kunlun+vue-ui-common', '@kunlun+vue-ui-el', '@kunlun+vue-widget' ]; // 递归查找目标包的 package.json function findPackageJson(rootDir, pkgName) { const entries = fs.readdirSync(rootDir); for (const entry of entries) { const entryPath = path.join(rootDir, entry); const stat = fs.statSync(entryPath); if (stat.isDirectory()) { if (entry.startsWith(pkgName)) { const [pkGroupName, name] = pkgName.split('+'); const pkgDir = path.join(entryPath, 'node_modules', pkGroupName, name); const pkgJsonPath = path.join(pkgDir, 'package.json'); if (fs.existsSync(pkgJsonPath)) { return pkgJsonPath; } } // 递归查找子目录 const found = findPackageJson(entryPath, pkgName); if (found) return found; } } return null; } // 从 node_modules/.pnpm 开始查找 const pnpmDir = path.join(__dirname, '../', 'node_modules', '.pnpm'); for (const pkgName of targetPackages) { const packageJsonPath = findPackageJson(pnpmDir, pkgName); if (packageJsonPath) { try { const packageJSON = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); if (packageJSON.main === 'index.ts') { const libName = packageJSON.name.replace('@', '').replace('/', '-'); packageJSON.main = `dist/${libName}.esm.js`; packageJSON.module = `dist/${libName}.esm.js`; const typings = 'dist/types/index.d.ts'; packageJSON.typings = typings; const [pwd] = packageJsonPath.split('package.json'); const typingsUrl = path.resolve(pwd, typings); const dir = fs.existsSync(typingsUrl); if (!dir)…

    2025年4月17日
    84200
  • 如何在表格的字段内添加动作

    介绍 在日常的业务中,我们经常需要在表格内直接点击动作完成一些操作,而不是只能在操作栏中,例如:订单的表格内点击商品名称或者里面的按钮跳转到商品详情页面,这里我们将带来大家来通过自定义表格字段来实现这个功能。 1.编写表格字段组件 组件ts文件TableBtnFieldWidget.ts import { ActionWidget, ActiveRecordExtendKeys, BaseFieldWidget, BaseListView, ModelFieldType, queryDslWidget, queryRowActionBar, RowContext, SPI, TableStringFieldWidget, BaseElementListViewWidget, ViewType, Widget } from '@kunlun/dependencies'; import { createVNode, VNode } from 'vue'; import { toString } from 'lodash-es'; import TableBtnField from './TableBtnField.vue'; @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Table, ttype: [ModelFieldType.String, ModelFieldType.Text], // widget: 'StringLink', // 以下3行配置代码测试用,生产建议在界面设计器自定义组件,widget填自定义组件的api名称 model: 'resource.k2.Model0000000109', name: 'name' }) ) export class TableBtnFieldWidget extends TableStringFieldWidget { @Widget.Reactive() private get triggerActionLabel() { // 界面设计器组件内设计该属性 return this.getDsl().triggerActionLabel || '更新'; } private getTriggerAction() { return this.model.modelActions.find((a) => a.label === this.triggerActionLabel); } private getTriggerActionWidget(widgetHandle: string, draftId: string, triggerActionLabel: string): ActionWidget | undefined { const listView = Widget.select(widgetHandle) as unknown as BaseListView; const listWidget = queryDslWidget(listView?.getChildrenInstance(), BaseElementListViewWidget); if (!listWidget) { return undefined; } const rowActionBar = queryRowActionBar(listWidget.getChildrenInstance(), draftId); const actionWidget = rowActionBar?.getChildrenInstance().find((a) => (a as ActionWidget).action.label === triggerActionLabel); return actionWidget as ActionWidget; } protected clickAction(context: RowContext) { const draftId = context.data[ActiveRecordExtendKeys.DRAFT_ID] as unknown as string; const actionWidget = this.getTriggerActionWidget(this.getRootHandle()!, draftId, this.triggerActionLabel); if (!actionWidget) { console.error('未找到action', this.triggerActionLabel); return; } actionWidget.click(); } @Widget.Method() public renderDefaultSlot(context: RowContext): VNode[] | string { const value = toString(this.compute(context)); if (value) { return [ createVNode(TableBtnField,…

    2024年5月16日
    2.4K00
  • oinone的rsql与传统sql语法对照表

    rsql sql 描述 field01 == "name" field01 = "name" 等于 field01 != "name" field01 != "name" 不等于 field01 =gt= 1 field01 > 1 大于 field01 =ge= 1 field01 >= 1 大于等于 field01 =lt= 1 field01 < 1 小于 field01 =le= 1 field01 <= 1 小于等于 field01 =isnull=true field01 is null 字段为null field01 =notnull= 1 field01 is not null 字段不为null field01 =in= ("foo") field01 in ("foo") 多条件 field01 =out= ("foo") field01 not in ("foo") 不在多条件中 field01 =cole= field02 field01 = field02 字段作为查询参数 field01 =colnt= field02 field01 != field02 字段作为查询参数 field01 =like="foo" field01 like "%foo%" 全模糊匹配,rsql语法中无需拼接通配符”%“ field01 =starts="foo" field01 like "foo%" 前缀模糊匹配,rsql语法中无需拼接通配符”%“ field01 =ends="foo" field01 like “%foo" 后缀模糊匹配,rsql语法中无需拼接通配符”%“ field01 =notlike="foo" field01 not like "%foo%" 全模糊不匹配,rsql语法中无需拼接通配符”%“ field01 =notstarts="foo" field01 not like "foo%" 前缀模糊不匹配,rsql语法中无需拼接通配符”%“ field01 =notends="foo" field01 not like “%foo" 后缀模糊不匹配,rsql语法中无需拼接通配符”%“ field01 =has=(ENUM_NAME1, ENUM_NAME2) 有多值枚举中的几个值 field01 =hasnt=(ENUM_NAME1,ENUM_NAME2) 没有多值枚举中的几个值 field01 =bit=ENUM_NAME1 有二进制枚举中的单个值 field01 =notbit=ENUM_NAME1 没有二进制枚举中的单个值 前端代码中使用工具类拼接rsql 该工具类在oinone的前端基础框架中提供 import { Condition } from '@kunlun/dependencies'; const rsqlCondition = new Condition('field01').equal('foo') .and(new Condition('field02').in(['bar'])) .and(new Condition('field03').notIn(['foo'])) .or(new Condition('field04').greaterThanOrEuqalTo(12)) .or(new Condition('field05').like('foo')) .or(new Condition('field06').notStarts('bar')) .or(new Condition('field07').isNull()) .or(new Condition('field08').notNull()) .and(new Condition('field09').bitEqual('BIT_ENUM_1')) .and(new Condition('field10').bitNotEqual('BIT_ENUM_2')) .and(new Condition('field11').has('ENUM_NAME_1')) .and(new Condition('field12').hasNot(['ENUM_NAME_2', 'ENUM_NAME_3'])); const rsqlStr = rsqlCondition.toString();…

    2023年11月1日
    4.6K00
  • 前端页面嵌套

    我们可能会遇到这些需求,如:页面中的一对多字段不是下拉框,而是另一个模型的表单组;页面中的步骤条表单,每一步的表单都需要界面设计器设计,同时这些表单可能属于不同模型。这时候我们就可以采取页面嵌套的方式,在当前页面中,动态创建一个界面设计器设计的子页面。以一对多字段,动态创建表单子页面举例,以下是代码实现和原理分析。 代码实现 AddSubformWidget 动态添加表单 ts 组件 import { ModelFieldType, ViewType, SPI, BaseFieldWidget, Widget, FormO2MFieldWidget, ActiveRecord, CallChaining, createRuntimeContextByView, queryViewDslByModelAndName, uniqueKeyGenerator } from '@oinone/kunlun-dependencies'; import { MyMetadataViewWidget } from './MyMetadataViewWidget'; import { watch } from 'vue'; import AddSubform from './AddSubform.vue'; @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.OneToMany, widget: 'AddSubform' }) ) export class AddSubformWidget extends FormO2MFieldWidget { public initialize(props) { super.initialize(props); this.setComponent(AddSubform); return this; } @Widget.Reactive() public myMetadataViewWidget: MyMetadataViewWidget[] = []; @Widget.Reactive() public myMetadataViewWidgetKeys: string[] = []; @Widget.Reactive() public myMetadataViewWidgetLength = 0; // region 子视图配置 public get subviewModel() { return this.getDsl().subviewModel || 'clm.contractcenter.ContractSignatory'; } public get subviewName() { return this.getDsl().subviewName || '签署方_FORM_uiViewa9c114903e104800b15e8f3749656b64'; } // region 添加子视图块 // 按钮添加点击事件 @Widget.Method() public async onAddSubviewBlock() { const resView = await queryViewDslByModelAndName(this.subviewModel, this.subviewName); this.createDynamicSubviewWidget(resView); } // 创建子视图块 public async createDynamicSubviewWidget(view, activeRecord: ActiveRecord = {}) { if (view) { // 根据视图构建上下文 const runtimeContext = createRuntimeContextByView( { type: ViewType.Form, model: view.model, modelName: view.modelDefinition.name, module: view.modelDefinition.module, moduleName: view.modelDefinition.moduleName, name: view.name, dsl: view.template }, true, uniqueKeyGenerator(), this.currentHandle ); // 取得上下文唯一标识 const runtimeContextHandle = runtimeContext.handle; const slotKey = `Form_${uniqueKeyGenerator()}`; // 创建子视图组件 const widget = this.createWidget(new MyMetadataViewWidget(runtimeContextHandle), slotKey, // 插槽名 { metadataHandle: runtimeContextHandle,…

    2025年7月21日
    1.4K00
  • 【界面设计器】自定义字段组件实战——千分位输入框

    阅读之前 此文章为实战教程,已假定你熟悉了【界面设计器】较为完整的【自定义组件】相关内容。 如果在阅读过程中出现的部分概念无法理解,请自行学习相关内容。【前端】文章目录 业务背景 用户在输入【金额】字段时,实时展示千分位格式。 业务分析 从需求来看,我们需要实现一个【千分位】组件,并且该组件允许在【表单】视图中使用。 扩展实现:该组件虽然仅要求在【表单】中使用,但也可以在【搜索】中使用完全相同的实现,因此这里我们在注册时会增加【搜索】视图,将【千分位】组件应用在【搜索】中。对于【表格】、【详情】和【画廊】来说,该组件是没有【输入】行为的展示组件,在这里我们不进行演示。 准备工作 此处你应该已经在某个业务模型下,可以完整执行当前模型的全部【增删改查】操作。 业务模型定义 (以下仅展示本文章用到的模型字段,忽略其他无关字段。) 名称 API名称 业务类型 是否多值 长度(单值长度) 编码 code 文本 否 128 名称 name 文本 否 128 金额 money 金额 否 – 创建组件、元件 准备工作完成后,我们需要根据【业务背景】确定【组件】以及【元件】相关信息,并在【界面设计器】中进行创建。 以下操作过程将省略详细步骤,仅展示可能需要确认的关键页面。 创建千分位组件 创建千分位元件 启动SDK工程进行组件基本功能开发 (npm相关操作请自行查看SDK工程中内置的README.MD) 关键点详解 数据交互类型的字段组件(以下简称展示组件)与仅展示类型的字段组件(以下简称交互组件)有一些差别。 通常情况下,在展示组件中仅需使用value属性即可展示相关内容。在交互组件中除了value用于展示外,还需使用change、focus以及blur处理用户输入时的基本交互逻辑。 数据交互方法主要功能说明: change方法:当值发生变更时调用,根据字段相关配置将值回填至表单中。 focus方法:当组件获取焦点时调用,记录当前值,并在调用blur方法时进行处理。 blur方法:当前组件失去焦点时调用,根据focus方法记录的值,进行失焦触发逻辑的执行。 这里的三个数据交互方法仅仅是对用户行为的抽象,而并非限定其使用场景。 通常来说,这三个方法的调用顺序为:focus –> change –> blur。 如在日期时间组件中,面板打开时调用了focus方法,面板值发生变更时,调用了change方法,面板关闭时调用了blur方法。 如在地图组件中,选中地图上的某个点时仅会调用change方法,用户交互上并不能体现出focus和blur的行为。因此对于这个组件而言,只会有change方法被执行。 代码实现参考 PS:oio-input-number样式必须升级到4.6.x以上的最新版本才支持 Thousandth.vue <template> <a-input-number class="oio-input-number" :value="inputValue" :formatter="formatter" :parser="parser" @update:value="change" @focus="focus" @blur="blur" /> </template> <script lang="ts"> import { InputNumber as AInputNumber } from 'ant-design-vue'; import { computed, defineComponent } from 'vue'; export default defineComponent({ name: 'Thousandth', components: { AInputNumber }, props: { value: { type: [String, Number] }, change: { type: Function }, focus: { type: Function }, blur: { type: Function } }, setup(props) { const inputValue = computed(() => { return props.value; }); const formatter = (value) => { return `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ','); }; const parser = (value) => { return value.replace(/\$\s?|(,*)/g, ''); }; return { inputValue, formatter, parser }; } }); </script> FormMoneyThousandthFieldWidget.ts import { FormFieldWidget, ModelFieldType, SPI, ViewType } from '@kunlun/dependencies'; import Thousandth from './Thousandth.vue'; @SPI.ClassFactory( FormFieldWidget.Token({ viewType: [ViewType.Form, ViewType.Search], ttype: ModelFieldType.Currency, widget:…

    2024年4月19日
    2.2K00

Leave a Reply

Please Login to Comment