前端编辑页面修改后和原始数据比较差异

在软件研发过程中,会遇到很多编辑页面,有时编辑页面和新增页面长的基本上一样,甚至就是一套页面供新增和编辑共用。编辑页面的场景比较多,例如:

场景一、字段比较多,但实际只修改了几个字段,如果把所有字段都回传给后端,冗余字段(未作变更的字段)修改就会有很多,而这些未作变更的字段中还有一些字段是后台有翻译和转换的(例如金额、类型、状态等),如果把这样的字段回传给后台,就会造成数据的变更(其实不想变更),和预期是严重不符的。

场景二、还有一些比较复杂的编辑页面,它会有大集合嵌套小集合,甚至还有多层嵌套的场景,在修改时,可能对各个层级的集合又有一些新增、修改、删除的操作。

场景三、场景一和场景二的混合场景。

基于这些复杂的场景,如果能够的精确的区分开哪些做了新增、哪些做了变更、哪些删掉了,就会让我们的前后端研发小伙伴更轻松的应对这些复杂场景。

接下来,我把我对这种复杂场景的处理方案分享出来,大家可以参考,共同探讨,看看还有没有更好的解决方案(前端使用的是vue的环境):

处理原理:

1、进入编辑页面时,将查询出来的数据复制成两份,一份作为初始数据,一份作为修改数据

2、比对初始数据和修改数据的差异(新增的、修改的、删除的)

3、将差异数据提交给后台,后台进行分门别类处理

比对算法:

/**

* 比较两个对象的差异

* 应用场景:编辑页面中,表单字段比较多(主页面,明细页面),而修改项很少,只需要将修改的字段和必要字段传递给后台就可以,不用传递表单中的所有字段(默认是传递所有字段)

* @param {Array/Object} original 原始对象

* @param {Array/Object} modified 修改后对象

* @param {Array} primaryKeys 主键(后台是根据主键修改数据)

* @param {Array} excludeKeys 过滤字段(不参与比较的字段)

* @return {Object} diffObject 返回有变更的对象字段

*/

export function obtainingObjectsDiffrences(original, modified, primaryKeys = ['id'], excludeKeys = []) {

let diffObject = {}

// 如果当前为数组,先分别区分哪些需要新增、修改、删除

if (typeof original === 'object' && Array.isArray(original) && typeof modified === 'object' && Array.isArray(modified)) {

diffObject = subObtainingObjectsDiffrences(original, modified, primaryKeys, excludeKeys)

} else if (typeof original === 'object' && typeof modified === 'object') {

// 当前对象字段是否有变更

let levelDiffFlag = false

// 主键

let primaryKey

for (const key in original) {

if (!excludeKeys.includes(key)) {

if (typeof original[key] === 'object' && Array.isArray(original[key]) && typeof modified[key] === 'object' && Array.isArray(modified[key])) {

const subDiffObject = subObtainingObjectsDiffrences(original[key], modified[key], primaryKeys, excludeKeys)

if (Object.keys(subDiffObject).length !== 0) {

diffObject[key] = subDiffObject.modifiedArray

diffObject[key + 'New'] = subDiffObject.newArray

diffObject[key + 'Removed'] = subDiffObject.removedArray

}

} else if (typeof original[key] === 'object' && typeof modified[key] === 'object') {

const subDiffObject = obtainingObjectsDiffrences(original[key], modified[key], primaryKeys, excludeKeys)

if (Object.keys(subDiffObject).length !== 0) {

diffObject[key] = subDiffObject

}

} else if (original[key] !== modified[key]) {

// 更新modifiedData对象中的属性值

diffObject[key] = modified[key]

levelDiffFlag = true

}

}

if (primaryKeys.includes(key)) {

primaryKey = key

}

}

// 设置主键字段值

if (levelDiffFlag && primaryKey !== undefined) {

diffObject[primaryKey] = original[primaryKey]

}

}

return diffObject

}

/**

* 比对数组之间的差异(新增、修改、删除)

* @param {Array} original 原始数组

* @param {Array} modified 修改后的数组

* @param {Array} primaryKeys 主键列表

* @param {Array} excludeKeys 不参与比对的字段

* @return {Object} diffObject 返回有差异的数组

*/

