507 lines
19 KiB
C#
507 lines
19 KiB
C#
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
|
||
{
|
||
private string selectedTableName;
|
||
private Dictionary<string, bool> expandedRelations = new Dictionary<string, bool>();
|
||
private Vector2 relationScrollPos;
|
||
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()
|
||
{
|
||
float windowWidth = position.width;
|
||
float windowHeight = position.height;
|
||
|
||
DrawHeader();
|
||
EditorGUILayout.Space();
|
||
DrawTableSelector(windowWidth, windowHeight);
|
||
}
|
||
|
||
private void DrawHeader()
|
||
{
|
||
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;
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
EditorGUILayout.LabelField("选择配置表查看其与其他表的关联关系", EditorStyles.miniLabel);
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawTableSelector(float windowWidth, float windowHeight)
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
|
||
EditorGUILayout.LabelField("选择配置表:", EditorStyles.boldLabel);
|
||
|
||
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
|
||
: 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();
|
||
|
||
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.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))
|
||
{
|
||
selectedTableName = tableKeys[i];
|
||
expandedRelations.Clear();
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
EditorGUILayout.EndVertical();
|
||
|
||
EditorGUILayout.BeginVertical("box", GUILayout.Width(rightPanelWidth), GUILayout.Height(panelHeight));
|
||
DrawCurrentTableInfo(rightPanelWidth);
|
||
EditorGUILayout.EndVertical();
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
var tableInfo = ConfigLinkDatabase.GetTableInfo(selectedTableName);
|
||
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 ? "存在于当前项目" : "当前项目不存在")}",
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
}
|
||
|
||
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)
|
||
{
|
||
string currentPath = ConfigLinkDatabase.GetExcelFolderPath();
|
||
string displayPath = string.IsNullOrEmpty(currentPath) ? "未设置" : currentPath;
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("Excel文件夹:", GUILayout.Width(70));
|
||
EditorGUILayout.LabelField(displayPath, EditorStyles.textField, GUILayout.ExpandWidth(true));
|
||
|
||
if (GUILayout.Button("选择文件夹", GUILayout.Width(80)))
|
||
{
|
||
string selectedPath = EditorUtility.OpenFolderPanel("选择Excel文件夹", "", "");
|
||
if (!string.IsNullOrEmpty(selectedPath))
|
||
{
|
||
ConfigLinkDatabase.SetExcelFolderPath(selectedPath);
|
||
}
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
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 static void CleanExcelEmptyRows()
|
||
{
|
||
string excelFolder = ConfigLinkDatabase.GetExcelFolderPath();
|
||
if (string.IsNullOrEmpty(excelFolder))
|
||
{
|
||
var acf = MFrame.ConfigEditorCf.ins;
|
||
if (acf != null)
|
||
{
|
||
excelFolder = MFrame.PathConf.project_root + acf.excel_path;
|
||
}
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(excelFolder) || !Directory.Exists(excelFolder))
|
||
{
|
||
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<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);
|
||
}
|
||
}
|
||
}
|