1. 简介

表单中的按钮操作逻辑为三类,分为内置按钮、接口按钮和在线编码。
按钮有前、中、后置三种执行逻辑,前置可以对表单数据做逻辑处理后,在中置按钮做数据请求,后置按钮可以基于数据请求后的结果做异常提醒。

联动相比事件缺少了data和btnConfig。utils,context,relation参数保持一致,extra 在事件和联动中会略有不同

1.1 按钮通用属性介绍

2. 内置按钮

内置按钮,封装了平台常用的按钮,比如保存、新增、删除、关联新增

3. 接口按钮

接口按钮,顾名思义,就是调用我们专门开发的业务接口。

3.1 注册接口服务

不同环境的域名配置,意味着不同环境下调用的接口是不一样的,特别说明:URI只能到域名级别
访问:https://csb.gz.cvte.cn ,建议让技术经理来配置,具体参考【转发服务

3.2 配置接口按钮

3.2.1 请求路径介绍

请求地址构成:https:// + 路由编码 + 服务路径

路径上配置的是3.1 中配置的路由编码,比如:配置了dp_rc
编写请求地址的时候,就是https://dp_rc/api/config/:name/deploy ,在dev环境代表请求的真实地址就是:https://rctest.cvte.com/api/config/:name/deploy
另外,在请求后端的具体服务时,天舟云代理服务会把当前的iac token和用户的jwt token都转发给对应的后端服务。

3.2.2 请求路径占位符参数传递

当URI上有 :占位 参数时,我们可以通过请求参数来表示真实的数据,比如 https://dp_rc/api/config/:name/deploy ,
为了保证URI上的 :name 我们需要再请求参数上配置 name 对应的参数

3.2.3 请求头、请求参数或请求body,需要加载当前表单或明细表的数据

  • 主表单按钮
data 表示当前表单的主表数据对象
data.[主表属性],如:${data.RESOURCE_NAME}

preData:上一次数据处理结果,用于按钮前、中、后置场景
extraData:额外的数据,一般存有当行的数据record,视图的entity
url:当前路由参数
form:可以获取组件的实例,一个Map,键是组件对应的code,值是组件暴露的方法,比如获取选中的值:form['someCode'].selectKeys
page: 获取页面配置,比如获取当前表单id: page.id
  • 明细表按钮
data 表示当前表单的主表数据对象
data.[主表属性]
data.[]
// form:可以获取组件的实例,一个Map,键是组件对应的code,值是组件暴露的方法,比如获取选中的值:form['someCode'].selectKeys
form.[明细表].selectedRows[0].[行属性] , 如:${form.T_RESOURCE_ITEM.selectedRows[0].RESOURCE_ITEM_VERSION}
extraData.record.ID 表示行上按钮,获取主键
  • 请求头或请求参数

  • 请求Body

3.2.4 接口请求

参考https://docs.cvte.com/docs/tzdoc_v2/927

4. 在线编码

4.1 在线编码说明

指在线写代码,写的内容会直接运行,全局已经定义了configs的变量,所有的数据通过configs获取。

变量 说明 例子
btnConfig 按钮配置 configs.btnConfig
context 表单上下文 configs.context
data 表单数据 configs.data
relation 联动,可以增加、触发联动 configs.relation
utils 工具类,可以弹窗,加载组件等 configs.utils

通用参数解释

变量 说明
formCode 组件的formCode ,
attrCode 组件的code,

4.1.1 context 上下文

变量 说明 入参 例子
addListener 添加监听方法,主要是事件执行前触发,事件执行后触发以及值改变时触发 { code: 唯一值即可; type: 'valueChange' | 'beforeEvent' | 'afterEvent'; handler: 回调方法 } configs.context.addListener({ code: ‘test’, type: ‘valueChange’,handler: ({ configs, extra, value }) => {} })
config 整体表单配置 configs.context.config
dictionary 表单数据字典,不包含明细表 configs.context.dictionary
form 表单,可用于校验值和获取值,具体参考antd form的api configs.context.form.getFieldValue()
getBtnConfig 获取按钮配置 { formCode: 空字符串,attrCode:按钮所在容器的attrCode,btnCode:按钮的code } configs.context.getBtnConfig({ formCode: ‘parentCode’, attrCode: ‘attrCode’, btnCode: ‘save’ })
getCompParams 获取由上下文传给组件的配置 { formCode: 组件的formCode,attrCode:组件的attrCode } configs.context.getCompParams({ formCode: ‘parentCode’, attrCode: ‘attrCode’})
getCompRef 获取组件对应api,比如获取明细表的数据字典,返回组件api,组件都有onChange的api { formCode: 组件的formCode,attrCode:组件的attrCode } configs.context.getCompRef({ formCode: ‘parentCode’, attrCode: ‘attrCode’}).getDictionary()
getConfig 获取组件配置,返回IConfig类型 { formCode: 组件的formCode,attrCode:组件的attrCode } configs.context.getConfig({ formCode: ‘parentCode’, attrCode: ‘attrCode’})
getDictionary 获取数据字典 configs.context.getDictionary()
getFormData 获取表单数据 configs.context.getFormData()
getFormId 获取表单id,如果创建则返回空 configs.context.getFormId()
refresh 刷新数据 configs.context.refresh()
refreshBtnConfigs 刷新按钮配置 { formCode: 空字符串,attrCode:按钮所在容器的attrCode } configs.context.refresh({ formCode: ‘parentCode’, attrCode: ‘attrCode’})
refreshConfigs 刷新配置 isDeep: 是否深度刷新,true的话比较整个配置会重新刷新 configs.context.refreshConfigs(true)
reset 编辑状态刷新为创建 configs.context.reset()
setBaseConfig 设置组件的基础配置,比如是否展示,是否编辑等,设置后需要调用refreshConfigs configs.context.setBaseConfig({formCode:’’,attrCode:’’, attr: ‘改变属性的key’, value: ‘改变属性的value’, extra: ‘如果是明细的话,对单行操作则需要填对应信息{ id:’’ }’})
setCompParams 设置需要传给组件的参数 { ‘对应组件的code’:{} } configs.context.setCompParams({ test: { onSearch: () => false } })
setFormData 设置表单内容,该方法改变值后不会触发联动,需要触发联动则需用getCompRef获取组件api,然后调用onChange,如:configs.context.getCompRef({formCode:’’,attrCode:’’}).onChange(1) configs.context.setFormData({a:12,b:21})
setRuleConfig 设置组件校验规则,比如必填等,设置后需要调用refreshConfigs configs.context.setRuleConfig({formCode:’’,attrCode:’’, attr: ‘改变属性的key’, value: ‘改变属性的value’, extra: ‘如果是明细的话,对单行操作则需要填对应信息{ id:’’ }’})
unlisten 注销监听 { code: 监听的值; type: ‘valueChange’ | ‘beforeEvent’ | ‘afterEvent’; } configs.context.unlisten({ code:’’, type: ‘valueChange’})

