331 lines
11 KiB
C#
331 lines
11 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Text.RegularExpressions;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
using OfficeOpenXml;
|
||
using Debug = UnityEngine.Debug;
|
||
|
||
namespace DesignTools.Friends
|
||
{
|
||
/// <summary>
|
||
/// 好友邀请奖励配置工具
|
||
/// 读取 Docs/config/Invite.xlsx 的 Reward Sheet,仅支持编辑固定 4 档邀请奖励
|
||
/// </summary>
|
||
public class InviteRewardEditor : BaseDesignToolEditor
|
||
{
|
||
private const string INVITE_EXCEL_NAME = "Invite.xlsx";
|
||
private const string REWARD_SHEET_NAME = "Reward";
|
||
|
||
private const int COL_ID = 1;
|
||
private const int COL_NEED = 2;
|
||
private const int COL_ITEMS = 3;
|
||
private const int FIXED_STAGE_COUNT = 4;
|
||
|
||
private readonly List<InviteRewardData> rewardDataList = new List<InviteRewardData>();
|
||
|
||
[MenuItem("策划工具/好友/邀请奖励")]
|
||
public static void ShowWindow()
|
||
{
|
||
var window = GetWindow<InviteRewardEditor>("邀请奖励配置");
|
||
window.minSize = window.GetMinWindowSize();
|
||
window.Show();
|
||
}
|
||
|
||
protected override string GetDocsPathPrefKey()
|
||
{
|
||
return "InviteRewardEditor_DocsPath";
|
||
}
|
||
|
||
protected override string GetWindowTitle()
|
||
{
|
||
return "邀请奖励配置工具";
|
||
}
|
||
|
||
protected override Vector2 GetMinWindowSize()
|
||
{
|
||
return new Vector2(820, 520);
|
||
}
|
||
|
||
protected override void LoadConfigData()
|
||
{
|
||
LoadInviteRewardExcel();
|
||
}
|
||
|
||
protected override void DrawDataEditor()
|
||
{
|
||
DrawTips();
|
||
EditorGUILayout.Space(6);
|
||
DrawRewardTable();
|
||
}
|
||
|
||
protected override void SaveDataToExcel()
|
||
{
|
||
SaveToExcel();
|
||
}
|
||
|
||
private void DrawTips()
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
EditorGUILayout.HelpBox("如果要添加阶段数量请联系开发组", MessageType.Warning);
|
||
|
||
if (rewardDataList.Count != FIXED_STAGE_COUNT)
|
||
{
|
||
EditorGUILayout.HelpBox(
|
||
$"当前 Reward Sheet 读取到 {rewardDataList.Count} 行有效数据,工具仅支持固定 {FIXED_STAGE_COUNT} 行配置。",
|
||
MessageType.Error);
|
||
}
|
||
|
||
bool hasParseError = false;
|
||
for (int i = 0; i < rewardDataList.Count; i++)
|
||
{
|
||
if (!rewardDataList[i].IsItemsValid)
|
||
{
|
||
hasParseError = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (hasParseError)
|
||
{
|
||
EditorGUILayout.HelpBox("存在无法解析的奖励数据,请先检查 Excel 中 Items 列格式,必须为 [{\"Id\":100001,\"Num\":50}] 这种结构。", MessageType.Error);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawRewardTable()
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("阶段", EditorStyles.boldLabel, GUILayout.Width(80));
|
||
EditorGUILayout.LabelField("需要邀请人数", EditorStyles.boldLabel, GUILayout.Width(180));
|
||
EditorGUILayout.LabelField("奖励数量", EditorStyles.boldLabel, GUILayout.Width(180));
|
||
GUILayout.FlexibleSpace();
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space(4);
|
||
|
||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||
|
||
for (int i = 0; i < rewardDataList.Count; i++)
|
||
{
|
||
InviteRewardData data = rewardDataList[i];
|
||
|
||
EditorGUILayout.BeginVertical("box");
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
EditorGUILayout.LabelField($"第 {i + 1} 档", GUILayout.Width(80));
|
||
data.Need = EditorGUILayout.IntField(data.Need, GUILayout.Width(180));
|
||
data.RewardNum = EditorGUILayout.IntField(data.RewardNum, GUILayout.Width(180));
|
||
GUILayout.FlexibleSpace();
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
GUILayout.Space(84);
|
||
EditorGUILayout.LabelField($"配置Id: {data.Id}", EditorStyles.miniLabel, GUILayout.Width(120));
|
||
EditorGUILayout.LabelField($"奖励ItemId: {data.RewardItemId}", EditorStyles.miniLabel, GUILayout.Width(160));
|
||
if (!data.IsItemsValid)
|
||
{
|
||
EditorGUILayout.LabelField("Items 格式异常", EditorStyles.miniBoldLabel, GUILayout.Width(100));
|
||
}
|
||
GUILayout.FlexibleSpace();
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void LoadInviteRewardExcel()
|
||
{
|
||
rewardDataList.Clear();
|
||
|
||
string excelPath = GetDocsConfigFilePath(INVITE_EXCEL_NAME);
|
||
if (!File.Exists(excelPath))
|
||
{
|
||
throw new FileNotFoundException($"未找到 {INVITE_EXCEL_NAME}", excelPath);
|
||
}
|
||
|
||
using (var package = new ExcelPackage(new FileInfo(excelPath)))
|
||
{
|
||
var worksheet = package.Workbook.Worksheets[REWARD_SHEET_NAME];
|
||
if (worksheet == null)
|
||
{
|
||
throw new Exception($"未找到 Sheet: {REWARD_SHEET_NAME}");
|
||
}
|
||
|
||
int rowCount = worksheet.Dimension?.Rows ?? 0;
|
||
for (int row = 3; row <= rowCount; row++)
|
||
{
|
||
string idText = worksheet.Cells[row, COL_ID].Text.Trim();
|
||
if (string.IsNullOrEmpty(idText))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (!int.TryParse(idText, out int id))
|
||
{
|
||
Debug.LogWarning($"Invite.xlsx/{REWARD_SHEET_NAME} 第 {row} 行 Id 不是有效整数,已跳过");
|
||
continue;
|
||
}
|
||
|
||
InviteRewardData data = new InviteRewardData
|
||
{
|
||
Id = id,
|
||
Need = ParseInt(worksheet.Cells[row, COL_NEED].Text.Trim()),
|
||
ItemsRawText = worksheet.Cells[row, COL_ITEMS].Text.Trim(),
|
||
RowIndex = row
|
||
};
|
||
|
||
data.IsItemsValid = TryParseItems(data.ItemsRawText, out int rewardItemId, out int rewardNum);
|
||
data.RewardItemId = rewardItemId;
|
||
data.RewardNum = rewardNum;
|
||
|
||
rewardDataList.Add(data);
|
||
}
|
||
}
|
||
|
||
if (rewardDataList.Count == 0)
|
||
{
|
||
throw new Exception("未读取到任何邀请奖励数据");
|
||
}
|
||
}
|
||
|
||
private void SaveToExcel()
|
||
{
|
||
if (!ValidateRewardData(out string errorMessage))
|
||
{
|
||
EditorUtility.DisplayDialog("校验失败", errorMessage, "确定");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
string excelPath = GetDocsConfigFilePath(INVITE_EXCEL_NAME);
|
||
|
||
using (var package = new ExcelPackage(new FileInfo(excelPath)))
|
||
{
|
||
var worksheet = package.Workbook.Worksheets[REWARD_SHEET_NAME];
|
||
if (worksheet == null)
|
||
{
|
||
EditorUtility.DisplayDialog("错误", $"未找到 Sheet: {REWARD_SHEET_NAME}", "确定");
|
||
return;
|
||
}
|
||
|
||
foreach (InviteRewardData data in rewardDataList)
|
||
{
|
||
int row = data.RowIndex;
|
||
worksheet.Cells[row, COL_ID].Value = data.Id;
|
||
worksheet.Cells[row, COL_NEED].Value = data.Need;
|
||
worksheet.Cells[row, COL_ITEMS].Value = BuildItemsJson(data.RewardItemId, data.RewardNum);
|
||
}
|
||
|
||
package.Save();
|
||
}
|
||
|
||
EditorUtility.DisplayDialog("成功", "邀请奖励配置已保存到 Excel!", "确定");
|
||
Debug.Log("邀请奖励配置已保存");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
EditorUtility.DisplayDialog("错误", $"保存失败: {e.Message}", "确定");
|
||
Debug.LogError($"保存邀请奖励配置失败: {e}");
|
||
}
|
||
}
|
||
|
||
private bool ValidateRewardData(out string errorMessage)
|
||
{
|
||
if (rewardDataList.Count != FIXED_STAGE_COUNT)
|
||
{
|
||
errorMessage = $"当前 Reward Sheet 不是固定的 {FIXED_STAGE_COUNT} 行配置,不能通过该工具保存。\n\n如果要添加阶段数量请联系开发组";
|
||
return false;
|
||
}
|
||
|
||
int previousNeed = int.MinValue;
|
||
for (int i = 0; i < rewardDataList.Count; i++)
|
||
{
|
||
InviteRewardData data = rewardDataList[i];
|
||
|
||
if (!data.IsItemsValid || data.RewardItemId <= 0)
|
||
{
|
||
errorMessage = $"第 {i + 1} 档奖励数据格式异常,无法保存。";
|
||
return false;
|
||
}
|
||
|
||
if (data.Need < 0)
|
||
{
|
||
errorMessage = $"第 {i + 1} 档的需要邀请人数不能小于 0。";
|
||
return false;
|
||
}
|
||
|
||
if (data.RewardNum < 0)
|
||
{
|
||
errorMessage = $"第 {i + 1} 档的奖励数量必须是整数,且不能小于 0。";
|
||
return false;
|
||
}
|
||
|
||
if (data.Need <= previousNeed)
|
||
{
|
||
errorMessage = "需要邀请人数必须按从小到大的顺序填写。";
|
||
return false;
|
||
}
|
||
|
||
previousNeed = data.Need;
|
||
}
|
||
|
||
errorMessage = string.Empty;
|
||
return true;
|
||
}
|
||
|
||
private bool TryParseItems(string itemsText, out int itemId, out int rewardNum)
|
||
{
|
||
itemId = 0;
|
||
rewardNum = 0;
|
||
|
||
if (string.IsNullOrWhiteSpace(itemsText))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
Match idMatch = Regex.Match(itemsText, @"""Id""\s*:\s*(\d+)");
|
||
Match numMatch = Regex.Match(itemsText, @"""Num""\s*:\s*(-?\d+)");
|
||
|
||
if (!idMatch.Success || !numMatch.Success)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
return int.TryParse(idMatch.Groups[1].Value, out itemId) &&
|
||
int.TryParse(numMatch.Groups[1].Value, out rewardNum);
|
||
}
|
||
|
||
private string BuildItemsJson(int itemId, int rewardNum)
|
||
{
|
||
return $"[{{\"Id\":{itemId},\"Num\":{rewardNum}}}]";
|
||
}
|
||
|
||
private int ParseInt(string text)
|
||
{
|
||
return int.TryParse(text, out int value) ? value : 0;
|
||
}
|
||
}
|
||
|
||
[Serializable]
|
||
public class InviteRewardData
|
||
{
|
||
public int Id;
|
||
public int Need;
|
||
public int RewardItemId;
|
||
public int RewardNum;
|
||
public bool IsItemsValid;
|
||
public int RowIndex;
|
||
public string ItemsRawText;
|
||
}
|
||
}
|