using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEngine; using UnityEditor; [Serializable] public class ConfigTableInfo { public string tableName; public string displayName; public string description; public List relations = new List(); public bool isExistInProject; public string fileExtension = ".xlsx"; } [Serializable] public class FieldRelation { public string fieldName; public string targetTable; public string targetField; public string relationFormat; public string description; } public class ReverseRelation { public string sourceTable; public string sourceDisplayName; public string fieldName; public string description; public string relationFormat; } [Serializable] public class ConfigLinkDataFile { public List tables = new List(); } [Serializable] public class ConfigLinkTableEntry { public string name; public string displayName; public string description; public string fileExtension; public List relations = new List(); } [Serializable] public class ConfigLinkRelation { public string field; public string target; public string targetField; public string format; public string description; } [Serializable] public class FormatTemplate { public string[] parts; public int minParts; } [Serializable] public class SettingsData { public string configFolderPath = "Resources/Resources_moved/config"; public string excelFolderPath = ""; public FormatTemplatesWrapper formatTemplates = new FormatTemplatesWrapper(); } [Serializable] public class FormatTemplatesWrapper { public FormatTemplate item_id_num = new FormatTemplate { parts = new[] { "类型", "ID", "数量" }, minParts = 3 }; public FormatTemplate type_id_num = new FormatTemplate { parts = new[] { "类型", "ID", "数量" }, minParts = 3 }; public FormatTemplate id_pos_lv = new FormatTemplate { parts = new[] { "ID", "位置", "等级" }, minParts = 3 }; public FormatTemplate id_lv_num = new FormatTemplate { parts = new[] { "ID", "等级", "数量" }, minParts = 3 }; public FormatTemplate id_lv_num_time = new FormatTemplate { parts = new[] { "ID", "等级", "数量", "时间" }, minParts = 4 }; public FormatTemplate buffid_lv = new FormatTemplate { parts = new[] { "BuffID", "等级" }, minParts = 2 }; public FormatTemplate rune_id_num = new FormatTemplate { parts = new[] { "符文ID", "数量" }, minParts = 2 }; public FormatTemplate equip_id_num = new FormatTemplate { parts = new[] { "装备ID", "数量" }, minParts = 2 }; public FormatTemplate hero_id = new FormatTemplate { parts = new[] { "英雄ID" }, minParts = 1 }; public FormatTemplate id_lv_count_delay = new FormatTemplate { parts = new[] { "ID", "等级", "数量", "延迟" }, minParts = 4 }; public FormatTemplate id_lv_num_hp = new FormatTemplate { parts = new[] { "ID", "等级", "数量", "血量%" }, minParts = 4 }; } public static class ConfigLinkDatabase { private static List _cachedData; private static HashSet _existingTableNames; private static string _excelFolderPath; private static SettingsData _settings; private static Dictionary _formatTemplateMap; private static Dictionary _tableInfoCache; private const string CONFIG_FILE_NAME = "ConfigLinkData.json"; private const string SETTINGS_FILE_NAME = "settings.json"; private const string TABLE_INFO_FILE_NAME = "table_info.json"; private const string EXCEL_PATH_KEY = "ConfigLinkViewer.ExcelFolderPath"; private static string ViewerDir { get { return Path.Combine(Application.dataPath, "Editor/ConfigLinkViewer"); } } private static string ConfigFilePath { get { return Path.Combine(ViewerDir, CONFIG_FILE_NAME); } } private static string SettingsFilePath { get { return Path.Combine(ViewerDir, SETTINGS_FILE_NAME); } } private static string TableInfoFilePath { get { return Path.Combine(ViewerDir, TABLE_INFO_FILE_NAME); } } public static string GetConfigFolderPath() { LoadSettings(); return _settings.configFolderPath; } public static void SetConfigFolderPath(string path) { LoadSettings(); _settings.configFolderPath = path; SaveSettings(); ClearCache(); } public static void SetExcelFolderPath(string path) { _excelFolderPath = path; EditorUserSettings.SetConfigValue(EXCEL_PATH_KEY, path); ClearCache(); } public static string GetExcelFolderPath() { if (string.IsNullOrEmpty(_excelFolderPath)) { _excelFolderPath = EditorUserSettings.GetConfigValue(EXCEL_PATH_KEY) ?? ""; } return _excelFolderPath; } public static string GetExcelFilePath(string tableName) { string folderPath = GetExcelFolderPath(); if (string.IsNullOrEmpty(folderPath)) return null; var tableInfo = GetTableInfo(tableName); string extension = tableInfo?.fileExtension ?? ".xlsx"; string excelPath = Path.Combine(folderPath, $"{tableName}{extension}"); return File.Exists(excelPath) ? excelPath : null; } public static bool IsExcelFolderSet() { return !string.IsNullOrEmpty(GetExcelFolderPath()); } public static bool IsExcelFileExists(string tableName) { return GetExcelFilePath(tableName) != null; } public static List GetAllTableInfo() { if (_cachedData != null) return _cachedData; _cachedData = LoadConfigFromFile(ConfigFilePath); MarkExistingTables(); return _cachedData; } public static bool IsConfigLoaded() { return _cachedData != null && _cachedData.Count > 0; } public static bool HasConfigFile() { return File.Exists(ConfigFilePath); } private static List LoadConfigFromFile(string path) { if (!File.Exists(path)) { Debug.LogWarning("[ConfigLinkViewer] 配置文件不存在,请点击[生成配置]按钮生成: " + path); return new List(); } try { string json = File.ReadAllText(path, Encoding.UTF8); var data = JsonUtility.FromJson(json); if (data == null || data.tables == null) return new List(); var result = new List(); foreach (var entry in data.tables) { var info = new ConfigTableInfo { tableName = entry.name, displayName = string.IsNullOrEmpty(entry.displayName) ? entry.name : entry.displayName, description = entry.description ?? "", fileExtension = string.IsNullOrEmpty(entry.fileExtension) ? ".xlsx" : entry.fileExtension, relations = new List() }; if (entry.relations != null) { foreach (var rel in entry.relations) { info.relations.Add(new FieldRelation { fieldName = rel.field, targetTable = rel.target, targetField = rel.targetField ?? "id", relationFormat = rel.format ?? "", description = rel.description ?? "" }); } } result.Add(info); } return result; } catch (Exception e) { Debug.LogError($"[ConfigLinkViewer] 加载配置文件失败: {e.Message}"); return new List(); } } public static void GenerateConfigFromExcelFolder() { string excelFolder = GetExcelFolderPath(); if (string.IsNullOrEmpty(excelFolder) || !Directory.Exists(excelFolder)) { EditorUtility.DisplayDialog("提示", "请先设置Excel文件夹路径", "确定"); return; } var files = Directory.GetFiles(excelFolder, "*.xlsx", SearchOption.AllDirectories) .Where(f => !Path.GetFileName(f).StartsWith("~$")) .ToArray(); if (files.Length == 0) { EditorUtility.DisplayDialog("提示", "Excel文件夹中未找到.xlsx文件", "确定"); return; } var data = new ConfigLinkDataFile(); var tableInfoDict = LoadTableInfoDict(); foreach (var file in files) { string name = Path.GetFileNameWithoutExtension(file); string displayName = name; string description = ""; if (tableInfoDict.TryGetValue(name.ToLower(), out var ti)) { displayName = ti.Item1; description = ti.Item2; } data.tables.Add(new ConfigLinkTableEntry { name = name, displayName = displayName, description = description, fileExtension = ".xlsx", relations = new List() }); } var txtFiles = Directory.GetFiles(excelFolder, "*.txt", SearchOption.AllDirectories); foreach (var file in txtFiles) { string name = Path.GetFileNameWithoutExtension(file); string displayName = name; string description = ""; if (tableInfoDict.TryGetValue(name.ToLower(), out var ti)) { displayName = ti.Item1; description = ti.Item2; } data.tables.Add(new ConfigLinkTableEntry { name = name, displayName = displayName, description = description, fileExtension = ".txt", relations = new List() }); } string json = JsonUtility.ToJson(data, true); File.WriteAllText(ConfigFilePath, json, Encoding.UTF8); ClearCache(); AssetDatabase.Refresh(); EditorUtility.DisplayDialog("生成完成", $"已扫描 {files.Length} 个文件,生成配置文件\n\n" + $"配置文件: {ConfigFilePath}\n\n" + $"提示: 可将该文件交给 AI 补全中文名称和表间关联关系", "确定"); } private static void MarkExistingTables() { _existingTableNames = new HashSet(); string configPath = Path.Combine(Application.dataPath, GetConfigFolderPath()); if (Directory.Exists(configPath)) { string[] jsonFiles = Directory.GetFiles(configPath, "*.json"); foreach (string file in jsonFiles) { string tableName = Path.GetFileNameWithoutExtension(file); _existingTableNames.Add(tableName.ToLower()); } } if (_cachedData == null) return; foreach (var table in _cachedData) { bool existInConfig = _existingTableNames.Contains(table.tableName.ToLower()); if (!existInConfig && table.fileExtension == ".txt") { string excelFolderPath = GetExcelFolderPath(); if (!string.IsNullOrEmpty(excelFolderPath)) { string txtPath = Path.Combine(excelFolderPath, $"{table.tableName}{table.fileExtension}"); existInConfig = File.Exists(txtPath); } } table.isExistInProject = existInConfig; } } public static bool IsTableExistInConfigFolder(string tableName) { string configPath = Path.Combine(Application.dataPath, GetConfigFolderPath()); if (!Directory.Exists(configPath)) return false; var tableInfo = GetTableInfo(tableName); string extension = tableInfo?.fileExtension ?? ".xlsx"; string jsonPath = Path.Combine(configPath, $"{tableName}.json"); if (extension == ".txt") { string excelFolderPath = GetExcelFolderPath(); if (!string.IsNullOrEmpty(excelFolderPath)) { string txtPath = Path.Combine(excelFolderPath, $"{tableName}{extension}"); if (File.Exists(txtPath)) return true; } } return File.Exists(jsonPath); } public static ConfigTableInfo GetTableInfo(string tableName) { return GetAllTableInfo().Find(x => x.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase)); } public static List GetAllTableNames() { return GetAllTableInfo().ConvertAll(x => x.tableName); } public static List GetReverseRelations(string targetTableName) { List result = new List(); foreach (var table in GetAllTableInfo()) { foreach (var relation in table.relations) { if (relation.targetTable.Equals(targetTableName, StringComparison.OrdinalIgnoreCase)) { result.Add(new ReverseRelation { sourceTable = table.tableName, sourceDisplayName = table.displayName, fieldName = relation.fieldName, description = relation.description, relationFormat = relation.relationFormat }); } } } return result; } public static string FormatRelationValue(string format, string value) { if (string.IsNullOrEmpty(format) || string.IsNullOrEmpty(value)) return value; string key = format.ToLower(); if (key == "表名") return $"表名: {value}"; LoadFormatTemplates(); if (_formatTemplateMap.TryGetValue(key, out var template)) { string[] parts = value.Split('_'); if (parts.Length >= template.minParts) { var sb = new StringBuilder(); for (int i = 0; i < template.parts.Length && i < parts.Length; i++) { if (i > 0) sb.Append(", "); sb.Append($"{template.parts[i]}:{parts[i]}"); } return sb.ToString(); } } return value; } public static List GetRelatedTableNames(string tableName) { var tableInfo = GetTableInfo(tableName); if (tableInfo == null) return new List(); return tableInfo.relations.Select(r => r.targetTable).Distinct().ToList(); } public static List GetAllDisplayNames() { return GetAllTableInfo().ConvertAll(x => $"{x.displayName} ({x.tableName})"); } public static List GetExistingTables() { return GetAllTableInfo().Where(x => x.isExistInProject).ToList(); } public static bool IsTableExist(string tableName) { if (_existingTableNames == null) { GetAllTableInfo(); } return _existingTableNames.Contains(tableName.ToLower()); } public static void ClearCache() { _cachedData = null; _existingTableNames = null; _settings = null; _formatTemplateMap = null; _tableInfoCache = null; } private static void LoadSettings() { if (_settings != null) return; if (File.Exists(SettingsFilePath)) { try { string json = File.ReadAllText(SettingsFilePath, Encoding.UTF8); _settings = JsonUtility.FromJson(json); } catch (Exception e) { Debug.LogWarning($"[ConfigLinkViewer] 加载 settings.json 失败: {e.Message}"); _settings = new SettingsData(); } } else { _settings = new SettingsData(); } } public static void SaveSettings() { try { string json = JsonUtility.ToJson(_settings, true); File.WriteAllText(SettingsFilePath, json, Encoding.UTF8); } catch (Exception e) { Debug.LogError($"[ConfigLinkViewer] 保存 settings.json 失败: {e.Message}"); } } private static void LoadFormatTemplates() { if (_formatTemplateMap != null) return; _formatTemplateMap = new Dictionary(); LoadSettings(); if (_settings.formatTemplates != null) { var wrapperType = typeof(FormatTemplatesWrapper); var fields = wrapperType.GetFields(); foreach (var field in fields) { if (field.FieldType == typeof(FormatTemplate)) { var template = field.GetValue(_settings.formatTemplates) as FormatTemplate; if (template != null) { _formatTemplateMap[field.Name] = template; } } } } } private static Dictionary LoadTableInfoDict() { if (_tableInfoCache != null) return _tableInfoCache; _tableInfoCache = new Dictionary(); if (!File.Exists(TableInfoFilePath)) return _tableInfoCache; try { string json = File.ReadAllText(TableInfoFilePath, Encoding.UTF8); var raw = JsonUtility.FromJson(json); if (raw?.entries != null) { foreach (var entry in raw.entries) { if (!string.IsNullOrEmpty(entry.name)) { _tableInfoCache[entry.name.ToLower()] = ( entry.displayName ?? entry.name, entry.description ?? "" ); } } } } catch (Exception e) { Debug.LogWarning($"[ConfigLinkViewer] 加载 table_info.json 失败: {e.Message}"); } return _tableInfoCache; } [Serializable] private class TableInfoFile { public List entries = new List(); } [Serializable] private class TableInfoEntry { public string name; public string displayName; public string description; } }