648 lines
20 KiB
C#
648 lines
20 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有缺少中文配置的表(displayName == name 或 description 为空)
|
||
/// </summary>
|
||
public static List<ConfigTableInfo> GetTablesMissingChineseConfig()
|
||
{
|
||
return GetAllTableInfo().Where(table =>
|
||
string.IsNullOrEmpty(table.displayName) ||
|
||
table.displayName.Equals(table.tableName, StringComparison.OrdinalIgnoreCase) ||
|
||
string.IsNullOrEmpty(table.description)
|
||
).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查是否存在缺少中文配置的表
|
||
/// </summary>
|
||
public static bool HasTablesMissingChineseConfig()
|
||
{
|
||
return GetTablesMissingChineseConfig().Count > 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 输出缺少中文配置的表信息到控制台
|
||
/// </summary>
|
||
public static void LogMissingChineseConfig()
|
||
{
|
||
var missingTables = GetTablesMissingChineseConfig();
|
||
if (missingTables.Count == 0)
|
||
{
|
||
Debug.Log("[ConfigLinkViewer] 所有表均已配置中文名称和描述");
|
||
return;
|
||
}
|
||
|
||
StringBuilder sb = new StringBuilder();
|
||
sb.AppendLine($"[ConfigLinkViewer] 发现 {missingTables.Count} 个表缺少中文配置:");
|
||
foreach (var table in missingTables)
|
||
{
|
||
sb.AppendLine($" - {table.tableName}: displayName='{table.displayName}', description='{table.description}'");
|
||
}
|
||
sb.AppendLine("建议使用智能体辅助补充中文配置。");
|
||
Debug.Log(sb.ToString());
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|