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
{
///
/// 好友邀请奖励配置工具
/// 读取 Docs/config/Invite.xlsx 的 Reward Sheet,仅支持编辑固定 4 档邀请奖励
///
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 rewardDataList = new List();
[MenuItem("策划工具/好友/邀请奖励")]
public static void ShowWindow()
{
var window = GetWindow("邀请奖励配置");
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;
}
}