function subObtainingObjectsDiffrences(original, modified, primaryKeys, excludeKeys) {

const diffObject = {}

// 子列表主键

let subPrimaryKey

for (const key in original[0]) {

if (primaryKeys.includes(key)) {

subPrimaryKey = key

}

}

// 具有相同主键的初始列表

const commonOrignal = original.filter(itemA => modified.some(itemB => itemA[subPrimaryKey] === itemB[subPrimaryKey]))

// 具有相同主键的修改列表

const commonModified = modified.filter(itemA => original.some(itemB => itemA[subPrimaryKey] === itemB[subPrimaryKey]))

// 删除列表

const removedArray = original.filter(itemA => !modified.some(itemB => itemA[subPrimaryKey] === itemB[subPrimaryKey]))

if (removedArray.length !== 0) {

diffObject.removedArray = removedArray

}

// 新增列表

const newArray = modified.filter(itemA => !original.some(itemB => itemA[subPrimaryKey] === itemB[subPrimaryKey]))

if (newArray.length !== 0) {

diffObject.newArray = newArray

}

// 修改条目:如果属性值是对象,则递归调用obtainingObjectsDiffrences进行比较

const modifiedArray = []

commonOrignal.forEach(subOrignal => {

const subModified = commonModified.filter(item => item[subPrimaryKey] === subOrignal[subPrimaryKey])[0]

const subModifiedObject = obtainingObjectsDiffrences(subOrignal, subModified, primaryKeys, excludeKeys)

if (Object.keys(subModifiedObject).length !== 0) {

modifiedArray.push(subModifiedObject)

}

})

if (modifiedArray.length !== 0) {

diffObject.modifiedArray = modifiedArray

}

return diffObject

}

调用示例:

const originalUserList = [{

userId: 'a1',

username: 'tom',

password: '123456',

remarks: 'asdfasdfadsf',

roles: [{

roleId: 'r1',

roleName: '管理员',

remarks: 'asdfasdfadsf',

menus: [{

menuId: 'm1',

menuName: '用户管理',

remarks: 'asdfasdfadsf'

}, {

menuId: 'm2',

menuName: '角色管理',

remarks: 'asdfasdfadsf'

}, {

menuId: 'm3',

menuName: '菜单管理',

remarks: 'asdfasdfadsf'

}]

}]

}, {

userId: 'a2',

username: 'cat',

password: '123456',

remarks: 'asdfasdfadsf',

roles: [{

roleId: 'r1',

roleName: '管理员',

remarks: 'asdfasdfadsf',

menus: [{

menuId: 'm1',

menuName: '用户管理',

remarks: 'asdfasdfadsf'

}, {

menuId: 'm2',

menuName: '角色管理',

remarks: 'asdfasdfadsf'

}, {

menuId: 'm3',

menuName: '菜单管理',

remarks: 'asdfasdfadsf'

}]

}]

}]

const modifiedUserList = [{

userId: 'a1',

username: 'tom',

password: '123456789',

remarks: 'asdf',

roles: [{

roleId: 'r1',

roleName: '管理员',

remarks: 'asdf',

menus: [{

menuId: 'm1',

menuName: '企业管理',

remarks: 'asdf'

}, {

menuId: 'm3',

menuName: '菜单管理',

remarks: 'asdf'

}]

}]

}, {

userId: 'a3',

username: 'tom1',

password: '12345686',

remarks: 'asdfasdfadsf',

roles: [{

roleId: 'r1',

roleName: '管理员',

remarks: 'asdfasdfadsf',

menus: [{

menuId: 'm1',

menuName: '用户管理',

remarks: 'asdfasdfadsf'

}, {

menuId: 'm2',

menuName: '角色管理',

remarks: 'asdfasdfadsf'

}, {

menuId: 'm3',

menuName: '菜单管理',

remarks: 'asdfasdfadsf'

}]

}]

}]

const diff = obtainingObjectsDiffrences(originalUserList, modifiedUserList, ['userId', 'roleId', 'menuId'], ['remarks'])

console.log('原始列表', originalUserList)

console.log('修改列表', modifiedUserList)

console.log('差异列表', diff)

调用结果:

结语

1、对于比较差异的返回字段的命名,前后端小伙伴可以商量着来

2、如果后端的新增保存方法是saveOrUpdate的通用方法,前端可以把对应的新增列表和修改列表合并了

3、如果后端的删除是removeByIds的方法,前端可以把返回的removeArray再按照主键过滤一下

4、注意2和3是要后端统一进行事务处理的,不要前端单独调用后端新增的、修改的、删除的方法

这是我的处理方案,供参考,如果有更有方案可以一起探讨

相关推荐
魔术师ID8 分钟前
vue2.0 组件生命周期
前端·javascript·vue.js·学习·visual studio code
胜玲龙21 分钟前
单点登录是是什么?具体流程是什么?
java·服务器·前端
小浪学编程24 分钟前
C#学习9——接口、抽象类
前端·学习·c#
Dontla25 分钟前
《黑马前端ajax+node.js+webpack+git教程》(笔记)——ajax教程(axios教程)
前端·ajax·node.js
打小就很皮...30 分钟前
基于 Vue 和 Node.js 实现图片上传功能:从前端到后端的完整实践
前端·vue.js·node.js
ange20171 小时前
前端工程的相关管理 git、branch、build
前端·git
C+ 安口木2 小时前
纯前端实现图文识别 OCR
前端·javascript·ocr
白熊1882 小时前
【通用智能体】Lynx :一款基于终端的纯文本网页浏览器
前端·人工智能·chrome·通用智能体
二川bro2 小时前
Cursor 模型深度分析:区别、优缺点及适用场景
前端
NoneCoder2 小时前
正则表达式与文本处理的艺术
前端·javascript·面试·正则表达式