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

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

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

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

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

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

接下来,我把我对这种复杂场景的处理方案分享出来,大家可以参考,共同探讨,看看还有没有更好的解决方案(前端使用的是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是要后端统一进行事务处理的,不要前端单独调用后端新增的、修改的、删除的方法

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

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax