using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEngine; using OfficeOpenXml; using ArtResource; namespace DesignTools { /// /// 一次性工具:将 Emoji.xlsx / Face.xlsx / Avatar.xlsx 的 Icon 字段从 ArtItemData.Id 迁移为 ArtItemData.Name /// 使用完毕后可删除此文件 /// public static class MigrateIconFormatTool { private static readonly string[] SO_SEARCH_PATHS = new[] { "Assets/Art_SubModule/Art_SO", "Assets/Art_SubModule/Art_SO/Collections" }; private struct MigrationTarget { public string ExcelName; public string SheetName; public string SOTableName; } private static readonly MigrationTarget[] Targets = new[] { new MigrationTarget { ExcelName = "Emoji.xlsx", SheetName = "Emoji", SOTableName = "EmojiResource" }, new MigrationTarget { ExcelName = "Face.xlsx", SheetName = "Face", SOTableName = "HeadResource" }, new MigrationTarget { ExcelName = "Avatar.xlsx", SheetName = "Avatar", SOTableName = "HeadFrameResource" }, }; [MenuItem("策划工具/一次性迁移/Icon格式 ID→Name (Emoji+Face+Avatar)")] public static void Execute() { // 1. 选择 Docs/config 目录 string configDir = EditorUtility.OpenFolderPanel("选择 Docs/config 目录(包含 Emoji.xlsx、Face.xlsx、Avatar.xlsx)", "", ""); if (string.IsNullOrEmpty(configDir)) return; // 2. 加载所有 ArtTableSO var allArtTables = new List(); foreach (string searchPath in SO_SEARCH_PATHS) { string[] guids = AssetDatabase.FindAssets("t:ArtTableSO", new[] { searchPath }); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); var so = AssetDatabase.LoadAssetAtPath(path); if (so != null && !allArtTables.Any(x => x.TableName == so.TableName)) allArtTables.Add(so); } } if (allArtTables.Count == 0) { EditorUtility.DisplayDialog("错误", "未找到任何 ArtTableSO 资源", "确定"); return; } int totalMigrated = 0; int totalSkipped = 0; int totalErrors = 0; var allMessages = new List(); foreach (var target in Targets) { string excelPath = Path.Combine(configDir, target.ExcelName); if (!File.Exists(excelPath)) { allMessages.Add($"[{target.ExcelName}] 文件不存在,跳过"); continue; } var tableSO = allArtTables.FirstOrDefault(x => x.TableName == target.SOTableName); if (tableSO == null) { allMessages.Add($"[{target.ExcelName}] 未找到 ArtTableSO: {target.SOTableName},跳过"); totalErrors++; continue; } // 备份 string backupPath = excelPath + ".bak"; File.Copy(excelPath, backupPath, true); int migrated = 0, skipped = 0, errors = 0; using (var package = new ExcelPackage(new FileInfo(excelPath))) { var ws = package.Workbook.Worksheets[target.SheetName]; if (ws == null) { allMessages.Add($"[{target.ExcelName}] 未找到 Sheet: {target.SheetName},跳过"); totalErrors++; continue; } // 找 Icon 列和 Id 列 int iconCol = -1, idCol = -1; int colCount = ws.Dimension?.Columns ?? 0; for (int c = 1; c <= colCount; c++) { string h = ws.Cells[1, c].Text; if (h == "Icon") iconCol = c; else if (h == "Id") idCol = c; } if (iconCol < 0) { allMessages.Add($"[{target.ExcelName}] 未找到 Icon 列,跳过"); totalErrors++; continue; } int rowCount = ws.Dimension?.Rows ?? 0; for (int row = 3; row <= rowCount; row++) { string iconText = ws.Cells[row, iconCol].Text.Trim(); if (string.IsNullOrEmpty(iconText)) { continue; } // 判断是否是旧格式(纯数字) if (!int.TryParse(iconText, out int artItemId)) { // 已经是Name格式 skipped++; continue; } string rowId = idCol > 0 ? ws.Cells[row, idCol].Text : row.ToString(); var artItem = tableSO.Items.FirstOrDefault(x => x.Id == artItemId); if (artItem == null) { errors++; allMessages.Add($"[{target.ExcelName}] Row {row} Id={rowId}: {target.SOTableName} 中无 ArtItemId={artItemId}"); continue; } ws.Cells[row, iconCol].Value = artItem.Name; migrated++; } package.Save(); } totalMigrated += migrated; totalSkipped += skipped; totalErrors += errors; allMessages.Add($"[{target.ExcelName}] 成功: {migrated}, 跳过: {skipped}, 错误: {errors} (备份: {backupPath})"); } // 报告 string msg = $"Icon 迁移完成!\n\n" + $"总计成功: {totalMigrated} 条\n" + $"已是新格式(跳过): {totalSkipped} 条\n" + $"错误: {totalErrors} 条\n\n" + string.Join("\n", allMessages); EditorUtility.DisplayDialog("迁移结果", msg, "确定"); Debug.Log(msg); } } }