20260711173006
This commit is contained in:
1 parent
d080a2a48d
commit
446758b2fe
384 files changed
+153926
-5937
No files matched your search
@@ -0,0 +1,471 @@
|
||||
/* 比对并分类数据变更
|
||||
* @param {Array} currentData 变更后的数据数组
|
||||
* @param {Array} originalData 原始数据数组
|
||||
* @param {String} primaryKey 主键字段名
|
||||
* @param {Array} ignoreFields 过滤字段(可传可不传)
|
||||
* @returns {Object} 包含分类结果的对象 { insertdata: [], updatedata: [], deletedata: [], hasChanges: Boolean }
|
||||
*/
|
||||
export function compareAndClassifyData(currentData, originalData, primaryKey) {
|
||||
const versionFields = ["mx_version", "b_version"];
|
||||
const getVersionFields = (row) => {
|
||||
const fields = {};
|
||||
versionFields.forEach((key) => {
|
||||
if (Object.prototype.hasOwnProperty.call(row, key)) {
|
||||
fields[key] = row[key] ?? "";
|
||||
}
|
||||
});
|
||||
return fields;
|
||||
};
|
||||
|
||||
const result = {
|
||||
inserts: [],
|
||||
updates: [],
|
||||
deletes: [],
|
||||
hasChanges: false, // 默认无变动
|
||||
};
|
||||
|
||||
// 1. 找出新增的数据
|
||||
result.inserts = currentData.filter(
|
||||
(row) =>
|
||||
!row[primaryKey] ||
|
||||
!originalData.some((originalRow) => originalRow[primaryKey] === row[primaryKey]),
|
||||
);
|
||||
|
||||
// 2. 找出修改的数据
|
||||
result.updates = currentData.reduce((acc, row) => {
|
||||
const rowId = row[primaryKey];
|
||||
if (!rowId) return acc;
|
||||
|
||||
const originalRow = originalData.find((r) => r[primaryKey] === rowId);
|
||||
if (!originalRow) return acc;
|
||||
|
||||
const changedFields = {};
|
||||
Object.keys(row).forEach((key) => {
|
||||
if (key !== primaryKey && !key.startsWith("v_") && !versionFields.includes(key)) {
|
||||
const oldVal = originalRow[key];
|
||||
const newVal = row[key];
|
||||
|
||||
const normalize = (v) => (v === null || v === undefined ? "" : v);
|
||||
|
||||
if (normalize(newVal) !== normalize(oldVal)) {
|
||||
// ✅ 避免 undefined 被丢弃
|
||||
changedFields[key] = newVal ?? "";
|
||||
}
|
||||
}
|
||||
});
|
||||
// Object.keys(row).forEach((key) => {
|
||||
// if (key !== primaryKey && !isEqual(row[key], originalRow[key])) {
|
||||
// if (!key.startsWith('v_')) {
|
||||
// changedFields[key] = row[key]
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
if (Object.keys(changedFields).length > 0) {
|
||||
acc.push({
|
||||
[primaryKey]: rowId,
|
||||
...getVersionFields(row),
|
||||
...changedFields,
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// 3. 找出删除的数据
|
||||
result.deletes = originalData.filter(
|
||||
(originalRow) => !currentData.some((row) => row[primaryKey] === originalRow[primaryKey]),
|
||||
);
|
||||
|
||||
// 4. 是否有任何变动
|
||||
result.hasChanges =
|
||||
result.inserts.length > 0 || result.updates.length > 0 || result.deletes.length > 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在数组对象的每个元素中覆盖式填入 JSON 对象的值
|
||||
* @param {Array} arr - 目标数组(将被修改)
|
||||
* @param {object} json - 包含要添加/覆盖的键值对的 JSON 对象
|
||||
* @returns {Array} 修改后的数组
|
||||
* @throws {Error} 当参数类型不正确时抛出错误
|
||||
*/
|
||||
// export function fillDefaultArray(arr, json) {
|
||||
// // 基本数据验证
|
||||
// if (!Array.isArray(arr))
|
||||
// throw new Error('第一个参数必须是数组')
|
||||
// if (typeof json !== 'object' || json === null || Array.isArray(json)) {
|
||||
// throw new Error('第二个参数必须是普通对象(JSON对象)')
|
||||
// }
|
||||
|
||||
// // 覆盖式填充每个元素
|
||||
// return arr.map((item) => {
|
||||
// // 只处理对象元素
|
||||
// if (typeof item === 'object' && item !== null) {
|
||||
// return { ...item, ...json } // 覆盖已有属性
|
||||
// }
|
||||
// return item // 非对象元素保持不变
|
||||
// })
|
||||
// }
|
||||
|
||||
export function fillDefaultArray(arr, json) {
|
||||
// 基本数据验证
|
||||
if (!Array.isArray(arr)) throw new Error("第一个参数必须是数组");
|
||||
|
||||
if (typeof json !== "object" || json === null || Array.isArray(json)) {
|
||||
throw new Error("第二个参数必须是普通对象(JSON对象)");
|
||||
}
|
||||
|
||||
// 原地修改数组中的每个对象
|
||||
for (const item of arr) {
|
||||
if (typeof item === "object" && item !== null) {
|
||||
Object.assign(item, json);
|
||||
}
|
||||
}
|
||||
|
||||
// 可选:方便链式调用
|
||||
return arr;
|
||||
}
|
||||
|
||||
// export function fillSerialNoAndDefaults(arr, defaults = {}) {
|
||||
// if (!Array.isArray(arr)) return;
|
||||
|
||||
// arr.forEach((item, index) => {
|
||||
// if (typeof item === "object" && item !== null) {
|
||||
// item.mx_xh = index + 1;
|
||||
// Object.assign(item, defaults);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
export function fillSerialNoAndDefaults(arr, defaults = {}) {
|
||||
if (!Array.isArray(arr)) return;
|
||||
|
||||
arr.forEach((item, index) => {
|
||||
if (typeof item === "object" && item !== null) {
|
||||
item.mx_xh = index + 1;
|
||||
|
||||
Object.entries(defaults).forEach(([key, value]) => {
|
||||
item[key] = typeof value === "function" ? value(item, index) : value;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function compareAndClassifyData1(currentData, originalData, primaryKey, ignoreFields = []) {
|
||||
const ignoreSet = new Set(ignoreFields);
|
||||
|
||||
const result = {
|
||||
insertdata: [],
|
||||
updatedata: [],
|
||||
deletedata: [],
|
||||
hasChanges: false,
|
||||
};
|
||||
|
||||
// 工具函数:剔除不需要的字段
|
||||
const omitFields = (row) => {
|
||||
const res = {};
|
||||
Object.keys(row).forEach((key) => {
|
||||
if (key !== primaryKey && ignoreSet.has(key)) return;
|
||||
res[key] = row[key];
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
// 1️⃣ 新增
|
||||
result.insertdata = currentData
|
||||
.filter(
|
||||
(row) => !row[primaryKey] || !originalData.some((o) => o[primaryKey] === row[primaryKey]),
|
||||
)
|
||||
.map((row) => omitFields(row)); // 在插入时过滤字段
|
||||
|
||||
// 2️⃣ 修改
|
||||
result.updatedata = currentData.reduce((acc, row) => {
|
||||
const rowId = row[primaryKey];
|
||||
if (!rowId) return acc;
|
||||
|
||||
const originalRow = originalData.find((r) => r[primaryKey] === rowId);
|
||||
if (!originalRow) return acc;
|
||||
|
||||
const changedFields = {};
|
||||
Object.keys(row).forEach((key) => {
|
||||
if (key === primaryKey || ignoreSet.has(key)) return; // 忽略指定字段
|
||||
|
||||
if (row[key] !== originalRow[key]) changedFields[key] = row[key];
|
||||
});
|
||||
|
||||
if (Object.keys(changedFields).length > 0) {
|
||||
acc.push({
|
||||
[primaryKey]: rowId,
|
||||
...omitFields(changedFields), // 在更新时也过滤字段
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// 3️⃣ 删除,只保留 primaryKey
|
||||
result.deletedata = originalData
|
||||
.filter((o) => !currentData.some((c) => c[primaryKey] === o[primaryKey]))
|
||||
.map((row) => ({ [primaryKey]: row[primaryKey] }));
|
||||
|
||||
// 4️⃣ 是否有变动
|
||||
result.hasChanges =
|
||||
result.insertdata.length > 0 || result.updatedata.length > 0 || result.deletedata.length > 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度比较两个值是否相等
|
||||
* @param {*} a 第一个值
|
||||
* @param {*} b 第二个值
|
||||
* @returns {boolean} 是否相等
|
||||
*/
|
||||
export function isEqual(a, b) {
|
||||
// 处理基本类型和null/undefined
|
||||
if (a === b) return true;
|
||||
if (typeof a !== typeof b) return false;
|
||||
if (typeof a !== "object" || a === null || b === null) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
// 处理数组
|
||||
if (Array.isArray(a) && Array.isArray(b)) {
|
||||
if (a.length !== b.length) return false;
|
||||
return a.every((item, i) => isEqual(item, b[i]));
|
||||
}
|
||||
|
||||
// 处理对象
|
||||
const keysA = Object.keys(a);
|
||||
const keysB = Object.keys(b);
|
||||
|
||||
if (keysA.length !== keysB.length) return false;
|
||||
|
||||
return keysA.every((key) => isEqual(a[key], b[key]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测并提取变更的字段(跳过v_开头的字段)
|
||||
* @param {object} currentData 当前数据
|
||||
* @param {object} originalData 原始数据
|
||||
* @param {string} primaryKey 主键字段名
|
||||
* @param {string} primaryKeyValue 主键值
|
||||
* @returns {object} 包含变更数据的对象,结构为 { hasChanges: boolean, updateData: Object }
|
||||
*/
|
||||
export function detectChanges(currentData, originalData, primaryKey, primaryKeyValue) {
|
||||
const updateValue = {};
|
||||
|
||||
// 遍历所有字段检测变更
|
||||
Object.keys(currentData).forEach((key) => {
|
||||
// 跳过以v_开头的字段
|
||||
if (key.startsWith("v_")) return;
|
||||
|
||||
if (key === "b_type") {
|
||||
const currentStr = normalizeOwnerCustomer(currentData[key]);
|
||||
const originalStr = normalizeOwnerCustomer(originalData[key]);
|
||||
|
||||
if (currentStr !== originalStr) {
|
||||
updateValue[key] = currentStr; // 保存时格式化为逗号分隔字符串
|
||||
}
|
||||
} else {
|
||||
// 比较前,先将 undefined 转换为空字符串
|
||||
const currentValue = currentData[key] === undefined ? "" : currentData[key];
|
||||
const originalValue = originalData[key] === undefined ? "" : originalData[key];
|
||||
|
||||
// 使用JSON.stringify进行深度比较
|
||||
if (JSON.stringify(currentValue) !== JSON.stringify(originalValue)) {
|
||||
updateValue[key] = currentValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没有有效变更
|
||||
if (Object.keys(updateValue).length === 0) {
|
||||
return { hasChanges: true, updateData: [] };
|
||||
}
|
||||
|
||||
// 返回变更数据和主键
|
||||
return {
|
||||
hasChanges: true,
|
||||
updateData: [
|
||||
{
|
||||
[primaryKey]: primaryKeyValue,
|
||||
...updateValue,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测并提取变更的字段(跳过v_开头的字段和指定排除字段)
|
||||
* @param {object} currentData 当前数据
|
||||
* @param {object} originalData 原始数据
|
||||
* @param {Array|string} primaryKeys 主键字段名或数组
|
||||
* @param {Array} excludeKeys 可选,排除的字段数组
|
||||
* @returns {object} 包含变更数据的对象,结构为 { hasChanges: boolean, updateData: Array }
|
||||
*/
|
||||
export function detectChangesFields(currentData, originalData, primaryKeys, excludeKeys = []) {
|
||||
const updateValue = {};
|
||||
|
||||
Object.keys(currentData).forEach((key) => {
|
||||
// 跳过以v_开头的字段
|
||||
if (key.startsWith("v_")) return;
|
||||
|
||||
// 跳过排除字段
|
||||
if (excludeKeys.includes(key)) return;
|
||||
|
||||
const currentValue = currentData[key] === undefined ? "" : currentData[key];
|
||||
const originalValue = originalData[key] === undefined ? "" : originalData[key];
|
||||
|
||||
if (JSON.stringify(currentValue) !== JSON.stringify(originalValue)) {
|
||||
updateValue[key] = currentValue;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(updateValue).length === 0) {
|
||||
return { hasChanges: false, updateData: [] };
|
||||
}
|
||||
|
||||
// 构建主键字段对象
|
||||
const primaryKeyObj = {};
|
||||
if (Array.isArray(primaryKeys)) {
|
||||
primaryKeys.forEach((key) => {
|
||||
primaryKeyObj[key] = originalData[key];
|
||||
});
|
||||
} else {
|
||||
primaryKeyObj[primaryKeys] = originalData[primaryKeys];
|
||||
}
|
||||
|
||||
return {
|
||||
hasChanges: true,
|
||||
updateData: [
|
||||
{
|
||||
...primaryKeyObj,
|
||||
...updateValue,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeOwnerCustomer(val) {
|
||||
if (typeof val === "string") {
|
||||
return val.split(",").filter(Boolean).join(","); // '001,002,003'
|
||||
}
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
return val.map((item) => (Array.isArray(item) ? item[0] : item)).join(",");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一数据差异比对(主表 / 子表通用)
|
||||
*
|
||||
* @param {Array | object} current 当前数据
|
||||
* @param {Array | object} original 原始数据
|
||||
* @param {string | Array} primaryKey 主键
|
||||
* @param {object} options
|
||||
* @param {Array} options.ignoreFields 忽略字段(如 v_ 字段)
|
||||
* @param {boolean} options.isSingle 是否单条主表
|
||||
*/
|
||||
export function diffData(
|
||||
current,
|
||||
original,
|
||||
primaryKey,
|
||||
{ ignoreFields = [], isSingle = false } = {},
|
||||
) {
|
||||
const ignoreSet = new Set(ignoreFields);
|
||||
|
||||
const result = {
|
||||
hasChanges: false,
|
||||
insertdata: [],
|
||||
updatedata: [],
|
||||
deletedata: [],
|
||||
};
|
||||
|
||||
// ===========================
|
||||
// 单表处理
|
||||
// ===========================
|
||||
if (isSingle) {
|
||||
const isNewRecord = !original || Object.keys(original).length === 0 || !original[primaryKey];
|
||||
|
||||
// 新增
|
||||
if (isNewRecord) {
|
||||
result.hasChanges = true;
|
||||
result.insertdata.push(JSON.parse(JSON.stringify(current)));
|
||||
return result;
|
||||
}
|
||||
|
||||
// 修改
|
||||
const changes = {};
|
||||
Object.keys(current).forEach((key) => {
|
||||
if (key.startsWith("v_") || ignoreSet.has(key)) return;
|
||||
const cur = current[key] ?? "";
|
||||
const org = original[key] ?? "";
|
||||
if (JSON.stringify(cur) !== JSON.stringify(org)) {
|
||||
changes[key] = cur;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(changes).length > 0) {
|
||||
result.hasChanges = true;
|
||||
result.updatedata.push({ [primaryKey]: current[primaryKey], ...changes });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// 子表处理(优化 Map 提升性能)
|
||||
// ===========================
|
||||
const pk = Array.isArray(primaryKey) ? primaryKey[0] : primaryKey;
|
||||
const originalMap = new Map(original.map((item) => [item[pk], item]));
|
||||
|
||||
// 新增
|
||||
result.insertdata = current.filter((row) => !row[pk] || !originalMap.has(row[pk]));
|
||||
|
||||
// 修改
|
||||
result.updatedata = [];
|
||||
current.forEach((row) => {
|
||||
const id = row[pk];
|
||||
if (!id) return;
|
||||
const oldRow = originalMap.get(id);
|
||||
if (!oldRow) return;
|
||||
|
||||
const changes = {};
|
||||
Object.keys(row).forEach((key) => {
|
||||
if (key === pk || key.startsWith("v_") || ignoreSet.has(key)) return;
|
||||
const cur = row[key] ?? "";
|
||||
const org = oldRow[key] ?? "";
|
||||
if (JSON.stringify(cur) !== JSON.stringify(org)) {
|
||||
changes[key] = cur;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(changes).length > 0) {
|
||||
result.updatedata.push({ [pk]: id, ...changes });
|
||||
}
|
||||
});
|
||||
|
||||
// 删除
|
||||
result.deletedata = original
|
||||
.filter((o) => !current.some((c) => c[pk] === o[pk]))
|
||||
.map((o) => ({ [pk]: o[pk] }));
|
||||
|
||||
result.hasChanges =
|
||||
result.insertdata.length > 0 || result.updatedata.length > 0 || result.deletedata.length > 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 默认导出所有方法
|
||||
export default {
|
||||
isEqual,
|
||||
compareAndClassifyData,
|
||||
detectChanges,
|
||||
fillDefaultArray,
|
||||
diffData,
|
||||
fillSerialNoAndDefaults,
|
||||
};
|
||||
Reference in new issue
Block a user