Files
Planner_Tools/Unity/配置表联动查看器/ConfigLinkViewer/ConfigLinkDatabase.cs
2026-06-02 14:11:28 +08:00

606 lines
19 KiB
C#

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<FieldRelation> relations = new List<FieldRelation>();
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<ConfigLinkTableEntry> tables = new List<ConfigLinkTableEntry>();
}
[Serializable]
public class ConfigLinkTableEntry
{
public string name;
public string displayName;
public string description;
public string fileExtension;
public List<ConfigLinkRelation> relations = new List<ConfigLinkRelation>();
}
[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<ConfigTableInfo> _cachedData;
private static HashSet<string> _existingTableNames;
private static string _excelFolderPath;
private static SettingsData _settings;
private static Dictionary<string, FormatTemplate> _formatTemplateMap;
private static Dictionary<string, (string displayName, string description)> _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<ConfigTableInfo> 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<ConfigTableInfo> LoadConfigFromFile(string path)
{
if (!File.Exists(path))
{
Debug.LogWarning("[ConfigLinkViewer] 配置文件不存在,请点击[生成配置]按钮生成: " + path);
return new List<ConfigTableInfo>();
}
try
{
string json = File.ReadAllText(path, Encoding.UTF8);
var data = JsonUtility.FromJson<ConfigLinkDataFile>(json);
if (data == null || data.tables == null) return new List<ConfigTableInfo>();
var result = new List<ConfigTableInfo>();
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<FieldRelation>()
};
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<ConfigTableInfo>();
}
}
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<ConfigLinkRelation>()
});
}
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<ConfigLinkRelation>()
});
}
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>();
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<string> GetAllTableNames()
{
return GetAllTableInfo().ConvertAll(x => x.tableName);
}
public static List<ReverseRelation> GetReverseRelations(string targetTableName)
{
List<ReverseRelation> result = new List<ReverseRelation>();
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<string> GetRelatedTableNames(string tableName)
{
var tableInfo = GetTableInfo(tableName);
if (tableInfo == null)
return new List<string>();
return tableInfo.relations.Select(r => r.targetTable).Distinct().ToList();
}
public static List<string> GetAllDisplayNames()
{
return GetAllTableInfo().ConvertAll(x => $"{x.displayName} ({x.tableName})");
}
public static List<ConfigTableInfo> 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<SettingsData>(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<string, FormatTemplate>();
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<string, (string, string)> LoadTableInfoDict()
{
if (_tableInfoCache != null)
return _tableInfoCache;
_tableInfoCache = new Dictionary<string, (string, string)>();
if (!File.Exists(TableInfoFilePath))
return _tableInfoCache;
try
{
string json = File.ReadAllText(TableInfoFilePath, Encoding.UTF8);
var raw = JsonUtility.FromJson<TableInfoFile>(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<TableInfoEntry> entries = new List<TableInfoEntry>();
}
[Serializable]
private class TableInfoEntry
{
public string name;
public string displayName;
public string description;
}
}