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

937 lines
36 KiB
C#

using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.IO.Compression;
using System.Xml.Linq;
public static class ConfigLinkViewerCallbacks
{
public static Action ExportConfigCallback;
public static Func<string> FallbackExcelPathCallback;
}
[InitializeOnLoad]
public static class ConfigLinkViewerAutoSetup
{
static ConfigLinkViewerAutoSetup()
{
TryRegisterExportConfig();
TryRegisterFallbackExcelPath();
}
private static void TryRegisterExportConfig()
{
if (ConfigLinkViewerCallbacks.ExportConfigCallback != null) return;
try
{
var type = FindType("MFrame.ConfigDeal");
if (type == null) return;
var method = type.GetMethod("ExportConfig",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (method == null) return;
ConfigLinkViewerCallbacks.ExportConfigCallback =
(Action)Delegate.CreateDelegate(typeof(Action), method);
}
catch { }
}
private static void TryRegisterFallbackExcelPath()
{
if (ConfigLinkViewerCallbacks.FallbackExcelPathCallback != null) return;
try
{
var pathConfType = FindType("MFrame.PathConf");
var editorCfType = FindType("MFrame.ConfigEditorCf");
if (pathConfType == null || editorCfType == null) return;
var rootField = pathConfType.GetField("project_root",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (rootField == null) return;
var insProp = editorCfType.GetProperty("ins",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (insProp == null) return;
var ins = insProp.GetValue(null);
if (ins == null) return;
var excelField = ins.GetType().GetField("excel_path",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (excelField == null) return;
ConfigLinkViewerCallbacks.FallbackExcelPathCallback = () =>
{
var root = rootField.GetValue(null) as string;
var inst = insProp.GetValue(null);
var ep = inst != null ? excelField.GetValue(inst) as string : null;
return root + ep;
};
}
catch { }
}
private static Type FindType(string fullName)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
var t = asm.GetType(fullName);
if (t != null) return t;
}
return null;
}
}
public class ConfigLinkViewerWindow : EditorWindow
{
private enum Tab { View, Config }
private Tab currentTab = Tab.View;
private string selectedTableName;
private Dictionary<string, bool> expandedRelations = new Dictionary<string, bool>();
private Vector2 relationScrollPos;
private Vector2 detailScrollPos;
private Vector2 configScrollPos;
private string searchFilter = "";
private bool showOnlyExisting = true;
private bool showReverseRelations = false;
[MenuItem("Tools/配置表联动查看器")]
public static void ShowWindow()
{
var window = GetWindow<ConfigLinkViewerWindow>("配置表联动");
window.minSize = new Vector2(900, 600);
window.position = new Rect(100, 100, 900, 600);
}
private void OnGUI()
{
DrawTabBar();
EditorGUILayout.Space();
float windowWidth = position.width;
float windowHeight = position.height;
if (currentTab == Tab.View)
{
DrawViewTab(windowWidth, windowHeight);
}
else
{
DrawConfigTab(windowWidth, windowHeight);
DrawLuoTianyiOverlay(windowWidth, windowHeight);
}
}
private void DrawTabBar()
{
EditorGUILayout.BeginHorizontal("Toolbar");
GUILayout.FlexibleSpace();
GUIStyle tabLeft, tabRight;
if (currentTab == Tab.View)
{
tabLeft = new GUIStyle("ToolbarButton") { fontStyle = FontStyle.Bold };
tabRight = new GUIStyle("ToolbarButton");
}
else
{
tabLeft = new GUIStyle("ToolbarButton");
tabRight = new GUIStyle("ToolbarButton") { fontStyle = FontStyle.Bold };
}
if (GUILayout.Button("查看", tabLeft, GUILayout.Width(60)))
{
currentTab = Tab.View;
}
if (GUILayout.Button("配置", tabRight, GUILayout.Width(60)))
{
currentTab = Tab.Config;
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
private void DrawViewTab(float windowWidth, float windowHeight)
{
DrawViewHeader();
EditorGUILayout.Space();
DrawTableSelector(windowWidth, windowHeight);
}
private void DrawViewHeader()
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("配置表联动关系查看器", new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 }, GUILayout.ExpandWidth(true));
if (GUILayout.Button("清理Excel空行", GUILayout.Height(22), GUILayout.Width(100)))
{
EditorApplication.delayCall += CleanExcelEmptyRows;
}
if (GUILayout.Button("导出配置", GUILayout.Height(22), GUILayout.Width(80)))
{
EditorApplication.delayCall += () =>
{
if (ConfigLinkViewerCallbacks.ExportConfigCallback != null)
{
ConfigLinkViewerCallbacks.ExportConfigCallback();
}
else
{
EditorUtility.DisplayDialog("提示",
"未注册导出配置回调。\n请在项目初始化代码中设置 ConfigLinkViewerCallbacks.ExportConfigCallback。",
"确定");
}
};
}
if (GUILayout.Button("刷新", GUILayout.Height(22), GUILayout.Width(60)))
{
ConfigLinkDatabase.ClearCache();
}
EditorGUILayout.EndHorizontal();
string configStatus = ConfigLinkDatabase.HasConfigFile()
? "<color=green>已加载 (ConfigLinkData.json)</color>"
: "<color=yellow>未找到配置文件,请切换到[配置]标签页生成</color>";
var statusStyle = new GUIStyle(EditorStyles.miniLabel) { richText = true };
EditorGUILayout.LabelField($"配置状态: {configStatus}", statusStyle);
EditorGUILayout.EndVertical();
}
private void DrawConfigTab(float windowWidth, float windowHeight)
{
configScrollPos = EditorGUILayout.BeginScrollView(configScrollPos);
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("项目配置", new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 });
EditorGUILayout.Space();
DrawProjectSettings();
EditorGUILayout.Space();
DrawExcelPathSettings(windowWidth);
EditorGUILayout.Space();
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("配置数据生成", EditorStyles.boldLabel);
EditorGUILayout.LabelField("首次使用或跨项目时,需要先生成配置数据。", EditorStyles.miniLabel);
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("生成配置", GUILayout.Height(30)))
{
EditorApplication.delayCall += () =>
{
ConfigLinkDatabase.GenerateConfigFromExcelFolder();
};
}
if (GUILayout.Button("补全配置", GUILayout.Height(30)))
{
EditorApplication.delayCall += RunAIConfigScript;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.LabelField("生成配置:从 Excel 文件夹扫描生成 ConfigLinkData.json 骨架", EditorStyles.miniLabel);
EditorGUILayout.LabelField("补全配置:运行 Python 脚本自动分析引用关系,补全字段关联", EditorStyles.miniLabel);
EditorGUILayout.EndVertical();
EditorGUILayout.EndVertical();
EditorGUILayout.EndScrollView();
}
private GUIStyle _artStyle;
private GUIStyle GetArtStyle(int fontSize)
{
if (_artStyle == null || _artStyle.fontSize != fontSize)
{
_artStyle = new GUIStyle(EditorStyles.label)
{
alignment = TextAnchor.MiddleCenter,
fontSize = fontSize,
richText = true,
wordWrap = false,
clipping = TextClipping.Overflow,
normal = { textColor = Color.white },
};
var font = Font.CreateDynamicFontFromOSFont("Consolas", fontSize);
if (font != null)
_artStyle.font = font;
}
return _artStyle;
}
private void DrawLuoTianyiOverlay(float windowWidth, float windowHeight)
{
float scale = Mathf.Clamp(Mathf.Min(windowWidth, windowHeight) / 600f, 0.35f, 0.7f);
int fontSize = Mathf.RoundToInt(9 * scale);
float lineHeight = fontSize * 1.1f;
string[] lines = {
" _________________",
" ____/:::::::::::::::::\\_____",
" __/::::::::::::::::::::::::::::\\___",
" _/:::::::::::::::::::::::::::::::::::\\__",
" _/::::::::::::::::::::::::::::::::::::::::\\_",
" /::::::::::::::::::::::::::::::::::::::::::::\\",
" |::::::::::::::::::::::::::::::::::::::::::::::\\",
" /::::::::::::::::::::::::::::::::::::::::::::::::\\",
" |:::/.:::::::;:::::::::::::::::::::::::::::::::::::|",
" /:::/.:::::::/..:::::::::::::::::::::::::::::::::::::\\",
" |:::|.::::::;/.::::::::::::::::::::::::::::::::::::::::|",
" |::/.::::::/..:::::::;;'.::::::::::::::::::::::::::::::|",
" |:|.::::/./.::::::;;/..:::::::::::::::::::::::::::::::::|",
" `:|.:::|.|.:::::;/..;;;;;;-'.:;;;-':::::::::::::::::::::|",
" \\|.:::|.|.:::;/.;;/ -..::'''...:::::::::::::::::::::::|",
" \\;;::|.|.::/.;/--__ |::::::::::::::::::::::::::::|",
" \\;;\\\\::/|/ =-__ --_ /::::::::::::::::::::::::::::::",
" \\/ /| -._ |.::::::::::::::::::::::::::::::",
" _.' /// /- ||::::::::::::::::::::::::::::::",
" _.-' //' ||::::::::::::::::::::::::::::::",
" | - `|::::::::::::::::::::::::::::::",
" \\ \\:::::::::::::::::::::::::::::",
" | \\:::::::::::::::::::::",
" / __/:::::::::::::::::::::::::::",
" \\ __/::;::;;:::::: ::::::::::",
" |` /;;;;/::| \\:::: :___: :::::::::",
" \\ |'_,::::/ \\ |:::: .| |`. :::::::::",
" / _/::::::/ / /:::: | \\.' | ::. .::::",
" | /.::;;:-'_)/_/::::: `._| .' ::..::::",
" ----.__ | |.::| \\___/::::::: ::..::::",
" :::::::`----\\_____ \\:::\\.-'::::::::::: ___:___ ::..::::",
" ;;;;;:::::::::::::`------ \\:::::::::::::::: ___|___ ::..::::",
" `-------:::::::\\ /:::::::::::::::: __| ::..::::",
" ___.--------'::::::::\\ |::::::::::::::::: | |-. ::..::::",
" :::::;;;:--:::::::::::| /::::::::::::::::: --' ' ::..::::",
" ----' _,-:.:::::::::::\\ |.::::::::::::::::: ::..::::",
" __/.::::::::::::::::| |.::::::::::::::::: : : ::..::::",
" __/.:::;;::::::::;/.:::| |.::::::::::::::::: | : ::. .::::",
" /.::::;/ /.:::::;/ |.::::| \\_.::::::::::::::: | . | :::::::::",
" :::::/ /.:::::/ /.:::::| \\__.:::::::::::: `.' ' :::::::::",
" ::::| |.:::::/ /.:::::.| \\,:::::::::::: ::::::::::",
" ::::| |.::::| |.:::::/| __/::::::::::::::::::::::::::::",
" \\.:::\\ \\.:::| |.::::||| __.--:::::::::::::::::::::::::::::",
" \\.:::\\_ \\.:::\\ \\.:::'/.:::::::::::::::::::::::::::::::::",
" \\.::::\\ \\.:::\\ \\.::::::::::::::::::::::::::::::::::::::::::"
};
float boxW = Mathf.Clamp(windowWidth * 0.45f, 300f, 500f);
float boxH = lines.Length * lineHeight + 16;
float boxX = windowWidth - boxW - 10;
float boxY = windowHeight - boxH - 10;
var bgColor = new Color(0.12f, 0.12f, 0.16f, 0.9f);
EditorGUI.DrawRect(new Rect(boxX, boxY, boxW, boxH), bgColor);
var borderColor = new Color(0f, 0.75f, 1f, 0.4f);
EditorGUI.DrawRect(new Rect(boxX, boxY, boxW, 1), borderColor);
EditorGUI.DrawRect(new Rect(boxX, boxY + boxH - 1, boxW, 1), borderColor);
EditorGUI.DrawRect(new Rect(boxX, boxY, 1, boxH), borderColor);
EditorGUI.DrawRect(new Rect(boxX + boxW - 1, boxY, 1, boxH), borderColor);
GUIStyle artStyle = new GUIStyle(EditorStyles.label)
{
alignment = TextAnchor.MiddleCenter,
fontSize = fontSize,
richText = false,
wordWrap = false,
clipping = TextClipping.Overflow,
};
artStyle.normal.textColor = new Color(0f, 1f, 1f);
float y = boxY + 8;
for (int i = 0; i < lines.Length; i++)
{
var lineRect = new Rect(boxX + 4, y, boxW - 8, lineHeight);
GUI.Label(lineRect, lines[i], artStyle);
y += lineHeight;
}
}
private void DrawProjectSettings()
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("路径设置", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
string configPath = ConfigLinkDatabase.GetConfigFolderPath();
EditorGUILayout.LabelField("Config目录:", GUILayout.Width(80));
EditorGUILayout.LabelField(configPath, EditorStyles.textField, GUILayout.ExpandWidth(true));
if (GUILayout.Button("选择", GUILayout.Width(60)))
{
string selected = EditorUtility.OpenFolderPanel("选择配置导出目录", Application.dataPath, "");
if (!string.IsNullOrEmpty(selected))
{
ConfigLinkDatabase.SetConfigFolderPath(selected);
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void DrawTableSelector(float windowWidth, float windowHeight)
{
EditorGUILayout.BeginVertical("box", GUILayout.Height(windowHeight - 60));
EditorGUILayout.BeginHorizontal();
searchFilter = EditorGUILayout.TextField("搜索:", searchFilter, EditorStyles.toolbarSearchField);
showOnlyExisting = EditorGUILayout.ToggleLeft("只显示当前项目存在的表", showOnlyExisting);
EditorGUILayout.EndHorizontal();
var allTables = showOnlyExisting ? ConfigLinkDatabase.GetExistingTables() : ConfigLinkDatabase.GetAllTableInfo();
var filteredTables = string.IsNullOrEmpty(searchFilter)
? allTables
: allTables.Where(t => t.displayName.Contains(searchFilter) ||
t.tableName.Contains(searchFilter) ||
t.description.Contains(searchFilter)).ToList();
var displayNames = filteredTables.Select(x =>
{
string existMark = showOnlyExisting ? "" : (x.isExistInProject ? "[存在]" : "[缺失]");
return $"{x.displayName} ({x.tableName}) {existMark}";
}).ToArray();
var tableKeys = filteredTables.Select(x => x.tableName).ToArray();
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal(GUILayout.ExpandHeight(true));
float leftPanelWidth = Mathf.Clamp(windowWidth * 0.3f, 180f, 300f);
float rightPanelWidth = windowWidth - leftPanelWidth - 20f;
EditorGUILayout.BeginVertical(GUILayout.Width(leftPanelWidth), GUILayout.ExpandHeight(true));
EditorGUILayout.LabelField($"配置表列表 ({filteredTables.Count}):", EditorStyles.miniBoldLabel);
relationScrollPos = EditorGUILayout.BeginScrollView(relationScrollPos, false, true, GUILayout.ExpandHeight(true));
GUIStyle listButtonStyle = new GUIStyle(EditorStyles.label) { wordWrap = false, stretchWidth = true };
for (int i = 0; i < displayNames.Length; i++)
{
bool isSelected = tableKeys[i] == selectedTableName;
GUIStyle buttonStyle = isSelected ? "Button" : listButtonStyle;
if (GUILayout.Button(displayNames[i], buttonStyle))
{
if (selectedTableName != tableKeys[i])
{
selectedTableName = tableKeys[i];
expandedRelations.Clear();
}
}
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(GUILayout.Width(rightPanelWidth), GUILayout.ExpandHeight(true));
EditorGUILayout.LabelField("当前表详情:", EditorStyles.miniBoldLabel);
detailScrollPos = EditorGUILayout.BeginScrollView(detailScrollPos, false, true, GUILayout.ExpandHeight(true));
DrawCurrentTableInfo(rightPanelWidth);
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void DrawCurrentTableInfo(float panelWidth)
{
if (string.IsNullOrEmpty(selectedTableName))
{
EditorGUILayout.LabelField("请从左侧选择一个配置表", EditorStyles.centeredGreyMiniLabel);
return;
}
var tableInfo = ConfigLinkDatabase.GetTableInfo(selectedTableName);
if (tableInfo == null)
{
EditorGUILayout.LabelField("未找到该表的配置信息", EditorStyles.helpBox);
return;
}
EditorGUILayout.LabelField($"当前表: {tableInfo.displayName}", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"表名: {tableInfo.tableName}", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"描述: {tableInfo.description}", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"状态: {(tableInfo.isExistInProject ? "" : "")}",
tableInfo.isExistInProject ? EditorStyles.miniLabel : EditorStyles.helpBox);
EditorGUILayout.Space();
DrawOpenExcelButtons(panelWidth);
EditorGUILayout.Space();
showReverseRelations = EditorGUILayout.ToggleLeft("显示反向引用", showReverseRelations);
EditorGUILayout.Space();
if (showReverseRelations)
{
DrawReverseRelations(panelWidth);
}
else
{
if (tableInfo.relations.Count == 0)
{
EditorGUILayout.LabelField("该表暂无联动关系", EditorStyles.miniLabel);
}
else
{
EditorGUILayout.LabelField($"联动关系 ({tableInfo.relations.Count}):", EditorStyles.boldLabel);
foreach (var relation in tableInfo.relations)
{
DrawRelation(relation, panelWidth);
}
}
}
}
private void DrawOpenExcelButtons(float panelWidth)
{
EditorGUILayout.BeginHorizontal();
string excelPath = ConfigLinkDatabase.GetExcelFilePath(selectedTableName);
bool canOpen = !string.IsNullOrEmpty(excelPath);
if (canOpen)
{
if (GUILayout.Button("打开表格", GUILayout.Height(30), GUILayout.Width(panelWidth / 2 - 5)))
{
OpenExcelFile(selectedTableName);
}
}
else
{
EditorGUILayout.LabelField("未找到Excel文件", EditorStyles.helpBox, GUILayout.Height(30), GUILayout.Width(panelWidth / 2 - 5));
}
if (GUILayout.Button("批量打开关联表", GUILayout.Height(30), GUILayout.Width(panelWidth / 2 - 5)))
{
OpenRelatedTables(selectedTableName);
}
EditorGUILayout.EndHorizontal();
}
private void OpenExcelFile(string tableName)
{
string excelPath = ConfigLinkDatabase.GetExcelFilePath(tableName);
if (!string.IsNullOrEmpty(excelPath) && File.Exists(excelPath))
{
System.Diagnostics.Process.Start(excelPath);
}
}
private void OpenRelatedTables(string tableName)
{
var relatedTables = ConfigLinkDatabase.GetRelatedTableNames(tableName);
foreach (var relatedTable in relatedTables)
{
OpenExcelFile(relatedTable);
}
}
private void DrawReverseRelations(float panelWidth)
{
var reverseRels = ConfigLinkDatabase.GetReverseRelations(selectedTableName);
if (reverseRels.Count == 0)
{
EditorGUILayout.LabelField("暂无其他表引用该表", EditorStyles.miniLabel);
}
else
{
EditorGUILayout.LabelField($"反向引用 ({reverseRels.Count}):", EditorStyles.boldLabel);
foreach (var rel in reverseRels)
{
bool sourceExist = ConfigLinkDatabase.IsTableExist(rel.sourceTable);
EditorGUILayout.BeginVertical("frameBox", GUILayout.Width(panelWidth - 10));
GUIStyle headerStyle = new GUIStyle(sourceExist ? EditorStyles.boldLabel : EditorStyles.helpBox) { wordWrap = false };
EditorGUILayout.LabelField($"{rel.sourceDisplayName} ({rel.sourceTable}) → {rel.fieldName}", headerStyle);
GUIStyle labelStyle = new GUIStyle(EditorStyles.miniLabel) { wordWrap = false };
EditorGUILayout.LabelField($"说明: {rel.description}", labelStyle);
if (!string.IsNullOrEmpty(rel.relationFormat))
{
EditorGUILayout.LabelField($"格式: {rel.relationFormat}", labelStyle);
}
if (sourceExist && GUILayout.Button($"查看 {rel.sourceTable} 表", EditorStyles.miniButton))
{
selectedTableName = rel.sourceTable;
expandedRelations.Clear();
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(2);
}
}
}
private void DrawExcelPathSettings(float windowWidth)
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("Excel 文件夹", EditorStyles.boldLabel);
string currentPath = ConfigLinkDatabase.GetExcelFolderPath();
string displayPath = string.IsNullOrEmpty(currentPath) ? "未设置" : currentPath;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("路径:", GUILayout.Width(36));
EditorGUILayout.LabelField(displayPath, EditorStyles.textField, GUILayout.ExpandWidth(true));
if (GUILayout.Button("选择", GUILayout.Width(60)))
{
string selectedPath = EditorUtility.OpenFolderPanel("选择Excel文件夹", "", "");
if (!string.IsNullOrEmpty(selectedPath))
{
ConfigLinkDatabase.SetExcelFolderPath(selectedPath);
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void DrawRelation(FieldRelation relation, float panelWidth)
{
string key = $"{selectedTableName}_{relation.fieldName}_{relation.targetTable}";
if (!expandedRelations.ContainsKey(key))
{
expandedRelations[key] = false;
}
bool targetExist = ConfigLinkDatabase.IsTableExist(relation.targetTable);
string existMark = targetExist ? "" : "[目标表缺失]";
EditorGUILayout.BeginVertical("frameBox", GUILayout.Width(panelWidth - 10));
GUIStyle foldoutStyle = new GUIStyle(EditorStyles.foldout) { wordWrap = false, richText = true };
expandedRelations[key] = EditorGUILayout.Foldout(expandedRelations[key],
$"{relation.fieldName} → {relation.targetTable}.{relation.targetField} {existMark}",
true, foldoutStyle);
if (expandedRelations[key])
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
EditorGUILayout.BeginVertical("helpBox", GUILayout.Width(panelWidth - 30));
GUIStyle labelStyle = new GUIStyle(EditorStyles.miniLabel) { wordWrap = false, richText = true };
EditorGUILayout.LabelField($"目标表: {relation.targetTable} {(targetExist ? "" : "()")}", labelStyle);
EditorGUILayout.LabelField($"目标字段: {relation.targetField}", labelStyle);
if (!string.IsNullOrEmpty(relation.relationFormat))
{
EditorGUILayout.LabelField($"格式: {relation.relationFormat}", labelStyle);
DrawFormatExample(relation.relationFormat);
}
EditorGUILayout.LabelField($"说明: {relation.description}", labelStyle);
if (targetExist && GUILayout.Button($"查看 {relation.targetTable} 表", EditorStyles.miniButton))
{
selectedTableName = relation.targetTable;
expandedRelations.Clear();
}
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(2);
}
private void DrawFormatExample(string format)
{
string example = ConfigLinkDatabase.FormatRelationValue(format, "1_2_3_4");
if (example != "1_2_3_4")
{
GUIStyle exampleStyle = new GUIStyle(EditorStyles.miniLabel) { fontStyle = FontStyle.Italic };
EditorGUILayout.LabelField($"示例: {example}", exampleStyle);
}
}
private static void RunAIConfigScript()
{
string scriptPath = Path.Combine(Application.dataPath, "Editor/ConfigLinkViewer", "generate_config_link_data.py");
if (!File.Exists(scriptPath))
{
EditorUtility.DisplayDialog("提示", "未找到脚本文件:\n" + scriptPath, "确定");
return;
}
string pythonExe = FindPythonExecutable();
if (string.IsNullOrEmpty(pythonExe))
{
EditorUtility.DisplayDialog("提示", "未找到 Python 环境,请确保已安装 Python 并添加到 PATH", "确定");
return;
}
try
{
string configDir = ConfigLinkDatabase.GetConfigFolderPath();
string tableInfoPath = Path.Combine(Application.dataPath, "Editor/ConfigLinkViewer", "table_info.json");
string outputPath = Path.Combine(Application.dataPath, "Editor/ConfigLinkViewer", "ConfigLinkData.json");
string arguments = $"\"{scriptPath}\" --config-dir \"{Path.Combine(Application.dataPath, configDir)}\" --output \"{outputPath}\"";
if (File.Exists(tableInfoPath))
{
arguments += $" --table-info \"{tableInfoPath}\"";
}
var startInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = pythonExe,
Arguments = arguments,
WorkingDirectory = Path.GetDirectoryName(scriptPath),
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = System.Text.Encoding.UTF8,
StandardErrorEncoding = System.Text.Encoding.UTF8,
};
using (var process = System.Diagnostics.Process.Start(startInfo))
{
string stdout = process.StandardOutput.ReadToEnd();
string stderr = process.StandardError.ReadToEnd();
process.WaitForExit();
if (process.ExitCode == 0)
{
Debug.Log("[ConfigLinkViewer] 补全配置完成:\n" + stdout);
ConfigLinkDatabase.ClearCache();
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("完成", "补全配置已完成\n\n" + stdout, "确定");
}
else
{
Debug.LogError("[ConfigLinkViewer] 补全配置失败:\n" + stderr);
EditorUtility.DisplayDialog("失败", "脚本执行失败:\n" + stderr, "确定");
}
}
}
catch (Exception ex)
{
Debug.LogError("[ConfigLinkViewer] 运行脚本异常: " + ex.Message);
EditorUtility.DisplayDialog("异常", "运行脚本时发生异常:\n" + ex.Message, "确定");
}
}
private static string FindPythonExecutable()
{
string[] candidates = { "python", "python3", "py" };
foreach (var candidate in candidates)
{
try
{
var startInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = candidate,
Arguments = "--version",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
};
using (var process = System.Diagnostics.Process.Start(startInfo))
{
process.WaitForExit();
if (process.ExitCode == 0)
return candidate;
}
}
catch { }
}
return null;
}
private static void CleanExcelEmptyRows()
{
string excelFolder = ConfigLinkDatabase.GetExcelFolderPath();
if (string.IsNullOrEmpty(excelFolder))
{
if (ConfigLinkViewerCallbacks.FallbackExcelPathCallback != null)
{
excelFolder = ConfigLinkViewerCallbacks.FallbackExcelPathCallback();
}
}
if (string.IsNullOrEmpty(excelFolder) || !Directory.Exists(excelFolder))
{
EditorUtility.DisplayDialog("提示",
"Excel文件夹路径未设置或不存在。\n请切换到[配置]标签页设置,或在 ConfigLinkViewerCallbacks.FallbackExcelPathCallback 中注册回退逻辑。",
"确定");
return;
}
var files = Directory.GetFiles(excelFolder, "*.xlsx", SearchOption.AllDirectories)
.Where(f => !Path.GetFileName(f).StartsWith("~$"))
.ToArray();
if (files.Length == 0)
{
EditorUtility.DisplayDialog("提示", "未找到Excel文件", "确定");
return;
}
int totalCleaned = 0;
int cleanedFileCount = 0;
try
{
foreach (var file in files)
{
try
{
int removedCount = CleanSingleExcelFile(file);
if (removedCount > 0)
{
totalCleaned += removedCount;
cleanedFileCount++;
}
}
catch (Exception ex)
{
Debug.LogError($"[清理空行] 处理文件失败: {Path.GetFileName(file)}, 错误: {ex.Message}");
}
}
}
finally
{
AssetDatabase.Refresh();
}
if (cleanedFileCount > 0)
{
EditorUtility.DisplayDialog("清理完成",
$"扫描 {files.Length} 个文件\n清理了 {cleanedFileCount} 个文件中共 {totalCleaned} 行空数据", "确定");
}
else
{
EditorUtility.DisplayDialog("清理完成",
$"扫描 {files.Length} 个文件,未发现需要清理的空行", "确定");
}
}
private static int CleanSingleExcelFile(string filePath)
{
const int headerRowCount = 3;
XNamespace ns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
XDocument doc;
using (var archive = ZipFile.Open(filePath, ZipArchiveMode.Read))
{
var sheetEntry = archive.GetEntry("xl/worksheets/sheet1.xml");
if (sheetEntry == null) return 0;
using (var stream = sheetEntry.Open())
{
doc = XDocument.Load(stream);
}
}
List<string> sharedStrings = null;
using (var archive = ZipFile.Open(filePath, ZipArchiveMode.Read))
{
var ssEntry = archive.GetEntry("xl/sharedStrings.xml");
if (ssEntry != null)
{
using (var stream = ssEntry.Open())
{
var ssDoc = XDocument.Load(stream);
sharedStrings = ssDoc.Root.Elements(ns + "si")
.Select(si => si.Value)
.ToList();
}
}
}
var sheetData = doc.Root.Element(ns + "sheetData");
if (sheetData == null) return 0;
var rows = sheetData.Elements(ns + "row").ToList();
if (rows.Count <= headerRowCount) return 0;
var emptyRows = new List<XElement>();
for (int i = headerRowCount; i < rows.Count; i++)
{
var row = rows[i];
var cells = row.Elements(ns + "c").ToList();
if (cells.Count == 0)
{
emptyRows.Add(row);
continue;
}
bool allEmpty = true;
foreach (var cell in cells)
{
var value = cell.Element(ns + "v");
if (value != null && !string.IsNullOrWhiteSpace(value.Value))
{
allEmpty = false;
break;
}
var cellType = cell.Attribute("t")?.Value;
if (cellType == "s" && value != null)
{
if (int.TryParse(value.Value, out int ssIdx) && sharedStrings != null && ssIdx < sharedStrings.Count)
{
if (!string.IsNullOrWhiteSpace(sharedStrings[ssIdx]))
{
allEmpty = false;
break;
}
}
}
}
if (allEmpty)
{
emptyRows.Add(row);
}
}
if (emptyRows.Count == 0) return 0;
foreach (var row in emptyRows)
{
row.Remove();
}
using (var archive = ZipFile.Open(filePath, ZipArchiveMode.Update))
{
var sheetEntry = archive.GetEntry("xl/worksheets/sheet1.xml");
if (sheetEntry != null)
{
sheetEntry.Delete();
var newEntry = archive.CreateEntry("xl/worksheets/sheet1.xml");
using (var stream = newEntry.Open())
{
doc.Save(stream);
}
}
}
return emptyRows.Count;
}
}