FM/Assets/Scripts/Editor/TaskManagerWindow.cs

690 lines
26 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using System.IO;
public class TaskManagerWindow : EditorWindow
{
// 颜色定义
private static class Colors
{
public static readonly Color PriorityLow = new Color(0.6f, 0.6f, 0.6f);
public static readonly Color PriorityMedium = new Color(0.2f, 0.5f, 0.8f);
public static readonly Color PriorityHigh = new Color(0.9f, 0.5f, 0.3f);
public static readonly Color PriorityUrgent = new Color(0.9f, 0.2f, 0.2f);
public static readonly Color StatusNotStarted = new Color(0.7f, 0.7f, 0.7f);
public static readonly Color StatusInProgress = new Color(0.2f, 0.7f, 1f);
public static readonly Color StatusCompleted = new Color(0.3f, 0.8f, 0.3f);
public static readonly Color StatusPaused = new Color(0.8f, 0.8f, 0.3f);
public static readonly Color StatusAll = new Color(0.5f, 0.5f, 0.5f);
public static readonly Color CategoryDevelopment = new Color(0.4f, 0.6f, 0.8f);
public static readonly Color CategoryBugFix = new Color(0.8f, 0.4f, 0.4f);
public static readonly Color CategoryOptimization = new Color(0.4f, 0.8f, 0.4f);
public static readonly Color CategoryUI = new Color(0.8f, 0.6f, 0.8f);
public static readonly Color CategoryTesting = new Color(0.6f, 0.8f, 0.8f);
public static readonly Color CategoryDocumentation = new Color(0.8f, 0.8f, 0.4f);
public static readonly Color CategoryOther = new Color(0.7f, 0.7f, 0.7f);
public static readonly Color ProgressBar = new Color(0.2f, 0.6f, 1f);
public static readonly Color ProgressBarBg = new Color(0.8f, 0.8f, 0.8f);
public static readonly Color StatsBg = new Color(0.9f, 0.9f, 0.9f);
}
// 任务枚举定义
public enum TaskPriority { , , , }
public enum TaskStatus { , , , }
public enum TaskCategory { , BUG, , UI, , , }
// 任务数据类
[Serializable]
public class TaskItem
{
public string id;
public string title;
public string description;
public TaskCategory category;
public TaskPriority priority;
public TaskStatus status;
public string creator;
public string assignee;
public DateTime createTime;
public DateTime? dueTime;
public DateTime? completeTime;
public float progress;
}
[Serializable]
private class TaskWrapper
{
public List<TaskItem> tasks = new List<TaskItem>();
}
// 数据与状态
private List<TaskItem> allTasks = new List<TaskItem>();
private List<TaskItem> filteredTasks = new List<TaskItem>();
private Vector2 mainScrollPosition;
private string savePath;
private bool isEditing = false;
private TaskItem currentTask = null;
private string searchKeyword = "";
private TaskStatus? filterStatus = null; // null表示"全部"状态
private TaskCategory? filterCategory = null;
private DateTime tempDueTime = DateTime.Now;
private bool isDueTimeSet = false;
[MenuItem("Tool/任务管理器")]
public static void ShowWindow()
{
var window = GetWindow<TaskManagerWindow>("任务管理器");
window.minSize = new Vector2(800, 600);
}
private void OnEnable()
{
savePath = Path.Combine(Application.dataPath, "../TaskData.json");
LoadTasks();
UpdateFilteredTasks();
}
private void OnFocus()
{
UpdateFilteredTasks();
Repaint();
}
private void OnGUI()
{
DrawToolbar();
GUILayout.Space(10);
// 显示分类状态统计
if (filterCategory.HasValue)
{
DrawCategoryStatusStats(filterCategory.Value);
GUILayout.Space(10);
}
mainScrollPosition = EditorGUILayout.BeginScrollView(mainScrollPosition);
if (isEditing)
{
DrawTaskEditor();
}
else
{
DrawTaskList();
}
EditorGUILayout.EndScrollView();
}
// 绘制分类状态统计信息
private void DrawCategoryStatusStats(TaskCategory category)
{
// 计算该分类下各状态的任务数量
int total = allTasks.FindAll(t => t.category == category).Count;
int notStarted = allTasks.FindAll(t => t.category == category && t.status == TaskStatus.).Count;
int inProgress = allTasks.FindAll(t => t.category == category && t.status == TaskStatus.).Count;
int completed = allTasks.FindAll(t => t.category == category && t.status == TaskStatus.).Count;
int paused = allTasks.FindAll(t => t.category == category && t.status == TaskStatus.).Count;
// 绘制统计面板
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label($"{category} 任务状态统计 (共 {total} 个)", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
DrawStatusStat("未开始", notStarted, Colors.StatusNotStarted);
DrawStatusStat("进行中", inProgress, Colors.StatusInProgress);
DrawStatusStat("已完成", completed, Colors.StatusCompleted);
DrawStatusStat("已暂停", paused, Colors.StatusPaused);
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
// 绘制单个状态的统计项
private void DrawStatusStat(string label, int count, Color color)
{
float percentage = allTasks.Count > 0 && filterCategory.HasValue ?
(float)count / allTasks.FindAll(t => t.category == filterCategory.Value).Count * 100 : 0;
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
EditorGUILayout.BeginHorizontal();
GUILayout.Label(label, GUILayout.Width(60));
GUILayout.Label($"{count}个 ({percentage:F1}%)", EditorStyles.miniLabel);
EditorGUILayout.EndHorizontal();
// 绘制统计进度条
Rect rect = EditorGUILayout.GetControlRect(GUILayout.Height(8));
EditorGUI.DrawRect(rect, Colors.StatsBg);
rect.width *= Mathf.Clamp01(percentage / 100f);
EditorGUI.DrawRect(rect, color);
EditorGUILayout.EndVertical();
}
private void DrawToolbar()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
var newTaskStyle = new GUIStyle(EditorStyles.toolbarButton);
newTaskStyle.normal.textColor = Color.white;
newTaskStyle.normal.background = MakeColorTexture(Colors.CategoryDevelopment);
if (GUILayout.Button("新增任务", newTaskStyle))
{
StartEditingNewTask();
}
GUILayout.FlexibleSpace();
string newSearchKeyword = EditorGUILayout.TextField("", searchKeyword, EditorStyles.toolbarSearchField, GUILayout.Width(200));
if (newSearchKeyword != searchKeyword)
{
searchKeyword = newSearchKeyword;
UpdateFilteredTasks();
}
// 状态筛选下拉框(包含"全部"选项)
EditorGUILayout.LabelField("状态:", EditorStyles.label, GUILayout.Width(40));
if (GUILayout.Button(filterStatus.HasValue ? filterStatus.Value.ToString() : "全部",
EditorStyles.toolbarDropDown, GUILayout.Width(80)))
{
// 创建状态筛选菜单
GenericMenu menu = new GenericMenu();
menu.AddItem(new GUIContent("全部"), !filterStatus.HasValue, () =>
{
filterStatus = null;
UpdateFilteredTasks();
});
foreach (TaskStatus status in Enum.GetValues(typeof(TaskStatus)))
{
bool isSelected = filterStatus.HasValue && filterStatus.Value == status;
menu.AddItem(new GUIContent(status.ToString()), isSelected, () =>
{
filterStatus = status;
UpdateFilteredTasks();
});
}
menu.ShowAsContext();
}
if (GUILayout.Button("清除筛选", EditorStyles.toolbarButton, GUILayout.Width(80)))
{
filterStatus = null;
filterCategory = null;
searchKeyword = "";
UpdateFilteredTasks();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.Label("分类筛选:", EditorStyles.label, GUILayout.Width(70));
foreach (TaskCategory category in Enum.GetValues(typeof(TaskCategory)))
{
bool isSelected = filterCategory.HasValue && filterCategory.Value == category;
GUIStyle buttonStyle = new GUIStyle(isSelected ? EditorStyles.toolbarButton : EditorStyles.miniButton);
buttonStyle.normal.textColor = Color.white;
buttonStyle.normal.background = MakeColorTexture(GetCategoryColor(category));
if (GUILayout.Button(category.ToString(), buttonStyle, GUILayout.Width(90)))
{
filterCategory = isSelected ? (TaskCategory?)null : category;
UpdateFilteredTasks();
}
}
EditorGUILayout.EndHorizontal();
}
private void DrawTaskList()
{
EditorGUILayout.LabelField($"任务总数: {allTasks.Count} | 显示: {filteredTasks.Count}", EditorStyles.miniLabel);
if (filteredTasks.Count == 0)
{
EditorGUILayout.LabelField("没有找到符合筛选条件的任务", EditorStyles.centeredGreyMiniLabel);
string filterInfo = "当前筛选: ";
if (filterStatus.HasValue) filterInfo += $"状态={filterStatus.Value} ";
else filterInfo += "状态=全部 ";
if (filterCategory.HasValue) filterInfo += $"分类={filterCategory.Value} ";
else filterInfo += "分类=全部 ";
if (!string.IsNullOrEmpty(searchKeyword)) filterInfo += $"关键词={searchKeyword}";
EditorGUILayout.LabelField(filterInfo, EditorStyles.miniLabel);
return;
}
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.Label("标题", EditorStyles.boldLabel, GUILayout.Width(200));
GUILayout.Label("分类", EditorStyles.boldLabel, GUILayout.Width(100));
GUILayout.Label("优先级", EditorStyles.boldLabel, GUILayout.Width(80));
GUILayout.Label("状态", EditorStyles.boldLabel, GUILayout.Width(80));
GUILayout.Label("负责人", EditorStyles.boldLabel, GUILayout.Width(100));
GUILayout.Label("进度", EditorStyles.boldLabel, GUILayout.Width(100));
GUILayout.Label("截止日期", EditorStyles.boldLabel, GUILayout.Width(120));
EditorGUILayout.EndHorizontal();
foreach (var task in filteredTasks)
{
EditorGUILayout.BeginHorizontal();
var titleStyle = new GUIStyle(EditorStyles.label);
titleStyle.normal.textColor = GetStatusColor(task.status);
GUILayout.Label(task.title, titleStyle, GUILayout.Width(200));
var categoryStyle = new GUIStyle(EditorStyles.label);
categoryStyle.normal.textColor = Color.white;
categoryStyle.alignment = TextAnchor.MiddleCenter;
categoryStyle.normal.background = MakeColorTexture(GetCategoryColor(task.category));
categoryStyle.padding = new RectOffset(4, 4, 2, 2);
GUILayout.Label(task.category.ToString(), categoryStyle, GUILayout.Width(100));
var priorityStyle = new GUIStyle(EditorStyles.label);
priorityStyle.normal.textColor = GetPriorityColor(task.priority);
GUILayout.Label(task.priority.ToString(), priorityStyle, GUILayout.Width(80));
var statusStyle = new GUIStyle(EditorStyles.label);
statusStyle.normal.textColor = GetStatusColor(task.status);
GUILayout.Label(task.status.ToString(), statusStyle, GUILayout.Width(80));
GUILayout.Label(task.assignee, GUILayout.Width(100));
EditorGUILayout.BeginVertical();
GUILayout.Label($"{task.progress}%", GUILayout.Height(14));
DrawProgressBar(task.progress);
EditorGUILayout.EndVertical();
string dueDateText = task.dueTime.HasValue ? task.dueTime.Value.ToString("yyyy-MM-dd HH:mm") : "无";
var dueDateStyle = new GUIStyle(EditorStyles.label);
if (task.dueTime.HasValue && task.dueTime < DateTime.Now && task.status != TaskStatus.)
{
dueDateStyle.normal.textColor = Color.red;
}
GUILayout.Label(dueDateText, dueDateStyle, GUILayout.Width(120));
if (GUILayout.Button("编辑", EditorStyles.miniButton, GUILayout.Width(50)))
{
StartEditingExistingTask(task);
}
if (GUILayout.Button("删除", EditorStyles.miniButton, GUILayout.Width(50)))
{
DeleteTask(task);
}
EditorGUILayout.EndHorizontal();
GUILayout.Space(2);
}
}
private void DeleteTask(TaskItem task)
{
if (EditorUtility.DisplayDialog("确认删除", $"确定要删除任务「{task.title}」吗?", "删除", "取消"))
{
int index = allTasks.IndexOf(task);
if (index != -1)
{
allTasks.RemoveAt(index);
SaveTasks();
UpdateFilteredTasks();
Repaint();
}
}
}
private void DrawProgressBar(float progress)
{
Rect rect = EditorGUILayout.GetControlRect(GUILayout.Height(16));
EditorGUI.DrawRect(rect, Colors.ProgressBarBg);
Color progressColor = progress >= 100 ? Colors.StatusCompleted :
progress > 50 ? new Color(0.2f, 0.7f, 0.5f) : Colors.ProgressBar;
rect.width *= Mathf.Clamp01(progress / 100f);
EditorGUI.DrawRect(rect, progressColor);
}
private void DrawTaskEditor()
{
var titleStyle = new GUIStyle(EditorStyles.boldLabel);
titleStyle.fontSize = 14;
titleStyle.normal.textColor = Colors.CategoryDevelopment;
GUILayout.Label(isEditing && currentTask.id == "" ? "新增任务" : "编辑任务", titleStyle);
GUILayout.Space(10);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
currentTask.title = EditorGUILayout.TextField("任务标题*", currentTask.title);
EditorGUILayout.BeginHorizontal();
GUILayout.Label("任务分类:", GUILayout.Width(80));
currentTask.category = (TaskCategory)EditorGUILayout.EnumPopup(currentTask.category);
Rect colorRect = EditorGUILayout.GetControlRect(GUILayout.Width(20), GUILayout.Height(20));
EditorGUI.DrawRect(colorRect, GetCategoryColor(currentTask.category));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
GUILayout.Label("优先级:", GUILayout.Width(80));
currentTask.priority = (TaskPriority)EditorGUILayout.EnumPopup(currentTask.priority);
Rect priorityRect = EditorGUILayout.GetControlRect(GUILayout.Width(20), GUILayout.Height(20));
EditorGUI.DrawRect(priorityRect, GetPriorityColor(currentTask.priority));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
GUILayout.Label("状态:", GUILayout.Width(80));
currentTask.status = (TaskStatus)EditorGUILayout.EnumPopup(currentTask.status);
Rect statusRect = EditorGUILayout.GetControlRect(GUILayout.Width(20), GUILayout.Height(20));
EditorGUI.DrawRect(statusRect, GetStatusColor(currentTask.status));
EditorGUILayout.EndHorizontal();
GUILayout.Space(10);
currentTask.creator = EditorGUILayout.TextField("创建人", currentTask.creator);
currentTask.assignee = EditorGUILayout.TextField("负责人*", currentTask.assignee);
GUILayout.Space(10);
EditorGUILayout.BeginHorizontal();
GUILayout.Label("创建时间:", GUILayout.Width(80));
GUILayout.Label(currentTask.createTime.ToString("yyyy-MM-dd HH:mm"), EditorStyles.label);
EditorGUILayout.EndHorizontal();
isDueTimeSet = EditorGUILayout.Toggle("设置截止时间", currentTask.dueTime.HasValue);
if (isDueTimeSet)
{
if (!currentTask.dueTime.HasValue)
{
tempDueTime = DateTime.Now;
}
else if (tempDueTime != currentTask.dueTime.Value)
{
tempDueTime = currentTask.dueTime.Value;
}
EditorGUILayout.BeginHorizontal();
GUILayout.Label("日期:", GUILayout.Width(60));
int year = EditorGUILayout.IntField(tempDueTime.Year, GUILayout.Width(60));
int month = EditorGUILayout.IntField(tempDueTime.Month, GUILayout.Width(40));
int day = EditorGUILayout.IntField(tempDueTime.Day, GUILayout.Width(40));
GUILayout.Label("时间:", GUILayout.Width(40));
int hour = EditorGUILayout.IntField(tempDueTime.Hour, GUILayout.Width(40));
int minute = EditorGUILayout.IntField(tempDueTime.Minute, GUILayout.Width(40));
EditorGUILayout.EndHorizontal();
try
{
tempDueTime = new DateTime(year, month, day, hour, minute, 0);
currentTask.dueTime = tempDueTime;
if (currentTask.dueTime < DateTime.Now && currentTask.status != TaskStatus.)
{
EditorGUILayout.LabelField("⚠️ 截止时间已过期", EditorStyles.helpBox);
}
}
catch
{
EditorGUILayout.LabelField("日期时间格式错误!", EditorStyles.label);
}
}
else
{
currentTask.dueTime = null;
}
if (currentTask.status == TaskStatus. && currentTask.completeTime.HasValue)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Label("完成时间:", GUILayout.Width(80));
GUILayout.Label(currentTask.completeTime.Value.ToString("yyyy-MM-dd HH:mm"), EditorStyles.label);
EditorGUILayout.EndHorizontal();
}
GUILayout.Space(10);
currentTask.progress = EditorGUILayout.Slider("完成进度", currentTask.progress, 0, 100);
DrawProgressBar(currentTask.progress);
EditorGUILayout.LabelField("任务描述:");
currentTask.description = EditorGUILayout.TextArea(currentTask.description, GUILayout.Height(100));
GUILayout.Space(20);
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
var cancelStyle = new GUIStyle(EditorStyles.miniButton);
cancelStyle.normal.background = MakeColorTexture(new Color(0.7f, 0.7f, 0.7f));
var saveStyle = new GUIStyle(EditorStyles.miniButton);
saveStyle.normal.background = MakeColorTexture(Colors.StatusCompleted);
saveStyle.normal.textColor = Color.white;
if (GUILayout.Button("取消", cancelStyle, GUILayout.Width(80)))
{
isEditing = false;
currentTask = null;
Repaint();
}
if (GUILayout.Button("保存", saveStyle, GUILayout.Width(80)))
{
SaveCurrentTask();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void SaveCurrentTask()
{
if (string.IsNullOrEmpty(currentTask.title?.Trim()))
{
EditorUtility.DisplayDialog("输入错误", "任务标题不能为空", "确定");
return;
}
if (string.IsNullOrEmpty(currentTask.assignee?.Trim()))
{
EditorUtility.DisplayDialog("输入错误", "负责人不能为空", "确定");
return;
}
if (currentTask.status == TaskStatus. && !currentTask.completeTime.HasValue)
{
currentTask.completeTime = DateTime.Now;
currentTask.progress = 100;
}
else if (currentTask.status != TaskStatus. && currentTask.progress >= 100)
{
currentTask.status = TaskStatus.;
currentTask.completeTime = DateTime.Now;
}
int existingIndex = allTasks.FindIndex(t => t.id == currentTask.id);
if (existingIndex != -1)
{
allTasks.RemoveAt(existingIndex);
}
allTasks.Add(currentTask);
SaveTasks();
UpdateFilteredTasks();
Repaint();
isEditing = false;
currentTask = null;
}
private void UpdateFilteredTasks()
{
filteredTasks = new List<TaskItem>(allTasks);
if (!string.IsNullOrEmpty(searchKeyword))
{
string keyword = searchKeyword.ToLower();
filteredTasks.RemoveAll(t =>
t.title == null || !t.title.ToLower().Contains(keyword) &&
t.description == null || !t.description.ToLower().Contains(keyword) &&
t.assignee == null || !t.assignee.ToLower().Contains(keyword)
);
}
// 状态筛选只有当filterStatus有值时才筛选
if (filterStatus.HasValue)
{
filteredTasks.RemoveAll(t => t.status != filterStatus.Value);
}
// 当filterStatus为null时即"全部"),不进行状态筛选
if (filterCategory.HasValue)
{
filteredTasks.RemoveAll(t => t.category != filterCategory.Value);
}
filteredTasks.Sort((a, b) =>
{
int priorityCompare = b.priority.CompareTo(a.priority);
if (priorityCompare != 0) return priorityCompare;
if (a.dueTime.HasValue && b.dueTime.HasValue)
{
return a.dueTime.Value.CompareTo(b.dueTime.Value);
}
return a.dueTime.HasValue ? -1 : 1;
});
}
private Color GetPriorityColor(TaskPriority priority)
{
return priority switch
{
TaskPriority. => Colors.PriorityLow,
TaskPriority. => Colors.PriorityMedium,
TaskPriority. => Colors.PriorityHigh,
TaskPriority. => Colors.PriorityUrgent,
_ => Color.black
};
}
private Color GetStatusColor(TaskStatus status)
{
return status switch
{
TaskStatus. => Colors.StatusNotStarted,
TaskStatus. => Colors.StatusInProgress,
TaskStatus. => Colors.StatusCompleted,
TaskStatus. => Colors.StatusPaused,
_ => Color.black
};
}
private Color GetCategoryColor(TaskCategory category)
{
return category switch
{
TaskCategory. => Colors.CategoryDevelopment,
TaskCategory.BUG => Colors.CategoryBugFix,
TaskCategory. => Colors.CategoryOptimization,
TaskCategory.UI => Colors.CategoryUI,
TaskCategory. => Colors.CategoryTesting,
TaskCategory. => Colors.CategoryDocumentation,
TaskCategory. => Colors.CategoryOther,
_ => Colors.CategoryOther
};
}
private Texture2D MakeColorTexture(Color color)
{
Texture2D tex = new Texture2D(1, 1);
tex.SetPixel(0, 0, color);
tex.Apply();
return tex;
}
private void StartEditingNewTask()
{
currentTask = new TaskItem
{
id = Guid.NewGuid().ToString(),
createTime = DateTime.Now,
creator = System.Environment.UserName,
progress = 0,
status = TaskStatus.,
priority = TaskPriority.
};
isEditing = true;
isDueTimeSet = false;
}
private void StartEditingExistingTask(TaskItem task)
{
currentTask = new TaskItem()
{
id = task.id,
title = task.title,
description = task.description,
category = task.category,
priority = task.priority,
status = task.status,
creator = task.creator,
assignee = task.assignee,
createTime = task.createTime,
dueTime = task.dueTime,
completeTime = task.completeTime,
progress = task.progress
};
isDueTimeSet = currentTask.dueTime.HasValue;
if (isDueTimeSet && currentTask.dueTime.HasValue)
{
tempDueTime = currentTask.dueTime.Value;
}
isEditing = true;
}
private void LoadTasks()
{
try
{
if (File.Exists(savePath))
{
string json = File.ReadAllText(savePath);
TaskWrapper wrapper = JsonUtility.FromJson<TaskWrapper>(json);
if (wrapper != null && wrapper.tasks != null)
{
allTasks = new List<TaskItem>(wrapper.tasks);
}
}
}
catch (Exception e)
{
Debug.LogError("加载任务数据失败: " + e.Message);
allTasks = new List<TaskItem>();
}
}
private void SaveTasks()
{
try
{
var saveData = new TaskWrapper { tasks = new List<TaskItem>(allTasks) };
string json = JsonUtility.ToJson(saveData, true);
File.WriteAllText(savePath, json);
}
catch (Exception e)
{
Debug.LogError("保存任务数据失败: " + e.Message);
}
}
}