937 lines
36 KiB
C#
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;
|
|
}
|
|
}
|