693 lines
15 KiB
JavaScript
693 lines
15 KiB
JavaScript
/* 比对并分类数据变更
|
|
* @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 忽略字段
|
|
* @param {boolean} options.isSingle 是否单条主表
|
|
*/
|
|
export function diffData(
|
|
current,
|
|
original,
|
|
primaryKey,
|
|
{ ignoreFields = [], isSingle = false } = {},
|
|
) {
|
|
const ignoreSet = new Set(ignoreFields);
|
|
|
|
const versionFields = new Set(["b_version", "mx_version"]);
|
|
|
|
|
|
/**
|
|
* 判断是否为数字类型
|
|
*
|
|
* 支持:
|
|
* 40
|
|
* "40"
|
|
* "40.00"
|
|
*/
|
|
const isNumberValue = (value) => {
|
|
if (typeof value === "number") {
|
|
return !Number.isNaN(value);
|
|
}
|
|
|
|
if (typeof value === "string") {
|
|
if (value.trim() === "") {
|
|
return false;
|
|
}
|
|
|
|
return !Number.isNaN(Number(value));
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* 数据值比较
|
|
*
|
|
* 数字:
|
|
* 40 === "40.00"
|
|
*
|
|
* 非数字:
|
|
* 保持原 JSON 比较逻辑
|
|
*/
|
|
const isEqualValue = (a, b) => {
|
|
|
|
// null undefined 空字符串统一
|
|
if (a == null || a === "") {
|
|
a = "";
|
|
}
|
|
|
|
if (b == null || b === "") {
|
|
b = "";
|
|
}
|
|
|
|
|
|
// 两边都是数字,按数值比较
|
|
if (isNumberValue(a) && isNumberValue(b)) {
|
|
return Math.abs(Number(a) - Number(b)) < 0.000001;
|
|
}
|
|
|
|
|
|
// 其他类型保持原来的比较方式
|
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
};
|
|
|
|
|
|
const result = {
|
|
hasChanges: false,
|
|
insertdata: [],
|
|
updatedata: [],
|
|
deletedata: [],
|
|
};
|
|
|
|
|
|
const filterFields = (row) => {
|
|
return Object.fromEntries(
|
|
Object.entries(row).filter(([key]) => {
|
|
return (
|
|
!key.startsWith("_") &&
|
|
!key.startsWith("v_") &&
|
|
!ignoreSet.has(key)
|
|
);
|
|
}),
|
|
);
|
|
};
|
|
|
|
|
|
// 获取版本字段
|
|
const getVersionData = (row) => {
|
|
const data = {};
|
|
|
|
Object.keys(row).forEach((key) => {
|
|
if (versionFields.has(key)) {
|
|
data[key] = row[key];
|
|
}
|
|
});
|
|
|
|
return data;
|
|
};
|
|
|
|
|
|
// ===========================
|
|
// 单表
|
|
// ===========================
|
|
if (isSingle) {
|
|
|
|
const isNewRecord =
|
|
!original ||
|
|
Object.keys(original).length === 0 ||
|
|
!original[primaryKey];
|
|
|
|
|
|
if (isNewRecord) {
|
|
|
|
result.hasChanges = true;
|
|
|
|
result.insertdata.push(filterFields(current));
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
const changes = {};
|
|
|
|
|
|
Object.keys(current).forEach((key) => {
|
|
|
|
// 版本字段不参与比较
|
|
if (versionFields.has(key)) {
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
key.startsWith("_") ||
|
|
key.startsWith("v_") ||
|
|
ignoreSet.has(key)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
|
|
const cur = current[key] ?? "";
|
|
const org = original[key] ?? "";
|
|
|
|
|
|
if (!isEqualValue(cur, org)) {
|
|
changes[key] = cur;
|
|
}
|
|
|
|
});
|
|
|
|
|
|
if (Object.keys(changes).length > 0) {
|
|
|
|
result.hasChanges = true;
|
|
|
|
|
|
result.updatedata.push({
|
|
|
|
[primaryKey]: current[primaryKey],
|
|
|
|
// 修改字段
|
|
...changes,
|
|
|
|
// 版本
|
|
...getVersionData(current),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
// ===========================
|
|
// 子表
|
|
// ===========================
|
|
|
|
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]),
|
|
)
|
|
|
|
.map(filterFields);
|
|
|
|
|
|
|
|
// 修改
|
|
|
|
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 (versionFields.has(key)) {
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
key === pk ||
|
|
key.startsWith("_") ||
|
|
key.startsWith("v_") ||
|
|
ignoreSet.has(key)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
|
|
const cur = row[key] ?? "";
|
|
const org = oldRow[key] ?? "";
|
|
|
|
|
|
if (!isEqualValue(cur, org)) {
|
|
changes[key] = cur;
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
if (Object.keys(changes).length > 0) {
|
|
|
|
|
|
result.updatedata.push({
|
|
|
|
[pk]: id,
|
|
|
|
...changes,
|
|
|
|
// 版本
|
|
...getVersionData(row),
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
// 删除
|
|
|
|
result.deletedata = original
|
|
|
|
.filter(
|
|
(o) =>
|
|
!current.some(
|
|
(c) => c[pk] === o[pk],
|
|
),
|
|
)
|
|
|
|
.map((o) => ({
|
|
[pk]: o[pk],
|
|
// 删除带版本
|
|
...getVersionData(o),
|
|
}));
|
|
|
|
|
|
result.hasChanges =
|
|
result.insertdata.length > 0 ||
|
|
result.updatedata.length > 0 ||
|
|
result.deletedata.length > 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
// 默认导出所有方法
|
|
export default {
|
|
isEqual,
|
|
compareAndClassifyData,
|
|
detectChanges,
|
|
fillDefaultArray,
|
|
diffData,
|
|
fillSerialNoAndDefaults,
|
|
}; |