using System; using System.Diagnostics; using System.IO; using UnityEditor; using UnityEngine; using Debug = UnityEngine.Debug; namespace DesignTools { /// /// 策划工具编辑器基类 /// 提供通用的Docs路径管理、Git操作、UI框架等功能 /// public abstract class BaseDesignToolEditor : EditorWindow { #region 常量定义 /// /// Design_SubModule相对路径 /// protected const string DESIGN_SUBMODULE_PATH = "Assets/Design_SubModule"; #endregion #region 字段定义 /// /// Docs项目根目录路径 /// protected string docsRootPath = ""; /// /// 数据是否已加载 /// protected bool isDataLoaded = false; /// /// 滚动位置 /// protected Vector2 scrollPosition; #endregion #region 抽象方法和虚方法 /// /// 获取EditorPrefs保存路径的Key /// 子类需要返回唯一的Key,例如: "ItemConfigEditor_DocsPath" /// protected abstract string GetDocsPathPrefKey(); /// /// 获取工具窗口标题 /// protected abstract string GetWindowTitle(); /// /// 加载具体的配置数据 /// 在Git检查和更新完成后调用 /// protected abstract void LoadConfigData(); /// /// 绘制数据编辑器UI /// 在数据加载完成后调用 /// protected abstract void DrawDataEditor(); /// /// 获取工具窗口的最小尺寸 /// 默认为 (1000, 600) /// protected virtual Vector2 GetMinWindowSize() { return new Vector2(1000, 600); } /// /// 是否需要检查Design_SubModule分支 /// 默认为true,子类可以覆盖 /// protected virtual bool NeedCheckDesignSubModuleBranch() { return true; } /// /// 获取Docs配置路径相对于Docs根目录的路径 /// 默认为 "config",子类可以覆盖 /// protected virtual string GetDocsConfigPath() { return "config"; } /// /// 是否显示底部操作按钮(保存、重新加载等) /// 默认为true /// protected virtual bool ShowBottomButtons() { return true; } /// /// 保存数据到Excel /// 子类可以覆盖此方法实现具体的保存逻辑 /// protected virtual void SaveDataToExcel() { EditorUtility.DisplayDialog("提示", "此工具未实现保存功能", "确定"); } #endregion #region Unity生命周期 protected virtual void OnEnable() { // 读取上次保存的路径 string prefKey = GetDocsPathPrefKey(); if (EditorPrefs.HasKey(prefKey)) { docsRootPath = EditorPrefs.GetString(prefKey); } } protected virtual void OnGUI() { // 绘制工具栏 DrawToolbar(); EditorGUILayout.Space(5); // 绘制Docs路径选择区域 DrawDocsPathSelector(); EditorGUILayout.Space(5); // 绘制数据编辑区域或提示信息 if (isDataLoaded) { DrawDataEditor(); // 绘制底部操作按钮 if (ShowBottomButtons()) { DrawBottomButtons(); } } else { EditorGUILayout.HelpBox("请先选择Docs根目录并加载配置数据", MessageType.Info); } } #endregion #region UI绘制方法 /// /// 绘制工具栏 /// protected virtual void DrawToolbar() { EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); EditorGUILayout.LabelField(GetWindowTitle(), EditorStyles.boldLabel); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); } /// /// 绘制Docs路径选择区域 /// protected virtual void DrawDocsPathSelector() { EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("Docs项目根目录", EditorStyles.boldLabel); EditorGUILayout.BeginHorizontal(); docsRootPath = EditorGUILayout.TextField("路径", docsRootPath); if (GUILayout.Button("选择文件夹", GUILayout.Width(100))) { SelectDocsPath(); } EditorGUILayout.EndHorizontal(); if (GUILayout.Button("加载配置数据", GUILayout.Height(30))) { LoadData(); } EditorGUILayout.EndVertical(); } /// /// 绘制底部操作按钮 /// protected virtual void DrawBottomButtons() { EditorGUILayout.Space(5); EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (GUILayout.Button("保存到Excel", GUILayout.Height(30), GUILayout.Width(150))) { SaveDataToExcel(); } if (GUILayout.Button("重新加载", GUILayout.Height(30), GUILayout.Width(150))) { LoadData(); } EditorGUILayout.EndHorizontal(); } #endregion #region 路径选择和数据加载 /// /// 选择Docs路径 /// protected virtual void SelectDocsPath() { string selectedPath = EditorUtility.OpenFolderPanel("选择Docs项目根目录", "", ""); if (!string.IsNullOrEmpty(selectedPath)) { docsRootPath = selectedPath; EditorPrefs.SetString(GetDocsPathPrefKey(), docsRootPath); } } /// /// 加载数据(最终方法,不可覆盖) /// protected void LoadData() { try { // 1. 校验路径 if (!ValidateDocsPath()) { return; } // 2. 检查Docs是否为Git仓库并更新 if (!CheckAndUpdateDocsRepository()) { return; } // 3. 检查Design_SubModule分支(如果需要) if (NeedCheckDesignSubModuleBranch()) { if (!CheckAndSwitchDesignSubModuleBranch()) { return; } } // 4. 调用子类的具体加载逻辑 LoadConfigData(); // 5. 设置数据加载完成标志 isDataLoaded = true; // 6. 显示成功提示 EditorUtility.DisplayDialog("成功", "配置数据加载成功!", "确定"); } catch (Exception e) { EditorUtility.DisplayDialog("错误", $"加载数据失败: {e.Message}\n{e.StackTrace}", "确定"); isDataLoaded = false; } } /// /// 校验Docs路径 /// protected virtual bool ValidateDocsPath() { if (string.IsNullOrEmpty(docsRootPath) || !Directory.Exists(docsRootPath)) { EditorUtility.DisplayDialog("错误", "请选择有效的Docs根目录", "确定"); return false; } return true; } #endregion #region Git操作方法 /// /// 执行Git命令 /// /// 工作目录 /// Git命令参数 /// 是否执行成功 /// 命令输出或错误信息 protected 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}"; } } /// /// 检查并更新Docs仓库 /// protected virtual bool CheckAndUpdateDocsRepository() { // 1. 检查是否为Git仓库 string gitPath = Path.Combine(docsRootPath, ".git"); if (!Directory.Exists(gitPath)) { EditorUtility.DisplayDialog("错误", $"Docs目录不是Git仓库\n路径: {docsRootPath}\n\n请确保Docs项目已正确克隆", "确定"); return false; } // 2. 执行Git Fetch 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; } // 3. 检查仓库状态 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; } // 4. 检查是否有未提交的更改 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; } } // 5. 如果远程有更新,则执行Pull 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; } /// /// 检查并切换Design_SubModule到main分支 /// protected virtual 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 分支"); } else { EditorUtility.ClearProgressBar(); Debug.Log("Design_SubModule已在main分支"); } return true; } #endregion #region 工具方法 /// /// 获取Docs配置文件的完整路径 /// /// 文件名(包含扩展名) /// 完整路径 protected string GetDocsConfigFilePath(string fileName) { return Path.Combine(docsRootPath, GetDocsConfigPath(), fileName); } /// /// 获取Docs根目录下的完整路径 /// /// 相对路径 /// 完整路径 protected string GetDocsFullPath(string relativePath) { return Path.Combine(docsRootPath, relativePath); } #endregion } }