4.1.2 data 数据

变量 说明 例子
allData 表单上所有数据 configs.data.allData
currentData 当前容器数据,比如执行在card上,就只拿到card里面的内容 configs.data.currentData
preData 同一事件里,上一环节执行事件结果,比如事件中会拿到前置事件的结果,后置事件会拿到事件中的结果 configs.data.preData
extraData 额外的数据,如果是明细表的操作按钮,可以获取到选中结果,如果是明细表的行按钮,可以获取到行数据,具体可以打印 configs.data.extraData

4.1.3 relation 联动

变量 说明 入参 例子
on 注册联动 {attrCode: 组件attrCode, formCode: 组件formCode, actionCode: 执行类型,类型有:blur(失焦执行)、focus(聚焦执行)、change(值改变执行)、search(搜索时执行)、create(打开创建时执行)、edit(打开编辑时执行)、destroyOnCreate(创建表单结束离开时执行)、destroyOnEdit(编辑表单结束离开时执行},{id:场景下唯一值, cb: 联动回调} configs.relation.on({attrCode:’’,formCode:’’,actionCode:’change’},{id: ‘唯一值,避免重复’, cb: (ctx) => {}})
emit 执行联动 {attrCode: 组件attrCode, formCode: 组件formCode, actionCode: 与on的actionCode相同},{attrCode: ‘可选’,formCode:’可选’,index:’明细表需要的序号’,id:’明细表需要的id’} configs.relation.emit({attrCode:’’,formCode:’’,actionCode:’change’}, {})

4.1.4 utils 工具类

变量 说明 例子
confirm 参考antd confirm用法 configs.utils.confirm({ title: ‘title’, content: ‘content’, onOk: () => {} })
notification 参考antd notification用法 configs.utils.notification.warning({ message: ‘warning’, description: ‘warning’ })
message 参考antd message用法 configs.utils.message.warning(‘message’)
useCompModal 入参:Content:组件, modalConfigs:modal配置,参考antd Modal,contentConfig:组件配置 configs.utils.useCompModal(Component, { title: ‘弹窗标题’}, {a: ‘组件入参1’, b: ‘组件入参2’})
loadCmp 拉取资源组件,入参:{sourceName: 资源名称,exposesKey:导出的资源} configs.utils.loadCmp({ sourceName:’lcp-page-component’, exposesKey: ‘charts’ })
fetch 调用接口,入参参考axios configs.utils.fetch({ url:’/apis/common/proxy/lcpGw/tz_api/服务编码/path路径’,method: ‘get’ ,params: {a:123}})
cache 缓存,可以获取以及存缓存,api有:setValue,getValue,clear;缓存实际挂载在window的天舟云缓存空间中 configs.utils.cache.setValue(‘key’, 123),configs.utils.cache.getValue(‘key’),configs.utils.cache.clear(‘key’)

4.1.5 extra 联动额外数据

变量 说明 例子
attrCode 当前触发的字段的code
formCode 当前触发的字段的formCode
id 如果在明细行行中,存在id
index 如果在明细行中,存在行下标,一般是数字,如果是树形明细表,下标是字符串 如果是1,表示第二行,如果是”1-2”,表示第1行的的第二个子行

4.2 示例

刷新详情页面

当请求成功后,我们希望按钮可以弹出对应提示信息,我们可以通过后置事件,配置在线编码

const preData = configs.data.preData;
if(preData.status!=="0"){
    configs.utils.notification.error({message:preData.message,content:preData.message});
}else{
    configs.utils.notification.success({message:"更新成功",content:"更新成功"});
}
// 获取当前详情页面的唯一标识
const pageId = configs.context.globalContext.pageTools.getDetailPageId(configs.context.config);
// 调用刷新api
configs.context.globalContext.pageTools.refresh(pageId);

获取元素/组件的值

在属性联动中清使用如下方式:

const formData = configs.context.getFormData();
const aValue = formData?.['a'];

在触发的事件中情使用如下方式:
方法二

const aValue = configs.data.allData?.['a'];

设置元素/组件的值

// 改变值
configs.context.setFormData({ a: 123 });
// 触发联动
configs.relation.emit({ attrCode: 'a', formCode: 'formCode', actionCode: 'change' }, { id: '如果是明细表,需要明细表行id', index: 0 });

设置元素/组件可编辑状态

正常元素/组件

// 1 表示可编辑,0 表示不可编辑
configs.context.setBaseConfig({ formCode: `123`, attrCode: '12', attr: 'isEditable', value: '1' });
configs.context.refreshConfigs(true);

如果是明细表,需要加上id和index

configs.context.setBaseConfig({ formCode: `111`, attrCode: '12', attr: 'isEditable', value: '1', extra: { id: '123', index: 1} });
configs.context.refreshConfigs(true);

设置元素校验规则

// attr 有 | 'required' 'max' 'min' 'maxLength' 'minLength'  'len' 'special'  'compare' 'pattern' 'accuracy'
// 开启必填
configs.context.setRuleConfig({ formCode: '', attrCode: '', attr: 'required', value: '1', extra: { id: '如果是针对明细表某一行,需要写id,如果是主表对明细表操作,不写', index: '如果是针对明细表某一行,需要写index,如果是主表对明细表操作,不写' } })

设置元素/组件(不含按钮)禁用状态

正常元素/组件

// 1 表示可用,0 表示禁用
configs.context.setBaseConfig({ formCode: `123`, attrCode: '12', attr: 'isEnabled', value: '1' });
configs.context.refreshConfigs(true);

如果是明细表,需要加上id和index

configs.context.setBaseConfig({ formCode: `111`, attrCode: '12', attr: 'isEnabled', value: '1', extra: { id: '123', index: 1} });
configs.context.refreshConfigs(true);

设置元素/组件(不含按钮)隐藏显示状态

正常元素/组件

// 1 表示显示,0 表示隐藏
configs.context.setBaseConfig({ formCode: `123`, attrCode: '12', attr: 'isVisible', value: '1' });
configs.context.refreshConfigs(true);

如果是容器组件,则不需要加上formCode参数

configs.context.setBaseConfig({ attrCode: '12', attr: 'isVisible', value: '1' });
configs.context.refreshConfigs(true);

如果是明细表,需要加上id和index

configs.context.setBaseConfig({ formCode: `111`, attrCode: '12', attr: 'isVisible', value: '1', extra: { id: '123', index: 1} });
configs.context.refreshConfigs(true);

设置按钮禁用状态

// formCode填按钮所在容器的formCode,attrCode填容器的code,btnCode填按钮的code,比如按钮所在容器是在card里面,点击设计器的card后,在设计配置可以看到对应的code,鼠标放上去可以看到对应的formCode。2.4版本后formCode可以在高级属性->表单组编码看到,如果没有,则填空字符串
// 如果是表单属性配置的表单按钮,formCode填空字符串,attrCode填'BTN_BLOCK_GLOBAL';
const btnConfig = configs.context.getBtnConfig({ formCode: ``, attrCode: '容器的code', btnCode: '按钮code'});
if (!btnConfig.config.uiConfig) {
     btnConfig.config.uiConfig = {}
 }
 if (!btnConfig.config.uiConfig.attr) {
     btnConfig.config.uiConfig.attr = {}
 }
 // true 表示禁用,false 表示可用
btnConfig.config.uiConfig.attr.disabled = true;
configs.context.refreshBtnConfigs({ formCode: ``, attrCode: '容器的code' });

设置按钮隐藏

// formCode填按钮所在容器的formCode,attrCode填容器的code,btnCode填按钮的code,比如按钮所在容器是在card里面,点击设计器的card后,在设计配置可以看到对应的code,鼠标放上去可以看到对应的formCode。2.4版本后formCode可以在高级属性->表单组编码看到,如果没有,则填空字符串
// 如果是表单属性配置的表单按钮,formCode填空字符串,attrCode填'BTN_BLOCK_GLOBAL';
const btnConfig = configs.context.getBtnConfig({ formCode: ``, attrCode: '容器的code', btnCode: '按钮code'});
// 1 表示显示,0 表示隐藏
btnConfig.config.baseConfig.isVisible = '1';
configs.context.refreshBtnConfigs({ formCode: ``, attrCode: '容器的code' });

设置属性区域的样式

在自定义属性联动中动态设置属性区域的样式

const itemConfig = configs.context.getConfig({
    formCode:'BASIC_INFO_GROUP', // 属性字段所在主表/明细表的编码
    attrCode:'属性字段编码',
})
console.log(
    'itemConfig.config.uiConfig',itemConfig.config.uiConfig
)
// 健壮逻辑
if(!itemConfig.config.uiConfig?.attr){
    itemConfig.config.uiConfig.attr={}
}
// 健壮逻辑
if(!itemConfig.config.uiConfig.attr.style){
    itemConfig.config.uiConfig.attr.style={}
}
// 在itemConfig.config.uiConfig.attr.style中设置区域的样式,需要将css的中横线属性转换为驼峰名称
itemConfig.config.uiConfig.attr.style.marginTop='50px';

// 触发渲染引擎重新渲染内容
configs.context.refreshConfigs(true);

明细表

  • 明细表是表单的一种元素,数据格式是数组,每一行数组都是一个对象,每一组对象都有ID的key,如果是新增一行数据,在当行数据需要加__add: true。
  • 如果是个树形的明细表,每一行的数据中都有children的key,children表示是子行,值是一个数组,数据格式明细表一样。
  • 如果有子行或者行数据中有children字段,说明是树形明细表。
    新增一行数据,利用context.getCompRef的能力实现新增一行并可以执行联动

    明细表新增一行

    const { context, data } = configs;
    const tableCode = '明细表的code';
    let tableData = context.getFormData()?.[tableCode];
    if (!Array.isArray(tableData)) {
    tableData = [];
    }
    const id = +new Date()
    tableData.push({ ID: id, __add: true, ...其他数据 });
    context.setFormData?.({ [tableCode]: [...tableData] });

    明细表新增一行,并在子行插入其他元素的数据

    const { context, data } = configs;
    const tableCode = '明细表的code';
    let tableData = context.getFormData()?.[tableCode];
    if (!Array.isArray(tableData)) {
    tableData = [];
    }
    const id = +new Date()
    const newRow = { ID: id, __add: true, children: [] };
    const aValue = ['1','2','3'];
    aValue?.forEach((_a) => {
    newRow.children.push({ ID: Math.random(), __add: true, aValue: _a})
    })
    tableData.push(newRow);
    context.setFormData?.({ [tableCode]: [...tableData] });

    弹窗点击确定

const { context } = configs;

// 通过弹窗等待用户确认
const isConfirmed = await new Promise(rs => {
configs.utils.confirm({
    title: '确认是否做什么事情',
    content: '',
    okText: '确认',
    cancelText: '取消',
    onOk: () => {
      // 确认
      rs(true);
    },
    onCancel() {
      // 取消
      rs(false)
    }
  })
});
if (isConfirmed) {
  // 做一些事情
}

弹窗提示

configs.utils.message.warning('message');

获取表单数据

例子1:

const formData = configs.context.getFormData();

例子2:

// allData 是表单上所有数据
const { allData } = configs.data;

获取用户信息

const userInfo = context.getContext()?.session?.user ?? {};
// 一般有账号,邮箱,用户id,名称,电话
const { account, email, id, name, telephone } = userInfo;

校验,可阻止后续事件执行

const { context } = configs;
const formData = context.getFormData();

const a = formData?.['a'];

if (a === 1) {
    // 后续事件可以拿到response内容,后续事件configs?.data的preData就是response
    response = { success: true, data1: { ttt: 1111 } }
} else {
    // success为false时,后续事件不继续执行
    response = { success: false }
}

设置表单详情页的加载动效

configs.context?.globalContext?.pageTools?.setDetailPageLoading?.(true);
// long time task

configs.context?.globalContext?.pageTools?.setDetailPageLoading?.(false);

跳转到对应tab

// 获取tab的ref,tab是没有formCode,所以填空字符串即可
const tabRef = context.getCompRef({ formCode: '', attrCode: 'TAB_62136AC9079FC'});
// 跳转到对应tab签
tabRef?.setTabKey?.('TABPANE_9AE31CDE5A86E');

按钮跳转事件/列字段跳链配置

跳转参数可以携带上下文,跳转目标如果是天舟云详情页面,可以从上下文数据中消费。

上下文参数字段metaConfig,上下文参数值为JSON字符串,JSON中字段参数说明

参数名 数据类型 说明
axios AxiosRequestConfig 全局axios参数,组件销毁后参数复原
serverApiConfig Record<string, any> node服务代理地址表
apiConfig Record<string, any> 业务渲染器api自定义配置表
`
` Record<string, any> 表单详情传入的默认表单数据,键名为表单的字段编码
contextData Record<string, any> 表单详情在公式计算、多方案外部传掺或其他渲染时需要的上下文数据对象
compParams Record<string, any> key为组件code,value为要传入的配置,底层cir-form-generator会用compParams接收入参

多方案示例

参数键名:metaConfig

参数值:{ "contextData": { "EXTRA_PARAM": 1 } }

// 当多方案条件为外部传参「EXTRA_PARAM」为「1」时,该参数可生效

默认参数

参数键名:metaConfig

参数值:{ "formDefaultData": { "aaa": "${data.test}" } }
aaa是表单上的字段,test是列表行上的字段

调用自定义按钮事件

目前天舟云已不建议注入本地事件,天舟云2.x版本下建议将本地自定义事件发布到线上资源中心后,在自定义代码中消费资源中的事件;示例如下:

假如事件在资源导出名为abcEvent,自定义代码内容如下:

const { loadCmp, useCompModal ,message, Loader, ReactRemoteLoaderComponent} = configs.utils;
const { apiConfigMap } = configs.context

const loader = new Loader({
    name:'事件所在的资源名称',
    env:'fat', // 环境变量,按实际填写,也可以写成动态的
});

const remoteHandler = await loader.load('abcEvent',{
    useShared:true,
    mode:'page',
}, {
    onExposesReady: (cpt)=>{
        console.log('remoteHandler', cpt)
    }
})

// 如果事件在文件中不是export default 的写法,那么则需要在remoteHandler.后跟导出的键名,比如remoteHandler.A
remoteHandler?.default?.();

如果不想迁移已有的本地事件,则需要使用自定义类型事件,触发本地注入的事件;示例如下:

假如本地注入事件名为abc,自定义代码内容如下:

const localHandler = configs.utils?.cache?.getCache?.()?.['FORMREGISTER']?.["abc"]?.eventHandler;
console.log('--localHandler', localHandler);
await localHandler?.()

调用天舟云内置保存事件

const saveHandler = configs.utils?.cache?.getCache?.()?.['FORMREGISTER']?.["__commonSave"]?.eventHandler;
console.log('--saveHandler', saveHandler);
const currentData = configs?.context?.getFormData?.();
const resp = await saveHandler?.({
    context: configs.context,
    data:{
        currentData,
        allData: currentData,
    },
    utils: configs.utils,
    btnConfig: {
        scope: ['__GLOBAL_SCOPE'],
    }
})

组件调用天舟云内置保存事件

import CirCache from '@cvte/cir-cache';

const Page = ({ utils }) => {
  const cirCacheRef = useRef(new CirCache({ storage: 'memory' }));
  const detailRef = useRef()

  const handleSave = async () => {
    const context = detailRef.current?.RenderRef?.current?.getContext() ?? {};
    const saveHandler = (cirCacheRef.current?.getCache?.() as any)?.FORMREGISTER
      ?.__commonSave?.eventHandler;
    if (!saveHandler) {
      throw new Error('unexpected savehandler');
    }
    if (!context) {
      throw new Error('unexpected context');
    }
    const currentData = context.getFormData?.();
    const resp = await saveHandler({
      context,
      data: {
        currentData,
        allData: currentData
      },
      utils,
      btnConfig: {
        scope: ['__GLOBAL_SCOPE']
      }
    });
    return resp;
  };

  return <DetailTemp ref={detailRef} {...其他参数} />

}

调用天舟云内置校验事件

const validateFn = configs.utils?.cache?.getCache?.()?.['FORMREGISTER']?.["__commonGetFormDetailData"]?.eventHandler;
// 会先校验,再返回保存的格式数据
const validatedFormData = await validateFn?.(configs);
// 如果能拿到数据,说明校验成功,如果失败,返回undefined

调用天舟云内置跳转事件

const jumpHandler = configs.utils?.cache?.getCache?.()?.['FORMREGISTER']?.["__objJump"]?.eventHandler;
console.log('--jumpHandler', jumpHandler, configs);
// jumpHandler的入参有两个:ctx和configs
// ctx是当前跳转按钮的运行时上下文,需要包含btnConfig, data, context, extra属性
// configs是当前跳转的配置参数,包含可选参数specialOpenType,用于控制采用那种方式打开跳转页面,可选值有:drawer, modal, tab, window
// 跳转的示例参考如下:
jumpHandler?.({
    ...configs||{},
    btnConfig:{
        ...configs?.btnConfig||{},
        config:{
            ...configs?.btnConfig?.config||{},
            query, // 必填参数,指定跳转的参数对象,结构为:{classId,appId,pageId,resourceName: tempResourceName,exposeName: tempExposeName,pageFlag: tempPageFlag,tenantId: tempTenantId,copyId,},与页面按钮跳转的参数一致
            openType = 'drawer', // 可选参数,指定打开跳转的方式,与specialOpenType效果一样
            width = '50%', // 可选参数,指定弹窗、抽屉跳转打开时的宽度
            closeOnSave = '0', // 可选参数,指定定弹窗、抽屉跳转打开后在关闭时,是否需要保存详情内容
            height = '100%' // 可选参数,指定弹窗、抽屉跳转打开时的高度
        }
    }
},{
    specialOpenType: 'tab'
})

储存外部工具

// 系统里面储存内容
import CirCache from '@cvte/cir-cache';
const cache = new CirCache({ storage: 'memory' })
cache.setValue('your tool name', 123);

// 在线代码消费内容
const tool = configs.utils.cache.getValue('your tool name');
// tool()

调用history

// 前端2.4版本前
const { cache } = configs.utils || {};
cache.getValue('history-cache').push('/your/url');
// 前端2.4版本后内置history
const { extra } = configs.utils || {};
extra.getValue('history').push('/your/url');

调用搜索

// 说明是谁调用事件,方便排查链路
const owner = 'test';
// 获取事件总线
const subjectEventBus = configs.utils?.getSubjectEventBus?.();
// 调用搜索事件,
subjectEventBus.publishEvent({ owner, eventName: 'form:onSearch' , data: { name: '模糊查询的值,和code二选一', code: '精确查询的值,和name二选一', formCode: '目标字段的formCode', attrCode: '目标字段的attrCode', afterSearchOperate: 'firstOption或者noValue, 搜索后连接器判断noValue的话,不会执行连接器配置的联动,firstOption暂时没支持', id: '如果目标在明细表中,需要行id', index: '如果目标在明细表中,需要行序号'  }});

模板调用渲染上下文

// 列表和详情调用不一样
// 详情
const context = ref.current?.RenderRef?.current?.getContext() ?? {};
// 列表
const context = ref.current?.getRef?.()?.getRenderRef?.()?.getContext?.() ?? {};

请求映射

// 表单按钮接口请求
{
    // 表单数据
    data: {},
    // 表单配置
    page: {
        classId,
        layout: [],
        code,
        id,
    },
    // 获取组织和用户信息
    session: {
        org: {
            orgCode,
            id,
            orgName
        },
        user: {
            account,
            name,
        },
    },
    // 获取url上的query
    url: {}
    // 前置事件数据
    preData: {}
}
// 例子:${data.字段}:${page.id}
// 联动请求
{
response,
formData
}
// 列表
{
    extraData: {
        entity,
        ref: {
            selectedKeys,
            selectedRows
        }
    },
    page: {
        layout: [],
        pageCode,
        id,
    },
    preData,
    session: {
        org: {
            orgCode,
            id,
            orgName
        },
        user: {
            account,
            name,
        },
        url: {

        }
    }
}
// 列表行按钮
{
    // 行数据,比如${data.pageName}
     data: {}
}

明细表获取选择行数据

const tableRef = configs.context.getCompRef({ formCode: '', attrCode: '明细表code' });
const { selectedRows, selects } = tableRef?.getSelects?.() || {}

触发联动

configs.relation.emit({
    formCode: '', // 主表/明细表编码;
    attrCode: '', // 属性字段编码;
    actionCode: 'change' // 内置联动触发动作编码:'blur'  | 'focus' | 'change' | 'search' | 'create' | 'edit' | 'destroyOnCreate' | 'destroyOnEdit'
},{
    index: 0, // 触发明细表中的字段需要的所在行序号
    id: '',  // 触发明细表中的字段需要的所在行的ID
    // 此处还可以增加触发联动时需要携带的额外数据参数
})
// 批量触发
configs.relation.emit([{
    formCode: '', // 主表/明细表编码;
    attrCode: '', // 属性字段编码;
    actionCode: 'change' // 内置联动触发动作编码:'blur'  | 'focus' | 'change' | 'search' | 'create' | 'edit' | 'destroyOnCreate' | 'destroyOnEdit'
    extra: [{
        index: 0, // 触发明细表中的字段需要的所在行序号
        id: '',  // 触发明细表中的字段需要的所在行的ID
        // 此处还可以增加触发联动时需要携带的额外数据参数
    }]
}])

监听联动

relation?.on?.(
      {
        // 监听当前设置选项映射的属性,发生选项值改变时
        // 则将配置了返回字段的属性值,改为该选项中映射字段的值
        actionCode: 'change',
        attrCode: '', // 属性字段编码;如果是监听明细表,那么此处要写明细表编码
        formCode: '', // 主表/明细表编码;如果是监听明细表,那么此处要写undefined
      },
      {
        // 内置联动id如果每次不唯一,那么每次都会增加无效的联动
        id: relationId,
        cbParams: {
            // 传给回调方法的参数
        },
        cb: async (relationCtx: IRelationClassOnConfigsFnCtx) => {
            // TODO
            const {
                cbParams, // 传给回调方法的参数
                id: relatedRowId, // 当前联动的行id
                index: relatedRowIndex, // 当前联动的行索引
                value: changedValue, // 当前触发联动的值内容
            } = relationCtx.extra || ({} as any);
        }
      },
    );

获取内置方法

// 前端tz-render@1.0.1.10版本后内置history,download,upload
const { extra } = configs.utils || {};
extra.getValue('history').push('/your/url');
extra.getValue('download')({ fileName: '文件名称', noTime: 是否加上时间, blob: 文件流 });
 const files = await extra.getValue('upload')({ fileType: 'file', fileAccept: '.xlsx,.xls,.et' });

调用模板

const { loadCmp } = configs?.utils || {};
// 调用列表模板
const Page = loadCmp({ sourceName: 'tz-render', exposesKey: 'Page'  });
// 调用表单模板
const Detail = loadCmp({ sourceName: 'tz-render', exposesKey: 'Detail'  });
// 参数参考:https://docs.cvte.com/docs/tzv16/216

保存前处理数据

// 前置事件
const data = configs?.context?.getFormData();
data.ATTR_CODE = 1;
response = { success: true, data };

自定义联动/按钮事件中实现请求

// 前端请求
const resp = await configs.utils.fetch({
    // 服务路由编码是在https://tz.gz.cvte.cn/portal/8fvlv4bu/bsm_sys_config_base_t_faas_route/list中配置,编码对应业务域的服务域名
    // Gw服务会将服务路由编码转换为业务域服务域名,然后拼接上后面的接口地址
    url: '/apis/common/proxy/lcpGw/服务路由编码/接口地址',
    method: 'post', // 可以 put, get,
    data, // 如果method是 post,或者put,用data传输数据
    params, // 如果是method是get,用params传输数据
})

批量调用翻译

// 获取事件总线
const subjectEventBus = utils.getSubjectEventBus?.();
// 发布事件
subjectEventBus?.publishEvent?.({
    owner: '取调试可以用的名字',
    eventName: 'form:batchSearch',
    data: {
        value: {
            // 对应字段以及数据
            MOULD_PRODUCT_MODEL_RATE: 123,
            TABLE: [{ test: '123' }]
        },
            // 如果是明细表,可以只关注某些字段进行翻译
            focusCode: ['需要翻译的字段'],
            // 是否触发联动
            isRelation: true,
        }
});
// 批量翻译调用完会发起form:batchDictionary事件,可以通过监听事件执行后续事件
const eventIds = subjectEventBus.subscribeEvent({
    owner: 'test',
    eventName: 'form:batchDictionary',
    callback: () => {
        // 获取最新的数据字典
        const dict = context.getDictionary();
        // 获取最新的数据字典map
        const dictMap = context.getDictionaryMap();
        console.log('dict', dict);
        // 做其他事情
        // 移除本次事件
        subjectEventBus.removeEvents({
            eventIds: [...Object.values(eventIds || {})],
            owner: 'test',
        });
     }
})

关闭tab

// 从入参获取页面标识。目前只有门户可以实现
const { pageFlag: urlPageFlag } = configs?.context.globalContext?.PageTools?.getDetailEngineProps?.() || {};
if (urlPageFlag) {
    // 获取上下文中的事件总线
    const { EventBusMessage, EventBusPublisher } = utils?.getSubjectEventBus?.() || {};
    // 发送关闭tab事件
    const tabPublisher = new EventBusPublisher({
        owner: '取调试可以用的名字',
        subject: urlPageFlag,
        eventName: `tab:close`,
    });
    const _message = new EventBusMessage('');
    tabPublisher.emit({ message: _message });
}

关闭弹窗或抽屉

// 获取上下文中的事件总线
const { utils } = configs;
// 获取页面上的flag
const pageFlag = utils?.getPageFlag?.();
// 获取来源的标识和目标code(适用于打开详情)
const { subject } = pageFlag || {};
// 如果打开列表,subject取 configs.context.config
// 获取上下文中的事件总线
const { EventBusMessage, EventBusPublisher, formatName } = utils?.getSubjectEventBus?.() || {};
// owner用于记录调用排查
const publisher = new EventBusPublisher({
    owner: 'huang',
    subject,
    eventName: `jump:close`,
});
const message = new EventBusMessage({ success: true });
publisher.emit({ message });

刷新列表

// 上下文类
const { context } = configs;
// 获取表格实例,code编码一般是VIEW_AREA,可以通过页面layout接口返回的数据找
// 后续可以通过设计器直观看到编码
const tableRef = context.getCompRef({ code: 'VIEW_AREA' }) ;
// 调用刷新, resetPageNum可以控制是否重设页码,默认true
tableRef?.refresh?.({ resetPageNum: false });

设置下拉选项

// 上下文类
const { context } = configs;
const dictionary = context.getDictionary();
const formCode = '对应字段的formCode';
const attrCode = '对应字段的code'
const dictionaryKey = `${formCode}:${attrCode}`;
// 如果是设置主表中的字段,可以直接赋值
context.setDictionary({ ...(dictionary || {}), [dictionaryKey]: [{ label: '展示在页面', name: '展示在页面上', value: '储存到数据库的值', key: '储存到数据库的值' }] });
context.refreshConfigs(true);
// 如果是设置明细表中的字段,要先看看获取到的值是对象还是数组
const dictionaryOptions = dictionary[dictionaryKey];
// 如果是数组,或者没有的话,直接赋值
if (Array.isArray(dictionaryOptions) || ['null', 'undefined'].includes(dictionaryOptions)) {
    const options = [{ label: '展示在页面', name: '展示在页面上', value: '储存到数据库的值', key: '储存到数据库的值' }]; 
    context.setDictionary({ ...(dictionary || {}), [dictionaryKey]: options });
    const subjectEventBus = utils.getSubjectEventBus();
    subjectEventBus.publishEvent({ owner: '打印调试有作用', eventName: `form:table:${formCode}:setDictionary`, data: { [dictionaryKey]: options } });
} else if (typeof dictionaryOptions === 'object') {
// 如果是对象,用对应行id作为key
    const options =  { ...dictionaryOptions, [id]: [{ label: '展示在页面', name: '展示在页面上', value: '储存到数据库的值', key: '储存到数据库的值' }]}
    context.setDictionary({ ...(dictionary || {}), [dictionaryKey]:  options });
    const subjectEventBus = utils.getSubjectEventBus();
    subjectEventBus.publishEvent({ owner: '打印调试有作用', eventName: `form:table:${formCode}:setDictionary`, data: { [dictionaryKey]: options } });
}
// 更新配置
context.refreshConfigs(true);

跳转到高级列表/数据列表页面需要设置默认搜索的值

设置跳转参数「metaConfig」,如果已有的话请直接补充他的json值内容

设置搜索面板中指定字段的默认值(高级列表/数据列表均适用)

参数值内容:

{
  "compParams": {
    "SEARCH_PENAL_CODE":{"data":{"FIELD_CODE":"${data.objId}"}}
 }
}

参数值说明:

SEARCH_PENAL_CODE:搜索面板的唯一编码,可以从列表的layout接口中通过找到name为「搜索表单」的元素;

data:告诉列表渲染引擎是设置这个区域的默认表单数据;
FIELD_CODE:搜索面板中指定字段的唯一编码,该编码与字段在模型中的编码一致,也可以从列表的layout接口中找到;
${data.objId}:搜索面板中搜索字段被赋的值内容,此处可以为固定值,也可以通过${}占位符获取上下文数据;

设置列表根据指定字段的值进行搜索(数据列表适用)

参数值内容:

{
  "compParams": {
    "SEARCH_PENAL_CODE":{"searchKey":{"FIELD_CODE":"${data.objId}"}}
 }
}

参数值说明:

SEARCH_PENAL_CODE:搜索面板的唯一编码「code」,可以从列表的layout接口中通过找到name为「搜索表单」的元素;

searchKey:告诉列表渲染引擎是需要通过搜索面板区域来触发搜索数据;
FIELD_CODE:搜索面板中指定字段的唯一编码「code」,该编码与字段在模型中的编码一致,也可以从列表的layout接口中找到;
${data.objId}:搜索面板中搜索字段被赋的值内容,此处可以为固定值,也可以通过${}占位符获取上下文数据;

设置列表根据指定字段的值进行搜索(高级列表适用)

参数值内容:

{
  "compParams": {
    "41e5a0":{"searchKey":{"0485bdfa10784028816bda6e6d4e249c":"${data.objId}"}}
 }
}

参数值说明:

41e5a0:视图设计方案中列表的code,只能从列表布局的layout接口中找到code;

searchKey:告诉列表渲染引擎是设置这个区域的默认搜索数据;
0485bdfa10784028816bda6e6d4e249c:搜索面板中指定字段的在视图中的「ID」,只能从列表的render接口中找到改字段列的id;

${data.objId}:搜索面板中搜索字段被赋的值内容,此处可以为固定值,也可以通过${}占位符获取上下文数据;

数据列表批量修改字段列数据并保存

按钮需要触发事件实现修改内容,在后置事件调用批量保存用于存储前序修改的内容

触发事件中的实现示例如下:
(该示例为修改选中行所有的「更新时间」字段列数据)

console.log('configs====', configs);

const extraData = configs?.data?.extraData || {};
const keyMap = extraData?.entity?.allColObj || {};
// 获取当前页列表的所有行数据
const tableList = extraData?.entity?.list?.data?.list || [];

const selectedRows = extraData?.ref?.selectedRows || [];
// 遍历获取当前选中行的行索引,并剔除非法选中行
const selectedRowsIndex = [...selectedRows || []]
    .map(row => typeof row?.__index === 'number' ? row.__index - 1 : undefined)
    .filter(v => typeof v === 'number');

// 获取「更新时间」字段列的id内容
const updTimeColKey = keyMap?.['UPD_TIME']?.dataIndex;
console.log('selectedRows====', selectedRows, selectedRowsIndex, keyMap);

const newUpdTimeValue = Date.now();
// 根据当前选中行索引修改当前页列表中其对应行的字段数据
selectedRowsIndex.forEach(i => {
    if (!tableList[i]) return;
    tableList[i][updTimeColKey] = newUpdTimeValue;
})
console.log('tableList', tableList)

调用弹窗打开详情模版渲染单据,并监听弹窗确认

集合了上下文中的三部分能力

  1. 打开弹窗
  2. 调用渲染模版
  3. 事件监听

如下案例为:

假设某表单的明细表(编码是「CASE_1_DETAILED_TABLE」)中有字段A(编码是「CASE_1_SINGLE_LINE_TEXT_1」)的值对应「classId」参数,字段B(编码是「CASE_1_SINGLE_LINE_TEXT_2」)的值对应「pageId」参数,通过打开弹窗的方式展示传入这两个参数的目标单据。

const { loadCmp, useCompModal,
// useCompDrawer 使用抽屉打开,用法与useCompModal保持一致
} = configs?.utils || {};
console.log('configs;', configs);
// 调用列表模板
// const Page = loadCmp({ sourceName: 'tz-render', exposesKey: 'Page'  });
// 调用表单模板
const Detail = loadCmp({ sourceName: 'tz-render', exposesKey: 'Detail' });

const { __index: rowNum } = configs?.data?.extraData?.record || {};

const formData = configs?.context?.getFormData?.() || {};
console.log('formData', formData, rowNum)
// 如果是获取明细表中的字段值则参考下面代码
const { CASE_1_SINGLE_LINE_TEXT_1: classId, CASE_1_SINGLE_LINE_TEXT_2: pageId } = formData?.['CASE_1_DETAILED_TABLE']?.[rowNum - 1] || {};
// 如果是获取主表中的字段值则参考下面代码
// const { CASE_1_SINGLE_LINE_TEXT_1: classId, CASE_1_SINGLE_LINE_TEXT_2: pageId } = formData || {};

let ContentRef;
// 使用弹窗
// useCompDrawer 使用抽屉打开,用法与useCompModal保持一致
useCompModal(Detail, {
    title: '数据处置',
    width: '70%',
    bodyStyle: { minHeight: 500, height: 500, padding: 0 },// 指定窗口大小
    onCancel: function () {
        // 弹窗取消点击事件
    },
    onOk: async function () {
        // 此段代码用于控制点击弹窗的ok按钮控制内部集成单据的保存按钮
        // ContentRef为当前渲染模版的运行实例对象
        const { RenderRef, FormUtils, GlobalContext } = ContentRef || {};

        // 1️⃣场景: 如果需要只是获取弹窗打开的表单数据则如下:
        // 列表和详情调用不一样
        // 详情
        const context = RenderRef?.current?.getContext() ?? {};
        const detailFormData = context?.getFormData?.();
        console.log('detailFormData', detailFormData);
        return true;

        // 2️⃣场景:如果需要直接保存弹窗打开的表单保存事件则如下:
        const eventCenter = RenderRef?.current?.getEventCenter?.();
        if (!eventCenter) throw new Error('当前关联表单页面渲染引擎事件中心未正常加载!');

        GlobalContext?.pageTools?.setDetailPageLoading?.(true);
        const { setCommonName: _setCommonName } = FormUtils || {};
        // 通过渲染引擎的事件中心调用表单详情保存事件
        const savedResp = await eventCenter.runEvent({
            code: 'customRelation_handleNewModalDetail' + Math.random(), // 此处code须为全局唯一,顾根据开发者的场景重写值内容
            config: { baseConfig: { event: { runType: 'local', name: _setCommonName('Save') } } },
        });
        GlobalContext?.pageTools?.setDetailPageLoading?.(false);
        console.log('savedResp', savedResp)
        return savedResp?.success;
    },
    didMount: (ref) => {
        // 用于传递当前表单数据体
        ContentRef = ref;
    }
}, {
    // 具体表单对象配置信息
    appId: '510e4864c85146fb81282ae6fe883865',
    tenantId: 'c518f53d-b405-4111-afe1-5c082b284971',
    classId, // 加载当前表单配置的对象id
    pageId, // 需要从后端加载表单数据的主键id
    // 详情组件的元配置信息
    metaConfig: {
        // 需要默认初始化的表单数据
        // formDefaultData: {
        //        [属性编码]: "默认值内容"
        // }
        // 需要公式读取的上下文数据
        // contextData: {
        // }
    }
});

列表获取选择的数据

// 上下文类
const { context } = configs;
// 获取表格实例,code编码一般是VIEW_AREA,可以通过页面layout接口返回的数据找
// 后续可以通过设计器直观看到编码
const tableRef = context.getCompRef({ code: 'VIEW_AREA' }) ;
// 获取选择的数据
tableRef?.selectedKeys
tableRef?.selectedRows

高级列表或者数据列表默认选择条件

在系统管理 -> 资源配置 -> 页面资源配置,找到对应的菜单
在菜单参数中增加

// 09f717 是视图对应code,defaultConditionId是默认条件的key
metaConfig={"compParams":{"09f717":{"defaultConditionId":"3372080caea74de98cb00ac66270f51b"}}}

高级列表按钮获取数据

const { context, data, btnConfig } = configs || {};
const { entity, ref } = data?.extraData || {};
// 如果有编辑,listData是编辑后的数据
const listData = entity?.list?.data?.list ?? [];
// listDataSnapshot是原始数据,可以与编辑后数据做对比
const listDataSnapshot = entity?.getListDataSnapshot?.();

表单中获取高级列表

const { context, data, btnConfig } = configs || {};
// 获取模版组件ref
const tableTempRef = context.getCompRef({ formCode: 'BASIC_INFO_GROUP', attrCode: 'SUBJECT_DETAILS'});
// 获取视图entity
const { entity  } = tableTempRef.getRef().getContext()?.getCompRef?.({ code: 'f9591a' }) || {};
// 获取视图数据
const list = entity?.list?.data?.list;
// 获取视图元数据
const originList = entity?.getListDataSnapshot?.();

明细表设置选中

const { context } = configs;
// 获取table的实例,一般formCode放空字符串
const tableRef = context.getCompRef({ formCode: '', attrCode: '' }) ;
// rowKey为行id,isSelected是否选中
tableRef?.setSelected([{ rowKey: '' ,  isSelected: true }])

手动触发API查询搜索

API查询分为:

模糊搜索

搜索API查询控件输入的文本内容,并可以和属性联动中的「查询并赋值」一样控制下拉选项内容的展示逻辑,由于是模糊搜索,所以无法触发API查询的额外值填充;

const eventName = 'LcpFormApiSearch:likeSearch'

// 说明是谁调用事件,方便排查链路,所以尽可能唯一
const owner = 'custom_online_relation_search';
// 获取事件总线对象实例
const subjectEventBus = configs.utils?.getSubjectEventBus?.();
// 调用API搜索事件,
subjectEventBus.publishEvent({
    owner,
    eventName,
    formCode: '字段所在主表/明细表的表表编码,主表为BASIC_INFO_GROUP',
    attrCode: '字段的编码',
    id: '如果字段在明细表中,需要传入行id',
    index: '如果字段在明细表中,需要传入行序号',
    data: {
        label : '用于模糊搜索的内容',
        extra: { // 额外传入的配置内容
            // setOptionValueMode: 用于在模糊搜索之后指定选中选项的模式,其模式与属性联动中的查询并赋值选中模式一致(noValue:不改变API查询的值内容,只更新API查询的下拉选项列表;selectedOnlyOneOption:如果返回下拉选项仅有一个,则直接将其作为被选中的项并更新下拉选项列表;firstOption:在返回的下拉选项中将首项作为被选中的项并更新下拉选项列表)
            // apiSearchConfig: 需要传入指定的api查询控件配置,该字段为空时则使用配置端的配置内容
        }
    }
});

精确翻译

搜索API查询控件在表单中存储的值,精确搜索值对应的返回内容,可以触发API查询的额外值填充;

// 此处record是用于当字段在明细表中时,某一行的事件对应被触发,需要传入整行的数据取其中的唯一标识ID
// 此处的attrCode是当前字段的编码,必传
const eventName = configs.utils?.getEventName?.()?.getSearchName?.({ record: propsData?.record, attrCode: dataIndex })

// 说明是谁调用事件,方便排查链路,所以尽可能唯一
const owner = 'custom_online_relation_transalte';
// 获取事件总线对象实例
const subjectEventBus = configs.utils?.getSubjectEventBus?.();
// 调用API搜索事件,
subjectEventBus.publishEvent({
    owner,
    eventName,
    data: {
        value: '用于精确翻译的内容',
    }
});

流程按钮前置/后置事件中获取当前流程信息/流程按钮

流程按钮的前置/后置事件在线编码中,获取流程按钮的运行时上下文。根据上下文的数据获取当前的流程按钮信息

const flowCtx = configs.extra?.flowButtonContext?.ctx;
const currentFlowBtn = flowCtx?.buttonConfig;
// 获取当前点击了哪一个流程按钮
console.log('currentFlowBtn-----', currentFlowBtn);

const currentFlowNode = flowCtx?.flowNode;
// 获取当前处于哪一个流程节点
console.log('currentFlowNode-----', currentFlowNode);

// 流程按钮的前置/后置事件的都必须设置返回结果的状态
response = { success: true };

流程按钮后置事件中获取流程处理数据结果

流程按钮的前置/后置事件在线编码中,获取流程按钮的运行时上下文。根据上下文的数据获取当前的流程按钮信息

const flowResp = configs.extra?.flowButtonContext?.prevData;
console.log('flowResp-----', flowResp);

// 流程按钮的前置/后置事件的都必须设置返回结果的状态
response = { success: true };

获取明细表选中数据

const tableRef = context.getCompRef({ formCode: '', attrCode: '明细表的code' })
console.log('tableRef', tableRef.getSelects())
作者:王浩彬  创建时间:2024-06-19 09:17
最后编辑:黄允桢  更新时间:2025-05-12 18:04