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

938 lines
36 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/Emoji.xlsx关联Art_SO/Collections/EmojiResource.asset
/// </summary>
public class EmojiConfigEditor : EditorWindow
{
private const string EMOJI_SO_PATH = "Assets/Art_SubModule/Art_SO/Collections";
private const string EMOJI_SO_NAME = "EmojiResource";
private const string EMOJI_EXCEL_NAME = "Emoji.xlsx";
private const string LANGUAGE_EXCEL_NAME = "AllLanguage.xlsx";
private const string EMOJI_SHEET_NAME = "Emoji";
private const string LANGUAGE_SHEET_NAME = "client";
private const string DOCS_PATH_PREF_KEY = "EmojiConfigEditor_DocsPath";
private const string DESIGN_SUBMODULE_PATH = "Assets/Design_SubModule";
private string docsRootPath = "";
private List<EmojiData> emojiDataList = new List<EmojiData>();
private ArtTableSO emojiTableSO;
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<EmojiConfigEditor>("表情配置");
window.minSize = new Vector2(1000, 600);
window.Show();
}
private void OnEnable()
{
// 读取上次保存的路径
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;
}
// 校验Emoji SO是否存在
string emojiSOPath = Path.Combine(EMOJI_SO_PATH, $"{EMOJI_SO_NAME}.asset");
emojiTableSO = AssetDatabase.LoadAssetAtPath<ArtTableSO>(emojiSOPath);
if (emojiTableSO == null)
{
EditorUtility.DisplayDialog("错误",
$"未找到表情资源配置\n路径: {emojiSOPath}\n\n请先在美术资源配置工具中创建",
"确定");
return;
}
if (emojiTableSO.Items == null || emojiTableSO.Items.Count == 0)
{
EditorUtility.DisplayDialog("错误", "表情资源配置数据为空", "确定");
return;
}
// 加载语言表
LoadLanguageData();
// 加载Emoji.xlsx
LoadEmojiExcel();
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()
{
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);
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;
}
}
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);
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>
/// 加载Emoji.xlsx
/// </summary>
private void LoadEmojiExcel()
{
emojiDataList.Clear();
string emojiExcelPath = Path.Combine(docsRootPath, "config", EMOJI_EXCEL_NAME);
if (!File.Exists(emojiExcelPath))
{
throw new Exception($"未找到Emoji配置文件: {emojiExcelPath}");
}
using (var package = new ExcelPackage(new FileInfo(emojiExcelPath)))
{
var worksheet = package.Workbook.Worksheets[EMOJI_SHEET_NAME];
if (worksheet == null)
{
throw new Exception($"Emoji.xlsx中未找到Sheet: {EMOJI_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("Emoji.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 emojiData = new EmojiData
{
Id = id,
NameKey = nameKey,
Init = init,
IconId = iconId
};
emojiDataList.Add(emojiData);
}
}
}
/// <summary>
/// 绘制数据编辑器
/// </summary>
private void DrawDataEditor()
{
pendingTooltipText = "";
EditorGUILayout.BeginVertical("box");
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"表情数据列表(共 {emojiDataList.Count} 条)", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
GUI.backgroundColor = Color.cyan;
if (GUILayout.Button("+ 添加表情", GUILayout.Height(25), GUILayout.Width(100)))
{
AddNewEmoji();
}
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 < emojiDataList.Count; i++)
{
DrawEmojiDataRow(emojiDataList[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 DrawEmojiDataRow(EmojiData data)
{
EditorGUILayout.BeginHorizontal("box");
// Id可编辑
data.Id = EditorGUILayout.IntField(data.Id, GUILayout.Width(50));
// 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 = emojiTableSO.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)
{
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)
{
data.IconId = iconItems[newIndex - 1].Id;
}
}
// 预览
if (data.IconId >= 0)
{
var item = iconItems.Find(x => x.Id == data.IconId);
if (item != null)
{
Sprite sprite = item.Sprite;
if (sprite != null && sprite.texture != null)
{
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;
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);
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
{
GUILayoutUtility.GetRect(50, 50, GUILayout.Width(50), GUILayout.Height(50));
GUILayout.Space(-50);
EditorGUILayout.LabelField("未选择", GUILayout.Width(50));
}
}
else
{
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}) 吗?",
"删除", "取消"))
{
emojiDataList.Remove(data);
}
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 添加新表情
/// </summary>
private void AddNewEmoji()
{
int newId = 1;
if (emojiDataList.Count > 0)
{
newId = emojiDataList.Max(x => x.Id) + 1;
}
var newEmoji = new EmojiData
{
Id = newId,
NameKey = "",
Init = 0,
IconId = -1
};
emojiDataList.Add(newEmoji);
scrollPosition = new Vector2(0, float.MaxValue);
}
/// <summary>
/// 保存数据到Excel
/// </summary>
private void SaveData()
{
try
{
string emojiExcelPath = Path.Combine(docsRootPath, "config", EMOJI_EXCEL_NAME);
if (!File.Exists(emojiExcelPath))
{
EditorUtility.DisplayDialog("错误", $"未找到Emoji配置文件: {emojiExcelPath}", "确定");
return;
}
using (var package = new ExcelPackage(new FileInfo(emojiExcelPath)))
{
var worksheet = package.Workbook.Worksheets[EMOJI_SHEET_NAME];
if (worksheet == null)
{
EditorUtility.DisplayDialog("错误", $"Emoji.xlsx中未找到Sheet: {EMOJI_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;
var emojiData = emojiDataList.Find(x => x.Id == id);
if (emojiData != null)
{
worksheet.Cells[row, nameKeyCol].Value = emojiData.NameKey;
worksheet.Cells[row, initCol].Value = emojiData.Init;
if (emojiData.IconId < 0)
{
worksheet.Cells[row, iconCol].Value = "";
}
else
{
worksheet.Cells[row, iconCol].Value = emojiData.IconId;
}
processedIds.Add(id);
}
else
{
worksheet.DeleteRow(row);
}
}
// 第二遍:添加新行
int currentRow = worksheet.Dimension?.Rows ?? 2;
foreach (var emojiData in emojiDataList)
{
if (!processedIds.Contains(emojiData.Id))
{
currentRow++;
worksheet.Cells[currentRow, idCol].Value = emojiData.Id;
worksheet.Cells[currentRow, nameKeyCol].Value = emojiData.NameKey;
worksheet.Cells[currentRow, initCol].Value = emojiData.Init;
if (emojiData.IconId < 0)
{
worksheet.Cells[currentRow, iconCol].Value = "";
}
else
{
worksheet.Cells[currentRow, iconCol].Value = emojiData.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 EmojiData
{
public int Id;
public string NameKey;
public int Init;
public int IconId;
}
}
}