MarketingSystemDataExportTool/web/modules/fields.js

463 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 字段处理模块 - 字段路径转换、树结构构建
* @module fields
*/
// 依赖AppConfig
/**
* 字段管理器类
* 管理字段元数据、树结构构建和路径转换
*/
class FieldsManager {
constructor() {
// 字段映射:{ tableName: [{ value, label }] }
this.fieldsMap = {};
// 表标签映射:{ tableName: label }
this.tableLabels = {};
// 推荐字段列表
this.recommendedFields = [];
}
/**
* 更新字段元数据
* @param {Array} tables - 表定义数组
* @param {Array} recommended - 推荐字段数组
*/
updateMetadata(tables, recommended = []) {
const map = {};
const labels = {};
tables.forEach(table => {
const fields = Array.isArray(table.fields) ? table.fields : [];
const visibleFields = fields.filter(f => !f.hidden);
map[table.table] = visibleFields.map(f => ({
value: f.field,
label: f.label
}));
if (table.label) {
labels[table.table] = table.label;
}
});
this.fieldsMap = map;
this.tableLabels = labels;
this.recommendedFields = recommended;
}
/**
* 获取表标签
* @param {string} table - 表名
* @returns {string} 表标签
*/
getTableLabel(table) {
return this.tableLabels[table]
|| window.AppConfig?.DEFAULT_TABLE_LABELS[table]
|| table;
}
/**
* 获取指定表的字段列表
* @param {string} table - 表名
* @returns {Array<{value: string, label: string}>} 字段列表
*/
getTableFields(table) {
return this.fieldsMap[table] || [];
}
/**
* 检查表中是否存在某字段
* @param {string} table - 表名
* @param {string} field - 字段名
* @returns {boolean} 是否存在
*/
hasField(table, field) {
const fields = this.fieldsMap[table] || [];
return fields.some(f => f.value === field);
}
/**
* 获取所有表名
* @returns {string[]} 表名列表
*/
getAllTables() {
return Object.keys(this.fieldsMap);
}
/**
* 构建树节点
* @param {string} table - 表名
* @param {Array} children - 子节点
* @returns {Object} 树节点
*/
buildTreeNode(table, children = []) {
return {
value: table,
label: this.getTableLabel(table),
children
};
}
/**
* 构建易码通订单子节点
* @param {number} orderType - 订单类型
* @returns {Array} 子节点数组
*/
buildYmtOrderChildren(orderType) {
const orderFields = this.getTableFields('order');
const children = orderFields.map(f => ({ value: f.value, label: f.label }));
// 添加公共子表
children.push(this.buildTreeNode('merchant', this.getTableFields('merchant')));
children.push(this.buildTreeNode('activity', this.getTableFields('activity')));
// 根据订单类型添加特定子表
if (orderType === 2) {
// 直充卡密
children.push(this.buildTreeNode('order_digit', this.getTableFields('order_digit')));
} else if (orderType === 3) {
// 立减金
children.push(this.buildTreeNode('order_voucher', this.getTableFields('order_voucher')));
children.push(this.buildTreeNode('goods_voucher_batch', this.getTableFields('goods_voucher_batch')));
children.push(this.buildTreeNode('goods_voucher_subject_config', this.getTableFields('goods_voucher_subject_config')));
} else if (orderType === 1) {
// 红包
children.push(this.buildTreeNode('order_cash', this.getTableFields('order_cash')));
} else {
// 全部类型
children.push(this.buildTreeNode('order_voucher', this.getTableFields('order_voucher')));
children.push(this.buildTreeNode('order_cash', this.getTableFields('order_cash')));
children.push(this.buildTreeNode('order_digit', this.getTableFields('order_digit')));
children.push(this.buildTreeNode('goods_voucher_batch', this.getTableFields('goods_voucher_batch')));
children.push(this.buildTreeNode('goods_voucher_subject_config', this.getTableFields('goods_voucher_subject_config')));
}
return children;
}
/**
* 构建营销系统订单子节点
* @param {number} orderType - 订单类型
* @returns {Array} 子节点数组
*/
buildMarketingOrderChildren(orderType) {
const orderFields = this.getTableFields('order');
const children = orderFields.map(f => ({ value: f.value, label: f.label }));
// 订单详情
children.push(this.buildTreeNode('order_detail', this.getTableFields('order_detail')));
// 构建计划节点(包含 key_batch -> code_batch 嵌套)
const planChildren = [
...this.getTableFields('plan').map(f => ({ value: f.value, label: f.label })),
this.buildTreeNode('key_batch', [
...this.getTableFields('key_batch').map(f => ({ value: f.value, label: f.label })),
this.buildTreeNode('code_batch', this.getTableFields('code_batch'))
])
];
// 构建立减金节点(包含 voucher -> voucher_batch 嵌套)
const voucherChildren = [
...this.getTableFields('order_voucher').map(f => ({ value: f.value, label: f.label })),
this.buildTreeNode('voucher', [
...this.getTableFields('voucher').map(f => ({ value: f.value, label: f.label })),
this.buildTreeNode('voucher_batch', this.getTableFields('voucher_batch'))
])
];
// 根据订单类型添加特定子表
if (orderType === 1) {
// 直充卡密
children.push(this.buildTreeNode('plan', planChildren));
children.push(this.buildTreeNode('merchant_key_send', this.getTableFields('merchant_key_send')));
} else if (orderType === 2) {
// 立减金
children.push(this.buildTreeNode('order_voucher', voucherChildren));
children.push(this.buildTreeNode('plan', planChildren));
} else if (orderType === 3) {
// 红包
children.push(this.buildTreeNode('order_cash', this.getTableFields('order_cash')));
children.push(this.buildTreeNode('plan', planChildren));
} else {
// 全部类型
children.push(this.buildTreeNode('order_cash', this.getTableFields('order_cash')));
children.push(this.buildTreeNode('order_voucher', voucherChildren));
children.push(this.buildTreeNode('plan', planChildren));
children.push(this.buildTreeNode('merchant_key_send', this.getTableFields('merchant_key_send')));
}
return children;
}
/**
* 构建字段选择树数据
* @param {string} datasource - 数据源
* @param {number} orderType - 订单类型
* @param {string} [mainTable] - 主表名(可选,用于编辑模式)
* @returns {Array} 树数据
*/
buildFieldTree(datasource, orderType, mainTable) {
const type = Number(orderType || 0);
if (datasource === 'ymt') {
const actualMainTable = (mainTable === 'order_info') ? 'order' : (mainTable || 'order');
const orderNode = this.buildTreeNode(actualMainTable, this.buildYmtOrderChildren(type));
return type ? [orderNode] : [{ value: 'scene_order', label: '订单数据', children: [orderNode] }];
} else {
const tableForTree = mainTable || 'order';
const orderNode = this.buildTreeNode(tableForTree, this.buildMarketingOrderChildren(type));
return type ? [orderNode] : [{ value: 'scene_order', label: '订单数据', children: [orderNode] }];
}
}
/**
* 将 table.field 格式转换为路径数组格式
* @param {string[]} fields - 字段数组,格式为 ['table.field', ...]
* @param {string} datasource - 数据源
* @param {string} mainTable - 主表名
* @returns {Array<string[]>} 路径数组,格式为 [['table', 'field'], ...]
*/
convertFieldsToPaths(fields, datasource, mainTable) {
const actualMainTable = (datasource === 'ymt' && mainTable === 'order_info') ? 'order' : mainTable;
const relations = window.AppConfig?.getTableRelations(datasource) || {};
const toPath = (tableField) => {
const parts = String(tableField || '').split('.');
if (parts.length !== 2) return null;
let table = parts[0];
const field = parts[1];
// ymt 数据源 order_info 映射为 order
if (datasource === 'ymt' && table === 'order_info') {
table = 'order';
}
// 查找表的路径关系
const relation = relations[table];
if (!relation) {
// 未知表,尝试直接返回
return [actualMainTable, table, field];
}
const pathToTable = relation.path || [];
if (pathToTable.length === 0) {
// 主表字段
return [actualMainTable, field];
} else {
// 子表字段
return [actualMainTable, ...pathToTable, field];
}
};
return fields
.map(toPath)
.filter(path => Array.isArray(path) && path.length >= 2)
.filter(path => {
// 验证字段存在
const table = path[path.length - 2];
const field = path[path.length - 1];
return this.hasField(table, field);
});
}
/**
* 将路径数组格式转换为 table.field 格式
* @param {Array<string[]>} paths - 路径数组
* @param {string} datasource - 数据源
* @param {string} mainTable - 主表名
* @returns {string[]} 字段数组,格式为 ['table.field', ...]
*/
convertPathsToFields(paths, datasource, mainTable) {
const actualMainTable = (datasource === 'ymt' && mainTable === 'order_info') ? 'order' : mainTable;
const allTables = this.getAllTables();
// 检查是否只选中了主表节点(表示选择整表)
const hasMainTableOnly = paths.some(
p => Array.isArray(p) && p.length === 1 && p[0] === actualMainTable
);
if (hasMainTableOnly) {
// 返回主表所有字段
return this.getTableFields(actualMainTable).map(f => `${mainTable}.${f.value}`);
}
return paths.flatMap(path => {
if (!Array.isArray(path)) return [];
// 检查是否是组节点(表节点而非字段节点)
const lastPart = path[path.length - 1];
if (allTables.includes(lastPart)) {
// 是表节点,跳过
return [];
}
if (path.length >= 2) {
const table = path[path.length - 2];
const field = path[path.length - 1];
// ymt 数据源需要将 order 转回 order_info
const finalTable = (datasource === 'ymt' && table === 'order') ? 'order_info' : table;
return [`${finalTable}.${field}`];
}
return [];
});
}
/**
* 路径数组去重
* @param {Array<string[]>} paths - 路径数组
* @returns {Array<string[]>} 去重后的路径数组
*/
deduplicatePaths(paths = []) {
const seen = new Set();
const result = [];
for (const path of paths) {
if (!Array.isArray(path)) continue;
const key = path.join('|');
if (seen.has(key)) continue;
seen.add(key);
result.push(path);
}
return result;
}
/**
* 获取主表所有叶子字段的路径
* @param {string} datasource - 数据源
* @returns {Array<string[]>} 路径数组
*/
getMainTableLeafPaths(datasource) {
const mainTable = datasource === 'ymt' ? 'order' : 'order';
return this.getTableFields(mainTable).map(f => [mainTable, f.value]);
}
}
/**
* 树节点工具函数
*/
const TreeUtils = {
/**
* 在树中查找节点
* @param {Array} nodes - 树节点数组
* @param {string} targetKey - 目标节点 key
* @returns {Object|null} 找到的节点
*/
findNode(nodes, targetKey) {
for (const node of nodes) {
if (node.value === targetKey) {
return node;
}
if (node.children) {
const found = TreeUtils.findNode(node.children, targetKey);
if (found) return found;
}
}
return null;
},
/**
* 查找节点的完整路径
* @param {Array} nodes - 树节点数组
* @param {string} targetKey - 目标节点 key
* @param {string[]} currentPath - 当前路径
* @returns {string[]|null} 节点路径
*/
findNodePath(nodes, targetKey, currentPath = []) {
for (const node of nodes) {
const newPath = [...currentPath, node.value];
if (node.value === targetKey) {
return newPath;
}
if (node.children && node.children.length > 0) {
const found = TreeUtils.findNodePath(node.children, targetKey, newPath);
if (found) return found;
}
}
return null;
},
/**
* 根据路径数组查找节点 value
* @param {Array} nodes - 树节点数组
* @param {string[]} path - 路径数组
* @param {number} index - 当前索引
* @returns {string|null} 节点 value
*/
findNodeByPath(nodes, path, index = 0) {
if (!nodes || index >= path.length) return null;
const target = path[index];
for (const node of nodes) {
if (node.value === target) {
if (index === path.length - 1) {
return node.value;
} else if (node.children && node.children.length > 0) {
const found = TreeUtils.findNodeByPath(node.children, path, index + 1);
if (found) return found;
}
}
}
return null;
},
/**
* 判断节点是否为叶子节点
* @param {Object} node - 节点对象
* @returns {boolean} 是否为叶子节点
*/
isLeafNode(node) {
return !node.children || node.children.length === 0;
},
/**
* 将选中的 keys 转换为路径数组
* @param {string[]} checkedKeys - 选中的节点 keys
* @param {Array} treeData - 树数据
* @returns {Array<string[]>} 路径数组(仅叶子节点)
*/
checkedKeysToLeafPaths(checkedKeys, treeData) {
return checkedKeys
.filter(key => {
const node = TreeUtils.findNode(treeData, key);
return node && TreeUtils.isLeafNode(node);
})
.map(key => {
const path = TreeUtils.findNodePath(treeData, key);
return path || [key];
})
.filter(path => path.length >= 2);
},
/**
* 将路径数组转换为节点 keys
* @param {Array<string[]>} paths - 路径数组
* @param {Array} treeData - 树数据
* @returns {string[]} 节点 keys
*/
pathsToNodeKeys(paths, treeData) {
return paths.map(path => {
if (Array.isArray(path)) {
const nodeValue = TreeUtils.findNodeByPath(treeData, path);
if (nodeValue) return nodeValue;
// 找不到时使用路径最后一部分
return path[path.length - 1];
} else if (typeof path === 'string') {
return path;
}
return null;
}).filter(Boolean);
}
};
// 创建全局实例
const fieldsManager = new FieldsManager();
// 导出模块
window.FieldsModule = {
FieldsManager,
TreeUtils,
fieldsManager
};