/** * 字段处理模块 - 字段路径转换、树结构构建 * @module fields */ ;(function() { 'use strict'; // 依赖: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 - 子节点 * @param {string} parentPrefix - 父级前缀,用于构建唯一value * @returns {Object} 树节点 */ buildTreeNode(table, children = [], parentPrefix = '') { // 为子表的字段节点添加表名前缀,确保 value 唯一 const childrenWithPrefix = children.map(child => { if (child.children) { // 是子表节点,递归处理 return child; } else { // 是字段节点,添加表名前缀 return { value: `${table}.${child.value}`, label: child.label }; } }); return { value: parentPrefix ? `${parentPrefix}.${table}` : table, label: this.getTableLabel(table), children: childrenWithPrefix }; } /** * 构建易码通订单子节点 * @param {number} orderType - 订单类型 * @returns {Array} 子节点数组 */ buildYmtOrderChildren(orderType) { const orderFields = this.getTableFields('order'); // 使用 table.field 格式作为唯一 value const children = orderFields.map(f => ({ value: `order.${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 { // 未指定类型或全部类型(orderType = 0 或其他) 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'))); } // 调试:检查是否有重复的 value const valueMap = new Map(); const checkDuplicates = (nodes, prefix = '') => { nodes.forEach(node => { const fullPath = prefix ? `${prefix}/${node.value}` : node.value; if (valueMap.has(node.value)) { console.warn(`[树节点重复] 检测到重复的节点value: "${node.value}", 路径: ${fullPath}, 之前出现在: ${valueMap.get(node.value)}`); } else { valueMap.set(node.value, fullPath); } if (node.children && node.children.length > 0) { checkDuplicates(node.children, fullPath); } }); }; checkDuplicates(children, 'order'); return children; } /** * 构建营销系统订单子节点 * @param {number} orderType - 订单类型 * @returns {Array} 子节点数组 */ buildMarketingOrderChildren(orderType) { const orderFields = this.getTableFields('order'); // 使用 table.field 格式作为唯一 value const children = orderFields.map(f => ({ value: `order.${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: `plan.${f.value}`, label: f.label })), this.buildTreeNode('key_batch', [ ...this.getTableFields('key_batch').map(f => ({ value: `key_batch.${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: `order_voucher.${f.value}`, label: f.label })), this.buildTreeNode('voucher', [ ...this.getTableFields('voucher').map(f => ({ value: `voucher.${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} 路径数组,格式为 [['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]; } }; const paths = fields .map(toPath) .filter(path => Array.isArray(path) && path.length >= 2); // 添加调试日志:验证字段存在性 const validPaths = paths.filter(path => { const table = path[path.length - 2]; const field = path[path.length - 1]; const exists = this.hasField(table, field); if (!exists) { console.warn(`[字段回显] 字段不存在于元数据中: ${table}.${field}, 路径: ${JSON.stringify(path)}`); } return exists; }); console.log(`[字段回显] 共 ${fields.length} 个字段, 转换为 ${paths.length} 个路径, 验证通过 ${validPaths.length} 个`); return validPaths; } /** * 将路径数组格式转换为 table.field 格式 * @param {Array} 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} paths - 路径数组 * @returns {Array} 去重后的路径数组 */ 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} 路径数组 */ 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 - 路径数组,如 ['order', 'status'] 或 ['order', 'merchant', 'name'] * @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) { // 节点value可能是 'order', 'order.status', 'merchant' 等格式 const nodeTableOrField = node.value.split('.').pop(); // 取最后一部分 const nodeValue = node.value; // 检查是否匹配当前路径段 const isMatch = nodeValue === target || nodeTableOrField === target || node.value.endsWith(`.${target}`); if (isMatch) { if (index === path.length - 1) { // 到达路径末尾,返回节点value return node.value; } else if (node.children && node.children.length > 0) { // 继续在子节点中查找 const found = TreeUtils.findNodeByPath(node.children, path, index + 1); if (found) return found; } } } // 特殊处理:如果路径是 ['order', 'status'],直接尝试查找 'order.status' if (index === 0 && path.length === 2) { const combinedValue = `${path[0]}.${path[1]}`; for (const node of nodes) { if (node.value === combinedValue) { return node.value; } if (node.children) { const found = TreeUtils.findNode(node.children, combinedValue); if (found) return found.value; } } } return null; }, /** * 判断节点是否为叶子节点 * @param {Object} node - 节点对象 * @returns {boolean} 是否为叶子节点 */ isLeafNode(node) { return !node.children || node.children.length === 0; }, /** * 将选中的 keys 转换为路径数组 * @param {string[]} checkedKeys - 选中的节点 keys,格式如 ['order.status', 'merchant.name'] * @param {Array} treeData - 树数据 * @returns {Array} 路径数组(仅叶子节点),格式如 [['order', 'status'], ['order', 'merchant', 'name']] */ checkedKeysToLeafPaths(checkedKeys, treeData) { return checkedKeys .filter(key => { const node = TreeUtils.findNode(treeData, key); return node && TreeUtils.isLeafNode(node); }) .map(key => { // key 格式可能是 'order.status' 或 'merchant.name' // 需要根据树结构找到完整路径 const path = TreeUtils.findNodePath(treeData, key); if (path && path.length >= 2) { // path 是节点value的数组,需要转换为实际的表/字段路径 // 例如:['order', 'order.status'] -> ['order', 'status'] // ['order', 'merchant', 'merchant.name'] -> ['order', 'merchant', 'name'] return path.map((segment, index) => { if (segment.includes('.')) { // 是字段节点,取字段名部分 return segment.split('.').pop(); } return segment; }); } // 回退方案:直接使用 key return key.includes('.') ? key.split('.') : [key]; }) .filter(path => path.length >= 2); }, /** * 将路径数组转换为节点 keys * @param {Array} paths - 路径数组 * @param {Array} treeData - 树数据 * @returns {string[]} 节点 keys */ pathsToNodeKeys(paths, treeData) { const keys = paths.map(path => { if (Array.isArray(path)) { const nodeValue = TreeUtils.findNodeByPath(treeData, path); if (nodeValue) { return nodeValue; } else { // 找不到时使用路径最后一部分 const fallbackKey = path[path.length - 1]; console.warn(`[树节点转换] 路径未找到对应节点: ${JSON.stringify(path)}, 使用回退key: ${fallbackKey}`); return fallbackKey; } } else if (typeof path === 'string') { return path; } return null; }).filter(Boolean); console.log(`[树节点转换] 共转换 ${paths.length} 个路径为 ${keys.length} 个节点key`); console.log(`[树节点转换] 节点keys:`, keys); return keys; } }; // 创建全局实例 const fieldsManager = new FieldsManager(); // 导出模块 window.FieldsModule = { FieldsManager, TreeUtils, fieldsManager }; })();