lcy
2025-12-29 991c31124d6818550bb2a14c23853476d72e7a14
251 每日特惠-客户端
6个文件已修改
19个文件已添加
1160 ■■■■■ 已修改文件
Main/Main.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials.meta 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsBaseWin.cs 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsBaseWin.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsDayGiftCell.cs 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsDayGiftCell.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsDayGiftWin.cs 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsDayGiftWin.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsItem.cs 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsItem.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsManager.cs 300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsManager.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsShopWin.cs 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsShopWin.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsWeekGiftCell.cs 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsWeekGiftCell.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsWeekGiftWin.cs 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsWeekGiftWin.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsWin.cs 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/DailySpecials/DailySpecialsWin.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Main/HomeWin.cs 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Redpoint/MainRedDot.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Store/StoreModel.cs 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Utility/EnumHelper.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Utility/UIHelper.cs 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Main.cs
@@ -96,6 +96,7 @@
        managers.Add(OSActivityManager.Instance);
        managers.Add(HeroFatesManager.Instance);
        managers.Add(BeautyMMManager.Instance);
        managers.Add(DailySpecialsManager.Instance);
        foreach (var manager in managers)
        {
            manager.Init();
Main/System/DailySpecials.meta
New file
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cbbbe8e7b4947df46809a886006847ad
folderAsset: yes
DefaultImporter:
  externalObjects: {}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/DailySpecials/DailySpecialsBaseWin.cs
New file
@@ -0,0 +1,193 @@
using System;
using UnityEngine;
using UnityEngine.UI;
public class DailySpecialsBaseWin : FunctionsBaseWin
{
    [SerializeField] ImageEx imgBG;
    [SerializeField] ImageEx imgFan0;
    [SerializeField] ImageEx imgFanOther;
    [SerializeField] ImageEx[] imgIHs;
    [SerializeField] ImageEx[] imgTitles;
    [SerializeField] Transform[] transMoneys;
    [SerializeField] ItemCell freeItem;
    [SerializeField] ImageEx imgFreeHave;
    [SerializeField] Image imgFreeRed;
    [SerializeField] RotationTween rotationTween;
    [SerializeField] ImageEx imgTimeBG1;
    [SerializeField] TextEx txtTime1;
    [SerializeField] ImageEx imgTimeBG2;
    [SerializeField] TextEx txtTime2;
    DailySpecialsManager manager { get { return DailySpecialsManager.Instance; } }
    StoreModel storeModel { get { return StoreModel.Instance; } }
    protected override void OnPreOpen()
    {
        GlobalTimeEvent.Instance.secondEvent += OnSecondEvent;
        storeModel.RefreshBuyShopLimitEvent += OnRefreshBuyShopLimitEvent;
        InitRedpoint();
        tabButtons[functionOrder].SelectBtn();
        OpenSubUIByTabIndex();
    }
    protected override void OnPreClose()
    {
        GlobalTimeEvent.Instance.secondEvent -= OnSecondEvent;
        storeModel.RefreshBuyShopLimitEvent -= OnRefreshBuyShopLimitEvent;
    }
    private void OnRefreshBuyShopLimitEvent()
    {
        DisplayFreeItem(functionOrder);
    }
    private void OnSecondEvent()
    {
        DisplayTime(functionOrder);
    }
    public void InitRedpoint()
    {
        for (int i = 0; i < tabButtons.Length; i++)
        {
            if (!Enum.IsDefined(typeof(DailySpecialsTabEnum), functionOrder))
            {
                return;
            }
            tabButtons[i].redpoint.redpointId = manager.GetTabRedponitId(i);
        }
    }
    public void DisplayTime(int functionOrder)
    {
        imgTimeBG1.SetActive(functionOrder == 0 || functionOrder == 1);
        imgTimeBG2.SetActive(functionOrder == 2 || functionOrder == 3);
        txtTime1.text = Language.Get("PhantasmPavilion10", functionOrder == 3 ? manager.GetCountdownToWeekEnd() : manager.GetCountdownToNextDay());
        txtTime2.text = Language.Get("PhantasmPavilion10", functionOrder == 3 ? manager.GetCountdownToWeekEnd() : manager.GetCountdownToNextDay());
    }
    public void DisplayFan(int functionOrder)
    {
        imgFan0.SetActive(functionOrder == 0);
        imgFanOther.SetActive(functionOrder != 0);
        imgFanOther.SetSprite(StringUtility.Concat("DailySpecialsFan", functionOrder.ToString()));
    }
    public void DisplayIH(int functionOrder)
    {
        SetActiveByIndex(imgIHs, functionOrder);
    }
    public void DisplayTitle(int functionOrder)
    {
        SetActiveByIndex(imgTitles, functionOrder);
    }
    public void DisplayMoney(int functionOrder)
    {
        for (int i = 0; i < transMoneys.Length; i++)
        {
            transMoneys[i].SetActive(i == functionOrder);
        }
    }
    public void DisplayBG(int functionOrder)
    {
        imgBG.SetSprite(functionOrder == 0 ? "DailySpecialsBG1" : "DailySpecialsBG2");
    }
    public void DisplayFreeItem(int functionOrder)
    {
        int itemId = 0;
        int itemCount = 0;
        int shopId = manager.GetShopIdByFunctionOrder(functionOrder);
        if (!StoreConfig.HasKey(shopId))
        {
            freeItem.SetActive(false);
            return;
        }
        freeItem.SetActive(true);
        StoreConfig storeConfig = StoreConfig.Get(shopId);
        itemId = storeConfig.ItemID;
        itemCount = storeConfig.ItemCnt;
        bool isReceived = manager.IsReceived(shopId);
        freeItem.Init(new ItemCellModel(itemId, false, itemCount));
        freeItem.button.SetListener(() =>
        {
            if (!isReceived)
            {
                storeModel.SendBuyShopItem(storeConfig, 1);
            }
            else
            {
                ItemTipUtility.Show(itemId);
            }
        });
        imgFreeHave.SetActive(isReceived);
        imgFreeRed.SetActive(!isReceived);
        if (!isReceived)
        {
            rotationTween.Play();
        }
        else
        {
            rotationTween.Stop();
            rotationTween.SetStartState();
        }
    }
    // 通用方法:激活指定索引的图片
    private void SetActiveByIndex(ImageEx[] images, int targetIndex)
    {
        for (int i = 0; i < images.Length; i++)
        {
            images[i].SetActive(i == targetIndex);
        }
    }
    protected override void OpenSubUIByTabIndex()
    {
        DisplayBG(functionOrder);
        DisplayFan(functionOrder);
        DisplayIH(functionOrder);
        DisplayTitle(functionOrder);
        DisplayFreeItem(functionOrder);
        DisplayTime(functionOrder);
        DisplayMoney(functionOrder);
        switch (functionOrder)
        {
            case 0:
                // 每日特惠
                currentSubUI = UIManager.Instance.OpenWindow<DailySpecialsWin>();
                break;
            case 1:
                // 特惠商城
                StoreModel.Instance.selectStoreFuncType = StoreFunc.SpecialStore;
                currentSubUI = UIManager.Instance.OpenWindow<DailySpecialsShopWin>();
                break;
            case 2:
                // 每日礼包
                currentSubUI = UIManager.Instance.OpenWindow<DailySpecialsDayGiftWin>();
                break;
            case 3:
                // 每周礼包
                currentSubUI = UIManager.Instance.OpenWindow<DailySpecialsWeekGiftWin>();
                break;
            default:
                Debug.LogWarning("未知的标签索引: " + functionOrder);
                break;
        }
    }
}
Main/System/DailySpecials/DailySpecialsBaseWin.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5334c94ed6e4c0041948de4f1c6c6f96
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/DailySpecials/DailySpecialsDayGiftCell.cs
New file
@@ -0,0 +1,80 @@
using System.Collections.Generic;
using UnityEngine;
public class DailySpecialsDayGiftCell : MonoBehaviour
{
    [SerializeField] ItemCell[] itemCells;
    [SerializeField] ImageEx imgGiftIcon;
    [SerializeField] ImageEx imgRate;
    [SerializeField] ImageEx imgMask;
    [SerializeField] TextEx txtRate;
    [SerializeField] TextEx txtGiftTitle;
    [SerializeField] TextEx txtLimitInfo;
    [SerializeField] ButtonEx btnBuy;
    [SerializeField] ImageEx imgBuy;
    [SerializeField] TextEx txtBuy;
    DailySpecialsManager manager { get { return DailySpecialsManager.Instance; } }
    public void Display(int index, List<int> ctgIDList)
    {
        if (ctgIDList.IsNullOrEmpty() || ctgIDList.Count <= index || index < 0)
        {
            return;
        }
        int ctgId = ctgIDList[index];
        if (!RechargeManager.Instance.TryGetOrderInfo(ctgId, out var orderInfoConfig))
        {
            return;
        }
        if (!CTGConfig.HasKey(ctgId))
        {
            return;
        }
        CTGConfig ctgConfig = CTGConfig.Get(ctgId);
        int[][] gainItemList = ctgConfig.GainItemList;
        if (gainItemList.IsNullOrEmpty())
        {
            return;
        }
        for (int i = 0; i < itemCells.Length; i++)
        {
            if (i < gainItemList.Length)
            {
                int itemId = gainItemList[i][0];
                int count = gainItemList[i][1];
                itemCells[i].Init(new ItemCellModel(itemId, false, count));
                itemCells[i].button.SetListener(() => { ItemTipUtility.Show(itemId); });
                itemCells[i].SetActive(true);
            }
            else
            {
                itemCells[i].SetActive(false);
            }
        }
        bool hasRechargeCount = RechargeManager.Instance.TryGetRechargeCount(ctgId, out RechargeCount _rechargeCount);
        bool isBuy = manager.IsDayGiftBuy(ctgId);
        imgRate.SetActive(!isBuy);
        imgMask.SetActive(isBuy);
        imgBuy.SetSprite(!isBuy ? "DailySpecialsBuy1" : "DailySpecialsBuy2");
        txtBuy.text = !isBuy ? Language.Get("PayMoneyNum", orderInfoConfig.PayRMBNumOnSale) : Language.Get("storename11");
        imgGiftIcon.SetSprite(ctgConfig.Icon);
        txtGiftTitle.text = ctgConfig.Title;
        txtRate.text = Language.Get("DailySpecials07", ctgConfig.Percentage);
        txtLimitInfo.text = Language.Get("storename6", ctgConfig.DailyBuyCount - _rechargeCount.todayCount, ctgConfig.DailyBuyCount);
        btnBuy.SetListener(() =>
        {
            if (isBuy)
            {
                return;
            }
            RechargeManager.Instance.CTG(ctgId);
        });
    }
}
Main/System/DailySpecials/DailySpecialsDayGiftCell.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1f954c52cc69b4e41a23025861342e5d
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/DailySpecials/DailySpecialsDayGiftWin.cs
New file
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
public class DailySpecialsDayGiftWin : UIBase
{
    readonly int payType = 20;
    [SerializeField] ScrollerController scroller;
    DailySpecialsManager manager { get { return DailySpecialsManager.Instance; } }
    RechargeManager rechargeManager { get { return RechargeManager.Instance; } }
    protected override void OnPreOpen()
    {
        scroller.OnRefreshCell += OnRefreshCell;
        rechargeManager.rechargeCountEvent += OnRechargeCountEvent;
        CreateScroller();
    }
    private void OnRechargeCountEvent(int obj)
    {
        CreateScroller();
    }
    protected override void OnPreClose()
    {
        scroller.OnRefreshCell -= OnRefreshCell;
        rechargeManager.rechargeCountEvent -= OnRechargeCountEvent;
        StoreModel.Instance.selectStoreFuncType = StoreFunc.Normal;
    }
    void OnRefreshCell(ScrollerDataType type, CellView cell)
    {
        var _cell = cell.GetComponent<DailySpecialsDayGiftCell>();
        _cell.Display(cell.index, ctgIDList);
    }
    List<int> ctgIDList = null;
    void CreateScroller()
    {
        if (ctgIDList.IsNullOrEmpty())
        {
            ctgIDList = RechargeManager.Instance.GetCTGIDListByType(payType);
        }
        if (!ctgIDList.IsNullOrEmpty())
        {
            ctgIDList = ctgIDList.OrderBy(ctgId => { return manager.IsDayGiftBuy(ctgId); }).ThenBy(ctgId => ctgId).ToList();
            scroller.Refresh();
            for (int i = 0; i < ctgIDList.Count; i++)
            {
                scroller.AddCell(ScrollerDataType.Header, i);
            }
            scroller.Restart();
        }
    }
}
Main/System/DailySpecials/DailySpecialsDayGiftWin.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10ad4bd016db44b4581bd3ebb2b84438
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/DailySpecials/DailySpecialsItem.cs
New file
@@ -0,0 +1,108 @@
using UnityEngine;
public class DailySpecialsItem : MonoBehaviour
{
    [SerializeField] TextEx txtRate;
    [SerializeField] ItemCell[] itemCells;
    [SerializeField] ImageEx imgBuy;
    [SerializeField] ButtonEx btnBuy;
    [SerializeField] TextEx txtBuy;
    DailySpecialsManager manager { get { return DailySpecialsManager.Instance; } }
    public void Display(int index, bool isAllBuy)
    {
        if (!manager.TryGetDailyCtgIdByIndex(index, out int ctgId))
        {
            return;
        }
        if (!RechargeManager.Instance.TryGetOrderInfo(ctgId, out var orderInfoConfig))
        {
            return;
        }
        if (!CTGConfig.HasKey(ctgId))
        {
            return;
        }
        CTGConfig ctgConfig = CTGConfig.Get(ctgId);
        int[][] gainItemList = ctgConfig.GainItemList;
        if (gainItemList.IsNullOrEmpty())
        {
            return;
        }
        for (int i = 0; i < itemCells.Length; i++)
        {
            if (i < gainItemList.Length)
            {
                int itemId = gainItemList[i][0];
                int count = gainItemList[i][1];
                itemCells[i].Init(new ItemCellModel(itemId, false, count));
                itemCells[i].button.SetListener(() => { ItemTipUtility.Show(itemId); });
                itemCells[i].SetActive(true);
            }
            else
            {
                itemCells[i].SetActive(false);
            }
        }
        if (isAllBuy)
        {
            btnBuy.SetActive(false);
            imgBuy.SetActive(true);
        }
        else
        {
            bool hasRechargeCount = RechargeManager.Instance.TryGetRechargeCount(ctgId, out RechargeCount _rechargeCount);
            bool isBuy = hasRechargeCount && _rechargeCount.todayCount > 0;
            btnBuy.SetActive(!isBuy);
            imgBuy.SetActive(isBuy);
            txtBuy.text = Language.Get("PayMoneyNum", orderInfoConfig.PayRMBNumOnSale);
            txtRate.text = Language.Get("DailySpecials07", ctgConfig.Percentage);
            btnBuy.SetListener(() =>
            {
                string iconName = UIHelper.GetIconNameWithMoneyType(54);
                if (!CTGConfig.HasKey(manager.bundleRechargeId))
                {
                    return;
                }
                CTGConfig ctgConfig = CTGConfig.Get(manager.bundleRechargeId);
                int[][] gainItemList = ctgConfig.GainItemList;
                if (gainItemList.IsNullOrEmpty())
                {
                    return;
                }
                int count = gainItemList[0][1];
                // 先检查是否已经勾选"本次登录不再提醒"
                if (manager.isDailySpecialsToggleOn)
                {
                    RechargeManager.Instance.CTG(ctgId);
                    return;
                }
                ConfirmCancel.ToggleConfirmCancel(
                    Language.Get("L1003"),
                    Language.Get("DailySpecials08", iconName, count),
                    Language.Get("TianziBillborad08"),
                    Language.Get("PopConfirmWin_OK"),
                    null,
                    (bool isOK, bool isToggle) =>
                    {
                        if (isOK)
                        {
                            manager.isDailySpecialsToggleOn = isToggle;
                            RechargeManager.Instance.CTG(ctgId);
                        }
                    },
                    false
                );
            });
        }
    }
}
Main/System/DailySpecials/DailySpecialsItem.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 779116b373fd11246a789aef55cb0157
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/DailySpecials/DailySpecialsManager.cs
New file
@@ -0,0 +1,300 @@
using System;
using System.Collections.Generic;
using LitJson;
public class DailySpecialsManager : GameSystemManager<DailySpecialsManager>
{
    public int[] dailyRechargeIds;      // 每日特惠礼包充值ID列表(单独购买用)
    public int bundleRechargeId;        // 打包购买充值ID
    public int originalPrice;
    public bool isDailySpecialsToggleOn;  // 本次登录不再提醒的状态
    RechargeManager rechargeManager { get { return RechargeManager.Instance; } }
    public override void Init()
    {
        DTC0102_tagCDBPlayer.beforePlayerDataInitializeEventOnRelogin += OnBeforePlayerDataInitializeEventOnRelogin;
        rechargeManager.rechargeCountEvent += OnRechargeCountEvent;
        StoreModel.Instance.RefreshShopEvent += OnRefreshShopEvent;
        StoreModel.Instance.RefreshBuyShopLimitEvent += OnRefreshBuyShopLimitEvent;
        FuncOpen.Instance.OnFuncStateChangeEvent += OnFuncStateChange;
        InitTable();
        InitRedpoints();
    }
    public override void Release()
    {
        DTC0102_tagCDBPlayer.beforePlayerDataInitializeEventOnRelogin -= OnBeforePlayerDataInitializeEventOnRelogin;
        rechargeManager.rechargeCountEvent += OnRechargeCountEvent;
        StoreModel.Instance.RefreshShopEvent -= OnRefreshShopEvent;
        StoreModel.Instance.RefreshBuyShopLimitEvent -= OnRefreshBuyShopLimitEvent;
        FuncOpen.Instance.OnFuncStateChangeEvent -= OnFuncStateChange;
    }
    private void OnFuncStateChange(int obj)
    {
        if (FuncOpen.Instance.IsFuncOpen((int)FuncOpenEnum.DailySpecials))
        {
            UpdateRedPoint();
        }
    }
    private void OnBeforePlayerDataInitializeEventOnRelogin()
    {
        isDailySpecialsToggleOn = false;
    }
    private void OnRechargeCountEvent(int obj)
    {
        UpdateRedPoint();
    }
    private void OnRefreshBuyShopLimitEvent()
    {
        UpdateRedPoint();
    }
    private void OnRefreshShopEvent()
    {
        UpdateRedPoint();
    }
    void InitTable()
    {
        var config = FuncConfigConfig.Get("DailyTehui");
        dailyRechargeIds = JsonMapper.ToObject<int[]>(config.Numerical1);
        bundleRechargeId = int.Parse(config.Numerical2);
        originalPrice = int.Parse(config.Numerical3);
    }
    public bool IsWeekGiftBuy(int ctgId)
    {
        if (!CTGConfig.HasKey(ctgId))
        {
            return false;
        }
        CTGConfig ctgConfig = CTGConfig.Get(ctgId);
        bool hasRechargeCount = RechargeManager.Instance.TryGetRechargeCount(ctgId, out RechargeCount _rechargeCount);
        bool isBuy = hasRechargeCount && _rechargeCount.weekPayCount >= ctgConfig.WeekBuyCount;
        return isBuy;
    }
    public bool IsDayGiftBuy(int ctgId)
    {
        if (!CTGConfig.HasKey(ctgId))
        {
            return false;
        }
        CTGConfig ctgConfig = CTGConfig.Get(ctgId);
        bool hasRechargeCount = RechargeManager.Instance.TryGetRechargeCount(ctgId, out RechargeCount _rechargeCount);
        bool isBuy = hasRechargeCount && _rechargeCount.todayCount >= ctgConfig.DailyBuyCount;
        return isBuy;
    }
    public readonly Dictionary<int, StoreFunc> FunctionOrderToStoreFunc = new Dictionary<int, StoreFunc>()
    {
        {0, StoreFunc.DailySpecialsFree},
        {2, StoreFunc.DailyGiftFree},
        {3, StoreFunc.WeeklyGiftFree},
    };
    public int GetShopIdByFunctionOrder(int functionOrder)
    {
        if (FunctionOrderToStoreFunc.TryGetValue(functionOrder, out StoreFunc storeFunc))
        {
            return GetShopIdByStoreFunc(storeFunc);
        }
        return 0;
    }
    private int GetShopIdByStoreFunc(StoreFunc storeFunc)
    {
        return GetFreeShopIDDict().TryGetValue(storeFunc, out int shopId) ? shopId : 0;
    }
    Dictionary<StoreFunc, int> freeShopIDDict = null;
    private Dictionary<StoreFunc, int> GetFreeShopIDDict()
    {
        if (freeShopIDDict.IsNullOrEmpty())
        {
            freeShopIDDict = new Dictionary<StoreFunc, int>();
            List<StoreConfig> storeConfigs = StoreConfig.GetValues();
            foreach (var storeConfig in storeConfigs)
            {
                foreach (var StoreFunc in FunctionOrderToStoreFunc.Values)
                {
                    if (storeConfig.ShopType == (int)StoreFunc)
                    {
                        freeShopIDDict[StoreFunc] = storeConfig.ID;
                    }
                }
            }
        }
        return freeShopIDDict;
    }
    public bool IsReceived(int shopId)
    {
        if (!StoreConfig.HasKey(shopId))
        {
            return false;
        }
        StoreConfig storeConfig = StoreConfig.Get(shopId);
        int boughtCount = StoreModel.Instance.GetShopLimitBuyCount(shopId);
        return boughtCount >= storeConfig.LimitCnt;
    }
    public string GetCountdownToNextDay()
    {
        int remainSeconds = TimeUtility.GetTodayRemainSeconds();
        return TimeUtility.SecondsToShortDHMS(remainSeconds);
    }
    public string GetCountdownToWeekEnd()
    {
        DateTime now = TimeUtility.ServerNow;
        int daysUntilMonday = (int)DayOfWeek.Monday - (int)now.DayOfWeek;
        if (daysUntilMonday <= 0)
        {
            daysUntilMonday += 7; // 下周一
        }
        DateTime nextMonday = now.AddDays(daysUntilMonday);
        DateTime weekEndTime = new DateTime(nextMonday.Year, nextMonday.Month, nextMonday.Day);
        int remainSeconds = (int)(weekEndTime - now).TotalSeconds;
        return TimeUtility.SecondsToShortDHMS(remainSeconds);
    }
    public bool TryGetDailyCtgIdByIndex(int index, out int ctgId)
    {
        ctgId = 0;
        if (dailyRechargeIds.IsNullOrEmpty())
        {
            return false;
        }
        if (index < 0 || index >= dailyRechargeIds.Length)
        {
            return false;
        }
        ctgId = dailyRechargeIds[index];
        return true;
    }
    /// 检查是否有任意单独商品已购买
    public bool HasAnySingleItemBought()
    {
        if (dailyRechargeIds.IsNullOrEmpty())
        {
            return false;
        }
        for (int i = 0; i < dailyRechargeIds.Length; i++)
        {
            int singleCtgId = dailyRechargeIds[i];
            if (rechargeManager.TryGetRechargeCount(singleCtgId, out RechargeCount singleRechargeCount))
            {
                if (singleRechargeCount.todayCount > 0)
                {
                    return true;
                }
            }
        }
        return false;
    }
    #region 红点
    public Redpoint parentRedpoint = new Redpoint(MainRedDot.DailyTehui);
    public Dictionary<int, Redpoint> tabRedpoints = new Dictionary<int, Redpoint>();
    public void InitRedpoints()
    {
        tabRedpoints.Clear();
        var enumValues = Enum.GetValues(typeof(DailySpecialsTabEnum));
        foreach (DailySpecialsTabEnum tab in enumValues)
        {
            int tabRedponitId = GetTabRedponitId((int)tab);
            tabRedpoints[tabRedponitId] = new Redpoint(MainRedDot.DailyTehui, tabRedponitId);
        }
    }
    public int GetTabRedponitId(int functionOrder)
    {
        if (!Enum.IsDefined(typeof(DailySpecialsTabEnum), functionOrder))
        {
            return 0;
        }
        return MainRedDot.DailyTehui * 10 + functionOrder + 1;
    }
    public void UpdateRedPoint()
    {
        parentRedpoint.state = RedPointState.None;
        foreach (DailySpecialsTabEnum tab in Enum.GetValues(typeof(DailySpecialsTabEnum)))
        {
            int redpointId = GetTabRedponitId((int)tab);
            if (tabRedpoints.ContainsKey(redpointId))
            {
                tabRedpoints[redpointId].state = RedPointState.None;
            }
        }
        // 功能没开
        if (!FuncOpen.Instance.IsFuncOpen((int)FuncOpenEnum.DailySpecials))
            return;
        foreach (DailySpecialsTabEnum tab in Enum.GetValues(typeof(DailySpecialsTabEnum)))
        {
            int redpointId = GetTabRedponitId((int)tab);
            if (!tabRedpoints.ContainsKey(redpointId))
            {
                continue;
            }
            if (tab == DailySpecialsTabEnum.DailySpecials || tab == DailySpecialsTabEnum.DailyGift || tab == DailySpecialsTabEnum.WeeklyGift)
            {
                int shopId = GetShopIdByFunctionOrder((int)tab);
                bool isReceived = IsReceived(shopId);
                if (!isReceived)
                {
                    tabRedpoints[redpointId].state = RedPointState.Simple;
                }
            }
            else if (tab == DailySpecialsTabEnum.SpecialStore)
            {
                Dictionary<int, List<int>> freeShopDict = StoreModel.Instance.freeShopDict;
                bool isFlag = false;
                if (!freeShopDict.IsNullOrEmpty() && freeShopDict.ContainsKey((int)StoreFunc.SpecialStore))
                {
                    var shopList = freeShopDict[(int)StoreFunc.SpecialStore];
                    for (int i = 0; i < shopList.Count; i++)
                    {
                        var shopId = shopList[i];
                        bool isReceived = IsReceived(shopId);
                        if (!isReceived)
                        {
                            isFlag = true;
                            break;
                        }
                    }
                    if (isFlag)
                    {
                        tabRedpoints[redpointId].state = RedPointState.Simple;
                    }
                }
            }
        }
    }
    #endregion
}
public enum DailySpecialsTabEnum
{
    DailySpecials = 0,  // 每日特惠
    SpecialStore = 1,   // 特惠商城
    DailyGift = 2,      // 每日礼包
    WeeklyGift = 3,     // 每周礼包
}
Main/System/DailySpecials/DailySpecialsManager.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 054be96b4996f534bb390b6275520623
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/DailySpecials/DailySpecialsShopWin.cs
New file
@@ -0,0 +1,49 @@
using System;
using UnityEngine;
using UnityEngine.UI;
public class DailySpecialsShopWin : UIBase
{
    [SerializeField] ScrollerController scroller;
    protected override void OnPreOpen()
    {
        scroller.OnRefreshCell += OnRefreshCell;
        StoreModel.Instance.RefreshShopEvent += CreateScroller;
        StoreModel.Instance.RefreshBuyShopLimitEvent += CreateScroller;
        CreateScroller();
    }
    protected override void OnPreClose()
    {
        scroller.OnRefreshCell -= OnRefreshCell;
        StoreModel.Instance.RefreshShopEvent -= CreateScroller;
        StoreModel.Instance.RefreshBuyShopLimitEvent -= CreateScroller;
        StoreModel.Instance.selectStoreFuncType = StoreFunc.Normal;
    }
    void OnRefreshCell(ScrollerDataType type, CellView cell)
    {
        var _cell = cell as StoreLineCell;
        _cell.Display(cell.index);
    }
    void CreateScroller()
    {
        if (!StoreModel.Instance.storeTypeDict.ContainsKey((int)StoreModel.Instance.selectStoreFuncType))
        {
            return;
        }
        scroller.Refresh();
        var list = StoreModel.Instance.storeTypeDict[(int)StoreModel.Instance.selectStoreFuncType];
        for (int i = 0; i < list.Count; i++)
        {
            if (i % 3 == 0)
            {
                scroller.AddCell(ScrollerDataType.Header, i);
            }
        }
        scroller.Restart();
        scroller.lockType = EnhanceLockType.KeepVertical;
    }
}
Main/System/DailySpecials/DailySpecialsShopWin.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f357c7a3d2108a146ae91e3227e0a969
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/DailySpecials/DailySpecialsWeekGiftCell.cs
New file
@@ -0,0 +1,80 @@
using System.Collections.Generic;
using UnityEngine;
public class DailySpecialsWeekGiftCell : MonoBehaviour
{
    [SerializeField] ItemCell[] itemCells;
    [SerializeField] ImageEx imgGiftIcon;
    [SerializeField] ImageEx imgRate;
    [SerializeField] ImageEx imgMask;
    [SerializeField] TextEx txtRate;
    [SerializeField] TextEx txtGiftTitle;
    [SerializeField] TextEx txtLimitInfo;
    [SerializeField] ButtonEx btnBuy;
    [SerializeField] ImageEx imgBuy;
    [SerializeField] TextEx txtBuy;
    DailySpecialsManager manager { get { return DailySpecialsManager.Instance; } }
    public void Display(int index, List<int> ctgIDList)
    {
        if (ctgIDList.IsNullOrEmpty() || ctgIDList.Count <= index || index < 0)
        {
            return;
        }
        int ctgId = ctgIDList[index];
        if (!RechargeManager.Instance.TryGetOrderInfo(ctgId, out var orderInfoConfig))
        {
            return;
        }
        if (!CTGConfig.HasKey(ctgId))
        {
            return;
        }
        CTGConfig ctgConfig = CTGConfig.Get(ctgId);
        int[][] gainItemList = ctgConfig.GainItemList;
        if (gainItemList.IsNullOrEmpty())
        {
            return;
        }
        for (int i = 0; i < itemCells.Length; i++)
        {
            if (i < gainItemList.Length)
            {
                int itemId = gainItemList[i][0];
                int count = gainItemList[i][1];
                itemCells[i].Init(new ItemCellModel(itemId, false, count));
                itemCells[i].button.SetListener(() => { ItemTipUtility.Show(itemId); });
                itemCells[i].SetActive(true);
            }
            else
            {
                itemCells[i].SetActive(false);
            }
        }
        bool hasRechargeCount = RechargeManager.Instance.TryGetRechargeCount(ctgId, out RechargeCount _rechargeCount);
        bool isBuy = manager.IsWeekGiftBuy(ctgId);
        imgRate.SetActive(!isBuy);
        imgMask.SetActive(isBuy);
        imgBuy.SetSprite(!isBuy ? "DailySpecialsBuy1" : "DailySpecialsBuy2");
        txtBuy.text = !isBuy ? Language.Get("PayMoneyNum", orderInfoConfig.PayRMBNumOnSale) : Language.Get("storename11");
        imgGiftIcon.SetSprite(ctgConfig.Icon);
        txtGiftTitle.text = ctgConfig.Title;
        txtRate.text = Language.Get("DailySpecials07", ctgConfig.Percentage);
        txtLimitInfo.text = Language.Get("storename7", ctgConfig.WeekBuyCount - _rechargeCount.weekPayCount, ctgConfig.WeekBuyCount);
        btnBuy.SetListener(() =>
        {
            if (isBuy)
            {
                return;
            }
            RechargeManager.Instance.CTG(ctgId);
        });
    }
}
Main/System/DailySpecials/DailySpecialsWeekGiftCell.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a145ea085a927e4793891ab25c05d3a
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/DailySpecials/DailySpecialsWeekGiftWin.cs
New file
@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class DailySpecialsWeekGiftWin : UIBase
{
    readonly int payType = 21;
    [SerializeField] ScrollerController scroller;
    DailySpecialsManager manager { get { return DailySpecialsManager.Instance; } }
    RechargeManager rechargeManager { get { return RechargeManager.Instance; } }
    protected override void OnPreOpen()
    {
        scroller.OnRefreshCell += OnRefreshCell;
        rechargeManager.rechargeCountEvent += OnRechargeCountEvent;
        CreateScroller();
    }
    private void OnRechargeCountEvent(int obj)
    {
        CreateScroller();
    }
    protected override void OnPreClose()
    {
        scroller.OnRefreshCell -= OnRefreshCell;
        rechargeManager.rechargeCountEvent -= OnRechargeCountEvent;
        StoreModel.Instance.selectStoreFuncType = StoreFunc.Normal;
    }
    void OnRefreshCell(ScrollerDataType type, CellView cell)
    {
        var _cell = cell.GetComponent<DailySpecialsWeekGiftCell>();
        _cell.Display(cell.index, ctgIDList);
    }
    List<int> ctgIDList = null;
    void CreateScroller()
    {
        if (ctgIDList.IsNullOrEmpty())
        {
            ctgIDList = RechargeManager.Instance.GetCTGIDListByType(payType);
        }
        if (!ctgIDList.IsNullOrEmpty())
        {
            ctgIDList = ctgIDList.OrderBy(ctgId => { return manager.IsWeekGiftBuy(ctgId); }).ThenBy(ctgId => ctgId).ToList();
            scroller.Refresh();
            for (int i = 0; i < ctgIDList.Count; i++)
            {
                scroller.AddCell(ScrollerDataType.Header, i);
            }
            scroller.Restart();
        }
    }
}
Main/System/DailySpecials/DailySpecialsWeekGiftWin.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8e1af0521d5ad21469154f8d34cce2d2
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/DailySpecials/DailySpecialsWin.cs
New file
@@ -0,0 +1,90 @@
using System;
using UnityEngine;
using UnityEngine.UI;
public class DailySpecialsWin : UIBase
{
    [SerializeField] DailySpecialsItem[] items;
    [SerializeField] GradientText txtRate;
    [SerializeField] TextEx txtCurrentPrice;
    [SerializeField] TextEx txtOriginalPrice;
    [SerializeField] ButtonEx btnBuyAll;
    [SerializeField] ImageEx imgBuyAll;
    [SerializeField] TextEx txtBuyAll;
    [SerializeField] ItemCell itemcellAllBuy;
    [SerializeField] ImageEx imgAllBuyHave;
    DailySpecialsManager manager { get { return DailySpecialsManager.Instance; } }
    RechargeManager rechargeManager { get { return RechargeManager.Instance; } }
    protected override void InitComponent()
    {
        btnBuyAll.SetListener(() =>
        {
            RechargeManager.Instance.CTG(manager.bundleRechargeId);
        });
    }
    protected override void OnPreOpen()
    {
        rechargeManager.rechargeCountEvent += OnRechargeCountEvent;
        Display();
    }
    protected override void OnPreClose()
    {
        rechargeManager.rechargeCountEvent -= OnRechargeCountEvent;
    }
    private void OnRechargeCountEvent(int obj)
    {
        Display();
    }
    void Display()
    {
        int ctgid = manager.bundleRechargeId;
        if (!CTGConfig.HasKey(ctgid))
        {
            return;
        }
        if (!RechargeManager.Instance.TryGetOrderInfo(ctgid, out var orderInfoConfig))
        {
            return;
        }
        CTGConfig ctgConfig = CTGConfig.Get(ctgid);
        int[][] gainItemList = ctgConfig.GainItemList;
        if (gainItemList.IsNullOrEmpty())
        {
            return;
        }
        txtRate.text = Language.Get("DailySpecials07", ctgConfig.Percentage);
        int itemId = gainItemList[0][0];
        int itemCount = gainItemList[0][1];
        itemcellAllBuy.Init(new ItemCellModel(itemId, false, itemCount));
        itemcellAllBuy.button.SetListener(() => { ItemTipUtility.Show(itemId); });
        bool hasRechargeCount = RechargeManager.Instance.TryGetRechargeCount(ctgid, out RechargeCount _rechargeCount);
        bool isBuyAll = hasRechargeCount && _rechargeCount.todayCount > 0;
        // 检查是否有任意单独商品已购买
        bool hasAnySingleItemBought = manager.HasAnySingleItemBought();
        // 如果有任意单独商品已购买,则打包也视为已购买状态
        bool finalIsBuyAll = isBuyAll || hasAnySingleItemBought;
        imgAllBuyHave.SetActive(isBuyAll);
        txtBuyAll.text = !finalIsBuyAll ? Language.Get("DailySpecials03", Language.Get("PayMoneyNum", orderInfoConfig.PayRMBNumOnSale)) : Language.Get("storename11");
        btnBuyAll.interactable = !finalIsBuyAll;
        imgBuyAll.gray = finalIsBuyAll;
        for (int i = 0; i < items.Length; i++)
        {
            items[i].Display(i, isBuyAll);
        }
    }
}
Main/System/DailySpecials/DailySpecialsWin.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fdfabe6b2123bfe4dbd292bcc96695f9
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/Main/HomeWin.cs
@@ -62,6 +62,7 @@
    [SerializeField] RightFuncInHome rightFuncInHome;
    [SerializeField] Button FirstChargeBtn;
    [SerializeField] Button DailySpecialsBtns;
    [SerializeField] Button osMainLevelBtn;
    [SerializeField] Button osHeroCallBtn;
    [SerializeField] Button osGalaBtn;
@@ -205,6 +206,12 @@
        {
            UIManager.Instance.OpenWindow<OSGalaBaseWin>();
        });
        DailySpecialsBtns.AddListener(() =>
        {
            UIManager.Instance.OpenWindow<DailySpecialsBaseWin>();
        });
    }
