516 lines
18 KiB
C#
516 lines
18 KiB
C#
using System;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
using Debug = UnityEngine.Debug;
|
||
|
||
namespace DesignTools
|
||
{
|
||
/// <summary>
|
||
/// 策划工具编辑器基类
|
||
/// 提供通用的Docs路径管理、Git操作、UI框架等功能
|
||
/// </summary>
|
||
public abstract class BaseDesignToolEditor : EditorWindow
|
||
{
|
||
#region 常量定义
|
||
|
||
/// <summary>
|
||
/// Design_SubModule相对路径
|
||
/// </summary>
|
||
protected const string DESIGN_SUBMODULE_PATH = "Assets/Design_SubModule";
|
||
|
||
#endregion
|
||
|
||
#region 字段定义
|
||
|
||
/// <summary>
|
||
/// Docs项目根目录路径
|
||
/// </summary>
|
||
protected string docsRootPath = "";
|
||
|
||
/// <summary>
|
||
/// 数据是否已加载
|
||
/// </summary>
|
||
protected bool isDataLoaded = false;
|
||
|
||
/// <summary>
|
||
/// 滚动位置
|
||
/// </summary>
|
||
protected Vector2 scrollPosition;
|
||
|
||
#endregion
|
||
|
||
#region 抽象方法和虚方法
|
||
|
||
/// <summary>
|
||
/// 获取EditorPrefs保存路径的Key
|
||
/// 子类需要返回唯一的Key,例如: "ItemConfigEditor_DocsPath"
|
||
/// </summary>
|
||
protected abstract string GetDocsPathPrefKey();
|
||
|
||
/// <summary>
|
||
/// 获取工具窗口标题
|
||
/// </summary>
|
||
protected abstract string GetWindowTitle();
|
||
|
||
/// <summary>
|
||
/// 加载具体的配置数据
|
||
/// 在Git检查和更新完成后调用
|
||
/// </summary>
|
||
protected abstract void LoadConfigData();
|
||
|
||
/// <summary>
|
||
/// 绘制数据编辑器UI
|
||
/// 在数据加载完成后调用
|
||
/// </summary>
|
||
protected abstract void DrawDataEditor();
|
||
|
||
/// <summary>
|
||
/// 获取工具窗口的最小尺寸
|
||
/// 默认为 (1000, 600)
|
||
/// </summary>
|
||
protected virtual Vector2 GetMinWindowSize()
|
||
{
|
||
return new Vector2(1000, 600);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否需要检查Design_SubModule分支
|
||
/// 默认为true,子类可以覆盖
|
||
/// </summary>
|
||
protected virtual bool NeedCheckDesignSubModuleBranch()
|
||
{
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取Docs配置路径相对于Docs根目录的路径
|
||
/// 默认为 "config",子类可以覆盖
|
||
/// </summary>
|
||
protected virtual string GetDocsConfigPath()
|
||
{
|
||
return "config";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否显示底部操作按钮(保存、重新加载等)
|
||
/// 默认为true
|
||
/// </summary>
|
||
protected virtual bool ShowBottomButtons()
|
||
{
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存数据到Excel
|
||
/// 子类可以覆盖此方法实现具体的保存逻辑
|
||
/// </summary>
|
||
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绘制方法
|
||
|
||
/// <summary>
|
||
/// 绘制工具栏
|
||
/// </summary>
|
||
protected virtual void DrawToolbar()
|
||
{
|
||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||
EditorGUILayout.LabelField(GetWindowTitle(), EditorStyles.boldLabel);
|
||
GUILayout.FlexibleSpace();
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绘制Docs路径选择区域
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绘制底部操作按钮
|
||
/// </summary>
|
||
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 路径选择和数据加载
|
||
|
||
/// <summary>
|
||
/// 选择Docs路径
|
||
/// </summary>
|
||
protected virtual void SelectDocsPath()
|
||
{
|
||
string selectedPath = EditorUtility.OpenFolderPanel("选择Docs项目根目录", "", "");
|
||
if (!string.IsNullOrEmpty(selectedPath))
|
||
{
|
||
docsRootPath = selectedPath;
|
||
EditorPrefs.SetString(GetDocsPathPrefKey(), docsRootPath);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载数据(最终方法,不可覆盖)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 校验Docs路径
|
||
/// </summary>
|
||
protected virtual bool ValidateDocsPath()
|
||
{
|
||
if (string.IsNullOrEmpty(docsRootPath) || !Directory.Exists(docsRootPath))
|
||
{
|
||
EditorUtility.DisplayDialog("错误", "请选择有效的Docs根目录", "确定");
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Git操作方法
|
||
|
||
/// <summary>
|
||
/// 执行Git命令
|
||
/// </summary>
|
||
/// <param name="workingDirectory">工作目录</param>
|
||
/// <param name="arguments">Git命令参数</param>
|
||
/// <param name="success">是否执行成功</param>
|
||
/// <returns>命令输出或错误信息</returns>
|
||
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}";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查并更新Docs仓库
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查并切换Design_SubModule到main分支
|
||
/// </summary>
|
||
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 工具方法
|
||
|
||
/// <summary>
|
||
/// 获取Docs配置文件的完整路径
|
||
/// </summary>
|
||
/// <param name="fileName">文件名(包含扩展名)</param>
|
||
/// <returns>完整路径</returns>
|
||
protected string GetDocsConfigFilePath(string fileName)
|
||
{
|
||
return Path.Combine(docsRootPath, GetDocsConfigPath(), fileName);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取Docs根目录下的完整路径
|
||
/// </summary>
|
||
/// <param name="relativePath">相对路径</param>
|
||
/// <returns>完整路径</returns>
|
||
protected string GetDocsFullPath(string relativePath)
|
||
{
|
||
return Path.Combine(docsRootPath, relativePath);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|