diff --git a/Unity/配置表联动查看器/ConfigLinkViewer/ConfigLinkViewerWindow.cs b/Unity/配置表联动查看器/ConfigLinkViewer/ConfigLinkViewerWindow.cs index 04b108d..4f0a463 100644 --- a/Unity/配置表联动查看器/ConfigLinkViewer/ConfigLinkViewerWindow.cs +++ b/Unity/配置表联动查看器/ConfigLinkViewer/ConfigLinkViewerWindow.cs @@ -1,8 +1,12 @@ using UnityEngine; using UnityEditor; +using System; using System.Collections.Generic; using System.Linq; using System.IO; +using System.IO.Compression; +using System.Text.RegularExpressions; +using System.Xml.Linq; public class ConfigLinkViewerWindow : EditorWindow { @@ -34,7 +38,17 @@ public class ConfigLinkViewerWindow : EditorWindow private void DrawHeader() { EditorGUILayout.BeginVertical("box"); - EditorGUILayout.LabelField("配置表联动关系查看器", new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 }); + 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; + } + EditorGUILayout.EndHorizontal(); EditorGUILayout.LabelField("选择配置表查看其与其他表的关联关系", EditorStyles.miniLabel); EditorGUILayout.EndVertical(); } @@ -319,48 +333,174 @@ public class ConfigLinkViewerWindow : EditorWindow EditorGUILayout.Space(2); } - private void DrawFormatExample(string format) + private static void CleanExcelEmptyRows() { - string example = ""; - string formatted = ""; - - switch (format.ToLower()) + string excelFolder = ConfigLinkDatabase.GetExcelFolderPath(); + if (string.IsNullOrEmpty(excelFolder)) { - case "item_id_num": - case "type_id_num": - example = "1001_10_5"; - formatted = "类型:1001, ID:10, 数量:5"; - break; - case "id_pos_lv": - example = "100_1_30"; - formatted = "ID:100, 位置:1, 等级:30"; - break; - case "id_lv_num": - example = "100_30_5"; - formatted = "ID:100, 等级:30, 数量:5"; - break; - case "id_lv_num_time": - example = "100_30_5_120"; - formatted = "ID:100, 等级:30, 数量:5, 时间:120秒"; - break; - case "buffid_lv": - example = "5_3"; - formatted = "BuffID:5, 等级:3"; - break; - case "rune_id_num": - example = "10_2"; - formatted = "符文ID:10, 数量:2"; - break; - case "equip_id_num": - example = "20_1"; - formatted = "装备ID:20, 数量:1"; - break; + var acf = MFrame.ConfigEditorCf.ins; + if (acf != null) + { + excelFolder = MFrame.PathConf.project_root + acf.excel_path; + } } - if (!string.IsNullOrEmpty(example)) + if (string.IsNullOrEmpty(excelFolder) || !Directory.Exists(excelFolder)) { - GUIStyle labelStyle = new GUIStyle(EditorStyles.miniLabel) { wordWrap = false, richText = true }; - EditorGUILayout.LabelField($"示例: {example} → {formatted}", labelStyle); + EditorUtility.DisplayDialog("提示", "Excel文件夹路径未设置或不存在,请先设置Excel文件夹", "确定"); + 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); + } + } + + var sheetData = doc.Root?.Element(ns + "sheetData"); + if (sheetData == null) return 0; + + var rows = sheetData.Elements(ns + "row").ToList(); + var rowsToRemove = new List(); + + foreach (var row in rows) + { + 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) + { + 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); + } + } + + if (rowsToRemove.Count == 0) return 0; + + foreach (var row in rowsToRemove) + { + 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()) + { + doc.Save(stream); + } + } + + Debug.Log($"[清理空行] {Path.GetFileName(filePath)}: 删除了 {rowsToRemove.Count} 行空数据"); + return rowsToRemove.Count; + } + + private static readonly Dictionary FormatExamples = new Dictionary + { + { "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); } } } diff --git a/Unity/配置表联动查看器/README.md b/Unity/配置表联动查看器/README.md index d6b1a63..f6ee855 100644 --- a/Unity/配置表联动查看器/README.md +++ b/Unity/配置表联动查看器/README.md @@ -27,7 +27,17 @@ - 一键打开配置表 Excel 文件 - 批量打开所有关联表 -### 6. 跨项目适配 +### 6. 导出配置 +- 一键将 Excel 配置表批量导出为 JSON 和 C# 代码 +- 通过 Tools → ConfigDeal → 导出配置 或窗口右上角按钮触发 + +### 7. 清理 Excel 空行 +- 自动扫描 Excel 文件夹中所有 .xlsx 文件 +- 检测并删除数据区域中第一列(id列)为空或非数字的行 +- 直接修改 Excel 源文件,清理后可重新导出配置 +- 通过窗口右上角"清理Excel空行"按钮触发 + +### 8. 跨项目适配 - 自动检测项目中存在的配置表 - 支持不同项目配置不同路径 @@ -59,6 +69,8 @@ git submodule add Assets/Editor/ConfigLinkViewer | 点击"打开表格" | 打开当前表的 Excel 文件 | | 点击"批量打开关联表" | 打开所有关联的配置表 | | 勾选"显示反向引用" | 查看哪些表引用了当前表 | +| 点击"清理Excel空行" | 清除 Excel 中 id 列为空的垃圾数据行 | +| 点击"导出配置" | 将 Excel 批量导出为 JSON 和 C# 代码 | ### 数据格式说明 @@ -104,9 +116,9 @@ new ConfigTableInfo { ``` Assets/Editor/ConfigLinkViewer/ -├── ConfigLinkDatabase.cs # 配置表数据和联动关系存储 -├── ConfigLinkViewerWindow.cs # Unity编辑器窗口界面 -└── README.md # 使用文档 +├── ConfigLinkDatabase.cs # 配置表数据和联动关系存储 +├── ConfigLinkViewerWindow.cs # Unity编辑器窗口界面(含导出配置、清理空行功能) +└── README.md # 使用文档 ``` ## 支持的配置表 @@ -130,29 +142,22 @@ Assets/Editor/ConfigLinkViewer/ 2. 路径设置后会自动保存到 PlayerPrefs 3. 建议将 Excel 文件夹设置为版本控制忽略 4. TXT 文件需要放置在 Excel 文件夹中 +5. 推荐工作流:先点击"清理Excel空行"清除垃圾数据,再点击"导出配置"生成 JSON 和 C# 代码 +6. 清理空行会直接修改 Excel 源文件,建议操作前确认已提交到版本控制 ## 更新日志 ### v1.0.0 -- 初始版本 -- 支持配置表列表和联动关系查看 - -### v1.1.0 -- 添加反向查询功能 -- 添加数据格式可视化 -- 添加批量打开关联表功能 - -### v1.2.0 +- 配置表列表展示,支持搜索过滤和存在性检测 +- 联动关系查看,支持正向关联和反向引用查询 +- 数据格式可视化,自动解析并显示格式示例 - 支持 TXT 文件识别 -- 优化跨项目适配 -- 更新使用文档 - -### v1.3.0 -- 移除所有水平滚动条 -- 左右面板自适应布局 -- Excel文件夹路径与搜索框对齐 -- 文本保持单行显示不换行 -- 窗口默认大小优化为 900×600 +- 左右面板自适应布局,窗口默认 900×600 +- 一键打开 Excel 文件,支持批量打开关联表 +- Excel 文件夹路径设置,自动保存到 PlayerPrefs +- 跨项目适配,自动检测项目中存在的配置表 +- "导出配置"按钮,一键将 Excel 批量导出为 JSON 和 C# 代码 +- "清理Excel空行"功能,自动检测并删除 Excel 中 id 列为空的垃圾数据行,直接操作 xlsx 内部 XML,兼容性好 ## 许可证