Files
ShallowT1Dream 3071ec4b8f 提交修改
2026-06-04 14:28:46 +08:00

648 lines
20 KiB
C#
Raw Permalink 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.
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;
}
}