修改配置的插件

This commit is contained in:
ShallowT1Dream
2026-06-02 14:11:28 +08:00
parent 9a0faaccd7
commit d4fbef751a
6 changed files with 1895 additions and 574 deletions

View File

@@ -5,14 +5,95 @@ using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.IO.Compression;
using System.Text.RegularExpressions;
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;
@@ -27,46 +108,284 @@ public class ConfigLinkViewerWindow : EditorWindow
private void OnGUI()
{
DrawTabBar();
EditorGUILayout.Space();
float windowWidth = position.width;
float windowHeight = position.height;
DrawHeader();
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 DrawHeader()
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 += MFrame.ConfigDeal.ExportConfig;
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();
EditorGUILayout.LabelField("选择配置表查看其与其他表的关联关系", EditorStyles.miniLabel);
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");
EditorGUILayout.LabelField("选择配置表:", EditorStyles.boldLabel);
EditorGUILayout.BeginVertical("box", GUILayout.Height(windowHeight - 60));
EditorGUILayout.BeginHorizontal();
searchFilter = EditorGUILayout.TextField("搜索:", searchFilter, EditorStyles.toolbarSearchField);
showOnlyExisting = EditorGUILayout.ToggleLeft("只显示当前项目存在的表", showOnlyExisting);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
DrawExcelPathSettings(windowWidth);
var allTables = showOnlyExisting ? ConfigLinkDatabase.GetExistingTables() : ConfigLinkDatabase.GetAllTableInfo();
var filteredTables = string.IsNullOrEmpty(searchFilter)
? allTables
@@ -84,13 +403,12 @@ public class ConfigLinkViewerWindow : EditorWindow
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.BeginHorizontal(GUILayout.ExpandHeight(true));
float leftPanelWidth = Mathf.Clamp(windowWidth * 0.3f, 180f, 300f);
float rightPanelWidth = windowWidth - leftPanelWidth - 20f;
float panelHeight = windowHeight - 150f;
EditorGUILayout.BeginVertical(GUILayout.Width(leftPanelWidth), GUILayout.Height(panelHeight));
EditorGUILayout.BeginVertical(GUILayout.Width(leftPanelWidth), GUILayout.ExpandHeight(true));
EditorGUILayout.LabelField($"配置表列表 ({filteredTables.Count}):", EditorStyles.miniBoldLabel);
relationScrollPos = EditorGUILayout.BeginScrollView(relationScrollPos, false, true, GUILayout.ExpandHeight(true));
@@ -103,16 +421,22 @@ public class ConfigLinkViewerWindow : EditorWindow
if (GUILayout.Button(displayNames[i], buttonStyle))
{
selectedTableName = tableKeys[i];
expandedRelations.Clear();
if (selectedTableName != tableKeys[i])
{
selectedTableName = tableKeys[i];
expandedRelations.Clear();
}
}
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical("box", GUILayout.Width(rightPanelWidth), GUILayout.Height(panelHeight));
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();
@@ -121,12 +445,9 @@ public class ConfigLinkViewerWindow : EditorWindow
private void DrawCurrentTableInfo(float panelWidth)
{
EditorGUILayout.BeginScrollView(Vector2.zero, false, true, GUILayout.ExpandHeight(true));
if (string.IsNullOrEmpty(selectedTableName))
{
EditorGUILayout.LabelField("请从左侧选择一个配置表", EditorStyles.centeredGreyMiniLabel);
EditorGUILayout.EndScrollView();
return;
}
@@ -134,14 +455,13 @@ public class ConfigLinkViewerWindow : EditorWindow
if (tableInfo == null)
{
EditorGUILayout.LabelField("未找到该表的配置信息", EditorStyles.helpBox);
EditorGUILayout.EndScrollView();
return;
}
EditorGUILayout.LabelField($"当前表: {tableInfo.displayName}", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"表名: {tableInfo.tableName}", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"描述: {tableInfo.description}", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"状态: {(tableInfo.isExistInProject ? "" : "")}",
EditorGUILayout.LabelField($"状态: {(tableInfo.isExistInProject ? "" : "")}",
tableInfo.isExistInProject ? EditorStyles.miniLabel : EditorStyles.helpBox);
EditorGUILayout.Space();
@@ -174,14 +494,12 @@ public class ConfigLinkViewerWindow : EditorWindow
}
}
}
EditorGUILayout.EndScrollView();
}
private void DrawOpenExcelButtons(float panelWidth)
{
EditorGUILayout.BeginHorizontal();
string excelPath = ConfigLinkDatabase.GetExcelFilePath(selectedTableName);
bool canOpen = !string.IsNullOrEmpty(excelPath);
@@ -238,10 +556,10 @@ public class ConfigLinkViewerWindow : EditorWindow
{
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);
@@ -264,14 +582,17 @@ public class ConfigLinkViewerWindow : EditorWindow
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("Excel文件夹:", GUILayout.Width(70));
EditorGUILayout.LabelField("路径:", GUILayout.Width(36));
EditorGUILayout.LabelField(displayPath, EditorStyles.textField, GUILayout.ExpandWidth(true));
if (GUILayout.Button("选择文件夹", GUILayout.Width(80)))
if (GUILayout.Button("选择", GUILayout.Width(60)))
{
string selectedPath = EditorUtility.OpenFolderPanel("选择Excel文件夹", "", "");
if (!string.IsNullOrEmpty(selectedPath))
@@ -280,6 +601,7 @@ public class ConfigLinkViewerWindow : EditorWindow
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void DrawRelation(FieldRelation relation, float panelWidth)
@@ -333,21 +655,128 @@ public class ConfigLinkViewerWindow : EditorWindow
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))
{
var acf = MFrame.ConfigEditorCf.ins;
if (acf != null)
if (ConfigLinkViewerCallbacks.FallbackExcelPathCallback != null)
{
excelFolder = MFrame.PathConf.project_root + acf.excel_path;
excelFolder = ConfigLinkViewerCallbacks.FallbackExcelPathCallback();
}
}
if (string.IsNullOrEmpty(excelFolder) || !Directory.Exists(excelFolder))
{
EditorUtility.DisplayDialog("提示", "Excel文件夹路径未设置或不存在请先设置Excel文件夹", "确定");
EditorUtility.DisplayDialog("提示",
"Excel文件夹路径未设置或不存在。\n请切换到[配置]标签页设置,或在 ConfigLinkViewerCallbacks.FallbackExcelPathCallback 中注册回退逻辑。",
"确定");
return;
}
@@ -416,91 +845,92 @@ public class ConfigLinkViewerWindow : EditorWindow
}
}
var sheetData = doc.Root?.Element(ns + "sheetData");
if (sheetData == null) return 0;
var rows = sheetData.Elements(ns + "row").ToList();
var rowsToRemove = new List<XElement>();
foreach (var row in rows)
List<string> sharedStrings = null;
using (var archive = ZipFile.Open(filePath, ZipArchiveMode.Read))
{
if (!int.TryParse(row.Attribute("r")?.Value, out int rowNum)) continue;
if (rowNum <= headerRowCount) continue;
var cells = row.Elements(ns + "c").ToList();
if (cells.Count == 0)
var ssEntry = archive.GetEntry("xl/sharedStrings.xml");
if (ssEntry != null)
{
rowsToRemove.Add(row);
continue;
}
var firstCell = cells[0];
var valueElement = firstCell.Element(ns + "v");
string val = valueElement?.Value?.Trim();
if (string.IsNullOrEmpty(val) || !int.TryParse(val, out _))
{
rowsToRemove.Add(row);
using (var stream = ssEntry.Open())
{
var ssDoc = XDocument.Load(stream);
sharedStrings = ssDoc.Root.Elements(ns + "si")
.Select(si => si.Value)
.ToList();
}
}
}
if (rowsToRemove.Count == 0) return 0;
var sheetData = doc.Root.Element(ns + "sheetData");
if (sheetData == null) return 0;
foreach (var row in rowsToRemove)
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();
}
var remainingRows = sheetData.Elements(ns + "row").ToList();
int newRowIndex = 1;
foreach (var row in remainingRows)
{
row.SetAttributeValue("r", newRowIndex);
foreach (var cell in row.Elements(ns + "c"))
{
string cellRef = cell.Attribute("r")?.Value;
if (!string.IsNullOrEmpty(cellRef))
{
string colPart = Regex.Replace(cellRef, @"\d", "");
cell.SetAttributeValue("r", colPart + newRowIndex);
}
}
newRowIndex++;
}
using (var archive = ZipFile.Open(filePath, ZipArchiveMode.Update))
{
var entry = archive.GetEntry("xl/worksheets/sheet1.xml");
if (entry != null) entry.Delete();
var newEntry = archive.CreateEntry("xl/worksheets/sheet1.xml");
using (var stream = newEntry.Open())
var sheetEntry = archive.GetEntry("xl/worksheets/sheet1.xml");
if (sheetEntry != null)
{
doc.Save(stream);
sheetEntry.Delete();
var newEntry = archive.CreateEntry("xl/worksheets/sheet1.xml");
using (var stream = newEntry.Open())
{
doc.Save(stream);
}
}
}
Debug.Log($"[清理空行] {Path.GetFileName(filePath)}: 删除了 {rowsToRemove.Count} 行空数据");
return rowsToRemove.Count;
}
private static readonly Dictionary<string, (string example, string desc)> FormatExamples = new Dictionary<string, (string, string)>
{
{ "item_id_num", ("1001_10_5", "类型:1001, ID:10, 数量:5") },
{ "type_id_num", ("1001_10_5", "类型:1001, ID:10, 数量:5") },
{ "id_pos_lv", ("100_1_30", "ID:100, 位置:1, 等级:30") },
{ "id_lv_num", ("100_30_5", "ID:100, 等级:30, 数量:5") },
{ "id_lv_num_time",("100_30_5_120","ID:100, 等级:30, 数量:5, 时间:120秒") },
{ "buffid_lv", ("5_3", "BuffID:5, 等级:3") },
{ "rune_id_num", ("10_2", "符文ID:10, 数量:2") },
{ "equip_id_num", ("20_1", "装备ID:20, 数量:1") },
};
private void DrawFormatExample(string format)
{
if (FormatExamples.TryGetValue(format.ToLower(), out var info))
{
var style = new GUIStyle(EditorStyles.miniLabel) { wordWrap = false, richText = true };
EditorGUILayout.LabelField($"示例: {info.example} → {info.desc}", style);
}
return emptyRows.Count;
}
}