diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..76069758 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "WebFetch(domain:www.donet5.com)" + ], + "deny": [], + "ask": [] + } +} diff --git a/Yi.Abp.Net8/.claude/settings.local.json b/Yi.Abp.Net8/.claude/settings.local.json new file mode 100644 index 00000000..54ea23cd --- /dev/null +++ b/Yi.Abp.Net8/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(dotnet build \"E:\\code\\github\\Yi\\Yi.Abp.Net8\\module\\ai-hub\\Yi.Framework.AiHub.Application\\Yi.Framework.AiHub.Application.csproj\" --no-restore)", + "Read(//e/code/github/Yi/Yi.Ai.Vue3/**)" + ], + "deny": [], + "ask": [] + } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/CardFlipStatusOutput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/CardFlipStatusOutput.cs new file mode 100644 index 00000000..3594dbb8 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/CardFlipStatusOutput.cs @@ -0,0 +1,93 @@ +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.CardFlip; + +/// +/// 翻牌任务状态输出 +/// +public class CardFlipStatusOutput +{ + /// + /// 本周总翻牌次数 + /// + public int TotalFlips { get; set; } + + /// + /// 剩余免费次数 + /// + public int RemainingFreeFlips { get; set; } + + /// + /// 剩余赠送次数 + /// + public int RemainingBonusFlips { get; set; } + + /// + /// 剩余邀请解锁次数 + /// + public int RemainingInviteFlips { get; set; } + + /// + /// 是否可以翻牌 + /// + public bool CanFlip { get; set; } + + /// + /// 用户的邀请码 + /// + public string? MyInviteCode { get; set; } + + /// + /// 本周邀请人数 + /// + public int InvitedCount { get; set; } + + /// + /// 是否已被邀请(被邀请后不可再提供邀请码) + /// + public bool IsInvited { get; set; } + + /// + /// 翻牌记录 + /// + public List FlipRecords { get; set; } = new(); + + /// + /// 下次可翻牌提示 + /// + public string? NextFlipTip { get; set; } +} + +/// +/// 翻牌记录 +/// +public class CardFlipRecord +{ + /// + /// 翻牌序号(1-10) + /// + public int FlipNumber { get; set; } + + /// + /// 是否已翻 + /// + public bool IsFlipped { get; set; } + + /// + /// 是否中奖 + /// + public bool IsWin { get; set; } + + /// + /// 奖励金额(token数) + /// + public long? RewardAmount { get; set; } + + /// + /// 翻牌类型描述 + /// + public string? FlipTypeDesc { get; set; } + + /// + /// 在翻牌顺序中的位置(1-10,表示第几个翻) + /// + public int FlipOrderIndex { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/FlipCardInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/FlipCardInput.cs new file mode 100644 index 00000000..e974dfd6 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/FlipCardInput.cs @@ -0,0 +1,12 @@ +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.CardFlip; + +/// +/// 翻牌输入 +/// +public class FlipCardInput +{ + /// + /// 翻牌序号(1-10) + /// + public int FlipNumber { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/FlipCardOutput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/FlipCardOutput.cs new file mode 100644 index 00000000..b817edf3 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/FlipCardOutput.cs @@ -0,0 +1,32 @@ +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.CardFlip; + +/// +/// 翻牌输出 +/// +public class FlipCardOutput +{ + /// + /// 翻牌序号(1-10) + /// + public int FlipNumber { get; set; } + + /// + /// 是否中奖 + /// + public bool IsWin { get; set; } + + /// + /// 奖励金额(token数) + /// + public long? RewardAmount { get; set; } + + /// + /// 奖励描述 + /// + public string? RewardDesc { get; set; } + + /// + /// 剩余可翻次数 + /// + public int RemainingFlips { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/InviteCodeOutput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/InviteCodeOutput.cs new file mode 100644 index 00000000..2531498a --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/InviteCodeOutput.cs @@ -0,0 +1,48 @@ +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.CardFlip; + +/// +/// 邀请码信息输出 +/// +public class InviteCodeOutput +{ + /// + /// 我的邀请码 + /// + public string? MyInviteCode { get; set; } + + /// + /// 本周邀请人数 + /// + public int InvitedCount { get; set; } + + /// + /// 是否已被邀请 + /// + public bool IsInvited { get; set; } + + /// + /// 邀请历史记录 + /// + public List InvitationHistory { get; set; } = new(); +} + +/// +/// 邀请历史记录项 +/// +public class InvitationHistoryItem +{ + /// + /// 被邀请人昵称(脱敏) + /// + public string InvitedUserName { get; set; } = string.Empty; + + /// + /// 邀请时间 + /// + public DateTime InvitationTime { get; set; } + + /// + /// 本周所在 + /// + public string WeekDescription { get; set; } = string.Empty; +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/UseInviteCodeInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/UseInviteCodeInput.cs new file mode 100644 index 00000000..e02cc6ab --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/UseInviteCodeInput.cs @@ -0,0 +1,12 @@ +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.CardFlip; + +/// +/// 使用邀请码输入 +/// +public class UseInviteCodeInput +{ + /// + /// 邀请码 + /// + public string InviteCode { get; set; } = string.Empty; +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/ICardFlipService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/ICardFlipService.cs new file mode 100644 index 00000000..9dbc2544 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/ICardFlipService.cs @@ -0,0 +1,41 @@ +using Yi.Framework.AiHub.Application.Contracts.Dtos.CardFlip; + +namespace Yi.Framework.AiHub.Application.Contracts.IServices; + +/// +/// 翻牌服务接口 +/// +public interface ICardFlipService +{ + /// + /// 获取本周翻牌任务状态 + /// + /// + Task GetWeeklyTaskStatusAsync(); + + /// + /// 翻牌 + /// + /// 翻牌输入 + /// + Task FlipCardAsync(FlipCardInput input); + + /// + /// 使用邀请码解锁翻牌次数 + /// + /// 邀请码输入 + /// + Task UseInviteCodeAsync(UseInviteCodeInput input); + + /// + /// 获取我的邀请码信息 + /// + /// + Task GetMyInviteCodeAsync(); + + /// + /// 生成我的邀请码(如果没有) + /// + /// + Task GenerateMyInviteCodeAsync(); +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/CardFlipService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/CardFlipService.cs new file mode 100644 index 00000000..d7bb6ba8 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/CardFlipService.cs @@ -0,0 +1,286 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Volo.Abp.Application.Services; +using Volo.Abp.Users; +using Yi.Framework.AiHub.Application.Contracts.Dtos.CardFlip; +using Yi.Framework.AiHub.Application.Contracts.IServices; +using Yi.Framework.AiHub.Domain.Entities; +using Yi.Framework.AiHub.Domain.Extensions; +using Yi.Framework.AiHub.Domain.Managers; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace Yi.Framework.AiHub.Application.Services; + +/// +/// 翻牌服务 - 应用层组合服务 +/// +[Authorize] +public class CardFlipService : ApplicationService, ICardFlipService +{ + private readonly CardFlipManager _cardFlipManager; + private readonly InviteCodeManager _inviteCodeManager; + private readonly ISqlSugarRepository _premiumPackageRepository; + private readonly ILogger _logger; + + public CardFlipService( + CardFlipManager cardFlipManager, + InviteCodeManager inviteCodeManager, + ISqlSugarRepository premiumPackageRepository, + ILogger logger) + { + _cardFlipManager = cardFlipManager; + _inviteCodeManager = inviteCodeManager; + _premiumPackageRepository = premiumPackageRepository; + _logger = logger; + } + + /// + /// 获取本周翻牌任务状态 + /// + public async Task GetWeeklyTaskStatusAsync() + { + var userId = CurrentUser.GetId(); + var weekStart = CardFlipManager.GetWeekStartDate(DateTime.Now); + + // 获取本周任务 + var task = await _cardFlipManager.GetOrCreateWeeklyTaskAsync(userId, weekStart, createIfNotExists: false); + + // 获取邀请码信息 + var inviteCode = await _inviteCodeManager.GetUserInviteCodeAsync(userId); + + // 统计本周邀请人数 + var invitedCount = await _inviteCodeManager.GetWeeklyInvitationCountAsync(userId, weekStart); + + // 检查用户是否已被邀请 + var isInvited = await _inviteCodeManager.IsUserInvitedAsync(userId); + + var output = new CardFlipStatusOutput + { + TotalFlips = task?.TotalFlips ?? 0, + RemainingFreeFlips = CardFlipManager.MAX_FREE_FLIPS - (task?.FreeFlipsUsed ?? 0), + RemainingBonusFlips = 0, // 已废弃 + RemainingInviteFlips = CardFlipManager.MAX_INVITE_FLIPS - (task?.InviteFlipsUsed ?? 0), + CanFlip = _cardFlipManager.CanFlipCard(task), + MyInviteCode = inviteCode?.Code, + InvitedCount = invitedCount, + IsInvited = isInvited, + FlipRecords = BuildFlipRecords(task) + }; + + // 生成提示信息 + output.NextFlipTip = GenerateNextFlipTip(output); + + return output; + } + + /// + /// 翻牌 + /// + public async Task FlipCardAsync(FlipCardInput input) + { + var userId = CurrentUser.GetId(); + var weekStart = CardFlipManager.GetWeekStartDate(DateTime.Now); + + // 执行翻牌逻辑(由Manager处理验证和翻牌) + var result = await _cardFlipManager.ExecuteFlipAsync(userId, input.FlipNumber, weekStart); + + // 如果中奖,发放奖励 + if (result.IsWin) + { + await GrantRewardAsync(userId, result.RewardAmount, $"翻牌活动第{input.FlipNumber}次中奖"); + } + + // 构建输出 + var output = new FlipCardOutput + { + FlipNumber = result.FlipNumber, + IsWin = result.IsWin, + RewardAmount = result.RewardAmount, + RewardDesc = result.RewardDesc, + RemainingFlips = CardFlipManager.TOTAL_MAX_FLIPS - input.FlipNumber + }; + + return output; + } + + /// + /// 使用邀请码解锁翻牌次数 + /// + public async Task UseInviteCodeAsync(UseInviteCodeInput input) + { + var userId = CurrentUser.GetId(); + var weekStart = CardFlipManager.GetWeekStartDate(DateTime.Now); + + // 获取本周任务 + var task = await _cardFlipManager.GetOrCreateWeeklyTaskAsync(userId, weekStart, createIfNotExists: true); + + // 验证是否已经使用了所有邀请解锁次数 + if (task.InviteFlipsUsed >= CardFlipManager.MAX_INVITE_FLIPS) + { + throw new UserFriendlyException("本周邀请解锁次数已用完"); + } + + // 使用邀请码(由Manager处理验证和邀请逻辑) + await _inviteCodeManager.UseInviteCodeAsync(userId, input.InviteCode); + + _logger.LogInformation($"用户 {userId} 使用邀请码 {input.InviteCode} 解锁翻牌次数成功"); + } + + /// + /// 获取我的邀请码信息 + /// + public async Task GetMyInviteCodeAsync() + { + var userId = CurrentUser.GetId(); + var weekStart = CardFlipManager.GetWeekStartDate(DateTime.Now); + + // 获取我的邀请码 + var inviteCode = await _inviteCodeManager.GetUserInviteCodeAsync(userId); + + // 统计本周邀请人数 + var invitedCount = await _inviteCodeManager.GetWeeklyInvitationCountAsync(userId, weekStart); + + // 获取邀请历史 + var invitationHistory = await _inviteCodeManager.GetInvitationHistoryAsync(userId, 10); + + return new InviteCodeOutput + { + MyInviteCode = inviteCode?.Code, + InvitedCount = invitedCount, + IsInvited = inviteCode?.IsUserInvited ?? false, + InvitationHistory = invitationHistory.Select(x => new InvitationHistoryItem + { + InvitedUserName = x.InvitedUserName, + InvitationTime = x.InvitationTime, + WeekDescription = GetWeekDescription(x.InvitationTime) + }).ToList() + }; + } + + /// + /// 生成我的邀请码 + /// + public async Task GenerateMyInviteCodeAsync() + { + var userId = CurrentUser.GetId(); + + // 生成邀请码(由Manager处理) + var code = await _inviteCodeManager.GenerateInviteCodeForUserAsync(userId); + + return code; + } + + #region 私有辅助方法 + + /// + /// 构建翻牌记录列表 + /// + private List BuildFlipRecords(CardFlipTaskAggregateRoot? task) + { + var records = new List(); + + // 获取已翻牌的顺序 + var flippedOrder = task != null ? _cardFlipManager.GetFlippedOrder(task) : new List(); + var flippedNumbers = new HashSet(flippedOrder); + + // 构建记录,按照原始序号1-10排列 + for (int i = 1; i <= CardFlipManager.TOTAL_MAX_FLIPS; i++) + { + var record = new CardFlipRecord + { + FlipNumber = i, + IsFlipped = flippedNumbers.Contains(i), + IsWin = false, + FlipTypeDesc = CardFlipManager.GetFlipTypeDesc(i), + // 设置在翻牌顺序中的位置(0表示未翻,>0表示第几个翻的) + FlipOrderIndex = flippedOrder.IndexOf(i) >= 0 ? flippedOrder.IndexOf(i) + 1 : 0 + }; + + // 设置中奖信息 + // 判断这张卡是第几次翻的 + if (task != null && flippedNumbers.Contains(i)) + { + var flipOrderIndex = flippedOrder.IndexOf(i) + 1; // 第几次翻的(1-based) + + // 第8次翻的卡中奖 + if (flipOrderIndex == 8) + { + record.IsWin = true; + record.RewardAmount = task.EighthRewardAmount; + } + // 第9次翻的卡中奖 + else if (flipOrderIndex == 9) + { + record.IsWin = true; + record.RewardAmount = task.NinthRewardAmount; + } + // 第10次翻的卡中奖 + else if (flipOrderIndex == 10) + { + record.IsWin = true; + record.RewardAmount = task.TenthRewardAmount; + } + } + + records.Add(record); + } + + return records; + } + + /// + /// 生成下次翻牌提示 + /// + private string GenerateNextFlipTip(CardFlipStatusOutput status) + { + if (status.TotalFlips >= CardFlipManager.TOTAL_MAX_FLIPS) + { + return "本周翻牌次数已用完,请下周再来!"; + } + + if (status.RemainingFreeFlips > 0) + { + return $"本周您还有{status.RemainingFreeFlips}次免费翻牌机会"; + } + else if (status.RemainingInviteFlips > 0) + { + if (status.TotalFlips >= 7) + { + return $"本周使用他人邀请码可解锁{status.RemainingInviteFlips}次翻牌,且必中大奖!每次中奖最大额度将翻倍!"; + } + + return $"本周使用他人邀请码可解锁{status.RemainingInviteFlips}次翻牌,必中大奖!每次中奖最大额度将翻倍!"; + } + + return "继续加油!"; + } + + /// + /// 发放奖励 + /// + private async Task GrantRewardAsync(Guid userId, long amount, string description) + { + var premiumPackage = new PremiumPackageAggregateRoot(userId, amount, description) + { + PurchaseAmount = 0, // 奖励不需要付费 + Remark = $"翻牌活动奖励:{amount / 10000}w tokens" + }; + + await _premiumPackageRepository.InsertAsync(premiumPackage); + + _logger.LogInformation($"用户 {userId} 获得翻牌奖励 {amount / 10000}w tokens"); + } + + /// + /// 获取周描述 + /// + private string GetWeekDescription(DateTime date) + { + var weekStart = CardFlipManager.GetWeekStartDate(date); + var weekEnd = weekStart.AddDays(6); + + return $"{weekStart:MM-dd} 至 {weekEnd:MM-dd}"; + } + + #endregion +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/CardFlipTaskAggregateRoot.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/CardFlipTaskAggregateRoot.cs new file mode 100644 index 00000000..3adaba7e --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/CardFlipTaskAggregateRoot.cs @@ -0,0 +1,187 @@ +using SqlSugar; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Yi.Framework.AiHub.Domain.Entities; + +/// +/// 翻牌任务记录 +/// +[SugarTable("Ai_CardFlipTask")] +[SugarIndex($"index_{nameof(UserId)}_{nameof(WeekStartDate)}", + nameof(UserId), OrderByType.Asc, + nameof(WeekStartDate), OrderByType.Desc)] +public class CardFlipTaskAggregateRoot : FullAuditedAggregateRoot +{ + public CardFlipTaskAggregateRoot() + { + } + + public CardFlipTaskAggregateRoot(Guid userId, DateTime weekStartDate) + { + UserId = userId; + WeekStartDate = weekStartDate.Date; // 确保只存储日期部分 + TotalFlips = 0; + FreeFlipsUsed = 0; + BonusFlipsUsed = 0; + InviteFlipsUsed = 0; + IsFirstFlipDone = false; + HasNinthReward = false; + HasTenthReward = false; + FlippedOrder = new List(); + } + + /// + /// 用户ID + /// + public Guid UserId { get; set; } + + /// + /// 本周开始日期(每周一) + /// + public DateTime WeekStartDate { get; set; } + + /// + /// 总共已翻牌次数 + /// + public int TotalFlips { get; set; } + + /// + /// 已使用的免费次数(最多7次) + /// + public int FreeFlipsUsed { get; set; } + + /// + /// 已使用的赠送次数(已废弃,保持为0) + /// + public int BonusFlipsUsed { get; set; } + + /// + /// 已使用的邀请解锁次数(最多3次) + /// + public int InviteFlipsUsed { get; set; } + + /// + /// 是否已完成首次翻牌(用于判断是否创建任务) + /// + public bool IsFirstFlipDone { get; set; } + + /// + /// 是否已获得第8次奖励 + /// + public bool HasEighthReward { get; set; } + + /// + /// 第8次奖励金额(100-300w) + /// + public long? EighthRewardAmount { get; set; } + + /// + /// 是否已获得第9次奖励 + /// + public bool HasNinthReward { get; set; } + + /// + /// 第9次奖励金额(100-500w) + /// + public long? NinthRewardAmount { get; set; } + + /// + /// 是否已获得第10次奖励 + /// + public bool HasTenthReward { get; set; } + + /// + /// 第10次奖励金额(100-1000w) + /// + public long? TenthRewardAmount { get; set; } + + /// + /// 备注信息 + /// + [SugarColumn(Length = 500, IsNullable = true)] + public string? Remark { get; set; } + + /// + /// 已翻牌的顺序(存储用户实际翻牌的序号列表,如[3,7,1,5]表示依次翻了3号、7号、1号、5号牌) + /// + [SugarColumn(IsJson = true, IsNullable = true)] + public List? FlippedOrder { get; set; } + + /// + /// 增加翻牌次数 + /// + /// 翻牌类型 + public void IncrementFlip(FlipType flipType) + { + TotalFlips++; + + switch (flipType) + { + case FlipType.Free: + FreeFlipsUsed++; + break; + case FlipType.Bonus: + BonusFlipsUsed++; + break; + case FlipType.Invite: + InviteFlipsUsed++; + break; + } + + if (!IsFirstFlipDone) + { + IsFirstFlipDone = true; + } + } + + /// + /// 记录第8次奖励 + /// + /// 奖励金额 + public void SetEighthReward(long amount) + { + HasEighthReward = true; + EighthRewardAmount = amount; + } + + /// + /// 记录第9次奖励 + /// + /// 奖励金额 + public void SetNinthReward(long amount) + { + HasNinthReward = true; + NinthRewardAmount = amount; + } + + /// + /// 记录第10次奖励 + /// + /// 奖励金额 + public void SetTenthReward(long amount) + { + HasTenthReward = true; + TenthRewardAmount = amount; + } +} + +/// +/// 翻牌类型枚举 +/// +public enum FlipType +{ + /// + /// 免费翻牌(1-7次) + /// + Free = 0, + + /// + /// 赠送翻牌(已废弃) + /// + Bonus = 1, + + /// + /// 邀请解锁翻牌(8-10次) + /// + Invite = 2 +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/InvitationRecordAggregateRoot.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/InvitationRecordAggregateRoot.cs new file mode 100644 index 00000000..6e1bea35 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/InvitationRecordAggregateRoot.cs @@ -0,0 +1,76 @@ +using SqlSugar; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Yi.Framework.AiHub.Domain.Entities; + +/// +/// 邀请记录 +/// +[SugarTable("Ai_InvitationRecord")] +[SugarIndex($"index_{nameof(InviterId)}_{nameof(InvitedUserId)}", + nameof(InviterId), OrderByType.Asc, + nameof(InvitedUserId), OrderByType.Asc)] +[SugarIndex($"index_{nameof(InvitedUserId)}", nameof(InvitedUserId), OrderByType.Asc)] +public class InvitationRecordAggregateRoot : FullAuditedAggregateRoot +{ + public InvitationRecordAggregateRoot() + { + } + + public InvitationRecordAggregateRoot(Guid inviterId, Guid invitedUserId, string inviteCode) + { + InviterId = inviterId; + InvitedUserId = invitedUserId; + InviteCode = inviteCode; + InvitationTime = DateTime.Now; + Status = InvitationStatus.Valid; + } + + /// + /// 邀请人ID + /// + public Guid InviterId { get; set; } + + /// + /// 被邀请人ID + /// + public Guid InvitedUserId { get; set; } + + /// + /// 使用的邀请码 + /// + [SugarColumn(Length = 50)] + public string InviteCode { get; set; } = string.Empty; + + /// + /// 邀请时间 + /// + public DateTime InvitationTime { get; set; } + + /// + /// 邀请状态(0=有效,1=已撤销) + /// + public InvitationStatus Status { get; set; } + + /// + /// 备注信息 + /// + [SugarColumn(Length = 500, IsNullable = true)] + public string? Remark { get; set; } +} + +/// +/// 邀请状态枚举 +/// +public enum InvitationStatus +{ + /// + /// 有效 + /// + Valid = 0, + + /// + /// 已撤销 + /// + Revoked = 1 +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/InviteCodeAggregateRoot.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/InviteCodeAggregateRoot.cs new file mode 100644 index 00000000..de569b49 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/InviteCodeAggregateRoot.cs @@ -0,0 +1,88 @@ +using SqlSugar; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Yi.Framework.AiHub.Domain.Entities; + +/// +/// 用户邀请码 +/// +[SugarTable("Ai_InviteCode")] +[SugarIndex($"index_{nameof(UserId)}", nameof(UserId), OrderByType.Asc, true)] +[SugarIndex($"index_{nameof(Code)}", nameof(Code), OrderByType.Asc, true)] +public class InviteCodeAggregateRoot : FullAuditedAggregateRoot +{ + public InviteCodeAggregateRoot() + { + } + + public InviteCodeAggregateRoot(Guid userId, string code) + { + UserId = userId; + Code = code; + IsUsed = false; + IsUserInvited = false; + UsedCount = 0; + } + + /// + /// 用户ID(邀请码拥有者) + /// + public Guid UserId { get; set; } + + /// + /// 邀请码(唯一) + /// + [SugarColumn(Length = 50)] + public string Code { get; set; } = string.Empty; + + /// + /// 是否已被使用(一个邀请码只能被使用一次) + /// + public bool IsUsed { get; set; } + + /// + /// 邀请码拥有者是否已被他人邀请(被邀请后不可再提供邀请码) + /// + public bool IsUserInvited { get; set; } + + /// + /// 被使用次数(统计用) + /// + public int UsedCount { get; set; } + + /// + /// 使用时间 + /// + public DateTime? UsedTime { get; set; } + + /// + /// 使用人ID + /// + public Guid? UsedByUserId { get; set; } + + /// + /// 备注信息 + /// + [SugarColumn(Length = 500, IsNullable = true)] + public string? Remark { get; set; } + + /// + /// 标记邀请码已被使用 + /// + /// 使用者ID + public void MarkAsUsed(Guid usedByUserId) + { + IsUsed = true; + UsedTime = DateTime.Now; + UsedByUserId = usedByUserId; + UsedCount++; + } + + /// + /// 标记用户已被邀请 + /// + public void MarkUserAsInvited() + { + IsUserInvited = true; + } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/CardFlipManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/CardFlipManager.cs new file mode 100644 index 00000000..0eae034c --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/CardFlipManager.cs @@ -0,0 +1,346 @@ +using Microsoft.Extensions.Logging; +using Volo.Abp.Domain.Services; +using Yi.Framework.AiHub.Domain.Entities; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace Yi.Framework.AiHub.Domain.Managers; + +/// +/// 翻牌管理器 - 负责翻牌核心业务逻辑 +/// +public class CardFlipManager : DomainService +{ + private readonly ISqlSugarRepository _cardFlipTaskRepository; + private readonly ISqlSugarRepository _invitationRecordRepository; + private readonly InviteCodeManager _inviteCodeManager; + private readonly ILogger _logger; + + // 翻牌规则配置 + public const int MAX_FREE_FLIPS = 7; // 免费翻牌次数 + public const int MAX_INVITE_FLIPS = 3; // 邀请解锁翻牌次数 + public const int TOTAL_MAX_FLIPS = 10; // 总最大翻牌次数 + + private const int EIGHTH_FLIP = 8; // 第8次翻牌 + private const int NINTH_FLIP = 9; // 第9次翻牌 + private const int TENTH_FLIP = 10; // 第10次翻牌 + + private const long EIGHTH_MIN_REWARD = 1000000; // 第8次最小奖励 100w + private const long EIGHTH_MAX_REWARD = 3000000; // 第8次最大奖励 300w + private const long NINTH_MIN_REWARD = 1000000; // 第9次最小奖励 100w + private const long NINTH_MAX_REWARD = 5000000; // 第9次最大奖励 500w + private const long TENTH_MIN_REWARD = 1000000; // 第10次最小奖励 100w + private const long TENTH_MAX_REWARD = 10000000; // 第10次最大奖励 1000w + + public CardFlipManager( + ISqlSugarRepository cardFlipTaskRepository, + ISqlSugarRepository invitationRecordRepository, + InviteCodeManager inviteCodeManager, + ILogger logger) + { + _cardFlipTaskRepository = cardFlipTaskRepository; + _invitationRecordRepository = invitationRecordRepository; + _inviteCodeManager = inviteCodeManager; + _logger = logger; + } + + /// + /// 获取或创建本周任务 + /// + public async Task GetOrCreateWeeklyTaskAsync( + Guid userId, + DateTime weekStart, + bool createIfNotExists) + { + var task = await _cardFlipTaskRepository._DbQueryable + .Where(x => x.UserId == userId && x.WeekStartDate == weekStart) + .FirstAsync(); + + if (task == null && createIfNotExists) + { + task = new CardFlipTaskAggregateRoot(userId, weekStart); + await _cardFlipTaskRepository.InsertAsync(task); + } + + return task; + } + + /// + /// 获取已翻牌的顺序列表 + /// + public List GetFlippedOrder(CardFlipTaskAggregateRoot task) + { + return task.FlippedOrder ?? new List(); + } + + /// + /// 执行翻牌逻辑 + /// + /// 用户ID + /// 翻牌序号 + /// 本周开始日期 + /// 翻牌结果 + public async Task ExecuteFlipAsync(Guid userId, int flipNumber, DateTime weekStart) + { + // 验证翻牌序号 + if (flipNumber < 1 || flipNumber > TOTAL_MAX_FLIPS) + { + throw new UserFriendlyException($"翻牌序号必须在1-{TOTAL_MAX_FLIPS}之间"); + } + + // 获取或创建本周任务 + var task = await GetOrCreateWeeklyTaskAsync(userId, weekStart, createIfNotExists: true); + + // 验证翻牌次数 + if (task.TotalFlips >= TOTAL_MAX_FLIPS) + { + throw new UserFriendlyException("本周翻牌次数已用完,请下周再来!"); + } + + // 验证该牌是否已经翻过 + var flippedOrder = GetFlippedOrder(task); + if (flippedOrder.Contains(flipNumber)) + { + throw new UserFriendlyException($"第 {flipNumber} 号牌已经翻过了!"); + } + + // 判断翻牌类型 + var flipType = DetermineFlipType(task); + + // 验证是否有足够的次数 + if (!CanUseFlipType(task, flipType)) + { + throw new UserFriendlyException(GetFlipTypeErrorMessage(flipType)); + } + + // 如果是邀请类型翻牌,必须验证用户本周填写的邀请码数量足够 + if (flipType == FlipType.Invite) + { + // 查询本周已使用的邀请码数量 + var weeklyInviteCodeUsedCount = await _invitationRecordRepository._DbQueryable + .Where(x => x.InvitedUserId == userId) + .Where(x => x.InvitationTime >= weekStart) + .CountAsync(); + + // 本周填写的邀请码数量必须 >= 即将使用的邀请翻牌次数 + // 例如: 要翻第8次(InviteFlipsUsed=0->1), 需要至少填写了1个邀请码 + // 要翻第9次(InviteFlipsUsed=1->2), 需要至少填写了2个邀请码 + // 要翻第10次(InviteFlipsUsed=2->3), 需要至少填写了3个邀请码 + var requiredInviteCodeCount = task.InviteFlipsUsed + 1; + if (weeklyInviteCodeUsedCount < requiredInviteCodeCount) + { + throw new UserFriendlyException($"需本周累积使用{requiredInviteCodeCount}个他人邀请码才能解锁第{task.TotalFlips + 1}次翻牌,您还差一个~"); + } + } + + // 计算翻牌结果(基于当前是第几次翻牌,而不是卡片序号) + var flipCount = task.TotalFlips + 1; // 当前这次翻牌是第几次 + var result = CalculateFlipResult(flipCount); + + // 将卡片序号信息也返回 + result.FlipNumber = flipNumber; + + // 更新翻牌次数(必须在记录奖励之前,因为需要先确定是第几次) + task.IncrementFlip(flipType); + + // 记录翻牌顺序 + if (task.FlippedOrder == null) + { + task.FlippedOrder = new List(); + } + task.FlippedOrder.Add(flipNumber); + + // 如果中奖,记录奖励金额(用于后续查询显示) + if (result.IsWin) + { + if (flipCount == EIGHTH_FLIP) + { + task.SetEighthReward(result.RewardAmount); + } + else if (flipCount == NINTH_FLIP) + { + task.SetNinthReward(result.RewardAmount); + } + else if (flipCount == TENTH_FLIP) + { + task.SetTenthReward(result.RewardAmount); + } + } + + await _cardFlipTaskRepository.UpdateAsync(task); + + _logger.LogInformation($"用户 {userId} 完成第 {flipNumber} 次翻牌,中奖:{result.IsWin}"); + + return result; + } + + /// + /// 判断是否可以翻牌 + /// + public bool CanFlipCard(CardFlipTaskAggregateRoot? task) + { + if (task == null) return true; // 没有任务记录,可以开始翻牌 + + return task.TotalFlips < TOTAL_MAX_FLIPS; + } + + /// + /// 判断翻牌类型 + /// + public FlipType DetermineFlipType(CardFlipTaskAggregateRoot task) + { + if (task.FreeFlipsUsed < MAX_FREE_FLIPS) + { + return FlipType.Free; + } + else + { + return FlipType.Invite; + } + } + + /// + /// 判断是否可以使用该翻牌类型 + /// + public bool CanUseFlipType(CardFlipTaskAggregateRoot task, FlipType flipType) + { + return flipType switch + { + FlipType.Free => task.FreeFlipsUsed < MAX_FREE_FLIPS, + FlipType.Invite => task.InviteFlipsUsed < MAX_INVITE_FLIPS, + _ => false + }; + } + + /// + /// 计算翻牌结果 + /// + /// 第几次翻牌(1-10) + private FlipResult CalculateFlipResult(int flipCount) + { + var result = new FlipResult + { + FlipNumber = 0, // 稍后会被设置为实际的卡片序号 + IsWin = false + }; + + // 前7次固定失败 + if (flipCount <= 7) + { + result.IsWin = false; + result.RewardDesc = "很遗憾,未中奖"; + } + // 第8次中奖 (邀请码解锁) + else if (flipCount == EIGHTH_FLIP) + { + var rewardAmount = GenerateRandomReward(EIGHTH_MIN_REWARD, EIGHTH_MAX_REWARD); + result.IsWin = true; + result.RewardAmount = rewardAmount; + result.RewardDesc = $"恭喜获得尊享包 {rewardAmount / 10000}w tokens!"; + } + // 第9次中奖 (邀请码解锁) + else if (flipCount == NINTH_FLIP) + { + var rewardAmount = GenerateRandomReward(NINTH_MIN_REWARD, NINTH_MAX_REWARD); + result.IsWin = true; + result.RewardAmount = rewardAmount; + result.RewardDesc = $"恭喜获得尊享包 {rewardAmount / 10000}w tokens!"; + } + // 第10次中奖 (邀请码解锁) + else if (flipCount == TENTH_FLIP) + { + var rewardAmount = GenerateRandomReward(TENTH_MIN_REWARD, TENTH_MAX_REWARD); + result.IsWin = true; + result.RewardAmount = rewardAmount; + result.RewardDesc = $"恭喜获得尊享包 {rewardAmount / 10000}w tokens!"; + } + + return result; + } + + /// + /// 获取翻牌类型错误提示 + /// + private string GetFlipTypeErrorMessage(FlipType flipType) + { + return flipType switch + { + FlipType.Free => "免费翻牌次数已用完", + FlipType.Invite => "需要使用邀请码解锁更多次数", + _ => "无法翻牌" + }; + } + + /// + /// 生成随机奖励金额 (最小单位100w) + /// + private long GenerateRandomReward(long min, long max) + { + var random = new Random(); + const long unit = 1000000; // 100w的单位 + + // 将min和max转换为100w的倍数 + long minUnits = min / unit; + long maxUnits = max / unit; + + // 在倍数范围内随机 + long randomUnits = random.Next((int)minUnits, (int)maxUnits + 1); + + // 返回100w的倍数 + return randomUnits * unit; + } + + /// + /// 获取本周开始日期(周一) + /// + public static DateTime GetWeekStartDate(DateTime date) + { + var dayOfWeek = (int)date.DayOfWeek; + // 将周日(0)转换为7 + if (dayOfWeek == 0) dayOfWeek = 7; + + // 计算本周一的日期 + var monday = date.Date.AddDays(-(dayOfWeek - 1)); + return monday; + } + + /// + /// 获取翻牌类型描述 + /// + public static string GetFlipTypeDesc(int flipNumber) + { + if (flipNumber <= MAX_FREE_FLIPS) + { + return "免费"; + } + else + { + return "邀请解锁"; + } + } +} + +/// +/// 翻牌结果 +/// +public class FlipResult +{ + /// + /// 翻牌序号 + /// + public int FlipNumber { get; set; } + + /// + /// 是否中奖 + /// + public bool IsWin { get; set; } + + /// + /// 奖励金额 + /// + public long RewardAmount { get; set; } + + /// + /// 奖励描述 + /// + public string RewardDesc { get; set; } = string.Empty; +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/InviteCodeManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/InviteCodeManager.cs new file mode 100644 index 00000000..1deed773 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/InviteCodeManager.cs @@ -0,0 +1,224 @@ +using Microsoft.Extensions.Logging; +using SqlSugar; +using Volo.Abp.Domain.Services; +using Yi.Framework.AiHub.Domain.Entities; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace Yi.Framework.AiHub.Domain.Managers; + +/// +/// 邀请码管理器 - 负责邀请码核心业务逻辑 +/// +public class InviteCodeManager : DomainService +{ + private readonly ISqlSugarRepository _inviteCodeRepository; + private readonly ISqlSugarRepository _invitationRecordRepository; + private readonly ILogger _logger; + + public InviteCodeManager( + ISqlSugarRepository inviteCodeRepository, + ISqlSugarRepository invitationRecordRepository, + ILogger logger) + { + _inviteCodeRepository = inviteCodeRepository; + _invitationRecordRepository = invitationRecordRepository; + _logger = logger; + } + + /// + /// 生成用户的邀请码 + /// + /// 用户ID + /// 邀请码 + public async Task GenerateInviteCodeForUserAsync(Guid userId) + { + // 检查是否已有邀请码 + var existingCode = await _inviteCodeRepository._DbQueryable + .Where(x => x.UserId == userId) + .FirstAsync(); + + if (existingCode != null) + { + return existingCode.Code; + } + + // 生成新邀请码 + var code = GenerateUniqueInviteCode(); + var inviteCode = new InviteCodeAggregateRoot(userId, code); + await _inviteCodeRepository.InsertAsync(inviteCode); + + _logger.LogInformation($"用户 {userId} 生成邀请码 {code}"); + + return code; + } + + /// + /// 获取用户的邀请码信息 + /// + public async Task GetUserInviteCodeAsync(Guid userId) + { + return await _inviteCodeRepository._DbQueryable + .Where(x => x.UserId == userId) + .FirstAsync(); + } + + /// + /// 统计用户本周邀请人数 + /// + public async Task GetWeeklyInvitationCountAsync(Guid userId, DateTime weekStart) + { + return await _invitationRecordRepository._DbQueryable + .Where(x => x.InvitedUserId == userId) + .Where(x => x.InvitationTime >= weekStart) + .CountAsync(); + } + + /// + /// 获取邀请历史记录 + /// + public async Task> GetInvitationHistoryAsync(Guid userId, int limit = 10) + { + return await _invitationRecordRepository._DbQueryable + .Where(x => x.InviterId == userId) + .OrderBy(x => x.InvitationTime, OrderByType.Desc) + .Take(limit) + .Select(x => new InvitationHistoryDto + { + InvitedUserName = "用户***", // 脱敏处理 + InvitationTime = x.InvitationTime + }) + .ToListAsync(); + } + + /// + /// 使用邀请码 + /// + /// 使用者ID + /// 邀请码 + /// 邀请人ID + public async Task UseInviteCodeAsync(Guid userId, string inviteCode) + { + if (string.IsNullOrWhiteSpace(inviteCode)) + { + throw new UserFriendlyException("邀请码不能为空"); + } + + // 查找邀请码 + var inviteCodeEntity = await _inviteCodeRepository._DbQueryable + .Where(x => x.Code == inviteCode) + .FirstAsync(); + + if (inviteCodeEntity == null) + { + throw new UserFriendlyException("邀请码不存在"); + } + + // 验证不能使用自己的邀请码 + if (inviteCodeEntity.UserId == userId) + { + throw new UserFriendlyException("不能使用自己的邀请码"); + } + + // 验证邀请码是否已被使用 + if (inviteCodeEntity.IsUsed) + { + throw new UserFriendlyException("该邀请码已被使用"); + } + + // 验证邀请码拥有者是否已被邀请 + if (inviteCodeEntity.IsUserInvited) + { + throw new UserFriendlyException("该用户已被邀请,邀请码无效"); + } + + // 验证本周邀请码使用次数 + var weekStart = CardFlipManager.GetWeekStartDate(DateTime.Now); + var weeklyUseCount = await _invitationRecordRepository._DbQueryable + .Where(x => x.InvitedUserId == userId) + .Where(x => x.InvitationTime >= weekStart) + .CountAsync(); + + if (weeklyUseCount >= CardFlipManager.MAX_INVITE_FLIPS) + { + throw new UserFriendlyException($"本周邀请码使用次数已达上限({CardFlipManager.MAX_INVITE_FLIPS}次),请下周再来"); + } + + // 检查当前用户的邀请码信息 + var myInviteCode = await _inviteCodeRepository._DbQueryable + .Where(x => x.UserId == userId) + .FirstAsync(); + + // 标记邀请码为已使用 + inviteCodeEntity.MarkAsUsed(userId); + await _inviteCodeRepository.UpdateAsync(inviteCodeEntity); + + // 标记当前用户已被邀请(仅第一次使用邀请码时标记) + if (myInviteCode == null) + { + myInviteCode = new InviteCodeAggregateRoot(userId, GenerateUniqueInviteCode()); + myInviteCode.MarkUserAsInvited(); + await _inviteCodeRepository.InsertAsync(myInviteCode); + } + else if (!myInviteCode.IsUserInvited) + { + myInviteCode.MarkUserAsInvited(); + await _inviteCodeRepository.UpdateAsync(myInviteCode); + } + + // 创建邀请记录 + var invitationRecord = new InvitationRecordAggregateRoot( + inviteCodeEntity.UserId, + userId, + inviteCode); + await _invitationRecordRepository.InsertAsync(invitationRecord); + + _logger.LogInformation($"用户 {userId} 使用邀请码 {inviteCode} 成功"); + + return inviteCodeEntity.UserId; + } + + /// + /// 检查用户是否已被邀请 + /// + public async Task IsUserInvitedAsync(Guid userId) + { + var inviteCode = await _inviteCodeRepository._DbQueryable + .Where(x => x.UserId == userId) + .FirstAsync(); + + return inviteCode?.IsUserInvited ?? false; + } + + /// + /// 生成唯一邀请码 + /// + private string GenerateUniqueInviteCode() + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var random = new Random(); + var code = new char[8]; + + for (int i = 0; i < code.Length; i++) + { + code[i] = chars[random.Next(chars.Length)]; + } + + return new string(code); + } +} + +/// +/// 邀请历史记录DTO +/// +public class InvitationHistoryDto +{ + /// + /// 被邀请人名称(脱敏) + /// + public string InvitedUserName { get; set; } = string.Empty; + + /// + /// 邀请时间 + /// + public DateTime InvitationTime { get; set; } +} diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs index 9e08520e..672e8164 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs @@ -29,6 +29,7 @@ using Volo.Abp.Swashbuckle; using Yi.Abp.Application; using Yi.Abp.SqlsugarCore; using Yi.Framework.AiHub.Application; +using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AspNetCore; using Yi.Framework.AspNetCore.Authentication.OAuth; using Yi.Framework.AspNetCore.Authentication.OAuth.Gitee; @@ -46,6 +47,7 @@ using Yi.Framework.Rbac.Application; using Yi.Framework.Rbac.Domain.Authorization; using Yi.Framework.Rbac.Domain.Shared.Consts; using Yi.Framework.Rbac.Domain.Shared.Options; +using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.Stock.Application; using Yi.Framework.TenantManagement.Application; @@ -350,6 +352,10 @@ namespace Yi.Abp.Web var app = context.GetApplicationBuilder(); app.UseRouting(); + // app.ApplicationServices.GetRequiredService().SqlSugarClient.CodeFirst.InitTables(); + // app.ApplicationServices.GetRequiredService().SqlSugarClient.CodeFirst.InitTables(); + // app.ApplicationServices.GetRequiredService().SqlSugarClient.CodeFirst.InitTables(); + //跨域 app.UseCors(DefaultCorsPolicyName); diff --git a/Yi.Ai.Vue3/.eslintrc-auto-import.json b/Yi.Ai.Vue3/.eslintrc-auto-import.json index af1083b7..313e6711 100644 --- a/Yi.Ai.Vue3/.eslintrc-auto-import.json +++ b/Yi.Ai.Vue3/.eslintrc-auto-import.json @@ -5,6 +5,8 @@ "ComputedRef": true, "DirectiveBinding": true, "EffectScope": true, + "ElMessage": true, + "ElMessageBox": true, "ExtractDefaultPropTypes": true, "ExtractPropTypes": true, "ExtractPublicPropTypes": true, diff --git a/Yi.Ai.Vue3/index.html b/Yi.Ai.Vue3/index.html index 5c1b414b..88c0e7d3 100644 --- a/Yi.Ai.Vue3/index.html +++ b/Yi.Ai.Vue3/index.html @@ -6,9 +6,9 @@ - + - + @@ -112,7 +112,7 @@
-
意心Ai 2.0
+
意心Ai 2.2
海外地址,仅首次访问预计加载约10秒
{ @@ -273,7 +277,7 @@ onMounted(() => { show-overflow-tooltip prop="rechargeAmount" label="金额(元)" - width="110" + min-width="110" sortable="custom" > --> - +