数据源知识沉淀
1、下拉框与Lov的注意点:
下拉框
{
name: 'intertradeType',
lookupCode: "HCMS.INTERTRADE_TYPE",
label: intl.get(`${prefix}.table.intertradeType`).d('贸易性质'),
required: true,
},
{
name: 'intertradeTypeMeaning',
label: intl.get(`${prefix}.table.intertradeType`).d('贸易性质'),
},
下拉框 lookupCode,后端需要配置对应的值集。填写时,可直接使用ID intertradeType,但是编辑预览时,无MC字端,但是后端会返回MC字端,需要预览intertradeTypeMeaning字段;—— 如何在修改时,将下拉数据中的meaning赋值给其他字端;
Lov字段
{
name: 'declareCustomsObj’, —— 不可与后端返回的字段相同,否则,新增保存后,再次修改其他表单,保存后,数据将丢失;
label: intl.get(`${prefix}.table.declareCustoms`).d('申报海关'),
lovCode: 'HWMS.CUSTOMS',
type: FieldType.object,
ignore: FieldIgnore.always,
},
{
name: 'declareCustoms', //* 构建提交数据
bind: 'declareCustomsObj.value' //* 对应lov数据的ID值
},
{
name: 'declareCustomsMeaning', //* 构建提交数据
bind: 'declareCustomsObj.meaning'//* 对应lov数据的MC值
},
后端需要配置对应的值集视图配置,且需要注意正确填写【值字段名】(对应lov数据的ID值)、【显示字段名】(对应lov数据的MC值)字段;否则需要前端手动配置textField: 'meaning';
提交时需要ignore这个declareCustomsObj对象,且需要将Lov数据中的值(value)绑定到declareCustoms 来保存ID值,需要将Lov数据中的展示字端(meaning)绑定到declareCustomsMeaning 来保存MC值,以便组织数据、编辑和预览;
- 填写时,bind会将用户选中的数据(declareCustomsObj)中的value、meaning分别赋值给declareCustoms字端和declareCustomsMeaning字端;
- 编辑与预览时,bind会将后端数据中declareCustoms字端和declareCustomsMeaning字端 组装为declareCustomsObj,用于页面的渲染。
2、查询一般都是get请求,查询条件有输入框,当有空格时,会自动以【+】号拼接;
get 请求参数重空格的处理方式(以+号拼接,后端会自动处理),查询时,query字段会自动拼接,但是如果是手动拼接queryString需要我们自己拼接
const invalidSubmit = async (res?: any, adjustSubmit?: string) => { //* 作废提交
const _urlParams = urlParams || '?'; //? 接口参数
if (adjustSubmit) { //* 有变更时间的提交
const adjustDate = InvalidDs.current?.get('adjustDate');
const adjustDateStr = adjustDate?.format('YYYY-MM-DD+HH:mm:ss') ?? ''; //* 手动将空格转成【+】,方便后端转换
tableDS['_urlParams'] += `&adjustDate=${adjustDateStr}`;
}
tableDS['_urlParams'] = _urlParams;
const result = await otherSubmit(tableDS, 'invalid'); //* 进行【作废】提交
result && closeModal(res);
};
3、非勾选的汇总值(所有页面数据的总和),可编辑的分页表格,当表格有变更汇总字段的数据,然后进行分页时,会提示有变更“有未保存的数据,是否继续?”,点击【确认】会重新查询,但是汇总字段依旧是修改后的数据;要求应该是初始的数据,使用reCompute将计算当前页面的总和,需要在load后,保存汇总值的数据,重新查询后,再次赋值上一次赋值的汇总值
const forceUpdateSummary = (dataSet) => { //* 强制刷新行的汇总字段
//* zhy added hzrl-0001-8738【采购发货】采购发货行结算数量汇总显示问题
const parentDataSet = dataSet.parent;
const summaryLineDS = summaryHeader?.current?.summaryLineDS;
setTimeout(() => {
summaryLineDS?.current?.init('40Sum', parentDataSet?.current?.get('totalPackingQuantity_origin') || parentDataSet?.current?.get('totalPackingQuantity'));
summaryLineDS?.current?.init('numberSum', parentDataSet?.current?.get('totalNumber_origin') || parentDataSet?.current?.get('totalNumber'));
summaryLineDS?.current?.init('amountSum', parentDataSet?.current?.get('totalAmount_origin') || parentDataSet?.current?.get('totalAmount'));
parentDataSet?.current?.init('totalNumber', parentDataSet?.current?.get('totalNumber_origin') || parentDataSet?.current?.get('totalNumber'));
parentDataSet?.current?.init('totalAmount', parentDataSet?.current?.get('totalAmount_origin') || parentDataSet?.current?.get('totalAmount'));
summaryHeader.current?.forceUpdate({});
}, 50);
}
useDataSetEvent(lineDS, 'load', ({ dataSet }) => {
const { records = [] } = dataSet;
records.forEach((res: any, index: number) => {
// ! 被引用或者被锁货都不可勾选
if (res.get('isQuote') || res.get('isLock')) {
const first = dataSet.get(index);
if (first) first.selectable = false;
}
});
setIsMoreLine(!!records.length);
if (!dataSet.getState('noNeedForceUpdateSummary')) { //* 需要“强制刷新行的汇总字段”
forceUpdateSummary(dataSet);
} //* 删除时已经处理过了,不需要再次“强制刷新行的汇总字段”
dataSet.setState('noNeedForceUpdateSummary', false);
});
4、一个Lov值集视图,多选,值字段为Nubmer类型,参数既有string,又有Number[],后端返回给前端的也是string + Number[]类型的两个字段。—— 我们组织数据源的数据时,必须把类型为string的字段放在类型为数组的字段的上面;以保证构建虚拟对象时,值字段为Nubmer类型!!!
{
name: 'deliveryStartPortObj',
label: `${intl.get(`cice.cms.in.contract.manage.table.data.deliveryEndPortName`).d('起运港')}`,
type: 'object' as FieldType,
ignore: 'always' as FieldIgnore,
lovCode: 'HMDM.PORT_COUNTRY',
multiple: true,
dynamicProps: {
required({ record }) {
return record.get('isStartPortRequire') === 1
}
}
},
{
name: 'deliveryStartPort', //* zhy 自动组织字符串数据,且必须放在deliveryStartPortList的上面,保证load后,ptId为Number类型
bind: 'deliveryStartPortObj.ptId',
type: 'string' as FieldType,
multiple: ',',
},
{
name: 'deliveryStartPortList',
bind: 'deliveryStartPortObj.ptId'
},
5、数据源dataSet中events的submitSuccess和feedback的submitSuccess的使用:
events: { //* 事件
submitSuccess: ({ dataSet, data}) => {
data.firstStep = dataSet.getState('firstStep');
dataSet.setState('firstStep', false);
},
},
feedback: { //* 查询和提交数据的反馈配置
submitSuccess: (res: any) => {
const content = res && res.content;
if (content.length && content[0].saveMsg && res.firstStep) { //* 如果后端返回提示语,并且是是第一步骤的保存操作
notification.success({
message: intl.get(`cice.common.massage.tip`).d('操作成功!'),
description: content[0].saveMsg,
});
} else {
notification.success({
message: intl.get(`cice.common.massage.tip`).d('提示'),
description: '操作成功!',
});
}
},
},
- 参数不同,events的可以获取到dataSet,feedback的获取不到;events的data是响应数据(将相应到feedback的数据),feedback的resp 是 响应值;
- 需要时可以配合使用,先执行events的submitSuccess,后执行feedback的submitSuccess;
- 在events的submitSuccess执行修改data(响应数据),执行feedback的submitSuccess时可以获取到修改后的响应值;
6、数据源,必录字段的隐藏,一定要设置为隐藏时非必录
7、光改common包可能不会触发编译,需要同时改动引用该common包的文件,才会触发common的再次变异
8、分页查询接口,参数page为空,size为空,后端默认page:0, size: 20,返回20条第一页的数据;
前端若是想查找全量的数据,需要设置page为-1,后端会将size置为总量;—— 返回的格式是一样的
9、useEffect与dataSet的load事件,请求基础配置的注意事项
// 页面初始化函数
useEffect(() => {
initPage(); //* 初始化页面数据
}, [foreignChainId]);
1、useEffect,是在初始化时以及依赖项变更时执行的,当编辑单据,修改后保存,useEffect的依赖项不会变更,也就不会触发里面的方法;—— 新建保存时,bug不会体现,但是再次编辑就会体现出来这个问题;
2、但是dataSet的load事件,会在数据源数据变更后始终执行;
qimingFlag标识丢失,getQmFlag是写在useEffect中初始化加载完毕后执行的,但是保存后刷新页面不会执行;
需要将getQmFlag写在对应dataSet的load事件中;
10、数据源重新请求后,会重置每一条record,dataSet.setState设置的值依旧存在,而record.setState、record.set、record.init设置的值丢失;
函数组件的useState的setState
类组件的setState
dataSet.setState —— 常用于保存基础配置
record.setState —— 常用于行内编辑。
record.set 会触发数据变更(dirty变为true),update时使用,不可在初始化时设置,组织提交数据时使用
record.init 不会触发数据变更()的差异 —— 初始化时设置,组织提交数据时使用
初始化设置默认数据、组织提交数据等,不要用set,否则会触发变更,go.back()提示有变更,提交时提示有变更需要先保存数据
11、&& 的优先级 大于 ||:
true && false || true,因为&& 的优先级大于 ||,所以相当于(true && false) || true;
Javascript中运算符的优先级
在js中存在很多的运算符,如何区分它们之间的优先级,今天总结常用的运算符,从上往下依次顺序:
1. 小括号()
有括号先算括号里面的;
2. 一元运算符
加加(++); 减减(--); 非(!)
3. 算数运算符
加(+);减(-);乘(*);除(/);取于(%);这里是先乘(*)除(/)取于(%)后加(+)减(-)。
4. 关系运算符
大于(>);大于等于(>=);小于(<);小于等于(<=)。
5. 相等运算符
等于(==);不等于(!=);全等于(===);不全等于(!==)。
6. 逻辑运算符
先且(&&)后或(||)。
7. 赋值运算符(=)。
8. 逗号运算符(,)。
12、无论何时,计算后的结果都需要圆整 round
13、Lov选择弹窗的问题,一般都是值集视图的值字段的配置有问题
- Lov选择弹窗,切换到第二页时,Lov的查询结果表格展示超过当前页数的数据;
- 新增保存后,Lov弹窗上有缓存数据(小眼睛图标);
14、axios设置了返回拦截器,接口200,返回的信息中res.failed等于true时,抛出异常,所以,我们要提示错误信息时,需要用catch来捕获错误信息并提示;
axios拦截器设置的源码:
withTokenAxios.interceptors.response.use(function (response) {
var status = response.status,
data = response.data;
if (status === 204) {
return undefined;
}
if (data && data.failed) {
// notification.error({
// message: data.message,
// });
throw data;
} else {
return data;
}
});
withTokenAxios._HZERO_AXIOS_IS_CONFIGED = true;
} // axios.defaults.headers.common.Authorization = `bearer ${getAccessToken()}`;
15、提交之前要求保存,保存需要有变更,提交无需变更(有变更不可提交)
提交的两种形式:
1、dataSet.submit()进行提交,会自动进行提交前的校验,触发接口,以及接口调用成功之后,刷新页面(autoQueryAfterSubmit为true时)
2、使用axios.post()进行提交,使用通用组件axiosSubmit模拟dataSet.submit()上面的三个步骤;
16、【操作-编辑】按钮的权限,是在其父级按钮【操作】权限基础上的,如果【操作】按钮都没有权限,【操作-编辑】设置再多的权限也没有用;
17、上游单据与下游单据,”含税金额“相差一分钱(0.01)的问题:
原因分析:
背景:上游单据,数量、单价、金额均是可以修改的,下游单据数量是可以修改的;(公式:数量 * 单价 = 金额)会根据公式自动计算
上游单据,修改单价会自动计算金额,当修改金额时又会自动计算单价;
下游单据,会自动计算金额,金额 = 数量 * 上游单据自动计算后的单价;
即,数量固定,根据用户手动录入的金额计算出来的单价 * 数量 与用户手动录入的金额相差0.01,甚至更大;
解决方案:
初始化场景,下游单据的金额,不再自动计算,展示上游单据的金额值;
编辑场景,需要在初始化时,保存用户可能会修改的字段,当字段修改为上游单据的数据时,相当于没有修改,金额赋值为上游单据的金额值;
18、window系统,改动了本地日期后,页面的当前日期有延迟,还是修改之前的日期,在缓冲时间内与本地时间不一致;mac系统无此问题
19、数据源的配置对象,建议使用函数形式来返回,执行函数的时候再去返回这个对象,而不是页面加载后就确定了这个对象;
20、默认当前日期,有两种方式:
1、直接使用dynamicProps属性,
例如:dynamicProps: {
defaultValue() {
return moment(new Date()).format(‘YYYY-MM-DD’)
},
};
2、使用defaultValue属性,但是该数据源的配置需要使用函数形式!!!
数据源的配置为对象时,直接使用defaultValue属性,会有日期会有问题:昨天打开查询页面,今天去新建时,日期会默认为昨天的时间。
例如:defaultValue: moment().format('YYYY-MM-DD’),
21、Lodash.multiply(11, undefined); //* 11
Lodash.sum([undefined]); //* undefined
Lodash.round(undefined, 2); //* NaN
解决方案:
Loadsh.multiply(11, undefined || 0); //* 11
Lodash.sum([undefined || 0]); //* 0
Lodash.round(undefined || 0, 2); //* 0
22、如何只选择表格当前操作的数据:
tableDS.unSelectAll(); //* 取消全选当前页
tableDS.clearCachedSelected(); //* 清除缓存的选中记录
tableDS.select(record);
23、日期列,重新渲染renderer时,有值的时候再去调用monent(value);因为moment()、moment(undefined)返回的都是当前日期;
{
name: 'invoiceDate',
width: 140,
align: ColumnAlign.center,
editor: false,
sortable: true,
renderer: ({ value }) => {
return moment(value).format('YYYY-MM-DD');
},
},
当value为空时,日期列会渲染当前日期,原因是因为moment()、moment(undefined)返回的都是当前日期;
解决方案如下:
{
name: 'invoiceDate',
width: 140,
align: ColumnAlign.center,
editor: false,
sortable: true,
renderer: ({ value }) => {
return value ? moment(value).format('YYYY-MM-DD') : '';
},
},
24、当数据源有关联的子级数据源,请求父数据源,会自动请求子数据源,调用两个接口;当关联的子数据源多时,会调用大量的接口;那如何只请求父数据源,而不请求关联的子数据源呢?解决方案如下:
const children: any = basicInfoHeader.children;
basicInfoHeader.children = {};
await basicInfoHeader.query(); //* zhy hzrl-0001-5059【采购合同】合同数据版本不一致的两个场景 —— 删除后,后端会变更头信息中的合同版本号,所以需要更新头信息
basicInfoHeader.children = children;
- dataSet.query()是异步的;
25、查询字段的联动,需要使用表格组件属性的onQueryChange属性;
26、触发单个字段的校验 record?.getField(‘paymentCategoryName’)?.checkValidity();
27、编辑的场景,默认值不生效,
28、表单字段的提示,help结合showHelp同时使用,showHelp有三种形式,newLine(字段下面新增一行提示信息),tooltip(表单框后面添加【?】,悬浮框提示),lable(在lable后面添加【?】,悬浮提示框);表格字段的提示,tooltip: TableColumnTooltip.overflow,内容超出,会自动展示省略号,悬浮自动展示悬浮窗;
29、tableDS.delete(),删除的数据如果不是新增的数据,会直接调用接口进行删除;
tableDS.remove(),临时删除,不会直接调用接口
30、tableDS.selected(),和勾选先后顺序没有关系;
31、方法some() 方法测试数组中是不是至少有 1 个元素通过了被提供的函数测试。它返回的是一个 Boolean 类型的值。
some()是在数组中找是否有符合条件的元素
如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
如果没有满足条件的元素,则返回false。
find()是在数组中找第一个符合条件的元素
当数组中的元素在测试条件时返回 true 时, find() 返回符合条件的元素,之后的值不会再调用执行函数。
如果没有符合条件的元素返回 undefined。
find()与some()的性能比较
32、dataSet.dataToJSON属性
用于配置 DataSet 数据转化规则,主要作用于 toJSONData 方法的执行
基本使用
const ds = new DataSet({
dataToJSON: 'dirty', // 只转换变更的数据,包括本身无变更但级联有变更的数据
});
实现逻辑
DataSet 在执行 toJSONData 方法时, 会以传入的 DataToJSON 值作为条件,仅对满足条件的数据集进行转换,具体规则如下
在有级联的数据源下,dataToJSON均默认为dirty,只组织有修改的数据,当没有变更又想按照上面的规则来组织提交数据时,需要对相应的数据源设置dataToJSON为对应的类型;
注意事项
注意根据实际需求,判断是否需要转换级联数据和附加字段等情况,选择合适的转换规则进行使用
关于级联的注意事项:
一个头dataSet(chainBasicInfoDS),有一个子级联的行dataSet(chainLineDS),现在想【提交审批】,如果有变更则需要先【保存】,没有变更时,则需要调用接口;提交时,需要提交子级联的行数据(根据用户选择的来,或者是所有的数据)
如何提交呢,首先需要设置勾选当前的头信息chainBasicInfoDS.select(chainBasicInfoDS.current),并设置chainBasicInfoDS.dataToJSON = ‘selected’,组织勾选的数据,但是发现,其子级联的行是没有数据,这是因为dataToJSON默认是dirty,只有更改后才会组织提交数据,头已经设置过了,行也需要设置,如果是根据用户选择的来,也需要设置chainLineDS.dataToJSON为selected,如果是默认所有的 和用户勾选无关的,chainLineDS.dataToJSON为selected;
33、
34、
35、
36、
37、
38、
39、
40、
41、
42、
43、
44、
45、
46、
47、
48、
49、
50、
git相关问题记录
1、解决代码冲突要注意的问题:—— 红线
1. 不要简单的全部采用传入的更改,而是要一个一个的去检查,看看到底是采用传入的更改,还是需要我们保留二者的变更(可能需要适当的变更),尤其是解决master的冲突;
2. 解决冲突时,遇到他人代码,不确定如何解决时,要找对应的开发同学;
3. 合并”解决代码冲突“代码的时候,开发同学最好能够检查一下;
2、daily/dev打包时,会自动将master的代码merge到daily/dev,当有冲突时,需要重新以origin/daily为基准拉取一个分支,Merge origin/master into daily,手动解决冲突后,提交 merge到 origin/daily;
3、直接在gitLab上解决冲突,会将daily上的代码Merge到自己的分支上,将来合master,会将daily上无需合到master的代码合到master; 万一这么操作了,该怎么办呢?
1. 在工作分支上找到错误的Merge记录(Merge daily into 当前分支),然后在该记录之前重新拉取一个中转分支;
2. 若工作分支的这个错误的Merge记录的上面有其他的提交记录,需要cherry pick到新的分支;
3. 然后通过比对工具(文件夹比较与合并,推荐App Beyong Compare),全量比对工作分支和中转分支,在差异文件中,将中转分支的代码 全量复制到工作分支上;—— 此时获取到相当于revert Merge 的变更;
4. 然后在工作分支上将全部的变更提交(revert Merge);
5. 最后将工作分支Merge到master;
4、解决Merge冲突有两个方案:
1. 以daily为基准去解决冲突;使用最新的daily拉取副本分支,然后将我们的分支代码Merge到daily的副本,手动解决冲突,提交后,去gitLab上提MR;—— 适合解决复杂冲突,需要保存两者,甚至是保存两者之后需要修改的冲突;
2. 以我们自己的分支为基准去解决冲突,使用我们自己的分支代码拉取副本分支,直接去gitLab上提交MR,有冲突直接在gitLab上解决即可;—— 更简单,适合解决简单明了的冲突;
均需要delete source Branch
5、切记不要在gitLab上解决daily/dev的冲突,不然会将daily/dev Merge到我们的分支,后面合生产时就会有「将别人代码合到生产」的问题;解决冲突有两个方案,一个是以daily为基准去解决冲突,一个是以我们自己的分支为基准去解决冲突;
Merge到master的冲突,可以直接在gitLab上解决;
6、问题现象:自己建的分支,只有自己修改,然后提交,提示需要先pull拉取一下,为啥?
原因分析:从gitLab新建一个分支(远程),想从vscode切换到该分支时,使用了第一种方案,vsCode切换到master,但是没有拉取最新的代码(本地master分支 版本落后),然后新建一个重名的分支,导致这两个重名分支后面自动关联时,版本不一致
7、从gitLab新建一个分支,想从vscode切换到该分支时,发现没有搜到远程的该分支origin/,解决方案目前有两种:
1. vsCode切换到master(本地),拉取最新的代码,然后新建一个重名的本地分支;相当于新建了两个重名分支,后面git会自动关联
2. 使用fetch抓取所有分支,就可以搜到该远程分支,就可以从远程的该分支来创建本地分支 ——推荐用法
8、git remote -v 查看当前的远程仓库地址 git remote set-url origin 新地址 , 重设远程仓库地址
其他沉淀
1、前端根据后端的fileUrl预览PDF时,文件名是根据fileUrl来的,但是下载时,也可以默认其他的文件名,即在document类型的请求中,response headers中 content-disposition的filename属性来设置文件名
response.setHeader(“Content-disposition”, “attachment;filename=” + fileName)。
Content-disposition为属性名。一般有两种方式:
- inline:直接在页面显示
- attchment:以附件形式下载
attachment表示以附件方式下载。如果要在页面中打开,则改为inline。
filename如果为中文,则会出现乱码。解决办法有两种:
-
1、使用fileName = new String(fileName.getBytes(), “ISO8859-1”)语句
-
2、使用fileName = HttpUtility.UrlEncode(filename, System.Text.Encoding.UTF8)语句
-
Content-type 指示响应内容的格式