1059 lines
40 KiB
C#
1059 lines
40 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using Thrift;
|
||
using Thrift.Protocol;
|
||
using Thrift.Transport;
|
||
using Thrift.Transport.Client;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
namespace BytesViewerTool
|
||
{
|
||
/// <summary>
|
||
/// Bytes文件查看器 - 将Thrift bytes数据以表格形式展示
|
||
/// </summary>
|
||
public class BytesViewerWindow : EditorWindow
|
||
{
|
||
private TextAsset selectedBytesFile;
|
||
private string currentFilePath = "";
|
||
private object parsedData;
|
||
private Vector2 scrollPosition;
|
||
private bool dataLoaded = false;
|
||
private string errorMessage = "";
|
||
|
||
// 表格显示相关
|
||
private List<string> columnNames = new List<string>();
|
||
private List<object> rowDataList = new List<object>();
|
||
private List<object> filteredRowDataList = new List<object>();
|
||
private Vector2 tableScrollPosition;
|
||
|
||
// 配置选择
|
||
private string[] availableConfigs = new string[0];
|
||
private string[] filteredConfigs = new string[0];
|
||
private int selectedConfigIndex = 0;
|
||
private string currentConfigName = "";
|
||
private string configSearchText = "";
|
||
private Vector2 configListScrollPosition;
|
||
private bool isConfigListExpanded = true;
|
||
|
||
// 分页相关
|
||
private int currentPage = 0;
|
||
private int pageSize = 50;
|
||
private int[] pageSizeOptions = new int[] { 20, 50, 100, 200, 500 };
|
||
private string pageInputText = "1";
|
||
|
||
// 筛选相关
|
||
private string filterText = "";
|
||
private string lastFilterText = "";
|
||
private int filterColumnIndex = 0;
|
||
private string[] filterColumnOptions = new string[0];
|
||
|
||
// 全局搜索
|
||
private string globalSearchText = "";
|
||
private List<GlobalSearchResult> globalSearchResults = new List<GlobalSearchResult>();
|
||
private Vector2 globalSearchScrollPosition;
|
||
|
||
// 单元格详情查看
|
||
private int selectedRowIndex = -1;
|
||
private string selectedColumnName = "";
|
||
private string selectedCellFullData = "";
|
||
private Vector2 detailScrollPosition;
|
||
|
||
private class GlobalSearchResult
|
||
{
|
||
public string ConfigName;
|
||
public int RowIndex;
|
||
public string ColumnName;
|
||
public object RowData;
|
||
public string MatchedValue;
|
||
}
|
||
|
||
[MenuItem("蹊径/Bytes查看器")]
|
||
public static void ShowWindow()
|
||
{
|
||
BytesViewerWindow window = GetWindow<BytesViewerWindow>("Bytes查看器");
|
||
window.minSize = new Vector2(1000, 600);
|
||
window.Show();
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
// 窗口启动时自动查找并加载AllConfigs.bytes
|
||
AutoLoadAllConfigs();
|
||
}
|
||
|
||
private void OnGUI()
|
||
{
|
||
EditorGUILayout.Space(10);
|
||
EditorGUILayout.LabelField("Bytes文件查看器", EditorStyles.boldLabel, GUILayout.Height(24));
|
||
EditorGUILayout.Space(5);
|
||
|
||
// 文件选择区域
|
||
EditorGUILayout.BeginVertical("box");
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("选择Bytes文件:", GUILayout.Width(100));
|
||
|
||
TextAsset newFile = EditorGUILayout.ObjectField(selectedBytesFile, typeof(TextAsset), false) as TextAsset;
|
||
|
||
if (newFile != selectedBytesFile)
|
||
{
|
||
selectedBytesFile = newFile;
|
||
dataLoaded = false;
|
||
errorMessage = "";
|
||
availableConfigs = new string[0];
|
||
selectedConfigIndex = 0;
|
||
}
|
||
|
||
if (GUILayout.Button("加载并解析", GUILayout.Width(100)))
|
||
{
|
||
LoadAndParseBytes();
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
if (!string.IsNullOrEmpty(currentFilePath))
|
||
{
|
||
EditorGUILayout.LabelField("当前文件:", currentFilePath, EditorStyles.miniLabel);
|
||
}
|
||
EditorGUILayout.EndVertical();
|
||
|
||
EditorGUILayout.Space(5);
|
||
|
||
// 全局搜索功能
|
||
if (availableConfigs.Length > 0)
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
EditorGUILayout.LabelField("🔍 全局搜索 (所有配置表)", EditorStyles.boldLabel);
|
||
EditorGUILayout.Space(3);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
EditorGUILayout.LabelField("关键词:", GUILayout.Width(60));
|
||
|
||
string newGlobalSearch = EditorGUILayout.TextField(globalSearchText, GUILayout.MinWidth(300));
|
||
if (newGlobalSearch != globalSearchText)
|
||
{
|
||
globalSearchText = newGlobalSearch;
|
||
}
|
||
|
||
if (GUILayout.Button("搜索", GUILayout.Width(80)))
|
||
{
|
||
PerformGlobalSearch();
|
||
}
|
||
|
||
if (GUILayout.Button("清空", GUILayout.Width(60)))
|
||
{
|
||
globalSearchText = "";
|
||
globalSearchResults.Clear();
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
// 显示搜索结果
|
||
if (globalSearchResults.Count > 0)
|
||
{
|
||
EditorGUILayout.Space(3);
|
||
EditorGUILayout.LabelField($"找到 {globalSearchResults.Count} 条结果:", EditorStyles.boldLabel);
|
||
|
||
globalSearchScrollPosition = EditorGUILayout.BeginScrollView(
|
||
globalSearchScrollPosition,
|
||
GUILayout.Height(Mathf.Min(globalSearchResults.Count * 22 + 10, 150)));
|
||
|
||
for (int i = 0; i < globalSearchResults.Count; i++)
|
||
{
|
||
var result = globalSearchResults[i];
|
||
string displayText = $"{result.ConfigName} - 行{result.RowIndex} - {result.ColumnName}: {result.MatchedValue}";
|
||
|
||
if (GUILayout.Button(displayText, EditorStyles.label))
|
||
{
|
||
// 点击结果跳转到对应配置
|
||
JumpToSearchResult(result);
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
}
|
||
else if (!string.IsNullOrEmpty(globalSearchText) && globalSearchResults.Count == 0)
|
||
{
|
||
EditorGUILayout.HelpBox("未找到匹配的结果", MessageType.Info);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
EditorGUILayout.Space(5);
|
||
}
|
||
|
||
// 配置类型选择
|
||
if (availableConfigs.Length > 0)
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
// 折叠按钮
|
||
isConfigListExpanded = EditorGUILayout.Foldout(isConfigListExpanded, "配置类型", true, EditorStyles.foldoutHeader);
|
||
|
||
GUILayout.FlexibleSpace();
|
||
|
||
EditorGUILayout.LabelField("配置类型:", GUILayout.Width(70));
|
||
|
||
// 搜索框
|
||
string newSearchText = EditorGUILayout.TextField(configSearchText, EditorStyles.toolbarSearchField, GUILayout.Width(200));
|
||
if (newSearchText != configSearchText)
|
||
{
|
||
configSearchText = newSearchText;
|
||
FilterConfigList();
|
||
}
|
||
|
||
if (GUILayout.Button("X", GUILayout.Width(25)))
|
||
{
|
||
configSearchText = "";
|
||
FilterConfigList();
|
||
}
|
||
|
||
// 显示当前选中的配置
|
||
if (!string.IsNullOrEmpty(currentConfigName))
|
||
{
|
||
GUILayout.FlexibleSpace();
|
||
EditorGUILayout.LabelField($"当前: {currentConfigName}", EditorStyles.boldLabel, GUILayout.Width(250));
|
||
}
|
||
|
||
EditorGUILayout.LabelField($"{rowDataList.Count} 条", EditorStyles.miniLabel, GUILayout.Width(60));
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
// 配置列表(可折叠)
|
||
if (isConfigListExpanded && filteredConfigs.Length > 0)
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.Space(10);
|
||
|
||
configListScrollPosition = EditorGUILayout.BeginScrollView(
|
||
configListScrollPosition,
|
||
GUILayout.Height(Mathf.Min(filteredConfigs.Length * 22 + 10, 120)));
|
||
|
||
for (int i = 0; i < filteredConfigs.Length; i++)
|
||
{
|
||
string configName = filteredConfigs[i];
|
||
int actualIndex = Array.IndexOf(availableConfigs, configName);
|
||
|
||
bool isSelected = actualIndex == selectedConfigIndex;
|
||
|
||
GUIStyle buttonStyle = isSelected ?
|
||
new GUIStyle(EditorStyles.toolbarButton) { fontStyle = FontStyle.Bold } :
|
||
EditorStyles.toolbarButton;
|
||
|
||
if (GUILayout.Button(configName, buttonStyle, GUILayout.Height(20)))
|
||
{
|
||
if (actualIndex != selectedConfigIndex)
|
||
{
|
||
selectedConfigIndex = actualIndex;
|
||
PrepareTableDataForSelectedConfig();
|
||
Repaint();
|
||
}
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
else if (!string.IsNullOrEmpty(configSearchText))
|
||
{
|
||
EditorGUILayout.HelpBox("没有匹配的配置类型", MessageType.Info);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
|
||
EditorGUILayout.Space(5);
|
||
}
|
||
|
||
// 筛选和分页控制
|
||
if (rowDataList.Count > 0)
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
EditorGUILayout.LabelField("📋 表格筛选 (仅过滤显示)", EditorStyles.boldLabel);
|
||
EditorGUILayout.Space(3);
|
||
|
||
// 筛选区域
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("选择字段:", GUILayout.Width(70));
|
||
|
||
if (filterColumnOptions.Length > 0)
|
||
{
|
||
filterColumnIndex = EditorGUILayout.Popup(filterColumnIndex, filterColumnOptions, GUILayout.Width(150));
|
||
}
|
||
|
||
EditorGUILayout.LabelField("条件:", GUILayout.Width(40));
|
||
|
||
string newFilterText = EditorGUILayout.TextField(filterText, GUILayout.MinWidth(200));
|
||
if (newFilterText != filterText)
|
||
{
|
||
filterText = newFilterText;
|
||
}
|
||
|
||
if (GUILayout.Button("筛选", GUILayout.Width(60)))
|
||
{
|
||
ApplyFilter();
|
||
}
|
||
|
||
if (GUILayout.Button("重置", GUILayout.Width(60)))
|
||
{
|
||
filterText = "";
|
||
ApplyFilter();
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
// 分页区域
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("每页:", GUILayout.Width(50));
|
||
|
||
int selectedPageSizeIndex = Array.IndexOf(pageSizeOptions, pageSize);
|
||
if (selectedPageSizeIndex < 0) selectedPageSizeIndex = 1;
|
||
|
||
int newPageSizeIndex = EditorGUILayout.Popup(selectedPageSizeIndex,
|
||
pageSizeOptions.Select(p => p.ToString()).ToArray(), GUILayout.Width(80));
|
||
|
||
if (newPageSizeIndex != selectedPageSizeIndex)
|
||
{
|
||
pageSize = pageSizeOptions[newPageSizeIndex];
|
||
currentPage = 0;
|
||
}
|
||
|
||
int totalPages = Mathf.CeilToInt((float)filteredRowDataList.Count / pageSize);
|
||
|
||
EditorGUILayout.LabelField($"显示 {filteredRowDataList.Count} 条 (共 {rowDataList.Count} 条)", EditorStyles.miniLabel, GUILayout.Width(150));
|
||
|
||
GUILayout.FlexibleSpace();
|
||
|
||
GUI.enabled = currentPage > 0;
|
||
if (GUILayout.Button("<<", GUILayout.Width(40)))
|
||
{
|
||
currentPage = 0;
|
||
pageInputText = "1";
|
||
}
|
||
if (GUILayout.Button("<", GUILayout.Width(40)))
|
||
{
|
||
currentPage--;
|
||
pageInputText = (currentPage + 1).ToString();
|
||
}
|
||
GUI.enabled = true;
|
||
|
||
// 页码输入框
|
||
EditorGUILayout.LabelField("第", GUILayout.Width(20));
|
||
string newPageInput = EditorGUILayout.TextField(pageInputText, GUILayout.Width(50));
|
||
if (newPageInput != pageInputText)
|
||
{
|
||
pageInputText = newPageInput;
|
||
// 尝试解析输入
|
||
if (int.TryParse(pageInputText, out int inputPage))
|
||
{
|
||
inputPage = Mathf.Clamp(inputPage, 1, Mathf.Max(1, totalPages));
|
||
currentPage = inputPage - 1;
|
||
}
|
||
}
|
||
|
||
if (GUILayout.Button("跳转", GUILayout.Width(50)))
|
||
{
|
||
if (int.TryParse(pageInputText, out int inputPage))
|
||
{
|
||
inputPage = Mathf.Clamp(inputPage, 1, Mathf.Max(1, totalPages));
|
||
currentPage = inputPage - 1;
|
||
pageInputText = (currentPage + 1).ToString();
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.LabelField($"/ {Mathf.Max(1, totalPages)} 页", GUILayout.Width(60));
|
||
|
||
GUI.enabled = currentPage < totalPages - 1;
|
||
if (GUILayout.Button(">", GUILayout.Width(40)))
|
||
{
|
||
currentPage++;
|
||
pageInputText = (currentPage + 1).ToString();
|
||
}
|
||
if (GUILayout.Button(">>", GUILayout.Width(40)))
|
||
{
|
||
currentPage = Mathf.Max(0, totalPages - 1);
|
||
pageInputText = (currentPage + 1).ToString();
|
||
}
|
||
GUI.enabled = true;
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
EditorGUILayout.EndVertical();
|
||
|
||
EditorGUILayout.Space(5);
|
||
}
|
||
|
||
// 错误信息
|
||
if (!string.IsNullOrEmpty(errorMessage))
|
||
{
|
||
EditorGUILayout.HelpBox(errorMessage, MessageType.Error);
|
||
}
|
||
|
||
// 数据表格
|
||
if (dataLoaded && parsedData != null && filteredRowDataList.Count > 0)
|
||
{
|
||
DrawDataTable();
|
||
|
||
// 单元格详细信息显示
|
||
if (selectedRowIndex >= 0 && !string.IsNullOrEmpty(selectedColumnName))
|
||
{
|
||
DrawCellDetailPanel();
|
||
}
|
||
}
|
||
else if (dataLoaded && rowDataList.Count > 0 && filteredRowDataList.Count == 0)
|
||
{
|
||
EditorGUILayout.HelpBox("没有匹配筛选条件的数据", MessageType.Info);
|
||
}
|
||
else if (dataLoaded && rowDataList.Count == 0 && availableConfigs.Length == 0)
|
||
{
|
||
EditorGUILayout.HelpBox("未找到可用的配置数据", MessageType.Warning);
|
||
}
|
||
else if (!dataLoaded && selectedBytesFile == null)
|
||
{
|
||
EditorGUILayout.HelpBox("请选择一个.bytes文件进行查看\n\n推荐路径: Assets/Resources/ConfigData/AllConfigs.bytes", MessageType.Info);
|
||
}
|
||
}
|
||
|
||
private void LoadAndParseBytes()
|
||
{
|
||
if (selectedBytesFile == null)
|
||
{
|
||
errorMessage = "请先选择一个bytes文件!";
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
currentFilePath = AssetDatabase.GetAssetPath(selectedBytesFile);
|
||
byte[] bytes = selectedBytesFile.bytes;
|
||
|
||
if (bytes == null || bytes.Length == 0)
|
||
{
|
||
errorMessage = "文件为空或无法读取!";
|
||
return;
|
||
}
|
||
|
||
// 解析Thrift数据
|
||
parsedData = DeserializeThriftData(bytes);
|
||
|
||
if (parsedData != null)
|
||
{
|
||
PrepareTableData();
|
||
dataLoaded = true;
|
||
errorMessage = "";
|
||
}
|
||
else
|
||
{
|
||
errorMessage = "无法解析文件数据,请确认这是一个有效的Thrift配置文件。";
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
errorMessage = $"解析失败: {ex.Message}\n{ex.StackTrace}";
|
||
dataLoaded = false;
|
||
}
|
||
}
|
||
|
||
private void AutoLoadAllConfigs()
|
||
{
|
||
// 如果已经加载了文件,不再自动加载
|
||
if (selectedBytesFile != null)
|
||
return;
|
||
|
||
// 在整个Assets目录下查找AllConfigs.bytes
|
||
string[] guids = AssetDatabase.FindAssets("AllConfigs t:TextAsset");
|
||
|
||
foreach (string guid in guids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
|
||
// 检查是否为.bytes文件
|
||
if (path.EndsWith(".bytes", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
TextAsset asset = AssetDatabase.LoadAssetAtPath<TextAsset>(path);
|
||
if (asset != null)
|
||
{
|
||
selectedBytesFile = asset;
|
||
Debug.Log($"[Bytes查看器] 自动找到并加载: {path}");
|
||
LoadAndParseBytes();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果没找到AllConfigs.bytes,尝试查找任何Config相关的bytes文件
|
||
if (selectedBytesFile == null)
|
||
{
|
||
guids = AssetDatabase.FindAssets("Config t:TextAsset");
|
||
foreach (string guid in guids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
if (path.EndsWith(".bytes", StringComparison.OrdinalIgnoreCase) &&
|
||
path.Contains("Config"))
|
||
{
|
||
TextAsset asset = AssetDatabase.LoadAssetAtPath<TextAsset>(path);
|
||
if (asset != null)
|
||
{
|
||
selectedBytesFile = asset;
|
||
Debug.Log($"[Bytes查看器] 自动找到并加载: {path}");
|
||
LoadAndParseBytes();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private object DeserializeThriftData(byte[] bytes)
|
||
{
|
||
try
|
||
{
|
||
// 创建Thrift transport和protocol
|
||
using (var transport = new TMemoryBufferTransport(bytes, new TConfiguration()))
|
||
using (var protocol = new TBinaryProtocol(transport))
|
||
{
|
||
// 尝试读取为AllConfigs
|
||
var allConfigs = new Byway.Thrift.Data.AllConfigs();
|
||
allConfigs.ReadAsync(protocol, System.Threading.CancellationToken.None).Wait();
|
||
|
||
return allConfigs;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"Thrift反序列化失败: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
private void PrepareTableData()
|
||
{
|
||
columnNames.Clear();
|
||
rowDataList.Clear();
|
||
List<string> configNames = new List<string>();
|
||
|
||
if (parsedData == null)
|
||
return;
|
||
|
||
Type dataType = parsedData.GetType();
|
||
|
||
// 获取所有public属性
|
||
PropertyInfo[] properties = dataType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||
|
||
foreach (var prop in properties)
|
||
{
|
||
// 跳过__isset等内部属性
|
||
if (prop.Name.StartsWith("__"))
|
||
continue;
|
||
|
||
object propValue = prop.GetValue(parsedData);
|
||
if (propValue == null)
|
||
continue;
|
||
|
||
// 检查配置对象内部是否有字典
|
||
Type propType = propValue.GetType();
|
||
PropertyInfo[] innerProps = propType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||
|
||
foreach (var innerProp in innerProps)
|
||
{
|
||
if (innerProp.Name.StartsWith("__"))
|
||
continue;
|
||
|
||
object innerValue = innerProp.GetValue(propValue);
|
||
if (innerValue != null && innerValue is IDictionary dict && dict.Count > 0)
|
||
{
|
||
// 找到了包含数据的字典,记录这个配置
|
||
configNames.Add(prop.Name);
|
||
break; // 找到一个字典就够了
|
||
}
|
||
}
|
||
}
|
||
|
||
availableConfigs = configNames.ToArray();
|
||
|
||
if (availableConfigs.Length > 0)
|
||
{
|
||
selectedConfigIndex = 0;
|
||
FilterConfigList();
|
||
PrepareTableDataForSelectedConfig();
|
||
}
|
||
}
|
||
|
||
private void FilterConfigList()
|
||
{
|
||
if (string.IsNullOrEmpty(configSearchText))
|
||
{
|
||
filteredConfigs = availableConfigs;
|
||
}
|
||
else
|
||
{
|
||
string searchLower = configSearchText.ToLower();
|
||
filteredConfigs = availableConfigs
|
||
.Where(c => c.ToLower().Contains(searchLower))
|
||
.ToArray();
|
||
}
|
||
}
|
||
|
||
private void PerformGlobalSearch()
|
||
{
|
||
globalSearchResults.Clear();
|
||
|
||
if (string.IsNullOrEmpty(globalSearchText) || parsedData == null)
|
||
return;
|
||
|
||
string searchLower = globalSearchText.ToLower();
|
||
|
||
Type dataType = parsedData.GetType();
|
||
PropertyInfo[] properties = dataType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||
|
||
foreach (var prop in properties)
|
||
{
|
||
if (prop.Name.StartsWith("__"))
|
||
continue;
|
||
|
||
object propValue = prop.GetValue(parsedData);
|
||
if (propValue == null)
|
||
continue;
|
||
|
||
// 检查配置对象内部的字典
|
||
Type propType = propValue.GetType();
|
||
PropertyInfo[] innerProps = propType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||
|
||
foreach (var innerProp in innerProps)
|
||
{
|
||
if (innerProp.Name.StartsWith("__"))
|
||
continue;
|
||
|
||
object innerValue = innerProp.GetValue(propValue);
|
||
if (innerValue != null && innerValue is IDictionary dict && dict.Count > 0)
|
||
{
|
||
// 在这个配置的数据中搜索
|
||
SearchInConfig(prop.Name, dict, searchLower);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void SearchInConfig(string configName, IDictionary dataDict, string searchLower)
|
||
{
|
||
int rowIndex = 0;
|
||
foreach (DictionaryEntry entry in dataDict)
|
||
{
|
||
object rowData = entry.Value;
|
||
if (rowData == null)
|
||
{
|
||
rowIndex++;
|
||
continue;
|
||
}
|
||
|
||
Type rowType = rowData.GetType();
|
||
PropertyInfo[] props = rowType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||
.Where(p => !p.Name.StartsWith("__"))
|
||
.ToArray();
|
||
|
||
foreach (var prop in props)
|
||
{
|
||
object value = prop.GetValue(rowData);
|
||
string valueStr = FormatValue(value).ToLower();
|
||
|
||
if (valueStr.Contains(searchLower))
|
||
{
|
||
globalSearchResults.Add(new GlobalSearchResult
|
||
{
|
||
ConfigName = configName,
|
||
RowIndex = rowIndex,
|
||
ColumnName = prop.Name,
|
||
RowData = rowData,
|
||
MatchedValue = FormatValue(value)
|
||
});
|
||
|
||
// 每个配置最多显示100个结果
|
||
if (globalSearchResults.Count >= 100)
|
||
return;
|
||
}
|
||
}
|
||
|
||
rowIndex++;
|
||
}
|
||
}
|
||
|
||
private void JumpToSearchResult(GlobalSearchResult result)
|
||
{
|
||
// 切换到对应的配置
|
||
int configIndex = Array.IndexOf(availableConfigs, result.ConfigName);
|
||
if (configIndex >= 0 && configIndex != selectedConfigIndex)
|
||
{
|
||
selectedConfigIndex = configIndex;
|
||
PrepareTableDataForSelectedConfig();
|
||
}
|
||
|
||
// 找到对应的行在filteredRowDataList中的位置
|
||
int rowIndexInFiltered = filteredRowDataList.IndexOf(result.RowData);
|
||
if (rowIndexInFiltered >= 0)
|
||
{
|
||
// 计算应该跳转到哪一页
|
||
currentPage = rowIndexInFiltered / pageSize;
|
||
pageInputText = (currentPage + 1).ToString();
|
||
|
||
// 高亮选中这一行的单元格
|
||
selectedRowIndex = rowIndexInFiltered;
|
||
selectedColumnName = result.ColumnName;
|
||
selectedCellFullData = GetFullValueText(result.RowData.GetType().GetProperty(result.ColumnName).GetValue(result.RowData));
|
||
}
|
||
|
||
Repaint();
|
||
}
|
||
|
||
private void PrepareTableDataForSelectedConfig()
|
||
{
|
||
columnNames.Clear();
|
||
rowDataList.Clear();
|
||
|
||
if (parsedData == null || availableConfigs.Length == 0)
|
||
return;
|
||
|
||
string configName = availableConfigs[selectedConfigIndex];
|
||
currentConfigName = configName;
|
||
|
||
Type dataType = parsedData.GetType();
|
||
PropertyInfo configProp = dataType.GetProperty(configName);
|
||
if (configProp == null)
|
||
return;
|
||
|
||
object configValue = configProp.GetValue(parsedData);
|
||
if (configValue == null)
|
||
return;
|
||
|
||
// 在配置对象中查找字典属性
|
||
Type configType = configValue.GetType();
|
||
PropertyInfo[] innerProps = configType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||
|
||
foreach (var innerProp in innerProps)
|
||
{
|
||
if (innerProp.Name.StartsWith("__"))
|
||
continue;
|
||
|
||
object innerValue = innerProp.GetValue(configValue);
|
||
if (innerValue != null && innerValue is IDictionary dict && dict.Count > 0)
|
||
{
|
||
PrepareTableForConfig(configName, dict);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void PrepareTableForConfig(string configName, IDictionary dataDict)
|
||
{
|
||
if (dataDict == null || dataDict.Count == 0)
|
||
return;
|
||
|
||
// 获取第一个元素来确定列结构
|
||
object firstValue = null;
|
||
foreach (DictionaryEntry entry in dataDict)
|
||
{
|
||
firstValue = entry.Value;
|
||
break;
|
||
}
|
||
|
||
if (firstValue == null)
|
||
return;
|
||
|
||
Type itemType = firstValue.GetType();
|
||
PropertyInfo[] itemProps = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||
.Where(p => !p.Name.StartsWith("__"))
|
||
.OrderBy(p => p.Name == "Id" || p.Name == "ID" ? 0 : 1) // ID放在最前面
|
||
.ToArray();
|
||
|
||
// 构建列名
|
||
foreach (var prop in itemProps)
|
||
{
|
||
columnNames.Add(prop.Name);
|
||
}
|
||
|
||
// 构建行数据
|
||
foreach (DictionaryEntry entry in dataDict)
|
||
{
|
||
rowDataList.Add(entry.Value);
|
||
}
|
||
|
||
// 初始化筛选列选项
|
||
filterColumnOptions = new string[columnNames.Count];
|
||
for (int i = 0; i < columnNames.Count; i++)
|
||
{
|
||
filterColumnOptions[i] = columnNames[i];
|
||
}
|
||
|
||
// 重置筛选和分页
|
||
filterText = "";
|
||
lastFilterText = "";
|
||
currentPage = 0;
|
||
filterColumnIndex = 0;
|
||
ApplyFilter();
|
||
}
|
||
|
||
private void ApplyFilter()
|
||
{
|
||
filteredRowDataList.Clear();
|
||
|
||
if (string.IsNullOrEmpty(filterText) || filterColumnIndex < 0 || filterColumnIndex >= columnNames.Count)
|
||
{
|
||
filteredRowDataList.AddRange(rowDataList);
|
||
lastFilterText = filterText;
|
||
currentPage = 0;
|
||
return;
|
||
}
|
||
|
||
string filterColumn = columnNames[filterColumnIndex];
|
||
string filterLower = filterText.ToLower();
|
||
|
||
foreach (var rowData in rowDataList)
|
||
{
|
||
Type rowType = rowData.GetType();
|
||
PropertyInfo prop = rowType.GetProperty(filterColumn);
|
||
|
||
if (prop != null)
|
||
{
|
||
object value = prop.GetValue(rowData);
|
||
string valueStr = FormatValue(value).ToLower();
|
||
|
||
if (valueStr.Contains(filterLower))
|
||
{
|
||
filteredRowDataList.Add(rowData);
|
||
}
|
||
}
|
||
}
|
||
|
||
lastFilterText = filterText;
|
||
currentPage = 0;
|
||
}
|
||
|
||
private void DrawDataTable()
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
|
||
tableScrollPosition = EditorGUILayout.BeginScrollView(tableScrollPosition);
|
||
|
||
// 绘制表头
|
||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||
foreach (string columnName in columnNames)
|
||
{
|
||
EditorGUILayout.LabelField(columnName, EditorStyles.toolbarButton, GUILayout.MinWidth(80), GUILayout.MaxWidth(250));
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
// 计算当前页的数据范围
|
||
int startIndex = currentPage * pageSize;
|
||
int endIndex = Mathf.Min(startIndex + pageSize, filteredRowDataList.Count);
|
||
|
||
// 绘制当前页的数据行
|
||
for (int i = startIndex; i < endIndex; i++)
|
||
{
|
||
Color backgroundColor = (i - startIndex) % 2 == 0 ? new Color(0.85f, 0.85f, 0.85f, 0.3f) : Color.clear;
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
Rect rowRect = EditorGUILayout.BeginVertical();
|
||
|
||
if (backgroundColor != Color.clear)
|
||
{
|
||
EditorGUI.DrawRect(rowRect, backgroundColor);
|
||
}
|
||
|
||
DrawRowData(filteredRowDataList[i], i);
|
||
|
||
EditorGUILayout.EndVertical();
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawRowData(object rowData, int rowIndex)
|
||
{
|
||
if (rowData == null)
|
||
return;
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
Type rowType = rowData.GetType();
|
||
PropertyInfo[] props = rowType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||
.Where(p => !p.Name.StartsWith("__"))
|
||
.OrderBy(p => p.Name == "Id" || p.Name == "ID" ? 0 : 1)
|
||
.ToArray();
|
||
|
||
foreach (var prop in props)
|
||
{
|
||
object value = prop.GetValue(rowData);
|
||
string displayValue = FormatValue(value);
|
||
|
||
GUIStyle style = new GUIStyle(EditorStyles.label);
|
||
style.wordWrap = false;
|
||
style.clipping = TextClipping.Clip;
|
||
|
||
// 高亮选中的单元格
|
||
if (selectedRowIndex == rowIndex && selectedColumnName == prop.Name)
|
||
{
|
||
style.normal.textColor = new Color(0.2f, 0.5f, 1f);
|
||
style.fontStyle = FontStyle.Bold;
|
||
}
|
||
|
||
Rect cellRect = GUILayoutUtility.GetRect(new GUIContent(displayValue), style,
|
||
GUILayout.MinWidth(80), GUILayout.MaxWidth(250));
|
||
|
||
// 检测点击
|
||
if (Event.current.type == EventType.MouseDown && cellRect.Contains(Event.current.mousePosition))
|
||
{
|
||
selectedRowIndex = rowIndex;
|
||
selectedColumnName = prop.Name;
|
||
selectedCellFullData = GetFullValueText(value);
|
||
Event.current.Use();
|
||
Repaint();
|
||
}
|
||
|
||
GUI.Label(cellRect, displayValue, style);
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
private void DrawCellDetailPanel()
|
||
{
|
||
EditorGUILayout.Space(5);
|
||
EditorGUILayout.BeginVertical("box");
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("单元格详情", EditorStyles.boldLabel);
|
||
|
||
if (GUILayout.Button("关闭", GUILayout.Width(60)))
|
||
{
|
||
selectedRowIndex = -1;
|
||
selectedColumnName = "";
|
||
selectedCellFullData = "";
|
||
}
|
||
|
||
if (GUILayout.Button("复制", GUILayout.Width(60)))
|
||
{
|
||
EditorGUIUtility.systemCopyBuffer = selectedCellFullData;
|
||
Debug.Log($"已复制到剪贴板: {selectedCellFullData}");
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.LabelField($"列: {selectedColumnName}", EditorStyles.miniLabel);
|
||
EditorGUILayout.LabelField($"行: {selectedRowIndex}", EditorStyles.miniLabel);
|
||
|
||
EditorGUILayout.Space(3);
|
||
|
||
// 显示完整内容
|
||
detailScrollPosition = EditorGUILayout.BeginScrollView(detailScrollPosition, GUILayout.Height(100));
|
||
EditorGUILayout.SelectableLabel(selectedCellFullData, EditorStyles.textArea, GUILayout.ExpandHeight(true));
|
||
EditorGUILayout.EndScrollView();
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private string GetFullValueText(object value)
|
||
{
|
||
if (value == null)
|
||
return "null";
|
||
|
||
// 处理列表
|
||
if (value is IList list)
|
||
{
|
||
var items = new System.Text.StringBuilder();
|
||
items.AppendLine($"List ({list.Count} items):");
|
||
int index = 0;
|
||
foreach (var item in list)
|
||
{
|
||
items.AppendLine($" [{index}]: {FormatValue(item)}");
|
||
index++;
|
||
if (index > 100) // 限制显示数量
|
||
{
|
||
items.AppendLine($" ... and {list.Count - 100} more items");
|
||
break;
|
||
}
|
||
}
|
||
return items.ToString();
|
||
}
|
||
|
||
// 处理字典
|
||
if (value is IDictionary dict)
|
||
{
|
||
var items = new System.Text.StringBuilder();
|
||
items.AppendLine($"Dictionary ({dict.Count} pairs):");
|
||
int index = 0;
|
||
foreach (DictionaryEntry entry in dict)
|
||
{
|
||
items.AppendLine($" [{FormatValue(entry.Key)}]: {FormatValue(entry.Value)}");
|
||
index++;
|
||
if (index > 100)
|
||
{
|
||
items.AppendLine($" ... and {dict.Count - 100} more pairs");
|
||
break;
|
||
}
|
||
}
|
||
return items.ToString();
|
||
}
|
||
|
||
// 处理基本类型
|
||
Type valueType = value.GetType();
|
||
if (valueType.IsPrimitive || valueType == typeof(string) || valueType == typeof(decimal))
|
||
{
|
||
return value.ToString();
|
||
}
|
||
|
||
// 复杂对象 - 显示所有属性
|
||
var props = valueType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||
.Where(p => !p.Name.StartsWith("__"));
|
||
|
||
var sb = new System.Text.StringBuilder();
|
||
sb.AppendLine($"{valueType.Name}:");
|
||
foreach (var prop in props)
|
||
{
|
||
try
|
||
{
|
||
object propValue = prop.GetValue(value);
|
||
sb.AppendLine($" {prop.Name}: {FormatValue(propValue)}");
|
||
}
|
||
catch
|
||
{
|
||
sb.AppendLine($" {prop.Name}: [Error reading value]");
|
||
}
|
||
}
|
||
return sb.ToString();
|
||
}
|
||
|
||
private string FormatValue(object value)
|
||
{
|
||
if (value == null)
|
||
return "null";
|
||
|
||
// 处理列表/数组
|
||
if (value is IList list)
|
||
{
|
||
return $"[{list.Count} items]";
|
||
}
|
||
|
||
// 处理字典
|
||
if (value is IDictionary dict)
|
||
{
|
||
return $"[{dict.Count} pairs]";
|
||
}
|
||
|
||
// 处理基本类型
|
||
Type valueType = value.GetType();
|
||
if (valueType.IsPrimitive || valueType == typeof(string) || valueType == typeof(decimal))
|
||
{
|
||
return value.ToString();
|
||
}
|
||
|
||
// 复杂对象显示类型名
|
||
return $"[{valueType.Name}]";
|
||
}
|
||
}
|
||
}
|