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