/* 比对并分类数据变更 * @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, };