@@ -232,6 +239,7 @@
        //inputFastChat.characterLimit = ChatManager.Instance.characterLimit;
        //UpdateSendButton();
        ShowChatInfo();
        DisplayDailySpecialsBtn();
    }
    protected override void OnPreOpen()
@@ -731,6 +739,12 @@
        DisplayFirstChargeBtn();
    }
    private void DisplayDailySpecialsBtn()
    {
        bool isOpen = FuncOpen.Instance.IsFuncOpen((int)FuncOpenEnum.DailySpecials);
        DailySpecialsBtns.SetActive(isOpen);
    }
    private void OnFuncStateChange(int funcId)
    {
        if (funcId == GeneralDefine.mainRightFuncOpenFuncID)
@@ -746,6 +760,10 @@
        {
            DisplayOSActivity();
        }
        else if (funcId == (int)FuncOpenEnum.DailySpecials)
        {
            DisplayDailySpecialsBtn();
        }
    }
    private void OnUpdateFirstChargeInfo()
Main/System/Redpoint/MainRedDot.cs
@@ -138,6 +138,7 @@
    public const int LineupRecommendRepoint = 473; //阵容推荐
    public const int FunctionPreviewRepoint = 474; //功能预告
    public const int HeroFatesRepoint = 475;//宿缘
    public const int DailyTehui = 476;//每日特惠
    public void Register()
    {
Main/System/Store/StoreModel.cs
@@ -572,7 +572,10 @@
    OSHeroCall = 4, //4:开服招募礼包
    OSGalaChange = 5, //5:开服庆典兑换
    OSGalaGift = 6, //6:开服庆典礼包
    SpecialStore = 7, //7:特惠商城
    DailySpecialsFree = 8, //8: 每日特惠-每日特惠免费
    DailyGiftFree = 9, //9: 每日特惠-每日礼包免费
    WeeklyGiftFree = 10, //10: 每日特惠-每周礼包免费
}
Main/Utility/EnumHelper.cs
@@ -711,6 +711,7 @@
    default44,  // 272 武将招募积分
    GoldRush = 285, // 淘金令
    ChallengeVoucher = 286,//挑战凭证
    DailySpecials = 287, //特惠印绶
    OSGalaScore = 288, //开服庆典积分
};
@@ -846,6 +847,7 @@
    FunctionPreview = 48, //功能预览
    HeroFatesUpgrade = 49, //宿缘升级
    LineupRecommend = 50, //阵容推荐
    DailySpecials = 51, //每日特惠
}
Main/Utility/UIHelper.cs
@@ -1004,6 +1004,7 @@
        {43, PlayerDataType.default34},
        {42, PlayerDataType.default33},
        {53, PlayerDataType.ChallengeVoucher},
        {54, PlayerDataType.DailySpecials},
        {55, PlayerDataType.OSGalaScore},
        {99, PlayerDataType.ExAttr11},
    };
@@ -1152,6 +1153,11 @@
                {
                    //挑战凭证
                    return PlayerDatas.Instance.GetPlayerDataByType(PlayerDataType.ChallengeVoucher);
                }
            case 54:
                {
                    //特惠印绶
                    return PlayerDatas.Instance.GetPlayerDataByType(PlayerDataType.DailySpecials);
                }
            case 55:
                {
@@ -1436,7 +1442,7 @@
    {
        return GUIUtility.systemCopyBuffer;
    }
    /// <summary>
    /// 强制刷新Layout,解决嵌套Layout和ContentSizeFitter的重叠问题
    /// </summary>
@@ -1457,6 +1463,6 @@
        }
    }
}