NLDClient-yudde/ProjectNLD/Assets/Code/Scripts/Gameplay/UI/UI_NoticeController.cs

624 lines
18 KiB
C#
Raw Normal View History

2023-11-06 19:46:50 +08:00
using TMPro;
2024-12-23 17:13:39 +08:00
using System;
using PhxhSDK;
using Gameplay;
2024-03-28 14:51:42 +08:00
using UnityEngine;
using UnityEngine.UI;
2024-12-23 17:13:39 +08:00
using System.Globalization;
using Cysharp.Threading.Tasks;
using System.Collections.Generic;
2023-11-06 19:46:50 +08:00
2024-03-28 14:51:42 +08:00
/// <summary>
/// 公告UI
/// </summary>
2023-11-06 19:46:50 +08:00
public class UI_NoticeController : UIWindow
{
2024-08-06 14:39:46 +08:00
/// <summary>
/// 打开公告界面
/// </summary>
2024-12-23 17:13:39 +08:00
public static async UniTask<UIWindow> Open(int id = -1, E_NoticeType type = E_NoticeType.Activity)
2024-08-06 14:39:46 +08:00
{
2024-12-23 17:13:39 +08:00
CommonUtils.CaptureScreenshot();
InputData inputData = new()
{
2024-12-23 17:13:39 +08:00
OpenType = type,
Idx = id,
};
return await UIManager.Instance.CreateAndOpenWindow(UINameConst.UI_Notice, inputData);
}
2024-03-28 17:44:23 +08:00
private struct InputData
{
public E_NoticeType OpenType;
public int Idx;
2024-08-06 14:39:46 +08:00
}
2023-11-06 19:46:50 +08:00
2024-03-28 14:51:42 +08:00
#region 类
2024-08-06 14:39:46 +08:00
2024-03-28 14:51:42 +08:00
/// <summary>
2024-12-23 17:13:39 +08:00
/// 公告类型
2024-03-28 14:51:42 +08:00
/// </summary>
private class NoticeTypeTab : UIGameObjectWrapper
2024-08-06 14:39:46 +08:00
{
2024-12-23 19:32:37 +08:00
#region UI
2024-08-06 14:39:46 +08:00
/// <summary>
/// 选中按钮
/// </summary>
private Button buttonChoose;
/// <summary>
/// 未选中按钮
/// </summary>
private Button buttonNormal;
/// <summary>
/// 公告类型
/// </summary>
private readonly E_NoticeType noticeType;
/// <summary>
/// 公告索引
/// </summary>
private readonly int index;
2024-08-06 14:39:46 +08:00
/// <summary>
/// 点击回调
/// </summary>
private readonly Action<E_NoticeType> clickAction;
2024-12-23 19:32:37 +08:00
/// <summary>
/// 选中红点
/// </summary>
private RedPointUIController redPointChoose;
/// <summary>
/// 未选中红点
/// </summary>
private RedPointUIController redPointNormal;
#endregion
2024-08-06 14:39:46 +08:00
/// <summary>
/// 是否选中
/// </summary>
public bool IsSelect
{
set
{
buttonChoose.gameObject.IsScaleShow(value);
2024-03-28 14:51:42 +08:00
buttonNormal.gameObject.IsScaleShow(!value);
}
2024-08-06 14:39:46 +08:00
}
2024-03-28 14:51:42 +08:00
public NoticeTypeTab(GameObject root, E_NoticeType noticeType, Action<E_NoticeType> click) : base(root, true)
2024-08-06 14:39:46 +08:00
{
this.noticeType = noticeType;
clickAction = click;
2024-03-28 14:51:42 +08:00
2024-08-06 14:39:46 +08:00
buttonChoose = GetComponent<Button>("ButtonChoose");
buttonNormal = GetComponent<Button>("ButtonNormal");
2024-12-23 19:32:37 +08:00
redPointChoose = GetComponent<RedPointUIController>("ButtonChoose/UI_RedPoint");
redPointNormal = GetComponent<RedPointUIController>("ButtonNormal/UI_RedPoint");
if (NoticeManager.Instance.RedPointNodeNoticeTypes.TryGetValue(noticeType, out var redPointNodeNotice) &&
redPointNodeNotice != null)
{
redPointChoose.NodeID = redPointNodeNotice.ID;
redPointNormal.NodeID = redPointNodeNotice.ID;
}
2024-03-28 14:51:42 +08:00
2024-08-06 14:39:46 +08:00
IsSelect = false;
2024-04-08 18:17:20 +08:00
2024-09-24 17:33:07 +08:00
CommonUtils.BindButton(buttonChoose, OnClick);
CommonUtils.BindButton(buttonNormal, OnClick);
2024-03-28 14:51:42 +08:00
}
2024-08-06 14:39:46 +08:00
private void OnClick()
{
clickAction?.Invoke(noticeType);
2025-04-15 13:24:27 +08:00
AudioManager.Instance.PlayAudio(Framework.Constants.Sound.COMMON_CLICK);
2024-03-28 14:51:42 +08:00
}
2024-08-06 14:39:46 +08:00
}
2023-11-06 19:46:50 +08:00
2024-08-06 14:39:46 +08:00
/// <summary>
/// 公告页签
/// </summary>
private class NoticeTab : UIGameObjectWrapper
{
2024-03-28 14:51:42 +08:00
#region UI
2024-08-06 14:39:46 +08:00
2024-03-28 14:51:42 +08:00
/// <summary>
/// 选中状态按钮
/// </summary>
private Button buttonChoose;
2024-08-06 14:39:46 +08:00
/// <summary>
/// 非选中状态按钮
/// </summary>
private Button buttonNormal;
/// <summary>
/// 选中状态标题
/// </summary>
private TMP_Text textSelectTitle;
/// <summary>
/// 选中状态日
/// </summary>
private TMP_Text textSelectDay;
2024-03-28 14:51:42 +08:00
/// <summary>
/// 选中状态月
/// </summary>
private TMP_Text textSelectWeek;
2024-08-06 14:39:46 +08:00
2024-03-28 14:51:42 +08:00
/// <summary>
/// 选中状态星期
/// </summary>
private TMP_Text textSelectMonth;
2024-08-06 14:39:46 +08:00
2024-03-28 14:51:42 +08:00
/// <summary>
/// 未选中状态标题
/// </summary>
private TMP_Text textTitle;
2024-08-06 14:39:46 +08:00
2024-03-28 14:51:42 +08:00
/// <summary>
/// 未选中状态日
/// </summary>
private TMP_Text textDay;
2024-08-06 14:39:46 +08:00
2024-03-28 14:51:42 +08:00
/// <summary>
/// 未选中状态月
/// </summary>
private TMP_Text textWeek;
2024-08-06 14:39:46 +08:00
2024-03-28 14:51:42 +08:00
/// <summary>
/// 未选中状态星期
/// </summary>
private TMP_Text textMonth;
2024-08-06 14:39:46 +08:00
2024-12-23 19:32:37 +08:00
/// <summary>
/// 选中红点
/// </summary>
private RedPointUIController redPointChoose;
/// <summary>
/// 未选中红点
/// </summary>
private RedPointUIController redPointNormal;
2024-08-06 14:39:46 +08:00
#endregion
2024-03-28 14:51:42 +08:00
2024-12-23 17:13:39 +08:00
public int ID;
2024-03-28 14:51:42 +08:00
/// <summary>
/// 选中回调
/// </summary>
private readonly Action<NoticeTab> selectAction;
2024-08-06 14:39:46 +08:00
2024-12-23 17:13:39 +08:00
/// <summary>
/// 公告数据
/// </summary>
2024-12-19 19:05:05 +08:00
private NoticeDataForDisplay noticeInfo;
2024-03-28 14:51:42 +08:00
/// <summary>
/// 是否选中
/// </summary>
2024-08-06 14:39:46 +08:00
public bool IsSelect
{
set
{
buttonChoose.gameObject.IsScaleShow(value);
buttonNormal.gameObject.IsScaleShow(!value);
}
}
2024-12-23 17:13:39 +08:00
public NoticeDataForDisplay NoticeInfo => noticeInfo;
2024-03-28 16:14:52 +08:00
2024-03-28 14:51:42 +08:00
public NoticeTab(GameObject root, Action<NoticeTab> select) : base(root)
{
2024-08-06 14:39:46 +08:00
selectAction = select;
2024-03-28 14:51:42 +08:00
2024-08-06 14:39:46 +08:00
buttonChoose = GetComponent<Button>("ButtonChoose");
buttonNormal = GetComponent<Button>("ButtonNormal");
textSelectTitle = GetComponent<TMP_Text>("ButtonChoose/TextTitle");
2024-03-28 14:51:42 +08:00
textSelectDay = GetComponent<TMP_Text>("ButtonChoose/TextDay");
textSelectWeek = GetComponent<TMP_Text>("ButtonChoose/TextWeek");
textSelectMonth = GetComponent<TMP_Text>("ButtonChoose/TextMonth");
textTitle = GetComponent<TMP_Text>("ButtonNormal/TextTitle");
textDay = GetComponent<TMP_Text>("ButtonNormal/TextDay");
textWeek = GetComponent<TMP_Text>("ButtonNormal/TextWeek");
textMonth = GetComponent<TMP_Text>("ButtonNormal/TextMonth");
2024-12-23 19:32:37 +08:00
redPointChoose = GetComponent<RedPointUIController>("ButtonChoose/UI_RedPoint");
redPointNormal = GetComponent<RedPointUIController>("ButtonNormal/UI_RedPoint");
2024-04-08 18:17:20 +08:00
IsSelect = false;
2024-09-24 17:33:07 +08:00
CommonUtils.BindButton(buttonChoose, OnClick);
CommonUtils.BindButton(buttonNormal, OnClick);
2024-03-28 14:51:42 +08:00
}
2024-12-23 17:13:39 +08:00
public void SetInfo(NoticeDataForDisplay notice)
2024-08-06 14:39:46 +08:00
{
2024-12-23 17:13:39 +08:00
if (notice == null)
2024-08-06 14:39:46 +08:00
{
IsScaleShow = false;
return;
}
2024-03-28 14:51:42 +08:00
2024-12-23 17:13:39 +08:00
noticeInfo = notice;
ID = notice.ID;
textSelectTitle.text = notice.Title;
textTitle.text = notice.Title;
ConvertTime();
2024-03-28 14:51:42 +08:00
2024-12-23 19:32:37 +08:00
if (notice.RedPointNodeNotice != null)
{
redPointChoose.NodeID = notice.RedPointNodeNotice.ID;
redPointNormal.NodeID = notice.RedPointNodeNotice.ID;
}
2024-08-06 14:39:46 +08:00
IsScaleShow = true;
}
2024-03-28 16:14:52 +08:00
2024-08-06 14:39:46 +08:00
private void OnClick()
{
selectAction?.Invoke(this);
2025-04-15 13:24:27 +08:00
AudioManager.Instance.PlayAudio(Framework.Constants.Sound.COMMON_CLICK);
2024-03-28 14:51:42 +08:00
}
2024-12-23 17:13:39 +08:00
private void ConvertTime()
{
try
{
// 解析时间字符串为 DateTime 对象
if (DateTime.TryParseExact(noticeInfo.UpdateTime, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture,
DateTimeStyles.None, out var dateTime))
{
var day = dateTime.Day.ToString(); // 日期1-31
var month = dateTime.Month.ToString();
var weekDayAbbreviation = dateTime.ToString("ddd", CultureInfo.InvariantCulture);
2024-12-23 19:32:37 +08:00
2024-12-23 17:13:39 +08:00
textSelectDay.text = day;
textDay.text = day;
textMonth.text = month + "/";
textSelectMonth.text = month + "/";
textWeek.text = weekDayAbbreviation;
textSelectWeek.text = weekDayAbbreviation;
/*// 输出结果
DebugUtil.LogError($"Day: {day}");
DebugUtil.LogError($"Month: {month}");
DebugUtil.LogError($"Weekday Abbreviation: {weekDayAbbreviation}");*/
}
else
{
DebugUtil.LogError("公告时间转换错误:{0}", noticeInfo.UpdateTime);
}
}
catch (Exception e)
{
DebugUtil.LogError("公告时间转换异常:{0}, Error:{1}", noticeInfo.UpdateTime, e);
}
}
2024-03-28 14:51:42 +08:00
}
2024-08-06 14:39:46 +08:00
/// <summary>
/// 公告内容
/// </summary>
private class NoticeContent : UIGameObjectWrapper
{
/// <summary>
/// 活动名
/// </summary>
private TMP_Text textActivityName;
/// <summary>
/// 横幅
/// </summary>
2024-03-28 14:51:42 +08:00
private Image imageBanner;
2024-10-24 17:28:46 +08:00
/// <summary>
2024-12-23 17:13:39 +08:00
/// 内置浏览器
2024-10-24 17:28:46 +08:00
/// </summary>
2024-12-19 19:05:05 +08:00
private UniWebView webView;
2024-12-25 18:45:03 +08:00
/// <summary>
/// 当前公告ID
/// </summary>
private int noticeID;
2024-08-06 14:39:46 +08:00
public NoticeContent(GameObject root) : base(root)
{
textActivityName = GetComponent<TMP_Text>("ImageTitle/TextActivityName");
2024-03-28 14:51:42 +08:00
imageBanner = GetComponent<Image>("ImageBanner");
2024-12-19 19:05:05 +08:00
webView = GetComponent<UniWebView>("TextContent");
2024-03-28 14:51:42 +08:00
}
2024-12-23 17:13:39 +08:00
public void SetInfo(NoticeDataForDisplay noticeInfo)
2024-08-06 14:39:46 +08:00
{
if (noticeInfo == null)
{
IsScaleShow = false;
return;
}
2024-12-25 18:45:03 +08:00
noticeID = noticeInfo.ID;
2024-12-24 16:34:45 +08:00
if (webView != null)
{
webView.LoadHTMLString(noticeInfo.Content, null, false);
2025-01-06 17:34:50 +08:00
webView.SetZoomEnabled(false); // 禁止缩放
2024-12-24 16:34:45 +08:00
webView.EmbeddedToolbar.Hide();
webView.Show();
webView.OnMessageReceived += HandleMessageReceived;
}
2024-08-06 14:39:46 +08:00
textActivityName.text = noticeInfo.Title;
2024-03-28 14:51:42 +08:00
IsScaleShow = true;
2024-12-30 14:23:55 +08:00
/*
2024-12-19 19:05:05 +08:00
if (noticeInfo.Bannersprite != null)
{
imageBanner.sprite = noticeInfo.ContentSprite;
2024-12-30 14:23:55 +08:00
}*/
2024-08-06 14:39:46 +08:00
}
2024-12-19 19:05:05 +08:00
2025-03-10 19:47:34 +08:00
protected override void Dispose(bool isDisposing)
2024-12-19 19:05:05 +08:00
{
2024-12-24 16:34:45 +08:00
if (webView != null)
webView.OnMessageReceived -= HandleMessageReceived;
2025-03-10 19:47:34 +08:00
base.Dispose(isDisposing);
2024-12-19 19:05:05 +08:00
}
2024-12-24 16:34:45 +08:00
private void HandleMessageReceived(UniWebView web, UniWebViewMessage message)
{
2024-12-24 19:26:46 +08:00
DebugUtil.Log("Message received: " + message.RawMessage);
2024-12-24 16:34:45 +08:00
if (!message.Path.Equals("open")) return;
var openUrl = message.Args["url"];
DebugUtil.Log($"公告跳转链接是: {openUrl}");
if (!string.IsNullOrEmpty(openUrl))
{
Application.OpenURL(openUrl);
2024-12-25 18:45:03 +08:00
NoticeManager.Instance.CloseRedNode(noticeID);
2024-12-24 16:34:45 +08:00
}
}
2024-08-06 14:39:46 +08:00
}
#endregion
2024-12-23 17:13:39 +08:00
#region UI
2024-08-06 14:39:46 +08:00
/// <summary>
/// 关闭按钮
/// </summary>
private Button buttonClose;
/// <summary>
/// 页签循环列表
/// </summary>
private RecycleView tabRecycleView;
2024-03-28 17:44:23 +08:00
/// <summary>
2024-08-06 14:39:46 +08:00
/// 高斯模糊遮罩
/// </summary>
private RawImage rawImageGaussianBlurMask;
2024-12-23 17:13:39 +08:00
#endregion
2024-10-30 15:46:39 +08:00
2024-08-06 14:39:46 +08:00
/// <summary>
/// 公告类型页签字典
/// </summary>
private readonly Dictionary<E_NoticeType, NoticeTypeTab> dicNoticeTypeTab = new();
/// <summary>
/// 页签字典
/// </summary>
private readonly Dictionary<int, NoticeTab> dicNoticeTap = new();
/// <summary>
/// 页签内容
/// </summary>
private NoticeContent noticeContent;
/// <summary>
/// 当前选中的公告类型
/// </summary>
private E_NoticeType curSelectNoticeType = E_NoticeType.None;
/// <summary>
/// 当前选择的页签
/// </summary>
private NoticeTab curSelectTab;
private ScrollRect contentScrollRect;
private InputData inputData;
2024-12-19 19:05:05 +08:00
2024-08-06 14:39:46 +08:00
public override void OnInit()
{
buttonClose = GetComponent<Button>("Image_NoticeBG/Button_Close");
tabRecycleView = GetComponent<RecycleView>("Image_NoticeBG/Tabs");
rawImageGaussianBlurMask = GetComponent<RawImage>("GaussianBlurMask");
dicNoticeTypeTab.Add(E_NoticeType.Activity,
new NoticeTypeTab(FindObj("Image_NoticeBG/Notices/ActivityNotice"), E_NoticeType.Activity,
OnNoticeTypeTab));
dicNoticeTypeTab.Add(E_NoticeType.System,
new NoticeTypeTab(FindObj("Image_NoticeBG/Notices/SystemNotice"), E_NoticeType.System, OnNoticeTypeTab));
2024-03-28 14:51:42 +08:00
noticeContent = new(FindObj("Image_NoticeBG/ContentRoot/Viewport/Content"));
contentScrollRect = GetComponent<ScrollRect>("Image_NoticeBG/ContentRoot");
2024-03-28 14:51:42 +08:00
rawImageGaussianBlurMask.texture = CommonUtils.GetScreenTexture2D();
2024-03-28 17:44:23 +08:00
tabRecycleView.Init(OnRefreshTab);
2024-12-19 19:05:05 +08:00
2024-03-28 14:51:42 +08:00
BindButton(buttonClose, OnClose);
2024-08-06 14:39:46 +08:00
}
2024-12-19 19:05:05 +08:00
protected override void OnShowWindow(object data)
2024-08-06 14:39:46 +08:00
{
2024-12-23 17:13:39 +08:00
if (data == null) return;
inputData = (InputData)data;
OnNoticeTypeTab(inputData.OpenType);
2024-03-28 14:51:42 +08:00
}
2024-05-08 13:59:40 +08:00
public override void OnRelease()
{
2024-08-06 14:39:46 +08:00
noticeContent.Dispose();
noticeContent = null;
2024-05-08 13:59:40 +08:00
base.OnRelease();
}
/// <summary>
/// 获取要显示的页签数量
/// </summary>
/// <returns></returns>
private int GetShowTabCount()
2024-08-06 14:39:46 +08:00
{
2024-03-28 14:51:42 +08:00
return curSelectNoticeType switch
{
2024-12-23 17:13:39 +08:00
E_NoticeType.Activity => NoticeManager.Instance.ActivityNoticeDataForDisplays.Count,
E_NoticeType.System => NoticeManager.Instance.SystemNoticeDataForDisplays.Count,
2024-03-28 14:51:42 +08:00
_ => 0,
};
}
2024-08-06 14:39:46 +08:00
#region 回调方法
/// <summary>
/// 刷新页签
/// </summary>
private void OnRefreshTab(GameObject cell, int index)
{
2024-03-28 14:51:42 +08:00
if (!dicNoticeTap.TryGetValue(cell.GetInstanceID(), out NoticeTab noticeTab))
{
noticeTab = new NoticeTab(cell, OnNoticeTab);
dicNoticeTap[cell.GetInstanceID()] = noticeTab;
}
2024-08-06 14:39:46 +08:00
switch (curSelectNoticeType)
{
case E_NoticeType.Activity:
{
2024-12-23 17:13:39 +08:00
if (NoticeManager.Instance.ActivityNoticeDataForDisplays.Count < 0 ||
index >= NoticeManager.Instance.ActivityNoticeDataForDisplays.Count)
2024-12-19 19:05:05 +08:00
{
DebugUtil.LogWarning("索引错误");
2024-08-06 14:39:46 +08:00
return;
2024-12-19 19:05:05 +08:00
}
2024-08-06 14:39:46 +08:00
2024-12-23 17:13:39 +08:00
if (!NoticeManager.Instance.ActivityNoticeDataForDisplays.TryGetValue(index, out var noticeInfo))
2024-12-19 19:05:05 +08:00
{
DebugUtil.LogError("没有索引为{0}的公告", index);
2024-03-28 16:14:52 +08:00
}
2024-10-30 15:46:39 +08:00
2024-12-23 17:13:39 +08:00
noticeTab.SetInfo(noticeInfo);
if ((inputData.Idx == -1 && curSelectTab == null) || inputData.Idx == noticeTab.ID)
2024-10-30 15:46:39 +08:00
{
curSelectTab = noticeTab;
OnNoticeTab(noticeTab);
}
else
{
noticeTab.IsSelect = false;
}
2024-08-06 14:39:46 +08:00
}
break;
case E_NoticeType.System:
{
2024-12-23 17:13:39 +08:00
if (NoticeManager.Instance.SystemNoticeDataForDisplays.Count < 0 ||
index >= NoticeManager.Instance.SystemNoticeDataForDisplays.Count)
2024-08-06 14:39:46 +08:00
{
DebugUtil.LogWarning("索引错误");
return;
2024-03-28 14:51:42 +08:00
}
2024-08-06 14:39:46 +08:00
2024-12-23 17:13:39 +08:00
if (!NoticeManager.Instance.SystemNoticeDataForDisplays.TryGetValue(index, out var noticeInfo))
{
DebugUtil.LogError("没有索引为{0}的系统公告", index);
}
noticeTab.SetInfo(noticeInfo);
if ((inputData.Idx == -1 && curSelectTab == null) || inputData.Idx == noticeTab.ID)
{
curSelectTab = noticeTab;
2024-08-06 14:39:46 +08:00
OnNoticeTab(noticeTab);
2024-12-23 17:13:39 +08:00
}
2024-08-06 14:39:46 +08:00
else
2024-12-23 17:13:39 +08:00
{
noticeTab.IsSelect = false;
}
2024-08-06 14:39:46 +08:00
}
break;
default:
{
noticeTab.IsSelect = false;
}
break;
}
2024-03-28 14:51:42 +08:00
}
/// <summary>
/// 关闭
/// </summary>
private void OnClose()
{
2025-04-15 15:16:08 +08:00
AudioManager.Instance.PlayAudio(Framework.Constants.Sound.COMMON_BACK);
2024-03-28 14:51:42 +08:00
CloseWindow();
}
2024-08-06 14:39:46 +08:00
/// <summary>
2024-12-23 17:13:39 +08:00
/// 公告类型切换
2024-08-06 14:39:46 +08:00
/// </summary>
private void OnNoticeTypeTab(E_NoticeType noticeType)
{
if (curSelectNoticeType == noticeType)
return;
2024-03-28 14:51:42 +08:00
2024-08-06 14:39:46 +08:00
if (dicNoticeTypeTab.TryGetValue(curSelectNoticeType, out NoticeTypeTab tab))
tab.IsSelect = false;
2024-03-28 14:51:42 +08:00
2024-08-06 14:39:46 +08:00
curSelectNoticeType = noticeType;
2024-12-23 17:13:39 +08:00
if (inputData.Idx != -1 && curSelectTab != null)
inputData.Idx = -1;
2024-08-06 14:39:46 +08:00
curSelectTab = null;
2024-03-28 14:51:42 +08:00
if (dicNoticeTypeTab.TryGetValue(curSelectNoticeType, out tab))
tab.IsSelect = true;
tabRecycleView.ShowList(GetShowTabCount());
}
2024-08-06 14:39:46 +08:00
/// <summary>
/// 公告页签
/// </summary>
private void OnNoticeTab(NoticeTab tab)
{
if (curSelectTab != null)
{
curSelectTab.IsSelect = false;
}
curSelectTab = tab;
curSelectTab.IsSelect = true;
contentScrollRect.verticalNormalizedPosition = 1f;
2024-12-19 19:05:05 +08:00
2024-12-24 12:12:45 +08:00
noticeContent.SetInfo(curSelectTab.NoticeInfo);
2024-12-25 18:45:03 +08:00
if (tab.NoticeInfo.LinkType != 2)
NoticeManager.Instance.CloseRedNode(tab.ID);
2024-03-28 14:51:42 +08:00
}
2024-08-06 14:39:46 +08:00
2024-03-28 14:51:42 +08:00
#endregion
2023-11-06 19:46:50 +08:00
}