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
}
}