MeowmentDesign/Assets/Editor/Design_Tools/Collections/HeadConfigEditor.cs
2026-02-01 15:37:46 +08:00

988 lines
38 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using OfficeOpenXml;
using ArtResource;
using Debug = UnityEngine.Debug;
namespace DesignTools.Collections
{
/// <summary>
/// 头像配置Editor工具
/// 读取Docs/config/Face.xlsx关联Art_SO/HeadResources.asset
/// </summary>
public class HeadConfigEditor : EditorWindow
{
private const string HEAD_SO_PATH = "Assets/Art_SubModule/Art_SO";
private const string FACE_EXCEL_NAME = "Face.xlsx";
private const string LANGUAGE_EXCEL_NAME = "AllLanguage.xlsx";
private const string FACE_SHEET_NAME = "Face";
private const string LANGUAGE_SHEET_NAME = "client";
private const string DOCS_PATH_PREF_KEY = "HeadConfigEditor_DocsPath";
private const string DESIGN_SUBMODULE_PATH = "Assets/Design_SubModule";
private string docsRootPath = "";
private List<FaceData> faceDataList = new List<FaceData>();
private ArtTableSO headTableSO;
private Dictionary<string, Dictionary<string, string>> languageDict = new Dictionary<string, Dictionary<string, string>>();
private Vector2 scrollPosition;
private bool isDataLoaded = false;
private string pendingTooltipText = "";
[MenuItem("策划工具/收藏品/头像")]
public static void ShowWindow()
{
var window = GetWindow<HeadConfigEditor>("头像配置");
window.minSize = new Vector2(900, 600);
window.Show();
}
private void OnEnable()
{
// 设置EPPlus许可证
// ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
// 读取上次保存的路径
if (EditorPrefs.HasKey(DOCS_PATH_PREF_KEY))
{
docsRootPath = EditorPrefs.GetString(DOCS_PATH_PREF_KEY);
}
}
private void OnGUI()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUILayout.LabelField("头像配置工具", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
// Docs路径选择
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("Docs项目根目录", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
docsRootPath = EditorGUILayout.TextField("路径", docsRootPath);
if (GUILayout.Button("选择文件夹", GUILayout.Width(100)))
{
string selectedPath = EditorUtility.OpenFolderPanel("选择Docs项目根目录", "", "");
if (!string.IsNullOrEmpty(selectedPath))
{
docsRootPath = selectedPath;
EditorPrefs.SetString(DOCS_PATH_PREF_KEY, docsRootPath);
}
}
EditorGUILayout.EndHorizontal();
if (GUILayout.Button("加载配置数据", GUILayout.Height(30)))
{
LoadData();
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
// 数据编辑区域
if (isDataLoaded)
{
DrawDataEditor();
}
else
{
EditorGUILayout.HelpBox("请先选择Docs根目录并加载配置数据", MessageType.Info);
}
}
/// <summary>
/// 加载配置数据
/// </summary>
private void LoadData()
{
try
{
// 校验路径
if (string.IsNullOrEmpty(docsRootPath) || !Directory.Exists(docsRootPath))
{
EditorUtility.DisplayDialog("错误", "请选择有效的Docs根目录", "确定");
return;
}
// 1. 检查Docs是否为Git仓库并更新
if (!CheckAndUpdateDocsRepository())
{
return; // 检查失败,提示已在方法内显示
}
// 2. 检查Design_SubModule分支
if (!CheckAndSwitchDesignSubModuleBranch())
{
return; // 检查失败,提示已在方法内显示
}
// 校验Head SO是否存在
string[] soGuids = AssetDatabase.FindAssets("t:ArtTableSO", new[] { HEAD_SO_PATH });
if (soGuids.Length == 0)
{
EditorUtility.DisplayDialog("错误", $"未在 {HEAD_SO_PATH} 目录下找到头像资源配置ArtTableSO\n请先在美术资源配置工具中创建", "确定");
return;
}
// 查找名称为"HeadResource"的SO
ArtTableSO foundSO = null;
foreach (var guid in soGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
var so = AssetDatabase.LoadAssetAtPath<ArtTableSO>(path);
if (so != null && so.TableName == "HeadResource")
{
foundSO = so;
break;
}
}
if (foundSO == null)
{
EditorUtility.DisplayDialog("错误", "无法加载头像资源配置", "确定");
return;
}
headTableSO = foundSO;
if (headTableSO.Items == null || headTableSO.Items.Count == 0)
{
EditorUtility.DisplayDialog("错误", "头像资源配置数据为空", "确定");
return;
}
// 加载语言表
LoadLanguageData();
// 加载Face.xlsx
LoadFaceExcel();
isDataLoaded = true;
EditorUtility.DisplayDialog("成功", "配置数据加载成功!", "确定");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"加载数据失败: {e.Message}\n{e.StackTrace}", "确定");
isDataLoaded = false;
}
}
/// <summary>
/// 执行Git命令
/// </summary>
private string ExecuteGitCommand(string workingDirectory, string arguments, out bool success)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "git",
Arguments = arguments,
WorkingDirectory = workingDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using (Process process = Process.Start(startInfo))
{
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();
success = process.ExitCode == 0;
return success ? output : error;
}
}
catch (Exception e)
{
success = false;
return $"执行Git命令失败: {e.Message}";
}
}
/// <summary>
/// 检查并更新Docs仓库
/// </summary>
private bool CheckAndUpdateDocsRepository()
{
// 检查是否是Git仓库
string gitPath = Path.Combine(docsRootPath, ".git");
if (!Directory.Exists(gitPath))
{
EditorUtility.DisplayDialog("错误",
$"Docs目录不是Git仓库\n路径: {docsRootPath}\n\n请确保Docs项目已正确克隆",
"确定");
return false;
}
EditorUtility.DisplayProgressBar("检查更新", "正在检查Docs仓库远程更新...", 0.3f);
// 执行git fetch检查远程更新
string fetchResult = ExecuteGitCommand(docsRootPath, "fetch", out bool fetchSuccess);
if (!fetchSuccess)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Git Fetch失败",
$"无法检查远程更新:\n{fetchResult}\n\n请在SourceTree或GitHubDesktop中检查网络连接和仓库状态",
"确定");
return false;
}
EditorUtility.DisplayProgressBar("检查更新", "正在检查是否有新提交...", 0.6f);
// 检查本地和远程的差异
string statusResult = ExecuteGitCommand(docsRootPath, "status -uno", out bool statusSuccess);
if (!statusSuccess)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Git Status失败",
$"无法获取仓库状态:\n{statusResult}\n\n请在SourceTree或GitHubDesktop中检查仓库状态",
"确定");
return false;
}
// 检查是否有未提交的更改
if (statusResult.Contains("Changes not staged") || statusResult.Contains("Changes to be committed"))
{
EditorUtility.ClearProgressBar();
bool proceed = EditorUtility.DisplayDialog("警告:有未提交的更改",
"Docs仓库中有未提交的更改这可能导致拉取时产生冲突。\n\n建议先提交或暂存这些更改。\n\n是否继续加载配置不推荐",
"继续(不推荐)", "取消,前往处理");
if (!proceed)
{
Debug.Log("请在SourceTree或GitHubDesktop中处理未提交的更改");
return false;
}
}
// 检查是否behind远程
if (statusResult.Contains("Your branch is behind"))
{
EditorUtility.DisplayProgressBar("更新中", "正在从远程拉取最新代码...", 0.8f);
string pullResult = ExecuteGitCommand(docsRootPath, "pull", out bool pullSuccess);
EditorUtility.ClearProgressBar();
if (!pullSuccess)
{
// 检查是否是合并冲突
if (pullResult.Contains("CONFLICT") || pullResult.Contains("conflict"))
{
EditorUtility.DisplayDialog("拉取失败:存在冲突",
$"拉取远程更新时发生冲突:\n{pullResult}\n\n请在SourceTree或GitHubDesktop中解决冲突后再操作",
"确定");
}
else
{
EditorUtility.DisplayDialog("拉取失败",
$"无法拉取远程更新:\n{pullResult}\n\n请在SourceTree或GitHubDesktop中检查并解决问题",
"确定");
}
return false;
}
Debug.Log($"Docs仓库已更新到最新版本:\n{pullResult}");
EditorUtility.DisplayDialog("更新成功", "Docs仓库已更新到最新版本", "确定");
}
else
{
EditorUtility.ClearProgressBar();
Debug.Log("Docs仓库已是最新版本");
}
return true;
}
/// <summary>
/// 检查并切换Design_SubModule到main分支
/// </summary>
private bool CheckAndSwitchDesignSubModuleBranch()
{
string designSubModulePath = Path.Combine(Application.dataPath, "..", DESIGN_SUBMODULE_PATH);
designSubModulePath = Path.GetFullPath(designSubModulePath);
// 检查Design_SubModule是否存在
if (!Directory.Exists(designSubModulePath))
{
EditorUtility.DisplayDialog("错误",
$"Design_SubModule目录不存在:\n{designSubModulePath}\n\n请确保子模块已正确初始化",
"确定");
return false;
}
EditorUtility.DisplayProgressBar("检查分支", "正在检查Design_SubModule分支...", 0.5f);
// 检查当前分支
string branchResult = ExecuteGitCommand(designSubModulePath, "branch --show-current", out bool branchSuccess);
if (!branchSuccess)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Git Branch失败",
$"无法获取当前分支:\n{branchResult}\n\n请在SourceTree或GitHubDesktop中检查Design_SubModule状态",
"确定");
return false;
}
string currentBranch = branchResult.Trim();
if (currentBranch != "main")
{
EditorUtility.DisplayProgressBar("切换分支", "正在切换到main分支...", 0.8f);
string checkoutResult = ExecuteGitCommand(designSubModulePath, "checkout main", out bool checkoutSuccess);
EditorUtility.ClearProgressBar();
if (!checkoutSuccess)
{
EditorUtility.DisplayDialog("切换分支失败",
$"无法切换到main分支:\n{checkoutResult}\n\n当前分支: {currentBranch}\n\n请在SourceTree或GitHubDesktop中手动切换到main分支",
"确定");
return false;
}
Debug.Log($"Design_SubModule已从 {currentBranch} 切换到 main 分支");
EditorUtility.DisplayDialog("分支切换成功",
$"Design_SubModule已从 {currentBranch} 切换到 main 分支",
"确定");
}
else
{
EditorUtility.ClearProgressBar();
Debug.Log("Design_SubModule已在main分支");
}
return true;
}
/// <summary>
/// 加载语言数据
/// </summary>
private void LoadLanguageData()
{
languageDict.Clear();
string languageExcelPath = Path.Combine(docsRootPath, "config", LANGUAGE_EXCEL_NAME);
if (!File.Exists(languageExcelPath))
{
Debug.LogWarning($"未找到语言表: {languageExcelPath}");
return;
}
using (var package = new ExcelPackage(new FileInfo(languageExcelPath)))
{
var worksheet = package.Workbook.Worksheets[LANGUAGE_SHEET_NAME];
if (worksheet == null)
{
Debug.LogWarning($"语言表中未找到Sheet: {LANGUAGE_SHEET_NAME}");
return;
}
// 查找Key列和语言列
int keyColumnIndex = -1;
int zhCNColumnIndex = -1;
int enUSColumnIndex = -1;
int ptBRColumnIndex = -1;
int columnCount = worksheet.Dimension.Columns;
for (int col = 1; col <= columnCount; col++)
{
string header = worksheet.Cells[1, col].Text;
if (header == "key")
{
keyColumnIndex = col;
}
else if (header == "zh_CN")
{
zhCNColumnIndex = col;
}
else if (header == "en_US")
{
enUSColumnIndex = col;
}
else if (header == "pt_BR")
{
ptBRColumnIndex = col;
}
}
if (keyColumnIndex < 0)
{
Debug.LogWarning("语言表中未找到Key列");
return;
}
// 读取数据从第3行开始
int rowCount = worksheet.Dimension.Rows;
for (int row = 3; row <= rowCount; row++)
{
string key = worksheet.Cells[row, keyColumnIndex].Text;
if (string.IsNullOrEmpty(key)) continue;
if (!languageDict.ContainsKey(key))
{
languageDict[key] = new Dictionary<string, string>();
}
if (zhCNColumnIndex > 0)
{
languageDict[key]["zh_CN"] = worksheet.Cells[row, zhCNColumnIndex].Text;
}
if (enUSColumnIndex > 0)
{
languageDict[key]["en_US"] = worksheet.Cells[row, enUSColumnIndex].Text;
}
if (ptBRColumnIndex > 0)
{
languageDict[key]["pt_BR"] = worksheet.Cells[row, ptBRColumnIndex].Text;
}
}
}
}
/// <summary>
/// 加载Face.xlsx
/// </summary>
private void LoadFaceExcel()
{
faceDataList.Clear();
string faceExcelPath = Path.Combine(docsRootPath, "config", FACE_EXCEL_NAME);
if (!File.Exists(faceExcelPath))
{
throw new Exception($"未找到Face配置文件: {faceExcelPath}");
}
using (var package = new ExcelPackage(new FileInfo(faceExcelPath)))
{
var worksheet = package.Workbook.Worksheets[FACE_SHEET_NAME];
if (worksheet == null)
{
throw new Exception($"Face.xlsx中未找到Sheet: {FACE_SHEET_NAME}");
}
// 读取表头第1行
int idCol = -1, nameKeyCol = -1, initCol = -1, iconCol = -1;
int columnCount = worksheet.Dimension.Columns;
for (int col = 1; col <= columnCount; col++)
{
string header = worksheet.Cells[1, col].Text;
switch (header)
{
case "Id":
idCol = col;
break;
case "NameKey":
nameKeyCol = col;
break;
case "Init":
initCol = col;
break;
case "Icon":
iconCol = col;
break;
}
}
if (idCol < 0 || nameKeyCol < 0 || initCol < 0 || iconCol < 0)
{
throw new Exception("Face.xlsx表结构不正确缺少必要列Id/NameKey/Init/Icon");
}
// 读取数据从第3行开始
int rowCount = worksheet.Dimension.Rows;
for (int row = 3; row <= rowCount; row++)
{
string idText = worksheet.Cells[row, idCol].Text;
if (string.IsNullOrEmpty(idText)) continue;
if (!int.TryParse(idText, out int id)) continue;
string nameKey = worksheet.Cells[row, nameKeyCol].Text;
string initText = worksheet.Cells[row, initCol].Text;
string iconText = worksheet.Cells[row, iconCol].Text;
int init = 0;
if (!string.IsNullOrEmpty(initText))
{
int.TryParse(initText, out init);
}
int iconId = -1; // 默认为未选择
if (!string.IsNullOrEmpty(iconText))
{
if (!int.TryParse(iconText, out iconId))
{
iconId = -1; // 解析失败设为未选择
}
}
var faceData = new FaceData
{
Id = id,
NameKey = nameKey,
Init = init,
IconId = iconId
};
faceDataList.Add(faceData);
}
}
}
/// <summary>
/// 绘制数据编辑器
/// </summary>
private void DrawDataEditor()
{
pendingTooltipText = "";
EditorGUILayout.BeginVertical("box");
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"头像数据列表(共 {faceDataList.Count} 条)", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
GUI.backgroundColor = Color.cyan;
if (GUILayout.Button("+ 添加头像", GUILayout.Height(25), GUILayout.Width(100)))
{
AddNewFace();
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(3);
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
// 表头
EditorGUILayout.BeginHorizontal("box");
EditorGUILayout.LabelField("Id", EditorStyles.boldLabel, GUILayout.Width(50));
EditorGUILayout.LabelField("NameKey", EditorStyles.boldLabel, GUILayout.Width(150));
EditorGUILayout.LabelField("中文名称", EditorStyles.boldLabel, GUILayout.Width(120));
EditorGUILayout.LabelField("Init", EditorStyles.boldLabel, GUILayout.Width(50));
EditorGUILayout.LabelField("Icon", EditorStyles.boldLabel, GUILayout.Width(200));
EditorGUILayout.LabelField("预览", EditorStyles.boldLabel, GUILayout.Width(100));
EditorGUILayout.LabelField("操作", EditorStyles.boldLabel, GUILayout.Width(60));
EditorGUILayout.EndHorizontal();
// 数据行
for (int i = 0; i < faceDataList.Count; i++)
{
DrawFaceDataRow(faceDataList[i]);
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
EditorGUILayout.Space(10);
// 保存按钮
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUI.backgroundColor = Color.green;
if (GUILayout.Button("保存配置到Excel", GUILayout.Height(30), GUILayout.Width(200)))
{
SaveData();
}
GUI.backgroundColor = Color.white;
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
// 在所有内容绘制完后绘制tooltip确保在最上层
if (!string.IsNullOrEmpty(pendingTooltipText))
{
Vector2 tooltipSize = GUI.skin.box.CalcSize(new GUIContent(pendingTooltipText));
tooltipSize.x += 10;
tooltipSize.y += 10;
// 使用当前鼠标位置,而不是之前记录的位置
Vector2 mousePos = Event.current.mousePosition;
Rect tooltipRect = new Rect(
mousePos.x + 1,
mousePos.y + 1,
tooltipSize.x,
tooltipSize.y
);
GUI.Box(tooltipRect, pendingTooltipText);
}
}
/// <summary>
/// 绘制单行数据
/// </summary>
private void DrawFaceDataRow(FaceData data)
{
EditorGUILayout.BeginHorizontal("box");
// Id只读
GUI.enabled = false;
EditorGUILayout.IntField(data.Id, GUILayout.Width(50));
GUI.enabled = true;
// NameKey可编辑
data.NameKey = EditorGUILayout.TextField(data.NameKey, GUILayout.Width(150));
// 中文名称(只读预览,带即时多语言显示)
GUI.enabled = false;
string zhName = "未找到语言Key";
if (languageDict.ContainsKey(data.NameKey))
{
var langs = languageDict[data.NameKey];
if (langs.ContainsKey("zh_CN"))
{
zhName = langs["zh_CN"];
}
}
Rect nameRect = GUILayoutUtility.GetRect(new GUIContent(zhName), GUI.skin.textField, GUILayout.Width(120));
EditorGUI.TextField(nameRect, zhName);
// 检测鼠标悬停并准备tooltip内容不立即绘制
if (nameRect.Contains(Event.current.mousePosition) && languageDict.ContainsKey(data.NameKey))
{
var langs = languageDict[data.NameKey];
List<string> tooltipLines = new List<string>();
if (langs.ContainsKey("zh_CN") && !string.IsNullOrEmpty(langs["zh_CN"]))
{
tooltipLines.Add($"[中文] {langs["zh_CN"]}");
}
if (langs.ContainsKey("en_US") && !string.IsNullOrEmpty(langs["en_US"]))
{
tooltipLines.Add($"[English] {langs["en_US"]}");
}
if (langs.ContainsKey("pt_BR") && !string.IsNullOrEmpty(langs["pt_BR"]))
{
tooltipLines.Add($"[Português] {langs["pt_BR"]}");
}
if (tooltipLines.Count > 0)
{
pendingTooltipText = string.Join("\n", tooltipLines);
Repaint();
}
}
GUI.enabled = true;
// InitCheckbox
bool initChecked = data.Init == 1;
bool newInitChecked = EditorGUILayout.Toggle(initChecked, GUILayout.Width(50));
data.Init = newInitChecked ? 1 : 0;
// Icon下拉列表包含未选择选项
var iconItems = headTableSO.Items;
var iconNamesList = new List<string> { "未选择" };
iconNamesList.AddRange(iconItems.Select(x => x.Name));
var iconNames = iconNamesList.ToArray();
// 查找当前选中的索引
int currentIndex = 0; // 默认为"未选择"
var currentItem = iconItems.Find(x => x.Id == data.IconId);
if (currentItem != null)
{
// 找到了对应的Item索引需要+1因为第0项是"未选择"
int itemIndex = iconItems.IndexOf(currentItem);
if (itemIndex >= 0)
{
currentIndex = itemIndex + 1;
}
}
EditorGUI.BeginChangeCheck();
int newIndex = EditorGUILayout.Popup(currentIndex, iconNames, GUILayout.Width(200));
if (EditorGUI.EndChangeCheck())
{
if (newIndex == 0)
{
// 选择了"未选择"
data.IconId = -1;
}
else if (newIndex > 0 && newIndex <= iconItems.Count)
{
// 选择了具体的Icon索引需要-1
data.IconId = iconItems[newIndex - 1].Id;
}
}
// 预览和Desc提示
if (data.IconId >= 0)
{
var item = iconItems.Find(x => x.Id == data.IconId);
if (item != null)
{
// 直接使用Sprite引用
Sprite sprite = item.Sprite;
if (sprite != null && sprite.texture != null)
{
// 正确处理图集sprite的预览
Rect previewRect = GUILayoutUtility.GetRect(50, 50, GUILayout.Width(50), GUILayout.Height(50));
// 绘制背景
EditorGUI.DrawRect(previewRect, new Color(0.5f, 0.5f, 0.5f, 1f));
Rect texCoords = sprite.textureRect;
Texture2D tex = sprite.texture;
// 归一化UV坐标
Rect normalizedCoords = new Rect(
texCoords.x / tex.width,
texCoords.y / tex.height,
texCoords.width / tex.width,
texCoords.height / tex.height
);
// 计算保持宽高比的显示区域
float aspect = texCoords.width / texCoords.height;
Rect drawRect = previewRect;
if (aspect > 1f)
{
float height = drawRect.width / aspect;
drawRect.y += (drawRect.height - height) * 0.5f;
drawRect.height = height;
}
else
{
float width = drawRect.height * aspect;
drawRect.x += (drawRect.width - width) * 0.5f;
drawRect.width = width;
}
GUI.DrawTextureWithTexCoords(drawRect, tex, normalizedCoords, true);
// 添加Tooltip
if (!string.IsNullOrEmpty(item.Desc))
{
GUI.Label(previewRect, new GUIContent("", item.Desc));
}
}
else
{
GUILayoutUtility.GetRect(50, 50, GUILayout.Width(50), GUILayout.Height(50));
GUILayout.Space(-50);
EditorGUILayout.LabelField("无图片", GUILayout.Width(50));
}
}
else
{
// ID存在但找不到对应的Item
GUILayoutUtility.GetRect(50, 50, GUILayout.Width(50), GUILayout.Height(50));
GUILayout.Space(-50);
EditorGUILayout.LabelField("未选择", GUILayout.Width(50));
}
}
else
{
// IconId < 0未选择状态
GUILayoutUtility.GetRect(50, 50, GUILayout.Width(50), GUILayout.Height(50));
GUILayout.Space(-50);
EditorGUILayout.LabelField("未选择", GUILayout.Width(50));
}
// 删除按钮
GUI.backgroundColor = Color.red;
if (GUILayout.Button("删除", GUILayout.Width(60)))
{
if (EditorUtility.DisplayDialog("确认删除",
$"确定要删除头像 {data.Id} ({data.NameKey}) 吗?",
"删除", "取消"))
{
faceDataList.Remove(data);
}
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 添加新头像
/// </summary>
private void AddNewFace()
{
// 计算新ID当前最大ID + 1
int newId = 1;
if (faceDataList.Count > 0)
{
newId = faceDataList.Max(x => x.Id) + 1;
}
var newFace = new FaceData
{
Id = newId,
NameKey = "",
Init = 0,
IconId = -1
};
faceDataList.Add(newFace);
// 滚动到底部
scrollPosition = new Vector2(0, float.MaxValue);
}
/// <summary>
/// 保存数据到Excel
/// </summary>
private void SaveData()
{
try
{
string faceExcelPath = Path.Combine(docsRootPath, "config", FACE_EXCEL_NAME);
if (!File.Exists(faceExcelPath))
{
EditorUtility.DisplayDialog("错误", $"未找到Face配置文件: {faceExcelPath}", "确定");
return;
}
using (var package = new ExcelPackage(new FileInfo(faceExcelPath)))
{
var worksheet = package.Workbook.Worksheets[FACE_SHEET_NAME];
if (worksheet == null)
{
EditorUtility.DisplayDialog("错误", $"Face.xlsx中未找到Sheet: {FACE_SHEET_NAME}", "确定");
return;
}
// 查找列索引
int idCol = -1, nameKeyCol = -1, initCol = -1, iconCol = -1;
int columnCount = worksheet.Dimension.Columns;
for (int col = 1; col <= columnCount; col++)
{
string header = worksheet.Cells[1, col].Text;
switch (header)
{
case "Id":
idCol = col;
break;
case "NameKey":
nameKeyCol = col;
break;
case "Init":
initCol = col;
break;
case "Icon":
iconCol = col;
break;
}
}
// 更新和删除数据从第3行开始
int rowCount = worksheet.Dimension.Rows;
var processedIds = new HashSet<int>();
// 第一遍:更新现有行或标记删除
for (int row = rowCount; row >= 3; row--)
{
string idText = worksheet.Cells[row, idCol].Text;
if (string.IsNullOrEmpty(idText)) continue;
if (!int.TryParse(idText, out int id)) continue;
// 查找对应的FaceData
var faceData = faceDataList.Find(x => x.Id == id);
if (faceData != null)
{
// 更新数据
worksheet.Cells[row, nameKeyCol].Value = faceData.NameKey;
worksheet.Cells[row, initCol].Value = faceData.Init;
// IconId为-1时写入空字符串否则写入实际值
if (faceData.IconId < 0)
{
worksheet.Cells[row, iconCol].Value = "";
}
else
{
worksheet.Cells[row, iconCol].Value = faceData.IconId;
}
processedIds.Add(id);
}
else
{
// 删除行
worksheet.DeleteRow(row);
}
}
// 第二遍:添加新行
int currentRow = worksheet.Dimension?.Rows ?? 2;
foreach (var faceData in faceDataList)
{
if (!processedIds.Contains(faceData.Id))
{
// 这是新增的数据
currentRow++;
worksheet.Cells[currentRow, idCol].Value = faceData.Id;
worksheet.Cells[currentRow, nameKeyCol].Value = faceData.NameKey;
worksheet.Cells[currentRow, initCol].Value = faceData.Init;
if (faceData.IconId < 0)
{
worksheet.Cells[currentRow, iconCol].Value = "";
}
else
{
worksheet.Cells[currentRow, iconCol].Value = faceData.IconId;
}
}
}
// 保存文件
package.Save();
}
// 提示成功并提醒推送
bool understood = EditorUtility.DisplayDialog("保存成功",
"配置已保存到Excel文件\n\n" +
"⚠️ 重要提醒:\n" +
"请及时在SourceTree或GitHubDesktop中\n" +
"1. 提交(Commit)本次修改\n" +
"2. 推送(Push)到远程仓库\n\n" +
"避免与其他策划产生冲突!",
"我知道了");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"保存数据失败: {e.Message}\n{e.StackTrace}", "确定");
}
}
/// <summary>
/// 头像数据类
/// </summary>
private class FaceData
{
public int Id;
public string NameKey;
public int Init;
public int IconId;
}
}
}