添加项目文件。

This commit is contained in:
2026-10-12 16:22:04 +08:00
parent 7c68a0ca0a
commit aad11be43d
1000 changed files with 120807 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.9" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ChessGameSrc\ChessGameDomain\ChessGameDomain.csproj" />
<ProjectReference Include="..\..\ChessGameSrc\ChessGameService\ChessGameService.csproj" />
<ProjectReference Include="..\..\FishRoomService\FishRoomService.csproj" />
<ProjectReference Include="..\..\CustomerSrc\CustomerService.Domain\CustomerService.Domain.csproj" />
<ProjectReference Include="..\AdminService\AdminService.csproj" />
<ProjectReference Include="..\AdminApplication\AdminApplication.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Lottery\" />
</ItemGroup>
</Project>
@@ -0,0 +1,665 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AdminService.GameConfigs;
using AdminService.User;
using AdminService.User.Param;
using AdminService.User.Response;
using Common;
using Common.Utils;
using CommonDomain;
using CommonDomain.BaseModel;
using CommonDomain.Enum;
using CommonInfrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RedisService;
using UserDomain.Entity;
namespace AdminApi.AdminUser
{
[ApiController, Route("adminapi/[controller]")]
[Authorize]
public class AdminUserController : BaseController
{
private readonly IAdminUserService _userService;
private readonly IRedisService _redisService;
public AdminUserController(IAdminUserService userService, IRedisService redisService)
{
_userService = userService;
_redisService = redisService;
}
[HttpPost("GetUserList")]
public async Task<PageResponse<UserListItemDto>> GetUserList(AdminUserListParam param)
{
var result = await _userService.GetAdminUserList(param);
var onlineUserIds = _redisService.GetAllLoginUserIds().ToHashSet(StringComparer.Ordinal);
var dtos = (result.list ?? new List<UserEntity>()).Select(u =>
{
var dto = MapToUserListItemDto(u);
dto.IsOnline = onlineUserIds.Contains(u.Id);
if (dto.CurrentGameType != 3)
{
var session = _redisService.GetRedisUserCId(u.Id);
if (session == null || string.IsNullOrWhiteSpace(session.RoomId))
{
dto.CurrentGameType = 0;
dto.RoomNumber = "";
}
}
return dto;
}).ToList();
return this.ToPageResult(dtos, result.count);
}
[HttpGet("GetUserDetail")]
public async Task<BaseResponse<UserEntity>> GetUserDetail(string userId)
{
var result = await _userService.GetUser(userId);
if (result == null)
return this.ToFailResult<UserEntity>("用户不存在");
return this.ToResult(result);
}
[HttpPost("UpdateUserAssets")]
public async Task<BaseResponse<string>> UpdateUserAssets(UpdateUserAssetsParam param)
{
var result = await _userService.UpdateUserAssets(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("GetUserStatistics")]
public async Task<BaseResponse<UserStatisticsDto>> GetUserStatistics()
{
var result = await _userService.GetUserStatistics();
return this.ToResult(MapToUserStatisticsDto(result));
}
[HttpGet("GetEmailStatistics")]
public async Task<BaseResponse<EmailStatisticsDto>> GetEmailStatistics()
{
var result = await _userService.GetEmailStatistics();
return this.ToResult(MapToEmailStatisticsDto(result));
}
[HttpPost("BatchBanUser")]
public async Task<BaseResponse<string>> BatchBanUser(BatchBanUserParam param)
{
var result = await _userService.BatchBanUser(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("UnbanUser")]
public async Task<BaseResponse<string>> UnbanUser(string userId)
{
var result = await _userService.UnbanUser(userId);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("BatchSendEmail")]
public async Task<BaseResponse<string>> BatchSendEmail(BatchSendEmailParam param)
{
var result = await _userService.BatchSendEmail(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("CancelUser")]
[Authorize]
public async Task<BaseResponse<string>> CancelUser()
{
var result = await _userService.CancelUser(loginUser);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("DeleteUser")]
[Authorize]
public async Task<BaseResponse<string>> DeleteUser(string id)
{
var result = await _userService.DeleteUser(id);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("BatchDeleteUser")]
[Authorize]
public async Task<BaseResponse<string>> BatchDeleteUser([FromBody] BatchDeleteUserParam param)
{
var result = await _userService.BatchDeleteUser(param.UserIds);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("BanUser")]
[Authorize]
public async Task<BaseResponse<string>> BanUser([FromBody] BanUserParam param)
{
var result = await _userService.BanUser(param.UserId, param.Reason, param.Days);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("UpdateUser")]
[Authorize]
public async Task<BaseResponse<string>> UpdateUser(UpdateUserDataParam param)
{
var result = await _userService.UpdateUser(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("SendEmail")]
[Authorize]
public async Task<BaseResponse<string>> SendEmail(SendEmailParam param)
{
var result = await _userService.SendEmail(param, loginUser);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("GetEmailList")]
[Authorize]
public async Task<PageResponse<EmailListItemDto>> GetEmailList([FromBody] EmailListQueryParam param)
{
var (list, count) = await _userService.GetEmailList(param);
var userIds = list.Where(e => !string.IsNullOrWhiteSpace(e.UserId))
.Select(e => e.UserId).Distinct().ToList();
var userNames = await _userService.GetEmailUserNamesAsync(userIds);
var dtos = list.Select(e =>
{
userNames.TryGetValue(e.UserId ?? "", out var info);
return new EmailListItemDto
{
Id = e.Id,
UserId = e.UserId ?? "",
UserName = info.UserName,
UserNumber = info.UserNumber,
Title = e.Title ?? "",
IsRead = e.IsRead,
IsReward = e.IsReward,
RewardGold = e.RewardGold,
RewardDiamond = e.RewardDiamond,
SendTime = e.SendTime,
ReadTime = e.ReadTime,
ExpireTime = e.ExpireTime,
RewardText = BuildRewardText(e)
};
}).ToList();
return this.ToPageResult(dtos, (int)count);
}
[HttpGet("DeleteEmail")]
[Authorize]
public async Task<BaseResponse<string>> DeleteEmail(string id)
{
var result = await _userService.DeleteEmail(id);
if (result.ok)
return this.ToResult("删除成功");
else
return this.ToFailResult<string>(result.message ?? "删除失败");
}
[HttpPost("BatchDeleteEmail")]
[Authorize]
public async Task<BaseResponse<string>> BatchDeleteEmail([FromBody] List<string> ids)
{
var (success, fail) = await _userService.BatchDeleteEmail(ids);
if (fail == 0)
return this.ToResult($"成功删除 {success} 条");
return this.ToResult($"删除完成:成功 {success} 条,失败 {fail} 条");
}
[HttpPost("SetUserType")]
public async Task<BaseResponse<string>> SetUserType(string userId, int userType)
{
var result = await _userService.SetUserType(userId, userType);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("BatchSetUserType")]
public async Task<BaseResponse<string>> BatchSetUserType([FromBody] BatchSetUserTypeParam param)
{
var result = await _userService.BatchSetUserType(param.UserIds, param.UserType);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("SetBlackUser")]
public async Task<BaseResponse<string>> SetBlackUser(string userId, bool isBlack)
{
var result = await _userService.SetBlackUser(userId, isBlack);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("BatchSetBlackUser")]
public async Task<BaseResponse<string>> BatchSetBlackUser([FromBody] BatchSetBlackUserParam param)
{
var result = await _userService.BatchSetBlackUser(param.UserIds, param.IsBlack);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("UpdateUserFactor")]
public async Task<BaseResponse<string>> UpdateUserFactor([FromBody] UpdateUserFactorFlatParam param)
{
var factor = new PlayerFactor
{
ViolateValue = param.ViolateValue,
MissRate = param.MissRate,
CompensateQuota = param.CompensateQuota,
PenaltyQuota = param.PenaltyQuota,
CompensateRate = param.CompensateRate,
PenaltyRate = param.PenaltyRate,
AdditionalFactor = param.AdditionalFactor
};
var result = await _userService.UpdateUserFactor(param.UserId, factor);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("GetBlackFactorConfig")]
public async Task<BaseResponse<PlayerFactorDto>> GetBlackFactorConfig()
{
var result = await _userService.GetBlackFactorConfig();
return this.ToResult(MapToPlayerFactorDto(result));
}
[HttpPost("ResetUserFactor")]
public async Task<BaseResponse<string>> ResetUserFactor(string userId)
{
var result = await _userService.ResetUserFactor(userId);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("InitUserPassword")]
public async Task<BaseResponse<string>> InitUserPassword([FromQuery] string userId)
{
var result = await _userService.InitUserPassword(userId);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("InitSafeBoxPassword")]
public async Task<BaseResponse<string>> InitSafeBoxPassword([FromQuery] string userId)
{
var result = await _userService.InitSafeBoxPassword(userId);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("GetUserFactorList")]
public async Task<PageResponse<UserFactorListItemDto>> GetUserFactorList([FromBody] UserFactorListParam param)
{
var result = await _userService.GetUserFactorList(param);
var pageIps = (result.list ?? new List<UserEntity>())
.Where(u => !string.IsNullOrEmpty(u.RegisterIp))
.Select(u => u.RegisterIp!)
.Distinct()
.ToList();
Dictionary<string, int> ipCounts = new Dictionary<string, int>();
if (pageIps.Count > 0)
{
var allUsersWithSameIp = await _userService.GetUsersByRegisterIps(pageIps);
ipCounts = allUsersWithSameIp
.GroupBy(u => u.RegisterIp)
.ToDictionary(g => g.Key, g => g.Count());
}
var dtos = (result.list ?? new List<UserEntity>()).Select(u => new UserFactorListItemDto
{
Id = u.Id,
Number = u.BaseInfo?.Number ?? "",
UserName = u.BaseInfo?.UserName ?? "",
Mobile = u.BaseInfo?.Mobile ?? "",
UserType = u.UserType,
RegisterIp = u.RegisterIp ?? "",
LoginIp = u.Static?.LastLoginIp ?? "",
IpCount = !string.IsNullOrEmpty(u.RegisterIp) && ipCounts.ContainsKey(u.RegisterIp) ? ipCounts[u.RegisterIp] : 1,
IsBlack = u.PlayerFactor?.IsBlack ?? false,
ViolateValue = u.PlayerFactor?.ViolateValue ?? 0.2,
MissRate = u.PlayerFactor?.MissRate ?? 0,
CompensateQuota = u.PlayerFactor?.CompensateQuota ?? 0,
PenaltyQuota = u.PlayerFactor?.PenaltyQuota ?? 0,
CompensateRate = u.PlayerFactor?.CompensateRate != null ? new CompensateRateDto
{
BossRate = u.PlayerFactor.CompensateRate.BossRate,
GoldRate = u.PlayerFactor.CompensateRate.GoldRate,
SpecialRate = u.PlayerFactor.CompensateRate.SpecialRate,
Ordinary = u.PlayerFactor.CompensateRate.Ordinary
} : new CompensateRateDto(),
PenaltyRate = u.PlayerFactor?.PenaltyRate != null ? new PenaltyRateDto
{
BossRate = u.PlayerFactor.PenaltyRate.BossRate,
GoldRate = u.PlayerFactor.PenaltyRate.GoldRate,
SpecialRate = u.PlayerFactor.PenaltyRate.SpecialRate,
Ordinary = u.PlayerFactor.PenaltyRate.Ordinary
} : new PenaltyRateDto(),
CreateTime = u.CreateTime,
TotalRecharge = u.Static?.TotalRecharge ?? 0,
TotalRechargeGold = u.Static?.TotalRechargeGold ?? 0,
TotalConsumeGold = u.Static?.TotalConsumeGold ?? 0,
CurrentGold = u.Assets?.Gold ?? 0,
Profit = (u.Assets?.Gold ?? 0) - ((u.Static?.TotalRechargeGold ?? 0) + (u.PlayerFactor?.StartingBonus ?? 0)),
AdditionalFactor = u.PlayerFactor?.AdditionalFactor ?? 0,
StartingBonus = u.PlayerFactor?.StartingBonus ?? 0
});
return this.ToPageResult(dtos, result.count);
}
private static UserListItemDto MapToUserListItemDto(UserEntity entity)
{
return new UserListItemDto
{
Id = entity.Id,
Number = entity.BaseInfo?.Number ?? "",
NickName = entity.BaseInfo?.UserName ?? "",
Avatar = entity.BaseInfo?.AvatarUrl ?? "",
Mobile = entity.BaseInfo?.Mobile ?? "",
Diamond = entity.Assets?.Diamond ?? 0,
Gold = entity.Assets?.Gold ?? 0,
SafeBoxGold = entity.SafeBox?.Amount ?? 0,
VipLevel = entity.Assets?.VipLevel ?? 0,
UserType = entity.UserType,
IsBan = entity.BanInfo?.IsBan ?? false,
BanReason = entity.BanInfo?.BanReason,
BanEndTime = entity.BanInfo?.BanEndTime ?? 0,
IsBlack = entity.PlayerFactor?.IsBlack ?? false,
RegisterTime = entity.CreateTime,
RegisterIp = entity.RegisterIp ?? "",
LastLoginIp = entity.Static?.LastLoginIp ?? "",
LastLoginTime = entity.Static?.LastLoginTime ?? 0,
RoomNumber = entity.Static?.RoomNumber ?? "",
CurrentGameType = entity.Static?.CurrentGameType ?? 0,
Channel = entity.BaseInfo?.Channel ?? "",
TotalRecharge = (long)(entity.Static?.TotalRecharge ?? 0)
};
}
private static UserStatisticsDto MapToUserStatisticsDto(UserStatistics stat)
{
return new UserStatisticsDto
{
TotalUsers = stat.TotalUsers,
TodayRegisteredUsers = stat.TodayRegisteredUsers,
MonthRegisteredUsers = stat.MonthRegisteredUsers,
OnlineUsers = stat.OnlineUsers,
BannedUsers = stat.BannedUsers,
VipUsers = stat.VipUsers,
VipLevelCount = stat.VipLevelCount ?? new Dictionary<int, long>(),
TotalGold = stat.TotalGold,
TotalDiamond = stat.TotalDiamond
};
}
private static EmailStatisticsDto MapToEmailStatisticsDto(EmailStatistics stat)
{
return new EmailStatisticsDto
{
TotalEmails = stat.TotalEmails,
RewardEmails = stat.RewardEmails,
UnclaimedEmails = stat.UnclaimedEmails,
TodayEmails = stat.TodayEmails,
MonthEmails = stat.MonthEmails
};
}
private static PlayerFactorDto MapToPlayerFactorDto(PlayerFactor factor)
{
if (factor == null)
return new PlayerFactorDto();
return new PlayerFactorDto
{
IsBlack = factor.IsBlack,
IsIgnore = factor.IsIgnore,
ViolateValue = factor.ViolateValue,
CompensateRate = factor.CompensateRate?.BossRate ?? 1,
PenaltyRate = factor.PenaltyRate?.BossRate ?? 0,
CompensateQuota = factor.CompensateQuota,
PenaltyQuota = factor.PenaltyQuota,
StartingBonus = factor.StartingBonus
};
}
private static string BuildRewardText(UserEmail email)
{
var parts = new List<string>();
if (email.RewardGold > 0)
parts.Add($"金币{email.RewardGold:N0}");
if (email.RewardDiamond > 0)
parts.Add($"钻石{email.RewardDiamond}");
if (email.RewardItems != null)
{
foreach (var item in email.RewardItems)
{
if (item != null && !string.IsNullOrWhiteSpace(item.Name))
parts.Add($"{item.Name}*{item.Count}");
}
}
return parts.Count > 0 ? string.Join(" ", parts) : "无";
}
[HttpGet("GetUserProps")]
public async Task<BaseResponse<List<BaseProductCount>>> GetUserProps(string userId)
{
var result = await _userService.GetUserProps(userId);
return this.ToResult(result);
}
[HttpPost("AddUserProp")]
public async Task<BaseResponse<string>> AddUserProp(string userId, string propNumber, int count)
{
var res = await _userService.AddUserProp(userId, propNumber, count);
if (res.isok) return this.ToResult(res.msg);
return this.ToFailResult<string>(res.msg);
}
[HttpPost("UpdateUserPropCount")]
public async Task<BaseResponse<string>> UpdateUserPropCount(string userId, string propNumber, int count)
{
var res = await _userService.UpdateUserPropCount(userId, propNumber, count);
if (res.isok) return this.ToResult(res.msg);
return this.ToFailResult<string>(res.msg);
}
[HttpPost("DeleteUserProp")]
public async Task<BaseResponse<string>> DeleteUserProp(string userId, string propNumber)
{
var res = await _userService.DeleteUserProp(userId, propNumber);
if (res.isok) return this.ToResult(res.msg);
return this.ToFailResult<string>(res.msg);
}
[HttpGet("SyncAllUserLevel")]
public async Task<BaseResponse<string>> SyncAllUserLevel()
{
var (total, updated, msg) = await _userService.SyncAllUserLevel();
return this.ToResult(msg);
}
}
public class UserListItemDto
{
public string Id { get; set; }
public string Number { get; set; }
public string NickName { get; set; }
public string Avatar { get; set; }
public string Mobile { get; set; }
public int Diamond { get; set; }
public long Gold { get; set; }
public long SafeBoxGold { get; set; }
public int VipLevel { get; set; }
public int UserType { get; set; }
public bool IsBan { get; set; }
public string BanReason { get; set; }
public long BanEndTime { get; set; }
public bool IsBlack { get; set; }
public long RegisterTime { get; set; }
public string RegisterIp { get; set; }
public string LastLoginIp { get; set; }
public long LastLoginTime { get; set; }
public string RoomNumber { get; set; }
public int CurrentGameType { get; set; }
public bool IsOnline { get; set; }
public string Channel { get; set; }
public long TotalRecharge { get; set; }
}
public class UserStatisticsDto
{
public long TotalUsers { get; set; }
public long TodayRegisteredUsers { get; set; }
public long MonthRegisteredUsers { get; set; }
public long OnlineUsers { get; set; }
public long BannedUsers { get; set; }
public long VipUsers { get; set; }
public Dictionary<int, long> VipLevelCount { get; set; }
public long TotalGold { get; set; }
public long TotalDiamond { get; set; }
}
public class EmailStatisticsDto
{
public long TotalEmails { get; set; }
public long RewardEmails { get; set; }
public long UnclaimedEmails { get; set; }
public long TodayEmails { get; set; }
public long MonthEmails { get; set; }
}
public class PlayerFactorDto
{
public bool IsBlack { get; set; }
public bool IsIgnore { get; set; }
public double ViolateValue { get; set; }
public double CompensateRate { get; set; }
public double PenaltyRate { get; set; }
public int CompensateQuota { get; set; }
public int PenaltyQuota { get; set; }
public long StartingBonus { get; set; }
}
public class UserFactorListItemDto
{
public string Id { get; set; }
public string Number { get; set; }
public string UserName { get; set; }
public string Mobile { get; set; }
public int UserType { get; set; }
public string RegisterIp { get; set; }
public string LoginIp { get; set; }
public int IpCount { get; set; }
public bool IsBlack { get; set; }
public double ViolateValue { get; set; }
public double MissRate { get; set; }
public int CompensateQuota { get; set; }
public int PenaltyQuota { get; set; }
public CompensateRateDto CompensateRate { get; set; }
public PenaltyRateDto PenaltyRate { get; set; }
public long CreateTime { get; set; }
public double TotalRecharge { get; set; }
public long TotalRechargeGold { get; set; }
public long TotalConsumeGold { get; set; }
public long CurrentGold { get; set; }
public long Profit { get; set; }
public double AdditionalFactor { get; set; }
public long StartingBonus { get; set; }
}
public class CompensateRateDto
{
public double BossRate { get; set; } = 1;
public double GoldRate { get; set; } = 1;
public double SpecialRate { get; set; } = 1;
public double Ordinary { get; set; } = 1;
}
public class PenaltyRateDto
{
public double BossRate { get; set; } = 1;
public double GoldRate { get; set; } = 1;
public double SpecialRate { get; set; } = 1;
public double Ordinary { get; set; } = 1;
}
public class BanUserParam
{
public string UserId { get; set; } = "";
public string Reason { get; set; } = "";
public int Days { get; set; } = 0;
}
public class EmailListItemDto
{
public string Id { get; set; } = "";
public string UserId { get; set; } = "";
public string UserName { get; set; } = "";
public string UserNumber { get; set; } = "";
public string Title { get; set; } = "";
public bool IsRead { get; set; }
public bool IsReward { get; set; }
public long RewardGold { get; set; }
public int RewardDiamond { get; set; }
public long SendTime { get; set; }
public long ReadTime { get; set; }
public long ExpireTime { get; set; }
public string RewardText { get; set; } = "";
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace AdminApi
{
public static class AssemblyMarker
{
}
}
@@ -0,0 +1,114 @@
using System.Threading.Tasks;
using Common;
using CommonDomain;
using CommonInfrastructure;
using CommonInfrastructure.Response;
using CommonService;
using CommonService.BackgroundOperation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using UserDomain.Entity;
namespace AdminApi.BackgroundOperation
{
[Authorize]
[Route("adminapi/[controller]")]
[ApiController]
public class BackgroundOperationController : BaseController
{
private readonly IBackgroundOperationService _operationService;
public BackgroundOperationController(IBackgroundOperationService operationService)
{
_operationService = operationService;
}
[HttpPost("charge")]
public async Task<BaseResponse<string>> Charge([FromBody] BackgroundOperationRequest request)
{
if (request == null || string.IsNullOrWhiteSpace(request.TargetUserId))
return this.ToFailResult<string>("目标用户ID不能为空");
if (request.Amount <= 0)
return this.ToFailResult<string>("操作数量必须大于0");
var (success, message) = await _operationService.Charge(
loginUser.UserId, request.TargetUserId, request.CurrencyType, request.Amount, request.Remark);
return success
? this.ToResult("充值成功")
: this.ToFailResult<string>(message);
}
[HttpPost("deduct")]
public async Task<BaseResponse<string>> Deduct([FromBody] BackgroundOperationRequest request)
{
if (request == null || string.IsNullOrWhiteSpace(request.TargetUserId))
return this.ToFailResult<string>("目标用户ID不能为空");
if (request.Amount <= 0)
return this.ToFailResult<string>("操作数量必须大于0");
var (success, message) = await _operationService.Deduct(
loginUser.UserId, request.TargetUserId, request.CurrencyType, request.Amount, request.Remark);
return success
? this.ToResult("扣除成功")
: this.ToFailResult<string>(message);
}
[HttpGet("logs")]
public async Task<PageResponse<BackgroundOperationLog>> GetLogs(
string? adminUserId = null,
string? targetUserId = null,
int? operationType = null,
int pageIndex = 1,
int pageSize = 20)
{
var (data, total) = await _operationService.QueryLogs(
adminUserId, targetUserId, operationType, pageIndex, pageSize);
return this.ToPageResult(data, total);
}
[HttpGet("search-user")]
public async Task<BaseResponse<SearchUserResponse>> SearchUser(string keyword)
{
if (string.IsNullOrWhiteSpace(keyword))
{
return this.ToFailResult<SearchUserResponse>("搜索关键词不能为空");
}
var user = await _operationService.SearchUser(keyword);
if (user == null)
{
return this.ToFailResult<SearchUserResponse>("用户不存在");
}
var dto = new SearchUserResponse
{
UserId = user.Id,
UserName = user.BaseInfo?.UserName ?? "",
UserPhone = user.BaseInfo?.Mobile ?? "",
Gold = user.Assets?.Gold ?? 0,
Diamond = user.Assets?.Diamond ?? 0,
RegisterTime = user.CreateTime / 1000
};
return this.ToResult(dto);
}
}
public class SearchUserResponse
{
public string UserId { get; set; }
public string UserName { get; set; }
public string UserPhone { get; set; }
public long Gold { get; set; }
public int Diamond { get; set; }
public long RegisterTime { get; set; }
}
}
@@ -0,0 +1,384 @@
using System.Collections.Generic;
using System.Linq;
using AdminDomain.BeautifulNumber;
using AdminDomain.Entity;
using AdminService.BeautifulNumber;
using Common;
using CommonInfrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using UserDomain.Entity;
namespace AdminApi.BeautifulNumber
{
[ApiController, Route("adminapi/[controller]")]
[Authorize]
public class BeautifulNumberController : BaseController
{
private readonly IBeautifulNumberService _beautifulNumberService;
public BeautifulNumberController(IBeautifulNumberService beautifulNumberService)
{
_beautifulNumberService = beautifulNumberService;
}
[HttpGet("list")]
public async Task<PageResponse<BeautifulNumberDto>> GetList([FromQuery] BeautifulNumberListParam param)
{
param.page = param.page > 0 ? param.page : 1;
param.size = param.size > 0 ? param.size : 20;
var result = await _beautifulNumberService.GetList(param);
var dtos = result.list.Select(MapToDto).ToList();
return this.ToPageResult(dtos, (int)result.count);
}
[HttpGet("available")]
public async Task<PageResponse<BeautifulNumberDto>> GetAvailableList([FromQuery] BeautifulNumberListParam param)
{
param.page = param.page > 0 ? param.page : 1;
param.size = param.size > 0 ? param.size : 20;
param.status = (int)BeautifulNumberStatus.Available;
var result = await _beautifulNumberService.GetList(param);
var dtos = result.list.Select(MapToDto).ToList();
return this.ToPageResult(dtos, (int)result.count);
}
[HttpGet("check/{number}")]
public async Task<BaseResponse<BeautifulNumberDto>> CheckNumber(string number)
{
var result = await _beautifulNumberService.CheckNumber(number);
return this.ToResult(MapToDto(result));
}
private static BeautifulNumberDto MapToDto(BeautifulNumberConfig entity)
{
if (entity == null) return new BeautifulNumberDto();
return new BeautifulNumberDto
{
Id = entity.Id ?? "",
Number = entity.Number ?? "",
RuleName = entity.RuleName ?? "",
Level = entity.Level ?? "",
Price = entity.Price,
Status = (int)entity.Status,
SaleTime = entity.SaleTime,
BuyerUserId = entity.BuyerUserId ?? "",
UserId = entity.UserId ?? "",
Remark = entity.Remark ?? "",
CreateTime = entity.CreateTime
};
}
[HttpPost("delete")]
public async Task<BaseResponse<string>> Delete([FromBody] DeleteBeautifulNumberParam param)
{
var result = await _beautifulNumberService.Delete(param.Id);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("set-normal")]
public async Task<BaseResponse<string>> SetToNormal([FromBody] DeleteBeautifulNumberParam param)
{
var result = await _beautifulNumberService.SetToNormal(param.Id);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("freeze")]
public async Task<BaseResponse<string>> Freeze([FromBody] DeleteBeautifulNumberParam param)
{
var result = await _beautifulNumberService.Freeze(param.Id);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("unfreeze")]
public async Task<BaseResponse<string>> Unfreeze([FromBody] DeleteBeautifulNumberParam param)
{
var result = await _beautifulNumberService.Unfreeze(param.Id);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("update-price")]
public async Task<BaseResponse<string>> UpdatePrice([FromBody] UpdateBeautifulNumberPriceParam param)
{
var result = await _beautifulNumberService.UpdatePrice(param.Id, param.Price);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("update-number")]
public async Task<BaseResponse<string>> UpdateNumber([FromBody] UpdateBeautifulNumberParam param)
{
var result = await _beautifulNumberService.UpdateNumber(param.Id, param.Level, param.Price);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("rules")]
public async Task<BaseResponse<List<BeautifulNumberRuleDto>>> GetRules()
{
var result = await _beautifulNumberService.GetRules();
var dtos = result.Select(MapRuleToDto).ToList();
return this.ToResult(dtos);
}
private static BeautifulNumberRuleDto MapRuleToDto(BeautifulNumberRule entity)
{
if (entity == null) return new BeautifulNumberRuleDto();
return new BeautifulNumberRuleDto
{
Id = entity.Id ?? "",
RuleName = entity.RuleName ?? "",
Level = entity.Level ?? "",
Price = entity.Price,
Sort = entity.Sort,
IsEnable = entity.IsEnable
};
}
[HttpPost("update-rules")]
public async Task<BaseResponse<string>> UpdateRules([FromBody] List<UpdateBeautifulNumberRuleParam> paramList)
{
var rules = new List<BeautifulNumberRule>();
foreach (var param in paramList)
{
rules.Add(new BeautifulNumberRule
{
Id = param.Id,
RuleName = param.RuleName,
Level = param.Level,
Price = param.Price,
Sort = param.Sort,
IsEnable = param.IsEnable
});
}
var result = await _beautifulNumberService.UpdateRules(rules);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("init-rules")]
public async Task<BaseResponse<string>> InitRules()
{
var result = await _beautifulNumberService.InitRules();
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("add-rule")]
public async Task<BaseResponse<string>> AddRule([FromBody] BeautifulNumberRule rule)
{
var result = await _beautifulNumberService.AddRule(rule);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("update-rule")]
public async Task<BaseResponse<string>> UpdateRule([FromBody] BeautifulNumberRule rule)
{
var result = await _beautifulNumberService.UpdateRule(rule);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("delete-rule")]
public async Task<BaseResponse<string>> DeleteRule([FromBody] DeleteBeautifulNumberRuleParam param)
{
var result = await _beautifulNumberService.DeleteRule(param.Id);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("give-to-user")]
public async Task<BaseResponse<string>> GiveToUser([FromBody] GiveBeautifulNumberParam param)
{
var result = await _beautifulNumberService.GiveToUser(param.Number, param.UserId);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("init-numbers")]
public async Task<BaseResponse<string>> InitNumbers()
{
var result = await _beautifulNumberService.InitBeautifulNumbers();
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("add-number")]
public async Task<BaseResponse<string>> AddNumber([FromBody] AddBeautifulNumberParam param)
{
var result = await _beautifulNumberService.AddNumber(param.Number, param.Level, param.Price);
if (result.isok)
return this.ToResult<string>(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("batch-add-numbers")]
public async Task<BaseResponse<BatchAddResult>> BatchAddNumbers([FromBody] BatchAddBeautifulNumbersParam param)
{
var result = await _beautifulNumberService.BatchAddNumbers(param.Numbers, param.Level, param.Price);
return this.ToResult(new BatchAddResult { SuccessCount = result.successCount, FailCount = result.failCount, Message = result.msg });
}
}
public class BatchAddResult
{
public int SuccessCount { get; set; }
public int FailCount { get; set; }
public string Message { get; set; }
}
public class BeautifulNumberDto
{
public string Id { get; set; }
public string Number { get; set; }
public string RuleName { get; set; }
public string Level { get; set; }
public long Price { get; set; }
public int Status { get; set; }
public long SaleTime { get; set; }
public string BuyerUserId { get; set; }
public string UserId { get; set; }
public string Remark { get; set; }
public long CreateTime { get; set; }
}
public class BeautifulNumberRuleDto
{
public string Id { get; set; }
public string RuleName { get; set; }
public string Level { get; set; }
public long Price { get; set; }
public int Sort { get; set; }
public bool IsEnable { get; set; }
}
public class BatchAddBeautifulNumbersParam
{
public List<string> Numbers { get; set; }
public string Level { get; set; }
public long Price { get; set; }
}
public class GiveBeautifulNumberParam
{
public string Number { get; set; }
public string UserId { get; set; }
}
public class AddBeautifulNumberParam
{
public string Number { get; set; }
public string Level { get; set; }
public long Price { get; set; }
public int Sort { get; set; }
public string Remark { get; set; }
}
public class UpdateBeautifulNumberParam
{
public string Id { get; set; }
public string Level { get; set; }
public long Price { get; set; }
}
public class DeleteBeautifulNumberParam
{
public string Id { get; set; }
}
public class UpdateBeautifulNumberPriceParam
{
public string Id { get; set; }
public long Price { get; set; }
}
public class UpdateBeautifulNumberRuleParam
{
public string Id { get; set; }
public string RuleName { get; set; }
public string Level { get; set; }
public long Price { get; set; }
public int Sort { get; set; }
public bool IsEnable { get; set; }
}
public class AddBeautifulNumberRuleParam
{
public string RuleName { get; set; }
public string Level { get; set; }
public long Price { get; set; }
public int Sort { get; set; }
public bool IsEnable { get; set; }
}
public class UpdateSingleBeautifulNumberRuleParam
{
public string Id { get; set; }
public string RuleName { get; set; }
public string Level { get; set; }
public long Price { get; set; }
public int Sort { get; set; }
public bool IsEnable { get; set; }
}
public class DeleteBeautifulNumberRuleParam
{
public string Id { get; set; }
}
}
@@ -0,0 +1,64 @@
using AdminService.ImageUpload;
using Common;
using CommonInfrastructure;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace AdminApi.Controllers
{
[Route("adminapi/[controller]")]
[ApiController]
public class ImageUploadController : BaseController
{
private readonly IImageUploadService _imageUploadService;
public ImageUploadController(IImageUploadService imageUploadService)
{
_imageUploadService = imageUploadService;
}
[HttpPost("upload")]
public async Task<IActionResult> UploadFile(IFormFile file, [FromQuery] string? name = null)
{
if (string.IsNullOrWhiteSpace(name))
name = Request.Form["name"].FirstOrDefault();
var result = await _imageUploadService.UploadImageAsync(file, name);
return result.isOk
? Json(this.ToResult(new { path = result.path, fileName = result.fileName }))
: Json(this.ToFailResult<object>(result.errorMessage));
}
[HttpPost("upload/temp")]
public async Task<IActionResult> UploadTempFile(IFormFile file)
{
var result = await _imageUploadService.UploadImageAsync(file);
return result.isOk
? Json(this.ToResult(new { path = result.path, fileName = result.fileName }))
: Json(this.ToFailResult<object>(result.errorMessage));
}
[HttpPost("upload/config")]
public async Task<IActionResult> UploadConfigFile(IFormFile file, [FromQuery] string? number = null)
{
if (!string.Equals(Path.GetExtension(file?.FileName), ".png", StringComparison.OrdinalIgnoreCase))
return Json(this.ToFailResult<object>("配置图片必须是 PNG 格式"));
if (string.IsNullOrWhiteSpace(number))
number = Request.Form["number"].FirstOrDefault();
number = number?.Trim();
if (string.IsNullOrWhiteSpace(number)
|| number != Path.GetFileName(number)
|| number.Any(c => !char.IsLetterOrDigit(c) && c != '_' && c != '-'))
{
return Json(this.ToFailResult<object>("图片编号无效"));
}
var result = await _imageUploadService.UploadImageAsync(file, number);
return result.isOk
? Json(this.ToResult(new { path = result.path, fileName = result.fileName, number }))
: Json(this.ToFailResult<object>(result.errorMessage));
}
}
}
@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AdminService.Admin;
using Common;
using CommonInfrastructure;
using Domain.Admin.Admin;
using Domain.Admin.Admin.ENum;
using Domain.Admin.Admin.Param;
using Microsoft.AspNetCore.Mvc;
using MongoRepository;
namespace AdminApi.Controllers
{
[Route("adminapi/[controller]")]
[ApiController]
public class OperationLogController : BaseController
{
private readonly IAdminOperationLogService _operationLogService;
private readonly IMongoBaseRepository<SystemUser> _systemUserRepo;
private readonly IMongoBaseRepository<AdminOperationLog> _logRepo;
public OperationLogController(IAdminOperationLogService operationLogService, IMongoBaseRepository<SystemUser> systemUserRepo, IMongoBaseRepository<AdminOperationLog> logRepo)
{
_operationLogService = operationLogService;
_systemUserRepo = systemUserRepo;
_logRepo = logRepo;
}
[HttpPost("list")]
public async Task<PageResponse<OperationLogDto>> GetList([FromBody] OperationLogQueryParam param)
{
var (list, count) = await _operationLogService.QueryPage(param);
if (list == null)
return this.ToPageResult(new List<OperationLogDto>(), 0);
var dtos = list.Select(MapToDto);
return this.ToPageResult(dtos, (int)count);
}
[HttpGet("admins")]
public async Task<BaseResponse<List<AdminOptionDto>>> GetAdmins()
{
var users = await _systemUserRepo.FilterByAsync(x => x.State == 1);
var result = users.Select(x => new AdminOptionDto { Value = x.Id, Label = x.DisplayName }).ToList();
return this.ToResult(result);
}
[HttpGet("modules")]
public BaseResponse<List<ModuleOptionDto>> GetModules()
{
var modules = Enum.GetValues<EnumOperationModule>()
.Select(x => new ModuleOptionDto { Value = (int)x, Label = ((int)x).ToString() })
.ToList();
return this.ToResult(modules);
}
[HttpPost("Delete")]
public async Task<BaseResponse<string>> Delete(string id)
{
var result = await _logRepo.DeleteByIdAsync(id);
if (result.ok)
return this.ToResult("删除成功");
else
return this.ToFailResult<string>("删除失败");
}
[HttpPost("BatchDelete")]
public async Task<BaseResponse<string>> BatchDelete([FromBody] List<string> ids)
{
int success = 0, fail = 0;
foreach (var id in ids)
{
var result = await _logRepo.DeleteByIdAsync(id);
if (result.ok)
success++;
else
fail++;
}
if (fail == 0)
return this.ToResult($"成功删除 {success} 条");
return this.ToResult($"删除完成:成功 {success} 条,失败 {fail} 条");
}
private static OperationLogDto MapToDto(AdminOperationLog entity)
{
return new OperationLogDto
{
Id = entity.Id,
AdminId = entity.AdminId ?? "",
AdminName = entity.AdminName ?? "",
Module = entity.Module,
ActionType = entity.ActionType ?? "",
Description = entity.Description ?? "",
HttpMethod = entity.HttpMethod ?? "",
RequestPath = entity.RequestPath ?? "",
Ip = entity.Ip ?? "",
Status = entity.Status,
OperateTime = entity.OperateTime
};
}
}
public class OperationLogDto
{
public string Id { get; set; }
public string AdminId { get; set; }
public string AdminName { get; set; }
public int Module { get; set; }
public string ActionType { get; set; }
public string Description { get; set; }
public string HttpMethod { get; set; }
public string RequestPath { get; set; }
public string Ip { get; set; }
public int Status { get; set; }
public DateTime OperateTime { get; set; }
}
public class AdminOptionDto
{
public string Value { get; set; }
public string Label { get; set; }
}
public class ModuleOptionDto
{
public int Value { get; set; }
public string Label { get; set; }
}
}
@@ -0,0 +1,244 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using AdminApplication.DTOs;
using AdminDomain.Customer;
using AdminDomain.Customer.Param;
using Common;
using CommonInfrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AdminApi.Customer
{
[ApiController]
[Route("adminapi/[controller]")]
[Authorize]
public class CustomerController : BaseController
{
private readonly ICustomerService _customerService;
public CustomerController(ICustomerService customerService)
{
_customerService = customerService;
}
[HttpGet("list")]
public async Task<PageResponse<CustomerDto>> GetCustomerList([FromQuery] GetCustomerListParam param)
{
var result = await _customerService.GetCustomerList(param);
var dtos = MapToCustomerDtos(result.customers);
return this.ToPageResult(dtos, result.total);
}
[HttpGet("{id}")]
public async Task<BaseResponse<CustomerDto>> GetCustomerById(string id)
{
var result = await _customerService.GetCustomerById(id);
var dto = MapToCustomerDto(result.customer);
return this.ToResult(dto);
}
[HttpPost]
public async Task<BaseResponse<CustomerDto>> AddCustomer([FromBody] AddCustomerParam param)
{
var result = await _customerService.AddCustomer(param);
if (result.isOk)
{
var dto = MapToCustomerDto(result.customer);
return this.ToResult(dto);
}
else
{
return this.ToFailResult<CustomerDto>(result.msg);
}
}
[HttpPut]
public async Task<BaseResponse<string>> UpdateCustomer([FromBody] UpdateCustomerParam param)
{
var result = await _customerService.UpdateCustomer(param);
if (result.isOk)
{
return this.ToResult(result.msg);
}
else
{
return this.ToFailResult<string>(result.msg);
}
}
[HttpDelete("{id}")]
public async Task<BaseResponse<string>> DeleteCustomer(string id)
{
var result = await _customerService.DeleteCustomer(id);
if (result.isOk)
{
return this.ToResult(result.msg);
}
else
{
return this.ToFailResult<string>(result.msg);
}
}
[HttpGet("conversations")]
public async Task<PageResponse<ConversationDto>> GetConversationList([FromQuery] GetConversationListParam param)
{
var result = await _customerService.GetConversationList(param);
var dtos = MapToConversationDtos(result.conversations);
return this.ToPageResult(dtos, result.total);
}
[HttpGet("conversation/{id}")]
public async Task<BaseResponse<ConversationDto>> GetConversationById(string id)
{
var result = await _customerService.GetConversationById(id);
var dto = MapToConversationDto(result.conversation);
return this.ToResult(dto);
}
[HttpDelete("conversation/{id}")]
public async Task<BaseResponse<string>> DeleteConversation(string id)
{
var result = await _customerService.DeleteConversation(id);
return result.isOk
? this.ToResult(result.msg)
: this.ToFailResult<string>(result.msg);
}
[HttpGet("chatlog/{conversationId}")]
public async Task<BaseResponse<List<MessageDto>>> GetChatLog(string conversationId)
{
var result = await _customerService.GetChatLog(conversationId);
if (result.isOk)
{
var dtos = MapToMessageDtos(result.messages);
return this.ToResult(dtos);
}
else
{
return this.ToFailResult<List<MessageDto>>(result.msg);
}
}
[HttpDelete("message/{id}")]
public async Task<BaseResponse<string>> DeleteMessage(string id)
{
var result = await _customerService.DeleteMessage(id);
if (result.isOk)
{
return this.ToResult(result.msg);
}
else
{
return this.ToFailResult<string>(result.msg);
}
}
[HttpDelete("messages/{conversationId}")]
public async Task<BaseResponse<string>> DeleteAllMessages(string conversationId)
{
var result = await _customerService.DeleteAllMessages(conversationId);
if (result.isOk)
{
return this.ToResult(result.msg);
}
else
{
return this.ToFailResult<string>(result.msg);
}
}
[HttpPost("messages/delete-batch")]
public async Task<BaseResponse<string>> DeleteMessagesBatch([FromBody] DeleteMessagesBatchRequest param)
{
var result = await _customerService.DeleteMessagesBatch(param.Ids);
if (result.isOk)
{
return this.ToResult(result.msg);
}
else
{
return this.ToFailResult<string>(result.msg);
}
}
private static CustomerDto MapToCustomerDto(CustomerService.Domain.Entity.CustomerEntity? entity)
{
if (entity == null) return null!;
return new CustomerDto
{
Id = entity.Id,
UserId = entity.UserId,
UserName = entity.UserName,
Status = entity.Status,
Avatar = entity.Avatar,
CreateTime = entity.CreateTime
};
}
private static List<CustomerDto> MapToCustomerDtos(IEnumerable<CustomerService.Domain.Entity.CustomerEntity> entities)
{
var dtos = new List<CustomerDto>();
foreach (var entity in entities)
{
dtos.Add(MapToCustomerDto(entity));
}
return dtos;
}
private static ConversationDto MapToConversationDto(CustomerService.Domain.Entity.ConversationEntity? entity)
{
if (entity == null) return null!;
return new ConversationDto
{
Id = entity.Id,
UserId = entity.UserId,
UserName = entity.UserName,
UserAvatar = entity.UserAvatar,
CustomerId = entity.CustomerId,
CustomerName = entity.CustomerName,
CustomerAvatar = entity.CustomerAvatar,
Status = entity.Status,
CreateTime = entity.CreateTime,
EndTime = entity.EndTime
};
}
private static List<ConversationDto> MapToConversationDtos(IEnumerable<CustomerService.Domain.Entity.ConversationEntity> entities)
{
var dtos = new List<ConversationDto>();
foreach (var entity in entities)
{
dtos.Add(MapToConversationDto(entity));
}
return dtos;
}
private static MessageDto MapToMessageDto(CustomerService.Domain.Entity.MessageEntity entity)
{
return new MessageDto
{
Id = entity.Id,
ConversationId = entity.ConversationId,
SenderType = entity.SenderType,
SenderId = entity.SenderId,
SenderName = entity.SenderName,
SenderAvatar = entity.SenderAvatar,
ContentType = entity.ContentType,
Content = entity.Content,
CreateTime = entity.CreateTime
};
}
private static List<MessageDto> MapToMessageDtos(IEnumerable<CustomerService.Domain.Entity.MessageEntity> entities)
{
var dtos = new List<MessageDto>();
foreach (var entity in entities)
{
dtos.Add(MapToMessageDto(entity));
}
return dtos;
}
}
}
@@ -0,0 +1,110 @@
using AdminService.EmptyRoomRate;
using Common;
using CommonDomain;
using CommonDomain.Enum;
using CommonInfrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AdminApi.EmptyRoomRate
{
[ApiController, Route("adminapi/[controller]")]
[Authorize]
public class EmptyRoomRateConfigController : BaseController
{
private readonly IEmptyRoomRateConfigService _emptyRoomRateConfigService;
public EmptyRoomRateConfigController(IEmptyRoomRateConfigService emptyRoomRateConfigService)
{
_emptyRoomRateConfigService = emptyRoomRateConfigService;
}
[HttpGet("list")]
public async Task<PageResponse<EmptyRoomRateDto>> GetList(
[FromQuery] int page = 1,
[FromQuery] int size = 20,
[FromQuery] GameTypeEnum? gameType = null,
[FromQuery] int? roomLevel = null)
{
page = page > 0 ? page : 1;
size = size > 0 ? size : 20;
var result = await _emptyRoomRateConfigService.GetList(page, size, gameType, roomLevel);
var dtos = result.list.Select(MapToDto).ToList();
return this.ToPageResult(dtos, (int)result.count);
}
[HttpPost("add")]
public async Task<BaseResponse<string>> Add([FromBody] AddEmptyRoomRateParam param)
{
var result = await _emptyRoomRateConfigService.Add(param.GameType, param.RoomLevel, param.EmptyRoomRate);
if (result.isok)
return this.ToResult<string>(result.msg);
return this.ToFailResult<string>(result.msg);
}
[HttpPost("update")]
public async Task<BaseResponse<string>> Update([FromBody] UpdateEmptyRoomRateParam param)
{
var result = await _emptyRoomRateConfigService.Update(param.Id, param.EmptyRoomRate);
if (result.isok)
return this.ToResult<string>(result.msg);
return this.ToFailResult<string>(result.msg);
}
[HttpPost("delete")]
public async Task<BaseResponse<string>> Delete([FromBody] DeleteEmptyRoomRateParam param)
{
var result = await _emptyRoomRateConfigService.Delete(param.Id);
if (result.isok)
return this.ToResult<string>(result.msg);
return this.ToFailResult<string>(result.msg);
}
private static EmptyRoomRateDto MapToDto(EmptyRoomRateConfig entity)
{
if (entity == null) return new EmptyRoomRateDto();
return new EmptyRoomRateDto
{
Id = entity.Id ?? "",
GameType = (int)entity.GameType,
GameTypeName = entity.GameType.ToString(),
RoomLevel = entity.RoomLevel,
EmptyRoomRate = entity.EmptyRoomRate
};
}
}
public class EmptyRoomRateDto
{
public string Id { get; set; }
public int GameType { get; set; }
public string GameTypeName { get; set; }
public int RoomLevel { get; set; }
public int EmptyRoomRate { get; set; }
}
public class AddEmptyRoomRateParam
{
public GameTypeEnum GameType { get; set; }
public int RoomLevel { get; set; }
public int EmptyRoomRate { get; set; } = 30;
}
public class UpdateEmptyRoomRateParam
{
public string Id { get; set; }
public int EmptyRoomRate { get; set; }
}
public class DeleteEmptyRoomRateParam
{
public string Id { get; set; }
}
}
+246
View File
@@ -0,0 +1,246 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Common;
using CommonDomain;
using CommonInfrastructure;
using Microsoft.AspNetCore.Mvc;
using AdminService.Faq;
namespace AdminApi.Faq
{
[ApiController, Route("adminapi/[controller]")]
public class FaqController : BaseController
{
private readonly IFaqService _faqService;
public FaqController(IFaqService faqService)
{
_faqService = faqService;
}
[HttpGet("Categories")]
public async Task<BaseCommonResponse> GetCategories()
{
var list = await _faqService.GetAllCategories();
if (list == null)
return new ListResponse<FaqCategoryDto> { Code = CommonResponseCodeEnum.Success, Data = new List<FaqCategoryDto>() };
var dtos = list.Select(MapToCategoryDto).ToList();
return new ListResponse<FaqCategoryDto>
{
Code = CommonResponseCodeEnum.Success,
Info = "获取成功",
Data = dtos
};
}
[HttpGet("CategoryTree")]
public async Task<BaseCommonResponse> GetCategoryTree()
{
var tree = await _faqService.GetCategoryTree();
var dtos = tree.Select(MapToTreeNodeDto).ToList();
return new ListResponse<FaqCategoryTreeNodeDto>
{
Code = CommonResponseCodeEnum.Success,
Info = "获取成功",
Data = dtos
};
}
[HttpGet("CategoryDetail")]
public async Task<BaseResponse<FaqCategoryDto>> GetCategoryDetail(string id)
{
var category = await _faqService.GetCategoryById(id);
if (category == null)
return this.ToFailResult<FaqCategoryDto>("板块不存在");
return this.ToResult(MapToCategoryDto(category));
}
[HttpPost("AddCategory")]
public async Task<BaseResponse<string>> AddCategory([FromBody] FaqCategory category)
{
var result = await _faqService.AddCategory(category);
if (result.ok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("UpdateCategory")]
public async Task<BaseResponse<string>> UpdateCategory([FromBody] FaqCategory category)
{
var result = await _faqService.UpdateCategory(category);
if (result.ok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("DeleteCategory")]
public async Task<BaseResponse<string>> DeleteCategory(string id)
{
var result = await _faqService.DeleteCategory(id);
if (result.ok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("Questions")]
public async Task<BaseCommonResponse> GetQuestions(string categoryId)
{
var list = await _faqService.GetQuestionsByCategory(categoryId);
if (list == null)
return new ListResponse<FaqQuestionDto> { Code = CommonResponseCodeEnum.Success, Data = new List<FaqQuestionDto>() };
var dtos = list.Select(MapToQuestionDto).ToList();
return new ListResponse<FaqQuestionDto>
{
Code = CommonResponseCodeEnum.Success,
Info = "获取成功",
Data = dtos
};
}
[HttpPost("QuestionList")]
public async Task<BaseCommonResponse> GetQuestionList([FromBody] FaqQueryParam param)
{
var result = await _faqService.QueryQuestions(param.CategoryId, param.PageIndex, param.PageSize);
var dtos = result.list.Select(MapToQuestionDto).ToList();
return new PageDataResponse<FaqQuestionDto>
{
Code = CommonResponseCodeEnum.Success,
Info = "获取成功",
Data = dtos,
Count = (int)result.count
};
}
[HttpGet("QuestionDetail")]
public async Task<BaseResponse<FaqQuestionDto>> GetQuestionDetail(string id)
{
var question = await _faqService.GetQuestionById(id);
if (question == null)
return this.ToFailResult<FaqQuestionDto>("问题不存在");
return this.ToResult(MapToQuestionDto(question));
}
[HttpPost("AddQuestion")]
public async Task<BaseResponse<string>> AddQuestion([FromBody] FaqQuestion question)
{
var result = await _faqService.AddQuestion(question);
if (result.ok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("UpdateQuestion")]
public async Task<BaseResponse<string>> UpdateQuestion([FromBody] FaqQuestion question)
{
var result = await _faqService.UpdateQuestion(question);
if (result.ok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("DeleteQuestion")]
public async Task<BaseResponse<string>> DeleteQuestion(string id)
{
var result = await _faqService.DeleteQuestion(id);
if (result.ok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
private static FaqCategoryDto MapToCategoryDto(FaqCategory entity)
{
if (entity == null) return new FaqCategoryDto();
return new FaqCategoryDto
{
Id = entity.Id ?? "",
ParentId = entity.ParentId ?? "",
Name = entity.Name ?? "",
Icon = entity.Icon ?? "",
Sort = entity.Sort,
IsEnable = entity.IsEnable
};
}
private static FaqCategoryTreeNodeDto MapToTreeNodeDto(FaqCategoryTreeNode entity)
{
if (entity == null) return new FaqCategoryTreeNodeDto();
return new FaqCategoryTreeNodeDto
{
Id = entity.Id ?? "",
Name = entity.Name ?? "",
Icon = entity.Icon ?? "",
Sort = entity.Sort,
IsEnable = entity.IsEnable,
Children = entity.Children?.Select(MapToTreeNodeDto).ToList() ?? new List<FaqCategoryTreeNodeDto>(),
QuestionCount = entity.QuestionCount
};
}
private static FaqQuestionDto MapToQuestionDto(FaqQuestion entity)
{
if (entity == null) return new FaqQuestionDto();
return new FaqQuestionDto
{
Id = entity.Id ?? "",
CategoryId = entity.CategoryId ?? "",
Title = entity.Title ?? "",
Content = entity.Content ?? "",
Sort = entity.Sort,
IsEnable = entity.IsEnable,
CreateTime = entity.CreateTime
};
}
}
public class FaqCategoryDto
{
public string Id { get; set; }
public string ParentId { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public int Sort { get; set; }
public bool IsEnable { get; set; }
}
public class FaqCategoryTreeNodeDto
{
public string Id { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public int Sort { get; set; }
public bool IsEnable { get; set; }
public List<FaqCategoryTreeNodeDto> Children { get; set; } = new();
public int QuestionCount { get; set; }
}
public class FaqQuestionDto
{
public string Id { get; set; }
public string CategoryId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int Sort { get; set; }
public bool IsEnable { get; set; }
public DateTime CreateTime { get; set; }
}
public class FaqQueryParam
{
public string? CategoryId { get; set; }
public int PageIndex { get; set; } = 1;
public int PageSize { get; set; } = 10;
}
}
@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Common;
using CommonInfrastructure;
using Microsoft.AspNetCore.Mvc;
using MongoRepository;
using UserDomain;
using UserDomain.Entity;
using UserDomain.Param;
namespace AdminApi.Feedbacks
{
[ApiController, Route("adminapi/[controller]")]
public class FeedbackManageController : BaseController
{
private readonly IUserFeedbackService _feedbackService;
private readonly IMongoBaseRepository<UserFeedback> _feedbackRepo;
public FeedbackManageController(IUserFeedbackService feedbackService, IMongoBaseRepository<UserFeedback> feedbackRepo)
{
_feedbackService = feedbackService;
_feedbackRepo = feedbackRepo;
}
[HttpPost("List")]
public async Task<PageResponse<FeedbackDto>> GetList([FromBody] FeedbackQueryParam param)
{
var result = await _feedbackService.QueryPage(param);
var dtos = result.list.Select(MapToDto);
return this.ToPageResult(dtos, (int)result.count);
}
[HttpGet("Detail")]
public async Task<BaseResponse<FeedbackDetailDto>> GetDetail(string id)
{
var feedback = await _feedbackRepo.FindOneAsync(x => x.Id == id);
if (feedback == null)
return this.ToFailResult<FeedbackDetailDto>("反馈不存在");
return this.ToResult(MapToDetailDto(feedback));
}
[HttpPost("Reply")]
public async Task<BaseResponse<string>> Reply([FromBody] ReplyFeedbackParam param)
{
var feedback = await _feedbackRepo.FindOneAsync(x => x.Id == param.Id);
if (feedback == null)
return this.ToFailResult<string>("反馈不存在");
feedback.Reply = param.Reply;
feedback.Status = param.Status;
feedback.ReplyTime = DateTime.Now;
feedback.ReplyAdminId = loginUser.UserId;
feedback.ReplyAdminName = loginUser.UserName;
var result = await _feedbackRepo.ReplaceOneAsync(feedback);
if (result.ok)
return this.ToResult("回复成功");
else
return this.ToFailResult<string>("回复失败");
}
[HttpPost("Delete")]
public async Task<BaseResponse<string>> Delete(string id)
{
var result = await _feedbackRepo.DeleteByIdAsync(id);
if (result.ok)
return this.ToResult("删除成功");
else
return this.ToFailResult<string>("删除失败");
}
private static FeedbackDto MapToDto(UserFeedback entity)
{
return new FeedbackDto
{
Id = entity.Id,
UserId = entity.UserId ?? "",
UserNumber = entity.UserNumber ?? "",
UserName = entity.UserName ?? "",
Type = entity.Type,
Title = entity.Title ?? "",
Content = entity.Content ?? "",
Images = string.IsNullOrEmpty(entity.Images) ? new List<string>() : entity.Images.Split(',').ToList(),
QQ = entity.QQ ?? "",
Contact = entity.Contact ?? "",
Status = entity.Status,
Reply = entity.Reply ?? "",
ReplyTime = entity.ReplyTime,
SubmitTime = entity.SubmitTime
};
}
private static FeedbackDetailDto MapToDetailDto(UserFeedback entity)
{
return new FeedbackDetailDto
{
Id = entity.Id,
UserId = entity.UserId ?? "",
UserNumber = entity.UserNumber ?? "",
UserName = entity.UserName ?? "",
Type = entity.Type,
Title = entity.Title ?? "",
Content = entity.Content ?? "",
Images = string.IsNullOrEmpty(entity.Images) ? new List<string>() : entity.Images.Split(',').ToList(),
QQ = entity.QQ ?? "",
Contact = entity.Contact ?? "",
Status = entity.Status,
Reply = entity.Reply ?? "",
ReplyTime = entity.ReplyTime,
ReplyAdminId = entity.ReplyAdminId ?? "",
ReplyAdminName = entity.ReplyAdminName ?? "",
SubmitTime = entity.SubmitTime
};
}
}
public class FeedbackDto
{
public string Id { get; set; }
public string UserId { get; set; }
public string UserNumber { get; set; }
public string UserName { get; set; }
public int Type { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List<string> Images { get; set; }
/// <summary>
/// QQ
/// </summary>
public string QQ { get; set; }
public string Contact { get; set; }
public int Status { get; set; }
public string Reply { get; set; }
public DateTime? ReplyTime { get; set; }
public DateTime SubmitTime { get; set; }
}
public class FeedbackDetailDto
{
public string Id { get; set; }
public string UserId { get; set; }
public string UserNumber { get; set; }
public string UserName { get; set; }
public int Type { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List<string> Images { get; set; }
/// <summary>
/// QQ
/// </summary>
public string QQ { get; set; }
public string Contact { get; set; }
public int Status { get; set; }
public string Reply { get; set; }
public DateTime? ReplyTime { get; set; }
public string ReplyAdminId { get; set; }
public string ReplyAdminName { get; set; }
public DateTime SubmitTime { get; set; }
}
}
@@ -0,0 +1,224 @@
using System.Text;
using AdminService.GameConfigs;
using Common;
using CommonDomain;
using CommonDomain.BaseModel;
using CommonInfrastructure;
using Microsoft.AspNetCore.Mvc;
using NewLife;
using RedisService;
using XAct;
namespace AdminApi.GameConfigs
{
[ApiController, Route("adminapi/[controller]")]
public class ActivityConfigController : BaseController
{
private readonly IActivityConfigService _activityConfigService;
private readonly IRedisService _redisService;
public ActivityConfigController(IActivityConfigService activityConfigService, IRedisService redisService)
{
_activityConfigService = activityConfigService;
_redisService = redisService;
}
[HttpGet]
[HttpGet("Get")]
public async Task<BaseResponse<ActivityConfig>> Get()
{
var config = await _activityConfigService.Get();
if (config != null)
return this.ToResult(config);
else
return this.ToFailResult<ActivityConfig>("配置不存在");
}
[HttpPost("Update")]
public async Task<BaseResponse<string>> Update(ActivityConfig config)
{
var res = await _activityConfigService.Update(config);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpPost("Delete")]
public async Task<BaseResponse<string>> Delete()
{
var res = await _activityConfigService.Delete();
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpPost("UpdateBattleOrder")]
public async Task<BaseResponse<string>> UpdateBattleOrder(BattleOrderConfig config)
{
var activityConfig = await _activityConfigService.Get() ?? new ActivityConfig();
activityConfig.BattleOrderConfig = config;
var res = await _activityConfigService.Update(activityConfig);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpPost("UpdateRelief")]
public async Task<BaseResponse<string>> UpdateRelief(ReliefConfig config)
{
var activityConfig = await _activityConfigService.Get() ?? new ActivityConfig();
activityConfig.ReliefConfig = config;
var res = await _activityConfigService.Update(activityConfig);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpPost("UpdateSign")]
public async Task<BaseResponse<string>> UpdateSign(List<SignConfig> configs)
{
var activityConfig = await _activityConfigService.Get() ?? new ActivityConfig();
activityConfig.SignConfigs = configs;
var res = await _activityConfigService.Update(activityConfig);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpPost("UpdateGoldShop")]
public async Task<BaseResponse<string>> UpdateGoldShop(List<GoldShopConfig> configs)
{
var activityConfig = await _activityConfigService.Get() ?? new ActivityConfig();
activityConfig.GoldShopConfigs = configs;
var res = await _activityConfigService.Update(activityConfig);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpPost("UpdateDiamondShop")]
public async Task<BaseResponse<string>> UpdateDiamondShop(List<DiamondShopConfig> configs)
{
var activityConfig = await _activityConfigService.Get() ?? new ActivityConfig();
activityConfig.DiamondShopConfigs = configs;
var res = await _activityConfigService.Update(activityConfig);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpPost("UpdateRegister")]
public async Task<BaseResponse<string>> UpdateRegister(RegisterConfig config)
{
var activityConfig = await _activityConfigService.Get() ?? new ActivityConfig();
activityConfig.RegisterConfig = config;
var res = await _activityConfigService.Update(activityConfig);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpPost("UpdateFishingReturn")]
public async Task<BaseResponse<string>> UpdateFishingReturn(FishingReturnConfig config)
{
var activityConfig = await _activityConfigService.Get() ?? new ActivityConfig();
activityConfig.FishingReturnConfig = config;
var res = await _activityConfigService.Update(activityConfig);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpGet("GetFishingReturn")]
public async Task<BaseResponse<FishingReturnConfig>> GetFishingReturn()
{
var config = await _activityConfigService.Get();
if (config?.FishingReturnConfig != null)
return this.ToResult(config.FishingReturnConfig);
else
return this.ToFailResult<FishingReturnConfig>("配置不存在");
}
[HttpGet("SaveSection/{sectionName}")]
public async Task<BaseResponse<string>> SaveSection(string sectionName)
{
var prop = typeof(ActivityConfig).GetProperty(sectionName);
if (prop == null)
return this.ToFailResult<string>($"板块 {sectionName} 不存在");
var config = await _activityConfigService.Get();
if (config == null)
return this.ToFailResult<string>("配置不存在");
var sectionValue = prop.GetValue(config);
if (sectionValue == null)
return this.ToFailResult<string>($"板块 {sectionName} 数据为空");
var jsonPath = global::System.IO.Path.Combine(AppContext.BaseDirectory, "Config", "activityconfig.json");
try
{
await _activityConfigService.Update(config);
ActivityConfig fileConfig;
if (global::System.IO.File.Exists(jsonPath))
{
var json = await global::System.IO.File.ReadAllTextAsync(jsonPath, global::System.Text.Encoding.UTF8);
fileConfig = global::System.Text.Json.JsonSerializer.Deserialize<ActivityConfig>(json, new global::System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true, ReadCommentHandling = global::System.Text.Json.JsonCommentHandling.Skip }) ?? new ActivityConfig();
}
else
{
fileConfig = new ActivityConfig();
}
prop.SetValue(fileConfig, sectionValue);
var outJson = global::System.Text.Json.JsonSerializer.Serialize(fileConfig, new global::System.Text.Json.JsonSerializerOptions
{
WriteIndented = true,
Encoder = global::System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
await global::System.IO.File.WriteAllTextAsync(jsonPath, outJson, global::System.Text.Encoding.UTF8);
return this.ToResult($"{sectionName} 已保存到 activityconfig.json");
}
catch (Exception ex)
{
return this.ToFailResult<string>($"保存失败: {ex.Message}");
}
}
[HttpGet("ExportSection/{sectionName}")]
public async Task<IActionResult> ExportSection(string sectionName)
{
var config = await _activityConfigService.Get();
if (config == null)
return NotFound("配置不存在");
var prop = typeof(ActivityConfig).GetProperty(sectionName);
if (prop == null)
return NotFound($"板块 {sectionName} 不存在");
var data = prop.GetValue(config);
if (data == null)
return NotFound($"板块 {sectionName} 数据为空");
var json = global::System.Text.Json.JsonSerializer.Serialize(data, new global::System.Text.Json.JsonSerializerOptions
{
WriteIndented = true,
Encoder = global::System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
return File(global::System.Text.Encoding.UTF8.GetBytes(json), "application/json", $"{sectionName}.json");
}
}
}
@@ -0,0 +1,828 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AdminService.GameConfigs;
using ChessGameDomain;
using Common;
using CommonDomain;
using CommonDomain.Enum;
using CommonInfrastructure;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MongoRepository;
using RedisService;
using RedisService.Param;
using UserDomain.Entity;
namespace AdminApi.GameConfigs
{
[ApiController, Route("adminapi/Admin/[controller]")]
[Authorize]
public class RoomController : BaseController
{
private readonly IRedisService _redisService;
private readonly IGameConfigService _gameConfigService;
private readonly ICapPublisher _capPublisher;
private readonly IMongoBaseRepository<UserEntity> _userMongo;
private readonly ILogger<RoomController> _logger;
public RoomController(IRedisService redisService, IGameConfigService gameConfigService, ICapPublisher capPublisher, IMongoBaseRepository<UserEntity> userMongo, ILogger<RoomController> logger)
{
_redisService = redisService;
_gameConfigService = gameConfigService;
_capPublisher = capPublisher;
_userMongo = userMongo;
_logger = logger;
}
[HttpGet("GetAllRooms")]
public async Task<PageResponse<RedisGameRoom>> GetAllRooms(
int pageIndex = 1,
int pageSize = 20,
int? roomLevel = null,
string? gameType = null,
string? sortField = null,
string? sortOrder = "desc")
{
var rooms = GetAllRoomsFromRedis()?.ToList() ?? new List<RedisGameRoom>();
foreach (var room in rooms)
{
if (room?.Players != null && room.Players.Count > 1)
room.Players = room.Players.OrderBy(p => p.Position).ToList();
}
if (roomLevel.HasValue)
{
rooms = rooms.Where(r => r != null && r.RoomLevel == roomLevel.Value).ToList();
}
if (!string.IsNullOrEmpty(gameType) && int.TryParse(gameType, out var gameTypeValue))
{
rooms = rooms.Where(r => r != null && (int)r.GameType == gameTypeValue).ToList();
}
var total = rooms.Count;
if (!string.IsNullOrEmpty(sortField))
{
switch (sortField.ToLower())
{
case "currentonline":
rooms = sortOrder?.ToLower() == "asc"
? rooms.OrderBy(r => r.CurrentOnlinePlayerCount).ToList()
: rooms.OrderByDescending(r => r.CurrentOnlinePlayerCount).ToList();
break;
case "robotcount":
rooms = sortOrder?.ToLower() == "asc"
? rooms.OrderBy(r => r.RobotCount).ToList()
: rooms.OrderByDescending(r => r.RobotCount).ToList();
break;
case "roomlevel":
rooms = sortOrder?.ToLower() == "asc"
? rooms.OrderBy(r => r.RoomLevel).ToList()
: rooms.OrderByDescending(r => r.RoomLevel).ToList();
break;
default:
rooms = rooms.OrderByDescending(r => r.RoomId).ToList();
break;
}
}
var pagedRooms = rooms.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
return this.ToPageResult(pagedRooms, total);
}
[HttpGet("GetAllChessRooms")]
public async Task<PageResponse<ChessGameRoom>> GetAllChessRooms(
int pageIndex = 1,
int pageSize = 20,
int? state = null,
int? gameType = null)
{
var rooms = _redisService.GetAllChessRooms()?.ToList();
if (state.HasValue)
{
rooms = rooms.Where(r => r != null && r.state == state.Value).ToList();
}
if (gameType.HasValue)
{
rooms = rooms.Where(r => r != null && r.gameType == gameType.Value).ToList();
}
var total = rooms.Count;
rooms = rooms.OrderByDescending(r => r.CreateTime).ToList();
var pagedRooms = rooms.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
return this.ToPageResult(pagedRooms, total);
}
[HttpGet("GetChessRoomDetail")]
public async Task<BaseResponse<ChessGameRoom>> GetChessRoomDetail(string roomId)
{
var room = _redisService.GetChessRoom(roomId);
if (room == null)
return this.ToFailResult<ChessGameRoom>("房间不存在");
return this.ToResult(room);
}
[HttpGet("ClearChessRoom")]
public async Task<BaseResponse<string>> ClearChessRoom(string roomId)
{
var room = _redisService.GetChessRoom(roomId);
if (room == null)
return this.ToFailResult<string>("房间不存在");
int playerCount = 0;
foreach (var user in room.users.ToList())
{
try
{
var userRedis = _redisService.GetRedisUserCId(user.userId);
if (userRedis != null)
{
userRedis.CId = "";
_redisService.SetRedisUserCId(userRedis);
}
playerCount++;
}
catch (Exception ex)
{
_logger.LogError(ex, "管理员清空象棋房间 清理userRedis失败 roomId={RoomId} userId={UserId}", roomId, user.userId);
}
}
room.users.Clear();
room.state = 0;
room.robotCount = 0;
room.gold = 0;
room.gameType = 0;
room.password = "";
room.IsGameEnded = false;
room.EndGameRequests?.Clear();
room.PendingWinUserId = null;
room.TimeoutLoserUserId = "";
room.LastActiveTime = 0;
_redisService.SetChessRoom(room);
return this.ToResult($"已清空房间 {roomId} 的所有玩家({playerCount} 人)");
}
[HttpGet("KickChessPlayer")]
public async Task<BaseResponse<string>> KickChessPlayer(string roomId, string userId)
{
if (string.IsNullOrWhiteSpace(roomId) || string.IsNullOrWhiteSpace(userId))
return this.ToFailResult<string>("房间ID和玩家ID不能为空");
var room = _redisService.GetChessRoom(roomId);
if (room == null)
return this.ToFailResult<string>("房间不存在");
if (room.state == 2)
return this.ToFailResult<string>("游戏进行中不能直接踢人,请先结束对局");
if (room.users?.Any(u => u.userId == userId) != true)
return this.ToFailResult<string>("玩家不在该房间中");
await _capPublisher.PublishAsync(TopicKeys.后台踢出象棋玩家, new AdminKickChessPlayerMessage
{
RoomId = roomId,
UserId = userId,
Reason = "被管理员移出房间"
});
return this.ToResult("踢人指令已提交");
}
[HttpGet("GetChessRoomStatistics")]
public async Task<BaseResponse<ChessRoomStatistics>> GetChessRoomStatistics()
{
var rooms = _redisService.GetAllChessRooms() ?? new List<ChessGameRoom>();
var stats = new ChessRoomStatistics
{
TotalRooms = rooms.Count,
EmptyRooms = rooms.Count(r => r.state == 0 &&
(r.users == null || !r.users.Any(u => u != null && !u.IsLeave)) &&
r.robotCount == 0),
WaitingRooms = rooms.Count(r => r.state == 1),
PlayingRooms = rooms.Count(r => r.state == 2),
FinishedRooms = rooms.Count(r => r.state == 3),
TotalPlayers = rooms.SelectMany(r => r.users ?? new List<RedisChessGameUser>()).Count(),
BlackSidePlayers = rooms.SelectMany(r => r.users ?? new List<RedisChessGameUser>()).Count(u => u.type == 1),
RedSidePlayers = rooms.SelectMany(r => r.users ?? new List<RedisChessGameUser>()).Count(u => u.type == 2)
};
stats.EmptyRoomRate = stats.TotalRooms == 0
? 0
: Math.Round(stats.EmptyRooms * 100.0 / stats.TotalRooms, 2);
return this.ToResult(stats);
}
[HttpGet("DeleteChessRoom")]
public async Task<BaseResponse<string>> DeleteChessRoom(string roomId)
{
var room = _redisService.GetChessRoom(roomId);
if (room == null)
return this.ToFailResult<string>("房间不存在");
_redisService.RemoveChessRoom(roomId);
return this.ToResult("已删除房间 " + roomId);
}
[HttpGet("AddChessRobot")]
public async Task<BaseResponse<string>> AddChessRobot(string roomId, int count)
{
var room = _redisService.GetChessRoom(roomId);
if (room == null)
return this.ToFailResult<string>("房间不存在");
var realPlayers = room.users?.Where(u => u != null && !u.IsLeave).ToList();
if (realPlayers != null && realPlayers.Count > 0)
return this.ToFailResult<string>("房间有真人玩家,不能修改机器人数量");
room.robotCount += count;
ApplyRobotStateRule(room);
_redisService.SetChessRoom(room);
return this.ToResult("添加成功");
}
[HttpGet("SetChessRobot")]
public async Task<BaseResponse<string>> SetChessRobot(string roomId, int count)
{
var room = _redisService.GetChessRoom(roomId);
if (room == null)
return this.ToFailResult<string>("房间不存在");
var realPlayers = room.users?.Where(u => u != null && !u.IsLeave).ToList();
if (realPlayers != null && realPlayers.Count > 0)
return this.ToFailResult<string>("房间有真人玩家,不能修改机器人数量");
room.robotCount = Math.Clamp(count, 0, 2);
ApplyRobotStateRule(room);
_redisService.SetChessRoom(room);
return this.ToResult("设置成功");
}
[HttpGet("GetRoomDetail")]
public async Task<BaseResponse<RedisGameRoom>> GetRoomDetail(string roomId)
{
var room = _redisService.GetGameRoom(roomId);
if (room == null)
return this.ToFailResult<RedisGameRoom>("房间不存在");
return this.ToResult(room);
}
[HttpGet("GetUserRoomRealtime")]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public async Task<BaseResponse<UserRoomRealtimeDto>> GetUserRoomRealtime(string userId)
{
if (string.IsNullOrWhiteSpace(userId))
return this.ToFailResult<UserRoomRealtimeDto>("用户ID不能为空");
var dto = new UserRoomRealtimeDto { Found = true };
var user = await _userMongo.FindByIdAsync(userId);
var factor = user?.PlayerFactor ?? new PlayerFactor();
dto.Gold = user?.Assets?.Gold ?? 0;
dto.TotalRechargeGold = user?.Static?.TotalRechargeGold ?? 0;
RedisGameRoom? matchedRoom = null;
RedisGamePlayer? matchedPlayer = null;
var session = _redisService.GetRedisUserCId(userId);
if (!string.IsNullOrWhiteSpace(session?.RoomId))
{
matchedRoom = _redisService.GetGameRoom(session.RoomId);
matchedPlayer = matchedRoom?.Players?
.FirstOrDefault(p => p != null && !p.IsBot && p.UserId == userId);
}
if (matchedPlayer == null)
{
var fallback = _redisService.GetGameRoomAllList()
.Where(r => r != null && r.Players != null)
.SelectMany(r => r.Players.Select(p => new { Room = r, Player = p }))
.FirstOrDefault(x => x.Player != null && !x.Player.IsBot && x.Player.UserId == userId);
matchedRoom = fallback?.Room;
matchedPlayer = fallback?.Player;
}
if (matchedRoom != null && matchedPlayer != null)
{
var room = matchedRoom;
var player = matchedPlayer;
dto.InRoom = true;
dto.RoomId = room.RoomId;
dto.RoomName = room.RoomName;
dto.GameType = (int)room.GameType;
dto.GameTypeName = room.GameType.ToString();
dto.RoomLevel = room.RoomLevel;
dto.RoomPartition = room.RoomPartition;
dto.RoomNumber = room.RoomNumber;
dto.SystemFactor = room.SystemFactor;
dto.CurrentOnlinePlayerCount = room.CurrentOnlinePlayerCount;
dto.RobotCount = room.RobotCount;
dto.Position = player.Position;
dto.UserName = player.UserName;
dto.UserNumber = player.UserNumber;
dto.Gold = player.Gold;
dto.MaxGold = player.MaxGold;
dto.BeginCoin = player.BeginCoin;
dto.CannonMultiple = player.CannonMultiple;
dto.CannonId = player.CannonId;
dto.State = player.State;
dto.IsAfk = player.IsAfk;
dto.TotalRechargeGold = player.TotalRechargeGold;
var delta = player.Gold - player.BeginCoin;
dto.WinGold = delta > 0 ? delta : 0;
dto.LoseGold = delta < 0 ? -delta : 0;
var realtimeFactor = player.PlayerFactor ?? new PlayerFactor();
if (realtimeFactor.ViolateValue > 0)
factor.ViolateValue = realtimeFactor.ViolateValue;
}
dto.IsBlack = factor.IsBlack;
dto.IsIgnore = factor.IsIgnore;
dto.ViolateValue = factor.ViolateValue;
dto.AdditionalFactor = factor.AdditionalFactor;
dto.MissRate = factor.MissRate;
dto.CompensateQuota = factor.CompensateQuota;
dto.CompensateStartGold = factor.CompensateStartGold;
dto.CompensateTargetGold = factor.CompensateTargetGold;
if (factor.CompensateQuota <= 0)
dto.CompensateRemainingQuota = 0;
else
dto.CompensateRemainingQuota = factor.CompensateTargetGold > 0
? Math.Max(0, factor.CompensateTargetGold - dto.Gold)
: Math.Max(0, factor.CompensateQuota);
dto.PenaltyQuota = factor.PenaltyQuota;
dto.StartingBonus = factor.StartingBonus;
dto.CompensateRateBoss = factor.CompensateRate?.BossRate ?? 0;
dto.CompensateRateGold = factor.CompensateRate?.GoldRate ?? 0;
dto.CompensateRateSpecial = factor.CompensateRate?.SpecialRate ?? 0;
dto.CompensateRateOrdinary = factor.CompensateRate?.Ordinary ?? 0;
dto.PenaltyRateBoss = factor.PenaltyRate?.BossRate ?? 0;
dto.PenaltyRateGold = factor.PenaltyRate?.GoldRate ?? 0;
dto.PenaltyRateSpecial = factor.PenaltyRate?.SpecialRate ?? 0;
dto.PenaltyRateOrdinary = factor.PenaltyRate?.Ordinary ?? 0;
return this.ToResult(dto);
}
[HttpGet("AddRobot")]
public async Task<BaseResponse<string>> AddRobot(string roomId, int count)
{
var room = _redisService.GetGameRoom(roomId);
if (room == null)
return this.ToFailResult<string>("房间不存在");
if (count < 0)
return this.ToFailResult<string>("机器人增加数量不能小于0");
var realPlayerCount = room.Players?.Count(p => !p.IsBot) ?? room.CurrentOnlinePlayerCount;
room.CurrentOnlinePlayerCount = realPlayerCount;
room.RobotCount = Math.Clamp(room.RobotCount + count, 0, Math.Max(0, room.MaxPlayerCount - realPlayerCount));
if (room.Players != null)
{
var excessBots = room.Players.Where(p => p.IsBot).Skip(room.RobotCount).ToList();
foreach (var bot in excessBots)
{
_redisService.ClearBulletIds(roomId, bot.UserId);
room.Players.Remove(bot);
}
room.RobotCount = Math.Max(room.RobotCount, room.Players.Count(p => p.IsBot));
}
_redisService.SetGameRoom(room);
return this.ToResult($"已为房间 {roomId} 添加 {count} 个机器人");
}
[HttpGet("SetRobotCount")]
public async Task<BaseResponse<string>> SetRobotCount(string roomId, int count)
{
var room = _redisService.GetGameRoom(roomId);
if (room == null)
return this.ToFailResult<string>("房间不存在");
if (count < 0)
return this.ToFailResult<string>("机器人数量不能小于0");
var realPlayerCount = room.Players?.Count(p => !p.IsBot) ?? room.CurrentOnlinePlayerCount;
room.CurrentOnlinePlayerCount = realPlayerCount;
room.RobotCount = Math.Clamp(count, 0, Math.Max(0, room.MaxPlayerCount - realPlayerCount));
if (room.Players != null)
{
var excessBots = room.Players.Where(p => p.IsBot).Skip(room.RobotCount).ToList();
foreach (var bot in excessBots)
{
_redisService.ClearBulletIds(roomId, bot.UserId);
room.Players.Remove(bot);
}
room.RobotCount = Math.Max(room.RobotCount, room.Players.Count(p => p.IsBot));
}
_redisService.SetGameRoom(room);
return this.ToResult($"已将房间 {roomId} 的机器人数量设置为 {count}");
}
[HttpGet("RegenerateAllBotIds")]
public async Task<BaseResponse<string>> RegenerateAllBotIds()
{
var rooms = GetAllRoomsFromRedis()?.ToList() ?? new List<RedisGameRoom>();
int totalBots = 0;
foreach (var room in rooms)
{
if (room?.Players == null || room.Players.Count == 0)
continue;
var bots = room.Players.Where(p => p.IsBot).ToList();
if (bots.Count == 0)
continue;
foreach (var bot in bots)
{
var oldUserId = bot.UserId;
bot.UserId = Guid.NewGuid().ToString("N");
bot.UserNumber = Common.Utils.RandomHelper.RandomNumbers(8);
totalBots++;
}
_redisService.SetGameRoom(room);
}
return this.ToResult($"已刷新 {totalBots} 个机器人编号(涉及 {rooms.Count(r => r?.Players?.Any(p => p.IsBot) == true)} 个房间)");
}
[HttpGet("ClearRoom")]
public async Task<BaseResponse<string>> ClearRoom(string roomId)
{
var room = _redisService.GetGameRoom(roomId);
if (room == null)
return this.ToFailResult<string>("房间不存在");
int success = 0, fail = 0;
foreach (var player in room.Players.Where(p => !p.IsBot).ToList())
{
try
{
await _capPublisher.PublishAsync(TopicKeys.游戏金币结算, new
{
UserId = player.UserId,
BeforeGold = player.BeginCoin,
AfterGold = player.Gold,
Remark = "管理员清空房间结算",
RoomName = room.RoomName ?? "",
RoomInfo = room.RoomId ?? ""
});
}
catch (Exception ex)
{
_logger.LogError(ex, "管理员清空房间 CAP 结算失败 roomId={RoomId} userId={UserId}", roomId, player.UserId);
}
try { _redisService.ClearBulletIds(roomId, player.UserId); }
catch (Exception ex) { _logger.LogError(ex, "管理员清空房间 清理子弹失败 roomId={RoomId} userId={UserId}", roomId, player.UserId); }
try
{
var userRedis = _redisService.GetRedisUserCId(player.UserId);
if (userRedis != null && userRedis.RoomId == roomId)
{
userRedis.RoomId = "";
userRedis.CId = "";
_redisService.SetRedisUserCId(userRedis);
}
success++;
}
catch (Exception ex)
{
_logger.LogError(ex, "管理员清空房间 清理userRedis失败 roomId={RoomId} userId={UserId}", roomId, player.UserId);
fail++;
}
}
foreach (var player in room.Players.Where(p => p.IsBot).ToList())
{
try { _redisService.ClearBulletIds(roomId, player.UserId); }
catch { }
}
var robotCount = room.RobotCount;
room.Players.Clear();
room.CurrentOnlinePlayerCount = 0;
room.RobotCount = Math.Clamp(robotCount, 0, room.MaxPlayerCount);
_redisService.SetGameRoom(room);
var msg = fail > 0
? $"已清空房间 {roomId}:成功 {success} 人,失败 {fail} 人"
: $"已清空房间 {roomId} 的所有玩家({success} 人)";
return this.ToResult(msg);
}
[HttpGet("GetRoomStatistics")]
public async Task<BaseResponse<RoomStatistics>> GetRoomStatistics()
{
var rooms = GetAllRoomsFromRedis();
if (rooms == null || rooms.Count() == 0)
{
return this.ToResult(new RoomStatistics());
}
var stats = new RoomStatistics
{
TotalRooms = rooms?.Count()?? 0,
TotalPlayers = rooms?.Where(_=>_!=null).Sum(r => r.CurrentOnlinePlayerCount) ?? 0,
TotalRobots = rooms?.Where(_ => _ != null).Sum(r => r.RobotCount) ?? 0
};
if (rooms != null && rooms.Count() > 0)
{
stats.RoomsByLevel = rooms.Where(_ => _ != null).GroupBy(r => r.RoomLevel)
.ToDictionary(g => g.Key, g => g.Count());
stats.RoomsByType = rooms.Where(_ => _ != null).GroupBy(r => r.GameType.ToString())
.ToDictionary(g => g.Key, g => g.Count());
}
return this.ToResult(stats);
}
[HttpGet("RepairChessRooms")]
public async Task<BaseResponse<string>> RepairChessRooms()
{
var rooms = _redisService.GetAllChessRooms();
if (rooms == null || rooms.Count == 0)
return this.ToFailResult<string>("没有象棋房间");
int fixedCount = 0;
foreach (var room in rooms)
{
if (room.users != null && room.users.Any(u => !u.IsLeave))
continue;
ApplyRobotStateRule(room);
_redisService.SetChessRoom(room);
fixedCount++;
}
return this.ToResult($"已修复 {fixedCount} 个象棋房间");
}
[HttpGet("InitRooms")]
public async Task<BaseResponse<string>> InitRooms()
{
_redisService.RemoveGameRoomAll();
_redisService.ClearAllChessRooms();
await _gameConfigService.InitRoom();
var config = await _gameConfigService.Get();
var chessRoomConfigs = config?.RoomConfigs.Where(c => c.GameType == GameTypeEnum.中国象棋).ToList();
if (chessRoomConfigs != null)
{
foreach (var roomConfig in chessRoomConfigs)
{
for (int y = 1; y <= roomConfig.RoomPartitionNum; y++)
{
for (int i = 0; i < roomConfig.RoomCount; i++)
{
var room = new ChessGameRoom
{
RoomId = $"{(int)GameTypeEnum.中国象棋}{y}{i.ToString("00")}",
CreateTime = DateTime.Now,
createUserId = "",
gameType = 0,
gold = roomConfig.RoomGoldLimit,
Id = Guid.NewGuid().ToString(),
users = new List<RedisChessGameUser>(),
EndGameRequests = new Dictionary<string, long>(),
LastActiveTime = 0,
TimeoutSeconds = 60,
robotCount = 0
};
ApplyRobotStateRule(room);
_redisService.SetChessRoom(room);
}
}
}
}
return this.ToResult("房间初始化成功");
}
[HttpGet("ReInitChessRooms")]
public async Task<BaseResponse<string>> ReInitChessRooms()
{
var config = await _gameConfigService.Get();
var chessConfig = config?.ChessConfig;
if (chessConfig == null)
return this.ToFailResult<string>("象棋配置不存在");
_redisService.ClearAllChessRooms();
var chessRoomConfigs = config.RoomConfigs.Where(c => c.GameType == GameTypeEnum.中国象棋).ToList();
if (chessRoomConfigs.Count == 0)
return this.ToFailResult<string>("未找到象棋房间配置");
int count = 0;
foreach (var roomConfig in chessRoomConfigs)
{
for (int y = 1; y <= roomConfig.RoomPartitionNum; y++)
{
for (int i = 0; i < roomConfig.RoomCount; i++)
{
var room = new ChessGameRoom
{
RoomId = $"{(int)GameTypeEnum.中国象棋}{y}{i.ToString("00")}",
CreateTime = DateTime.Now,
createUserId = "",
gameType = 0,
gold = roomConfig.RoomGoldLimit,
Id = Guid.NewGuid().ToString(),
users = new List<RedisChessGameUser>(),
EndGameRequests = new Dictionary<string, long>(),
LastActiveTime = 0,
TimeoutSeconds = 60,
robotCount = 0
};
ApplyRobotStateRule(room);
_redisService.SetChessRoom(room);
count++;
}
}
}
return this.ToResult($"象棋房间已重新初始化,共 {count} 个房间");
}
private IEnumerable<RedisGameRoom> GetAllRoomsFromRedis()
{
return _redisService.GetGameRoomAllList();
}
private static void ApplyRobotStateRule(ChessGameRoom room)
{
var userCount = room.users?.Count ?? 0;
var totalCount = userCount + room.robotCount;
if (totalCount == 0)
{
room.state = 0;
room.password = "";
}
else if (totalCount == 1)
{
room.state = 1;
if (string.IsNullOrEmpty(room.password))
room.password = Random.Shared.Next(100000, 999999).ToString();
}
else
{
room.state = 2;
if (string.IsNullOrEmpty(room.password))
room.password = Random.Shared.Next(100000, 999999).ToString();
}
}
}
public class RoomStatistics
{
public int TotalRooms { get; set; } = 0;
public int TotalPlayers { get; set; } = 0;
public int TotalRobots { get; set; } = 0;
public Dictionary<int, int> RoomsByLevel { get; set; } = new();
public Dictionary<string, int> RoomsByType { get; set; } = new();
}
public class ChessRoomStatistics
{
public int TotalRooms { get; set; } = 0;
public int EmptyRooms { get; set; } = 0;
public double EmptyRoomRate { get; set; } = 0;
public int WaitingRooms { get; set; } = 0;
public int PlayingRooms { get; set; } = 0;
public int FinishedRooms { get; set; } = 0;
public int TotalPlayers { get; set; } = 0;
public int BlackSidePlayers { get; set; } = 0;
public int RedSidePlayers { get; set; } = 0;
}
public class UserRoomRealtimeDto
{
public bool Found { get; set; } = false;
public bool InRoom { get; set; } = false;
public long WinGold { get; set; } = 0;
public long LoseGold { get; set; } = 0;
public string RoomId { get; set; } = string.Empty;
public string RoomName { get; set; } = string.Empty;
public int GameType { get; set; }
public string GameTypeName { get; set; } = string.Empty;
public int RoomLevel { get; set; }
public int RoomPartition { get; set; }
public int RoomNumber { get; set; }
public double SystemFactor { get; set; } = 1.0;
public int CurrentOnlinePlayerCount { get; set; }
public int RobotCount { get; set; }
public int Position { get; set; }
public string UserName { get; set; } = string.Empty;
public string UserNumber { get; set; } = string.Empty;
public long Gold { get; set; }
public long BeginCoin { get; set; }
public long MaxGold { get; set; }
public int CannonMultiple { get; set; }
public string CannonId { get; set; } = string.Empty;
public int State { get; set; }
public bool IsAfk { get; set; }
public long TotalRechargeGold { get; set; }
public bool IsBlack { get; set; }
public bool IsIgnore { get; set; }
public double ViolateValue { get; set; }
public double AdditionalFactor { get; set; }
public double MissRate { get; set; }
public int CompensateQuota { get; set; }
public long CompensateStartGold { get; set; }
public long CompensateTargetGold { get; set; }
public long CompensateRemainingQuota { get; set; }
public int PenaltyQuota { get; set; }
public long StartingBonus { get; set; }
public double CompensateRateBoss { get; set; }
public double CompensateRateGold { get; set; }
public double CompensateRateSpecial { get; set; }
public double CompensateRateOrdinary { get; set; }
public double PenaltyRateBoss { get; set; }
public double PenaltyRateGold { get; set; }
public double PenaltyRateSpecial { get; set; }
public double PenaltyRateOrdinary { get; set; }
}
}
@@ -0,0 +1,63 @@
using Common;
using CommonDomain;
using CommonInfrastructure;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Driver;
using MongoRepository;
namespace AdminApi.GameConfigs
{
[ApiController, Route("adminapi/[controller]")]
public class BotConfigController : BaseController
{
private readonly IMongoBaseRepository<BotProfileEntity> _botRepo;
public BotConfigController(IMongoBaseRepository<BotProfileEntity> botRepo)
{
_botRepo = botRepo;
}
[HttpGet("GetAll")]
public async Task<BaseResponse<List<BotProfileEntity>>> GetAll()
{
var list = await _botRepo.FilterByAsync(_ => true);
return this.ToResult(list.ToList());
}
[HttpPost("Add")]
public async Task<BaseResponse<string>> Add(BotProfileEntity entity)
{
if (string.IsNullOrWhiteSpace(entity.Name))
return this.ToFailResult<string>("名称不能为空");
await _botRepo.InsertOneAsync(entity);
return this.ToResult("添加成功");
}
[HttpPost("Update")]
public async Task<BaseResponse<string>> Update(BotProfileEntity entity)
{
if (string.IsNullOrWhiteSpace(entity.Id))
return this.ToFailResult<string>("缺少Id");
if (string.IsNullOrWhiteSpace(entity.Name))
return this.ToFailResult<string>("名称不能为空");
var update = Builders<BotProfileEntity>.Update
.Set(x => x.Name, entity.Name)
.Set(x => x.Avatar, entity.Avatar ?? string.Empty);
await _botRepo.UpdateByIdAsync(entity.Id, update);
return this.ToResult("更新成功");
}
[HttpPost("Delete")]
public async Task<BaseResponse<string>> Delete([FromQuery] string id)
{
if (string.IsNullOrWhiteSpace(id))
return this.ToFailResult<string>("缺少Id");
await _botRepo.DeleteByIdAsync(id);
return this.ToResult("删除成功");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,98 @@
using System;
using Common;
using CommonDomain;
using CommonInfrastructure;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using RedisService;
namespace AdminApi.GameConfigs
{
[ApiController, Route("adminapi/Admin/[controller]")]
[AllowAnonymous]
public class TempSpawnFishController : BaseController
{
private readonly ICapPublisher _capPublisher;
private readonly IRedisService _redisService;
private readonly ILogger<TempSpawnFishController> _logger;
public TempSpawnFishController(ICapPublisher capPublisher, IRedisService redisService, ILogger<TempSpawnFishController> logger)
{
_capPublisher = capPublisher;
_redisService = redisService;
_logger = logger;
}
[HttpGet("SpawnFish")]
public async Task<BaseResponse<string>> SpawnFish(
string roomId,
string fishNumber,
int count = 1,
int? seconds = null,
int? routeId = null)
{
if (string.IsNullOrWhiteSpace(roomId) || string.IsNullOrWhiteSpace(fishNumber))
{
return new BaseResponse<string>
{
Code = CommonResponseCodeEnum.ParamError,
Info = "roomId / fishNumber 不能为空"
};
}
var room = _redisService.GetGameRoom(roomId);
if (room == null)
{
return new BaseResponse<string>
{
Code = CommonResponseCodeEnum.ParamError,
Info = $"房间 {roomId} 不存在或不在线"
};
}
count = Math.Clamp(count <= 0 ? 1 : count, 1, 2000);
var effectiveSeconds = (seconds.HasValue && seconds.Value > 0) ? seconds.Value : 30;
var operationId = Guid.NewGuid().ToString("N");
var publishedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var msg = new TempSpawnFishMessage
{
RoomId = roomId,
FishNumber = fishNumber,
Count = count,
RouteId = routeId,
Seconds = effectiveSeconds,
OperationId = operationId,
PublishedAtUnixMs = publishedAt
};
try
{
await _capPublisher.PublishAsync(TopicKeys.临时强制出鱼, msg);
return new BaseResponse<string>
{
Code = CommonResponseCodeEnum.Success,
Info = "已推送临时出鱼指令(异步),operationId=" + operationId,
Data = operationId
};
}
catch (Exception ex)
{
_logger.LogError(ex,
"[TempSpawn] CAP发布失败 operationId={OperationId} roomId={RoomId} fishNumber={FishNumber} count={Count}",
operationId, roomId, fishNumber, count);
return new BaseResponse<string>
{
Code = CommonResponseCodeEnum.Error,
Info = "发布失败:" + ex.Message
};
}
}
}
}
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Common;
using CommonInfrastructure;
using Microsoft.AspNetCore.Mvc;
using MongoRepository;
using UserDomain;
using UserDomain.Entity;
using UserDomain.Param;
namespace AdminApi.LoginLogs
{
[ApiController, Route("adminapi/[controller]")]
public class UserLoginLogController : BaseController
{
private readonly IMongoBaseRepository<UserLoginLog> _logRepo;
public UserLoginLogController(IMongoBaseRepository<UserLoginLog> logRepo)
{
_logRepo = logRepo;
}
[HttpPost("List")]
public async Task<PageResponse<UserLoginLogDto>> GetList([FromBody] UserLoginLogQueryParam param)
{
var result = await _logRepo.QueryPageAsync(
filterExpression: x =>
(string.IsNullOrEmpty(param.UserId) || x.UserId == param.UserId) &&
(string.IsNullOrEmpty(param.UserNumber) || x.UserNumber == param.UserNumber) &&
(string.IsNullOrEmpty(param.UserName) || (x.UserName != null && x.UserName.Contains(param.UserName))) &&
(!param.LoginType.HasValue || x.LoginType == param.LoginType) &&
(!param.Status.HasValue || x.Status == param.Status) &&
(!param.Platform.HasValue || x.Platform == param.Platform) &&
(!param.StartTime.HasValue || x.LoginTime >= param.StartTime) &&
(!param.EndTime.HasValue || x.LoginTime <= param.EndTime),
orderExpression: x => x.LoginTime,
desc: true,
pageSize: param.PageSize,
pageIndex: param.PageIndex
);
var dtos = result.list.Select(MapToDto);
return this.ToPageResult(dtos, (int)result.count);
}
[HttpGet("Detail")]
public async Task<BaseResponse<UserLoginLogDto>> GetDetail(string id)
{
var log = await _logRepo.FindOneAsync(x => x.Id == id);
if (log == null)
return this.ToFailResult<UserLoginLogDto>("日志不存在");
return this.ToResult(MapToDto(log));
}
[HttpPost("Delete")]
public async Task<BaseResponse<string>> Delete(string id)
{
var result = await _logRepo.DeleteByIdAsync(id);
if (result.ok)
return this.ToResult("删除成功");
else
return this.ToFailResult<string>("删除失败");
}
[HttpPost("BatchDelete")]
public async Task<BaseResponse<string>> BatchDelete([FromBody] List<string> ids)
{
int success = 0, fail = 0;
foreach (var id in ids)
{
var result = await _logRepo.DeleteByIdAsync(id);
if (result.ok)
success++;
else
fail++;
}
if (fail == 0)
return this.ToResult($"成功删除 {success} 条");
return this.ToResult($"删除完成:成功 {success} 条,失败 {fail} 条");
}
private static UserLoginLogDto MapToDto(UserLoginLog entity)
{
return new UserLoginLogDto
{
Id = entity.Id,
UserId = entity.UserId ?? "",
UserNumber = entity.UserNumber ?? "",
UserName = entity.UserName ?? "",
LoginType = entity.LoginType,
Status = entity.Status,
Ip = entity.Ip ?? "",
IpLocation = entity.IpLocation ?? "",
DeviceType = entity.DeviceType ?? "",
DeviceId = entity.DeviceId ?? "",
Platform = entity.Platform,
ClientVersion = entity.ClientVersion ?? "",
FailReason = entity.FailReason ?? "",
LoginTime = entity.LoginTime
};
}
}
public class UserLoginLogDto
{
public string Id { get; set; }
public string UserId { get; set; }
public string UserNumber { get; set; }
public string UserName { get; set; }
public int LoginType { get; set; }
public int Status { get; set; }
public string Ip { get; set; }
public string IpLocation { get; set; }
public string DeviceType { get; set; }
public string DeviceId { get; set; }
public int Platform { get; set; }
public string ClientVersion { get; set; }
public string FailReason { get; set; }
public DateTime LoginTime { get; set; }
}
}
@@ -0,0 +1,236 @@
using AdminService.GameConfigs;
using Common;
using CommonDomain;
using CommonDomain.BaseModel;
using CommonInfrastructure;
using Microsoft.AspNetCore.Mvc;
namespace AdminApi.Lottery
{
[ApiController, Route("adminapi/[controller]")]
public class LotteryController : BaseController
{
private readonly IActivityConfigService _activityConfigService;
private readonly IGameConfigService _gameConfigService;
public LotteryController(IActivityConfigService activityConfigService, IGameConfigService gameConfigService)
{
_activityConfigService = activityConfigService;
_gameConfigService = gameConfigService;
}
[HttpGet("list")]
public async Task<BaseResponse<List<LotteryActivityVm>>> List()
{
var config = await _activityConfigService.Get();
if (config == null)
return this.ToResult(new List<LotteryActivityVm>());
var lotto = config.LotteryActivityConfig;
if (lotto == null || string.IsNullOrWhiteSpace(lotto.ActivityName))
return this.ToResult(new List<LotteryActivityVm>());
LotteryPrizeProductHelper.Enrich(lotto, await _gameConfigService.Get());
var vm = new LotteryActivityVm
{
ActivityId = config.Id,
ActivityName = lotto.ActivityName,
IsOpen = lotto.IsOpen,
StartTime = lotto.StartTime,
EndTime = lotto.EndTime,
CostsByPool = lotto.CostsByPool,
UserMaxDraw = lotto.UserMaxDraw,
Pools = lotto.Pools
};
return this.ToResult(new List<LotteryActivityVm> { vm });
}
[HttpGet("detail")]
public async Task<BaseResponse<LotteryActivityVm>> Detail([FromQuery] string activityId)
{
var config = await _activityConfigService.Get();
if (config == null)
return this.ToFailResult<LotteryActivityVm>("活动配置不存在");
LotteryPrizeProductHelper.Enrich(config.LotteryActivityConfig, await _gameConfigService.Get());
var vm = new LotteryActivityVm
{
ActivityId = config.Id,
ActivityName = config.LotteryActivityConfig.ActivityName,
IsOpen = config.LotteryActivityConfig.IsOpen,
StartTime = config.LotteryActivityConfig.StartTime,
EndTime = config.LotteryActivityConfig.EndTime,
CostsByPool = config.LotteryActivityConfig.CostsByPool,
UserMaxDraw = config.LotteryActivityConfig.UserMaxDraw,
Pools = config.LotteryActivityConfig.Pools
};
return this.ToResult(vm);
}
[HttpPost("save")]
public async Task<BaseResponse<string>> Save([FromBody] LotteryActivityVm param)
{
if (string.IsNullOrWhiteSpace(param.ActivityName))
return this.ToFailResult<string>("活动名称不能为空");
var pools = param.Pools ?? new List<LotteryPoolLevel>();
if (pools.SelectMany(pool => pool.PrizeList ?? new List<LotteryPrize>())
.Any(prize => !string.IsNullOrWhiteSpace(prize.OtherId)))
{
var gameConfig = await _gameConfigService.Get();
if (gameConfig == null)
return this.ToFailResult<string>("道具配置不存在,无法补全奖项信息");
EnrichPrizeProducts(pools, gameConfig);
}
var activityConfig = await _activityConfigService.Get() ?? new ActivityConfig();
var previousLottery = activityConfig.LotteryActivityConfig;
var cycleChanged = previousLottery == null
|| previousLottery.StartTime != param.StartTime
|| previousLottery.EndTime != param.EndTime;
if (cycleChanged)
{
foreach (var prize in pools.SelectMany(pool => pool.PrizeList ?? new List<LotteryPrize>()))
prize.DrawnCount = 0;
}
var normalizedCostsByPool = new Dictionary<int, List<long>>();
var poolNames = new Dictionary<int, string> { [1] = "炮台", [2] = "战神", [3] = "翅膀" };
for (var poolType = 1; poolType <= 3; poolType++)
{
List<long>? poolCosts = null;
param.CostsByPool?.TryGetValue(poolType, out poolCosts);
var validPoolCosts = poolCosts?.Where(cost => cost > 0).ToList();
if (validPoolCosts == null || validPoolCosts.Count == 0)
return this.ToFailResult<string>($"请配置{poolNames[poolType]}奖池消耗金币");
normalizedCostsByPool[poolType] = validPoolCosts;
}
activityConfig.LotteryActivityConfig = new LotteryActivityConfig
{
ActivityName = param.ActivityName,
IsOpen = param.IsOpen,
StartTime = param.StartTime,
EndTime = param.EndTime,
CostsByPool = normalizedCostsByPool,
UserMaxDraw = param.UserMaxDraw,
Pools = pools
};
var res = await _activityConfigService.Update(activityConfig);
if (res.isok)
return this.ToResult("保存成功");
else
return this.ToFailResult<string>(res.msg);
}
private static void EnrichPrizeProducts(List<LotteryPoolLevel> pools, GameConfig gameConfig)
{
LotteryPrizeProductHelper.Enrich(new LotteryActivityConfig { Pools = pools }, gameConfig);
}
[HttpPost("toggle")]
public async Task<BaseResponse<string>> Toggle([FromBody] LotteryToggleParam param)
{
var activityConfig = await _activityConfigService.Get();
if (activityConfig == null)
return this.ToFailResult<string>("活动配置不存在");
activityConfig.LotteryActivityConfig.IsOpen = param.IsOpen;
var res = await _activityConfigService.Update(activityConfig);
if (res.isok)
return this.ToResult(param.IsOpen ? "活动已开启" : "活动已关闭");
else
return this.ToFailResult<string>(res.msg);
}
[HttpPost("initialize")]
public async Task<BaseResponse<string>> Initialize()
{
var path = Path.Combine(AppContext.BaseDirectory, "Config", "lottery-init.json");
if (!global::System.IO.File.Exists(path))
return this.ToFailResult<string>("奖池初始化模板不存在");
LotteryActivityConfig? template;
try
{
var json = await global::System.IO.File.ReadAllTextAsync(path);
template = global::System.Text.Json.JsonSerializer.Deserialize<LotteryActivityConfig>(json,
new global::System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
catch (global::System.Text.Json.JsonException)
{
return this.ToFailResult<string>("奖池初始化模板格式错误");
}
var pools = template?.Pools;
var prizes = pools?.SelectMany(pool => pool.PrizeList ?? new List<LotteryPrize>()).ToList();
if (template == null || string.IsNullOrWhiteSpace(template.ActivityName)
|| template.CostsByPool == null
|| new[] { 1, 2, 3 }.Any(type => !template.CostsByPool.TryGetValue(type, out var costs) || costs == null || costs.Count == 0)
|| pools == null || pools.Count == 0 || prizes == null || prizes.Count == 0
|| new[] { 1, 2, 3 }.Any(type => !pools.Any(pool => pool.PoolType == type && pool.PrizeList?.Count > 0))
|| prizes.Any(prize => string.IsNullOrWhiteSpace(prize.PrizeId))
|| prizes.Select(prize => prize.PrizeId).Distinct().Count() != prizes.Count)
return this.ToFailResult<string>("奖池初始化模板不完整,未修改现有配置");
template.IsOpen = false;
foreach (var prize in prizes) prize.DrawnCount = 0;
var config = await _activityConfigService.Get() ?? new ActivityConfig();
config.LotteryActivityConfig = template;
var result = await _activityConfigService.Update(config);
return result.isok
? this.ToResult("奖池配置已初始化,活动保持关闭")
: this.ToFailResult<string>(result.msg);
}
[HttpDelete("delete")]
public async Task<BaseResponse<string>> Delete()
{
var activityConfig = await _activityConfigService.Get();
if (activityConfig == null)
return this.ToFailResult<string>("活动配置不存在");
activityConfig.LotteryActivityConfig = new LotteryActivityConfig();
var res = await _activityConfigService.Update(activityConfig);
if (res.isok)
return this.ToResult("删除成功");
else
return this.ToFailResult<string>(res.msg);
}
}
public class LotteryActivityVm
{
public string ActivityId { get; set; } = string.Empty;
public string ActivityName { get; set; } = string.Empty;
public bool IsOpen { get; set; } = false;
public long StartTime { get; set; } = 0;
public long EndTime { get; set; } = 0;
public Dictionary<int, List<long>> CostsByPool { get; set; } = new Dictionary<int, List<long>>();
public int UserMaxDraw { get; set; } = 100;
public List<LotteryPoolLevel> Pools { get; set; } = new List<LotteryPoolLevel>();
}
public class LotteryToggleParam
{
public string ActivityId { get; set; } = string.Empty;
public bool IsOpen { get; set; }
}
}
@@ -0,0 +1,88 @@
using Domain.Admin.Notices;
using AdminService.Notices;
using AdminService.Notices.Param;
using Common;
using CommonInfrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AdminApi.Notices;
[ApiController, Route("adminapi/[controller]")]
[Authorize]
public class NoticeController : BaseController
{
private readonly INoticeService _noticeService;
public NoticeController(INoticeService noticeService)
{
_noticeService = noticeService;
}
[HttpPost("add")]
public async Task<BaseResponse<string>> AddNotice([FromBody] AddNoticeParam param)
{
var result = await _noticeService.Add(param);
return result.isok
? this.ToResult(result.msg)
: this.ToFailResult<string>(result.msg);
}
[HttpPost("update")]
public async Task<BaseResponse<string>> UpdateNotice([FromBody] UpdateNoticeParam param)
{
var result = await _noticeService.Update(param);
return result.isok
? this.ToResult(result.msg)
: this.ToFailResult<string>(result.msg);
}
[HttpGet("delete")]
public async Task<BaseResponse<string>> DeleteNotice([FromQuery] string id)
{
var result = await _noticeService.Delete(id);
return result.isok
? this.ToResult(result.msg)
: this.ToFailResult<string>(result.msg);
}
[HttpPost("list")]
public async Task<PageResponse<NoticeListItemDto>> GetNoticeList([FromBody] GetNoticeListParam param)
{
var (list, count) = await _noticeService.GetList(param);
if (list == null)
return this.ToPageResult(new List<NoticeListItemDto>(), 0);
var dtos = list.Select(n => new NoticeListItemDto
{
Id = n.Id,
Title = n.Title,
Recommend = n.Recommend,
TopImage = n.TopImage,
CreateTime = n.CreateTime
});
return this.ToPageResult(dtos, (int)count);
}
[HttpGet("get")]
[AllowAnonymous]
public async Task<BaseResponse<NoticeDto>> GetNotice([FromQuery] string id)
{
var notice = await _noticeService.Get(id);
if (notice == null)
{
return this.ToFailResult<NoticeDto>("公告不存在");
}
var dto = new NoticeDto
{
Id = notice.Id,
Title = notice.Title,
Content = notice.Content,
Recommend = notice.Recommend,
TopImage = notice.TopImage,
ContentImages = notice.ContentImages,
CreateTime = notice.CreateTime
};
return this.ToResult(dto);
}
}
+2
View File
@@ -0,0 +1,2 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
@@ -0,0 +1,330 @@
using System.Linq;
using AdminService.System;
using AdminService.System.Param;
using AdminService.System.Response;
using Common;
using CommonInfrastructure ;
using Domain.Admin.Admin;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AdminApi.System
{
[ApiController, Route("adminapi/[controller]")]
[Authorize]
public class SystemController : BaseController
{
private readonly ISystemService _systemService;
public SystemController(ISystemService systemService)
{
_systemService = systemService;
}
[HttpPost("user/add")]
public async Task<BaseResponse<string>> AddUser(AddUserParam param)
{
var result = await _systemService.AddUser(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("user/update")]
public async Task<BaseResponse<string>> UpdateUser(UpdateUserParam param)
{
var result = await _systemService.UpdateUser(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("user/update/icon")]
public async Task<BaseResponse<string>> UpdateUserIcon(string id, string icon)
{
var result = await _systemService.UpdateUserIcon(id,icon);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("user/update/state")]
public async Task<BaseResponse<string>> UpdateUserState(string id, int state)
{
var result = await _systemService.UpdateUserState(id, state);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("user/delete")]
public async Task<BaseResponse<string>> DeleteUser(string id)
{
var result = await _systemService.DeleteUser(id);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("user/login")]
[AllowAnonymous]
public async Task<BaseResponse<AdminUserToken>> UserLogin(string userName, string password)
{
var result = await _systemService.UserLogin(userName, password);
if (result.isok)
return this.ToResult(result.model);
else
return this.ToFailResult<AdminUserToken>(result.msg);
}
[HttpPost("user/list")]
public async Task<PageResponse<SystemUserDto>> GetUserList(GetUserListParam param)
{
var result = await _systemService.GetUserList(param);
var dtos = result.list.Select(MapToSystemUserDto).ToList();
return this.ToPageResult(dtos, (int)result.count);
}
[HttpGet("user/getuser")]
public async Task<BaseResponse<SystemUserDto>> GetUser()
{
if (string.IsNullOrEmpty(loginUser.UserId))
return this.ToFailResult<SystemUserDto>("用户未登录");
var result = await _systemService.GetUser(loginUser.UserId);
if (result == null)
return this.ToFailResult<SystemUserDto>("用户不存在");
return this.ToResult(MapToSystemUserDto(result));
}
private static SystemUserDto MapToSystemUserDto(SystemUser entity)
{
if (entity == null) return new SystemUserDto();
return new SystemUserDto
{
Id = entity.Id ?? "",
UserName = entity.UserName ?? "",
DisplayName = entity.DisplayName ?? "",
Icon = entity.Icon ?? "",
RegisterTime = entity.RegisterTime,
State = entity.State,
RoleId = entity.SystemRole?.Id ?? "",
RoleName = entity.SystemRole?.Name ?? ""
};
}
[HttpGet("user/nav")]
public async Task<ListResponse<SystemNavigationResponse>> GetUserNav()
{
if (string.IsNullOrEmpty(loginUser.UserId))
return this.ToListFailResult<SystemNavigationResponse>("用户未登录");
var result = await _systemService.GetUserNav(this.loginUser);
if (result == null)
return this.ToListFailResult<SystemNavigationResponse>("获取菜单失败");
return this.ToListResult(result);
}
[HttpPost("role/add")]
public async Task<BaseResponse<string>> AddRole(AddRoleParam param)
{
var result = await _systemService.AddRole(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("role/update")]
public async Task<BaseResponse<string>> UpdateRole(UpdateRoleParam param)
{
var result = await _systemService.UpdateRole(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("role/updatenav")]
public async Task<BaseResponse<string>> UpdateRoleNav(UpdateRoleNavParam param)
{
var result = await _systemService.UpdateRoleNav(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("role/delete")]
public async Task<BaseResponse<string>> DeleteRole(string id)
{
var result = await _systemService.DeleteRole(id);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("role/list")]
public async Task<ListResponse<SystemUserRoleDto>> GetRoleList(int page, int limit)
{
var result = await _systemService.GetRoleList();
var dtos = result.Select(MapToSystemUserRoleDto).ToList();
return this.ToListResult(dtos);
}
private static SystemUserRoleDto MapToSystemUserRoleDto(SystemUserRole entity)
{
if (entity == null) return new SystemUserRoleDto();
return new SystemUserRoleDto
{
Id = entity.Id ?? "",
RoleName = entity.RoleName ?? "",
Description = entity.Description ?? "",
NavIds = entity.NavIds ?? new List<string>()
};
}
[HttpGet("role/get")]
public async Task<BaseResponse<SystemUserRoleDto>> GetUserRole(string id)
{
var result = await _systemService.GetUserRole(id);
return this.ToResult(MapToSystemUserRoleDto(result));
}
[HttpPost("nav/add")]
public async Task<BaseResponse<string>> AddNav(AddNavParam param)
{
var result = await _systemService.AddNav(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("nav/update")]
public async Task<BaseResponse<string>> UpdateNav(UpdateNavParam param)
{
var result = await _systemService.UpdateNav(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("nav/delete")]
public async Task<BaseResponse<string>> DeleteNav(string id)
{
var result = await _systemService.DeleteNav(id);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("nav/list")]
public async Task<ListResponse<SystemNavigationResponse>> GetNavList(string? id="")
{
var result = await _systemService.GetNavList(id);
return this.ToListResult(result);
}
public class SystemUserDto
{
public string Id { get; set; }
public string UserName { get; set; }
public string DisplayName { get; set; }
public string Icon { get; set; }
public DateTime RegisterTime { get; set; }
public int State { get; set; }
public string RoleId { get; set; }
public string RoleName { get; set; }
}
public class SystemUserRoleDto
{
public string Id { get; set; }
public string RoleName { get; set; }
public string Description { get; set; }
public List<string> NavIds { get; set; } = new List<string>();
}
[HttpPost("dic/add")]
public async Task<BaseResponse<string>> AddDic(AddDicParam param)
{
var result = await _systemService.AddDic(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpPost("dic/update")]
public async Task<BaseResponse<string>> UpdateDic(UpdateDicParam param)
{
var result = await _systemService.UpdateDic(param);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("dic/delete")]
public async Task<BaseResponse<string>> DeleteDic(string id)
{
var result = await _systemService.DeleteDic(id);
if (result.isok)
return this.ToResult(result.msg);
else
return this.ToFailResult<string>(result.msg);
}
[HttpGet("dic/list")]
public async Task<ListResponse<SystemDicResponse>> GetDicList(string id="")
{
var result = await _systemService.GetDicList(id);
return this.ToListResult(result);
}
[HttpGet("dic/list/code")]
public async Task<ListResponse<SystemDicResponse>> GetDicListByCode(string code)
{
var result = await _systemService.GetDicListByCode(code);
return this.ToListResult(result);
}
[HttpGet("dic/get/code")]
public async Task<BaseResponse<SystemDic>> GetDictByCode(string code)
{
var result = await _systemService.GetDictByCode(code);
return this.ToResult(result);
}
}
}
+78
View File
@@ -0,0 +1,78 @@
using AdminService.Tasks;
using AdminService.Tasks.Param;
using Common;
using CommonDomain;
using CommonInfrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AdminApi.Task
{
[ApiController, Route("adminapi/[controller]")]
[Authorize]
public class TaskController : BaseController
{
private readonly ITaskService _taskService;
public TaskController(ITaskService taskService)
{
_taskService = taskService;
}
[HttpGet("Init")]
public async Task<string> Init()
{
return await _taskService.InitDefaultTasks();
}
[HttpGet("DayTaskReward")]
public async Task<string> DayTaskReward()
{
return await _taskService.DayTaskReward();
}
[HttpPost("AddTask")]
public async Task<BaseResponse<string>> AddTask([FromBody] GameTask param)
{
var res = await _taskService.AddTask(param);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpGet("DeleteTask")]
public async Task<BaseResponse<string>> DeleteTask(string id)
{
var res = await _taskService.DeleteTask(id);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpGet("UpdateTaskState")]
public async Task<BaseResponse<string>> UpdateTaskState(string id, int state)
{
var res = await _taskService.UpdateTaskState(id, state);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpPost("UpdateTask")]
public async Task<BaseResponse<string>> UpdateTask([FromBody] GameTask param)
{
var res = await _taskService.UpdateTask(param);
if (res.isok)
return this.ToResult(res.msg);
else
return this.ToFailResult<string>(res.msg);
}
[HttpPost("GetTaskList")]
public async Task<PageResponse<GameTask>> GetTaskList(GetTaskListParam param)
{
var res = await _taskService.GetTaskList(param);
return this.ToPageResult(res.list, res.count);
}
}
}
@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AdminService.User.Param;
using Common;
using Common.Utils;
using CommonDomain.BaseModel;
using CommonInfrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Driver;
using MongoRepository;
using UserDomain.Entity;
using UserDomain.Enum;
namespace AdminApi.UserAssetChanges
{
[ApiController, Route("adminapi/[controller]")]
[Authorize]
public class UserAssetChangeController : BaseController
{
private readonly IMongoBaseRepository<UserReward> _rewardMongo;
private readonly IMongoBaseRepository<UserEntity> _userMongo;
public UserAssetChangeController(IMongoBaseRepository<UserReward> rewardMongo, IMongoBaseRepository<UserEntity> userMongo)
{
_rewardMongo = rewardMongo;
_userMongo = userMongo;
}
[HttpPost("list")]
public async Task<PageResponse<UserAssetChangeDto>> GetList([FromBody] UserAssetChangeQueryParam param)
{
var filter = Builders<UserReward>.Filter.Empty;
filter &= Builders<UserReward>.Filter.Nin(x => x.Source, new List<RewardSource>() { RewardSource.开炮, RewardSource.击杀鱼});
if (!string.IsNullOrWhiteSpace(param.UserId))
{
filter &= Builders<UserReward>.Filter.Eq(x => x.UserId, param.UserId);
}
if (param.CurrencyTypes != null && param.CurrencyTypes.Count > 0)
{
filter &= Builders<UserReward>.Filter.In(x => x.CurrencyType, param.CurrencyTypes);
}
if (param.ChangeTypes != null && param.ChangeTypes.Count > 0)
{
filter &= Builders<UserReward>.Filter.In(x => x.Type, param.ChangeTypes);
}
if (param.Sources != null && param.Sources.Count > 0)
{
var sourceEnums = param.Sources.Select(s => Enum.TryParse<RewardSource>(s, out var src) ? src : (RewardSource?)null)
.Where(x => x.HasValue)
.Select(x => x.Value)
.ToList();
if (sourceEnums.Count > 0)
{
filter &= Builders<UserReward>.Filter.In(x => x.Source, sourceEnums);
}
}
if (param.CurrencyType.HasValue)
{
filter &= Builders<UserReward>.Filter.Eq(x => x.CurrencyType, param.CurrencyType.Value);
}
if (param.TimeStart.HasValue)
{
filter &= Builders<UserReward>.Filter.Gte(x => x.CreateTime, param.TimeStart.Value);
}
if (param.TimeEnd.HasValue)
{
filter &= Builders<UserReward>.Filter.Lte(x => x.CreateTime, param.TimeEnd.Value);
}
var sort = Builders<UserReward>.Sort.Descending(x => x.CreateTime);
var result = await _rewardMongo.QueryPageAsync(
filter,
sort,
true,
param.PageSize.Value,
param.PageIndex.Value
);
var list = result.list ?? new List<UserReward>();
var userIds = list.Select(x => x.UserId).Distinct().ToList();
var users = await _userMongo.FilterByAsync(x => userIds.Contains(x.Id));
var userDict = users.ToDictionary(u => u.Id, u => u.BaseInfo?.UserName ?? "");
if (list == null)
return this.ToPageResult(new List<UserAssetChangeDto>(), 0);
var dtos = list.Select(x => new UserAssetChangeDto
{
Id = x.Id,
UserId = x.UserId,
UserName = userDict.TryGetValue(x.UserId, out var name) ? name : "",
Type = x.Type,
ChangeTypeText = x.Type == 1 ? "增加" : "减少",
Source = x.Source.ToString(),
CurrencyType = x.CurrencyType,
CurrencyTypeText = x.CurrencyType == 1 ? "金币" : x.CurrencyType == 2 ? "保险箱金币" : "钻石",
BeforeCount = x.BeforeCount,
ChangeCount = x.ChangeCount,
AfterCount = x.AfterCount,
Remark = x.Remark,
CreateTime = x.CreateTime.ToDateTime()
});
return this.ToPageResult(dtos, (int)result.count);
}
[HttpGet("sources")]
public BaseResponse<List<SourceOptionDto>> GetSources()
{
var sources = Enum.GetValues<RewardSource>()
.Select(x => new SourceOptionDto { Value = x.ToString(), Label = x.ToString() })
.ToList();
return this.ToResult(sources);
}
}
public class UserAssetChangeDto
{
public string Id { get; set; }
public string UserId { get; set; }
public string UserName { get; set; }
public int Type { get; set; }
public string ChangeTypeText { get; set; }
public string Source { get; set; }
public int CurrencyType { get; set; }
public string CurrencyTypeText { get; set; }
public long BeforeCount { get; set; }
public long ChangeCount { get; set; }
public long AfterCount { get; set; }
public string Remark { get; set; }
public DateTime CreateTime { get; set; }
}
public class SourceOptionDto
{
public string Value { get; set; }
public string Label { get; set; }
}
}
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,67 @@
using System;
namespace AdminApplication.DTOs;
public class CustomerDto
{
public string Id { get; set; }
public string? UserId { get; set; }
public string? UserName { get; set; }
public int Status { get; set; }
public string? Avatar { get; set; }
public DateTime CreateTime { get; set; }
}
public class ConversationDto
{
public string Id { get; set; }
public string? UserId { get; set; }
public string? UserName { get; set; }
public string? UserAvatar { get; set; }
public string? CustomerId { get; set; }
public string? CustomerName { get; set; }
public string? CustomerAvatar { get; set; }
public int Status { get; set; }
public DateTime CreateTime { get; set; }
public DateTime? EndTime { get; set; }
}
public class MessageDto
{
public string Id { get; set; }
public string? ConversationId { get; set; }
public int SenderType { get; set; }
public string? SenderId { get; set; }
public string? SenderName { get; set; }
public string? SenderAvatar { get; set; }
public int ContentType { get; set; }
public string? Content { get; set; }
public DateTime CreateTime { get; set; }
}
public class DeleteMessagesBatchRequest
{
public List<string> Ids { get; set; } = new List<string>();
}
@@ -0,0 +1,50 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoRepository;
using MongoRepository.Attributes;
using MongoRepository.Config;
namespace Domain.Admin.Admin
{
[BsonCollection("admin_operation_log", ServiceDatabases.Admin)]
[BsonIgnoreExtraElements]
public class AdminOperationLog : MongoBaseEntity
{
[BsonElement("adminid")]
public string? AdminId { get; set; }
[BsonElement("adminname")]
public string? AdminName { get; set; }
[BsonElement("module")]
public int Module { get; set; }
[BsonElement("actiontype")]
public string? ActionType { get; set; }
[BsonElement("description")]
public string? Description { get; set; }
[BsonElement("httpmethod")]
public string? HttpMethod { get; set; }
[BsonElement("requestpath")]
public string? RequestPath { get; set; }
[BsonElement("requestparams")]
public string? RequestParams { get; set; }
[BsonElement("response")]
public string? Response { get; set; }
[BsonElement("ip")]
public string? Ip { get; set; }
[BsonElement("status")]
public int Status { get; set; } = 1;
[BsonElement("operatetime")]
[BsonDateTimeOptions(Kind = DateTimeKind.Local, DateOnly = false)]
public DateTime OperateTime { get; set; } = DateTime.Now;
}
}
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Domain.Admin.Admin.ENum
{
public enum EnumNavigationCategory
{
[Description("后台菜单")]
System,
[Description("前台菜单")]
Customer
}
}
@@ -0,0 +1,43 @@
using System.ComponentModel;
namespace Domain.Admin.Admin.ENum
{
public enum EnumOperationModule
{
[Description("用户管理")]
User = 1,
[Description("系统用户")]
SystemUser = 2,
[Description("角色管理")]
Role = 3,
[Description("菜单管理")]
Navigation = 4,
[Description("字典管理")]
Dictionary = 5,
[Description("游戏配置")]
GameConfig = 6,
[Description("房间管理")]
Room = 7,
[Description("客服管理")]
Customer = 8,
[Description("靓号管理")]
BeautifulNumber = 9,
[Description("公告管理")]
Notice = 10,
[Description("任务管理")]
Task = 11,
[Description("其他")]
Other = 99
}
}
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Domain.Admin.Admin.ENum
{
public enum EnumResource
{
/// <summary>
///
/// </summary>
[Description("菜单")]
Navigation,
/// <summary>
///
/// </summary>
[Description("资源")]
Resource,
/// <summary>
///
/// </summary>
[Description("代码块")]
Block
}
}
@@ -0,0 +1,11 @@
using Domain.Admin.Admin;
namespace Domain.Admin.Admin
{
public interface IAdminOperationLogService
{
Task<bool> AddLog(AdminOperationLog log);
Task<(List<AdminOperationLog> list, long count)> QueryPage(Param.OperationLogQueryParam param);
}
}
@@ -0,0 +1,46 @@
namespace Domain.Admin.Admin.Param
{
public class OperationLogQueryParam
{
public int PageIndex { get; set; } = 1;
public int PageSize { get; set; } = 20;
public string? AdminId { get; set; }
public string? AdminName { get; set; }
public int? Module { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public int? Status { get; set; }
}
public class AddOperationLogParam
{
public string? AdminId { get; set; }
public string? AdminName { get; set; }
public int Module { get; set; }
public string? ActionType { get; set; }
public string? Description { get; set; }
public string? HttpMethod { get; set; }
public string? RequestPath { get; set; }
public string? RequestParams { get; set; }
public string? Response { get; set; }
public string? Ip { get; set; }
public int Status { get; set; } = 1;
}
}
+46
View File
@@ -0,0 +1,46 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoRepository;
using MongoRepository.Attributes;
using MongoRepository.Config;
namespace Domain.Admin.Admin
{
[BsonCollection("system_user_dic", ServiceDatabases.Admin)]
[BsonIgnoreExtraElements]
public class SystemDic :MongoBaseEntity
{
public string ParentId { set; get; } = "";
[BsonElement("rolename")]
public string? Name { get; set; }
[BsonElement("order")]
public int Order { get; set; }
[BsonElement("icon")]
public string? Icon { get; set; }
[BsonElement("value")]
public string? Value { get; set; }
[BsonElement("code")]
public string? Code { get; set; }
[BsonElement("remark")]
public string? Remark { get; set; }
[BsonElement("remark1")]
public string? Remark1 { get; set; }
public void Init()
{
Id = ObjectId.GenerateNewId().ToString();
}
}
}
@@ -0,0 +1,45 @@
using Domain.Admin.Admin.ENum;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoRepository;
using MongoRepository.Attributes;
using MongoRepository.Config;
namespace Domain.Admin.Admin
{
[BsonCollection("system_user_nav", ServiceDatabases.Admin)]
[BsonIgnoreExtraElements]
public class SystemNavigation :MongoBaseEntity
{
[BsonElement("parentId")]
public string ParentId { set; get; } = "";
[BsonElement("name")]
public string? Name { get; set; }
[BsonElement("order")]
public int Order { get; set; }
[BsonElement("icon")]
public string? Icon { get; set; }
[BsonElement("url")]
public string? Url { get; set; }
[BsonElement("category")]
public EnumNavigationCategory Category { get; set; }
[BsonElement("target")]
public string? Target { get; set; }
[BsonElement("isresource")]
public EnumResource IsResource { get; set; }
public void Init()
{
Id = ObjectId.GenerateNewId().ToString();
}
}
}
+47
View File
@@ -0,0 +1,47 @@
using Common.CommonModel;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoRepository;
using MongoRepository.Attributes;
using MongoRepository.Config;
namespace Domain.Admin.Admin
{
[BsonCollection("system_user", ServiceDatabases.Admin)]
[BsonIgnoreExtraElements]
public class SystemUser:MongoBaseEntity
{
[BsonElement("username")]
public string? UserName { get; set; }
[BsonElement("displayname")]
public string? DisplayName { get; set; }
[BsonElement("icon")]
public string? Icon { get; set; }
[BsonElement("password")]
public string? Password { get; set; }
[BsonElement("registertime")]
[BsonDateTimeOptions(Kind = DateTimeKind.Local, DateOnly = false)]
public DateTime RegisterTime { get; set; } = DateTime.Now;
[BsonElement("state")]
public int State { get; set; } = 1;
[BsonElement("systemrole")]
public BaseIdName SystemRole { get; set; }
public void Init()
{
Id = ObjectId.GenerateNewId().ToString();
State = 1;
RegisterTime=DateTime.Now;
}
}
}
@@ -0,0 +1,30 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoRepository;
using MongoRepository.Attributes;
using MongoRepository.Config;
namespace Domain.Admin.Admin
{
[BsonCollection("system_user_role", ServiceDatabases.Admin)]
[BsonIgnoreExtraElements]
public class SystemUserRole:MongoBaseEntity
{
[BsonElement("rolename")]
public string? RoleName { get; set; }
[BsonElement("description")]
public string? Description { get; set; }
[BsonElement("navIds")]
public List<string> NavIds { get; set; } = new List<string>();
public void Init()
{
Id = ObjectId.GenerateNewId().ToString();
NavIds = new List<string>();
}
}
}
+19
View File
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
<PackageReference Include="MongoDB.Bson" Version="3.6.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\MongoRepository\MongoRepository.csproj" />
<ProjectReference Include="..\..\CustomerSrc\CustomerService.Domain\CustomerService.Domain.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,50 @@
using AdminDomain.BeautifulNumber;
using Common.Utils;
using MongoDB.Bson.Serialization.Attributes;
using MongoRepository;
using MongoRepository.Attributes;
using MongoRepository.Config;
namespace AdminDomain.Entity
{
[BsonCollection("beautiful_number_config", ServiceDatabases.Admin)]
[BsonIgnoreExtraElements]
public class BeautifulNumberConfig : MongoBaseEntity
{
public string Number { get; set; }
public string RuleName { get; set; }
public string Level { get; set; }
public long Price { get; set; }
public BeautifulNumberStatus Status { get; set; }= BeautifulNumberStatus.Available;
public long SaleTime { get; set; } = 0;
public string BuyerUserId { get; set; }
public string UserId { get; set; }
public string Remark { get; set; }
public long CreateTime { get; set; } = DateTime.Now.ToTimeStamp();
}
[BsonCollection("beautiful_number_rule", ServiceDatabases.Admin)]
[BsonIgnoreExtraElements]
public class BeautifulNumberRule : MongoBaseEntity
{
public string RuleName { get; set; }
public string Level { get; set; }
public long Price { get; set; }
public int Sort { get; set; }
public bool IsEnable { get; set; } = true;
}
}
@@ -0,0 +1,94 @@
using System.ComponentModel;
namespace AdminDomain.BeautifulNumber
{
public enum BeautifulNumberRuleEnum
{
[Description("普通号")]
Normal = 0,
[Description("3连号")]
TripleRepeat = 1,
[Description("4连号")]
QuadrupleRepeat = 2,
[Description("5连号")]
QuintupleRepeat = 3,
[Description("6连号")]
SextupleRepeat = 4,
[Description("7连号")]
SeptupleRepeat = 5,
[Description("4顺子")]
Straight4 = 10,
[Description("5顺子")]
Straight5 = 11,
[Description("6顺子")]
Straight6 = 12,
[Description("双连2组")]
DoubleRepeat2 = 20,
[Description("双连3组")]
DoubleRepeat3 = 21,
[Description("双连4组")]
DoubleRepeat4 = 22,
[Description("混搭")]
Mixed = 30,
[Description("5连+3任意")]
QuintupleWithAny = 31,
[Description("3任意+5连")]
AnyWithQuintuple = 32,
[Description("夹心3连")]
SandwichTriple = 40,
[Description("间隔2连")]
Alternating2 = 41,
[Description("对称3连")]
SymmetricTriple = 42
}
public enum BeautifulNumberLevel
{
[Description("D")]
D = 0,
[Description("C")]
C = 1,
[Description("B")]
B = 2,
[Description("A")]
A = 3,
[Description("S")]
S = 4
}
public enum BeautifulNumberStatus
{
[Description("未售")]
Available = 0,
[Description("已售")]
Sold = 1,
[Description("冻结")]
Frozen = 2,
[Description("普通号")]
Normal = 3
}
}
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AdminDomain.Customer.Param;
using CustomerService.Domain.Entity;
namespace AdminDomain.Customer
{
public interface ICustomerService
{
Task<(bool isOk, string msg, CustomerEntity? customer)> AddCustomer(AddCustomerParam param);
Task<(bool isOk, string msg)> UpdateCustomer(UpdateCustomerParam param);
Task<(bool isOk, string msg)> DeleteCustomer(string id);
Task<(bool isOk, string msg, List<CustomerEntity>? customers, int total)> GetCustomerList(GetCustomerListParam param);
Task<(bool isOk, string msg, CustomerEntity? customer)> GetCustomerById(string id);
Task<(bool isOk, string msg, ConversationEntity? conversation)> GetConversationById(string id);
Task<(bool isOk, string msg, List<ConversationEntity>? conversations, int total)> GetConversationList(GetConversationListParam param);
Task<(bool isOk, string msg)> DeleteConversation(string conversationId);
Task<(bool isOk, string msg, List<MessageEntity>? messages)> GetChatLog(string conversationId);
Task<(bool isOk, string msg)> DeleteMessage(string id);
Task<(bool isOk, string msg)> DeleteAllMessages(string conversationId);
Task<(bool isOk, string msg)> DeleteMessagesBatch(List<string> ids);
}
}
@@ -0,0 +1,36 @@
using System;
namespace AdminDomain.Customer.Param
{
public class AddCustomerParam
{
public string UserId { get; set; } = "";
public string UserName { get; set; } = "";
public string? Avatar { get; set; }
}
public class UpdateCustomerParam
{
public string Id { get; set; } = "";
public string UserName { get; set; } = "";
public string? Avatar { get; set; }
public int Status { get; set; }
}
public class GetCustomerListParam
{
public int PageIndex { get; set; } = 1;
public int PageSize { get; set; } = 10;
public string? UserName { get; set; }
public int? Status { get; set; }
}
public class GetConversationListParam
{
public int PageIndex { get; set; } = 1;
public int PageSize { get; set; } = 10;
public string? UserId { get; set; }
public string? CustomerId { get; set; }
public int? Status { get; set; }
}
}
@@ -0,0 +1,33 @@
using System;
namespace Domain.Admin.Notices;
public class NoticeDto
{
public string Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public string? Recommend { get; set; }
public string? TopImage { get; set; }
public List<string>? ContentImages { get; set; }
public long CreateTime { get; set; }
}
public class NoticeListItemDto
{
public string Id { get; set; }
public string Title { get; set; }
public string? Recommend { get; set; }
public string? TopImage { get; set; }
public long CreateTime { get; set; }
}
+30
View File
@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**
+54
View File
@@ -0,0 +1,54 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EnableDefaultContentItems>false</EnableDefaultContentItems>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.9" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.24" />
<PackageReference Include="NLog.Web.AspNetCore" Version="6.1.2" />
<PackageReference Include="Autofac" Version="8.3.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="DotNetCore.CAP" Version="8.3.4" />
<PackageReference Include="DotNetCore.CAP.Dashboard" Version="8.3.4" />
<PackageReference Include="DotNetCore.CAP.PostgreSql" Version="8.3.4" />
<PackageReference Include="DotNetCore.CAP.RabbitMQ" Version="8.3.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\JWTHelper\JWTHelper.csproj" />
<ProjectReference Include="..\..\Infrastructure\MongoRepository\MongoRepository.csproj" />
<ProjectReference Include="..\..\Infrastructure\RabbitMQHelper\RabbitMQHelper.csproj" />
<ProjectReference Include="..\..\Infrastructure\RedisHelper\RedisHelper.csproj" />
<ProjectReference Include="..\..\Infrastructure\ScheduledService\ScheduledService.csproj" />
<ProjectReference Include="..\..\Infrastructure\SharpAbp.Infrastructure.Api\SharpAbp.Infrastructure.Api.csproj" />
<ProjectReference Include="..\..\UserSrc\UserService\UserService.csproj" />
<ProjectReference Include="..\..\FishGameSrc\FishGameService\FishGameService.csproj" />
<ProjectReference Include="..\AdminApi\AdminApi.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\nlog.config" Link="nlog.config" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
<None Update="docker-compose.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Dockerfile">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="wwwroot\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\PublishProfiles\" />
</ItemGroup>
</Project>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+986
View File
@@ -0,0 +1,986 @@
{
"ActivityName": "炮台/战神/翅膀奖池",
"IsOpen": false,
"StartTime": 0,
"EndTime": 0,
"Pools": [
{
"PoolType": 1,
"Level": "A",
"Count": 2,
"Weight": 1,
"PrizeList": [
{
"PrizeId": "587001",
"Name": "剑气如虹",
"Icon": "",
"Type": 3,
"Count": 1,
"OtherId": "11028",
"Weight": 1,
"TotalCount": 0
},
{
"PrizeId": "587002",
"Name": "万圣通灵",
"Icon": "",
"Type": 3,
"Count": 1,
"OtherId": "11029",
"Weight": 2,
"TotalCount": 0
}
]
},
{
"PoolType": 1,
"Level": "B",
"Count": 8,
"Weight": 10,
"PrizeList": [
{
"PrizeId": "587101",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 60,
"OtherId": "40004",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "587102",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 60,
"OtherId": "40004",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "587103",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 500000,
"OtherId": "",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "587104",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 500000,
"OtherId": "",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "587105",
"Name": "钻石",
"Icon": "",
"Type": 2,
"Count": 50,
"OtherId": "",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "587106",
"Name": "钻石",
"Icon": "",
"Type": 2,
"Count": 50,
"OtherId": "",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "587107",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 50,
"OtherId": "40003",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "587108",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 50,
"OtherId": "40003",
"Weight": 10,
"TotalCount": 0
}
]
},
{
"PoolType": 1,
"Level": "C",
"Count": 20,
"Weight": 20,
"PrizeList": [
{
"PrizeId": "587201",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587202",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587203",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587204",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587205",
"Name": "钻石",
"Icon": "",
"Type": 2,
"Count": 5,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587206",
"Name": "钻石",
"Icon": "",
"Type": 2,
"Count": 5,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587207",
"Name": "钻石",
"Icon": "",
"Type": 2,
"Count": 15,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587208",
"Name": "钻石",
"Icon": "",
"Type": 2,
"Count": 15,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587209",
"Name": "召唤卡",
"Icon": "http://47.109.132.12/uploads/40006.png",
"Type": 3,
"Count": 3,
"OtherId": "40006",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587210",
"Name": "召唤卡",
"Icon": "http://47.109.132.12/uploads/40006.png",
"Type": 3,
"Count": 3,
"OtherId": "40006",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587211",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587212",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587213",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 100000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587214",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 100000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587215",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 50000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587216",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 50000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587217",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587218",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587219",
"Name": "刮刮卡",
"Icon": "http://47.109.132.12/uploads/40005.png",
"Type": 3,
"Count": 1,
"OtherId": "40005",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "587220",
"Name": "刮刮卡",
"Icon": "http://47.109.132.12/uploads/40005.png",
"Type": 3,
"Count": 1,
"OtherId": "40005",
"Weight": 20,
"TotalCount": 0
}
]
},
{
"PoolType": 2,
"Level": "A",
"Count": 3,
"Weight": 1,
"PrizeList": [
{
"PrizeId": "588001",
"Name": "孙悟空",
"Icon": "",
"Type": 3,
"Count": 1,
"OtherId": "13002",
"Weight": 1,
"TotalCount": 0
},
{
"PrizeId": "588002",
"Name": "杨戬",
"Icon": "",
"Type": 3,
"Count": 1,
"OtherId": "13003",
"Weight": 2,
"TotalCount": 0
},
{
"PrizeId": "588003",
"Name": "熊猫侠客",
"Icon": "",
"Type": 3,
"Count": 1,
"OtherId": "13004",
"Weight": 3,
"TotalCount": 0
}
]
},
{
"PoolType": 2,
"Level": "B",
"Count": 3,
"Weight": 10,
"PrizeList": [
{
"PrizeId": "588101",
"Name": "穹渊核弹",
"Icon": "http://47.109.132.12/uploads/48004.png?timespan=1788935361",
"Type": 3,
"Count": 1,
"OtherId": "48004",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "588102",
"Name": "噬浪核弹",
"Icon": "http://47.109.132.12/uploads/48003.png?timespan=1788935284",
"Type": 3,
"Count": 1,
"OtherId": "48003",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "588103",
"Name": "霜雷核弹",
"Icon": "http://47.109.132.12/uploads/48002.png?timespan=1788935210",
"Type": 3,
"Count": 1,
"OtherId": "48002",
"Weight": 10,
"TotalCount": 0
}
]
},
{
"PoolType": 2,
"Level": "C",
"Count": 20,
"Weight": 20,
"PrizeList": [
{
"PrizeId": "588201",
"Name": "涌爆核弹",
"Icon": "http://47.109.132.12/uploads/48001.png?timespan=1788935117",
"Type": 3,
"Count": 1,
"OtherId": "48001",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588202",
"Name": "涌爆核弹",
"Icon": "http://47.109.132.12/uploads/48001.png?timespan=1788935117",
"Type": 3,
"Count": 1,
"OtherId": "48001",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588203",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588204",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588205",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588206",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588207",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 100000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588208",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 100000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588209",
"Name": "钻石",
"Icon": "",
"Type": 2,
"Count": 5,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588210",
"Name": "钻石",
"Icon": "",
"Type": 2,
"Count": 5,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588211",
"Name": "召唤卡",
"Icon": "http://47.109.132.12/uploads/40006.png",
"Type": 3,
"Count": 3,
"OtherId": "40006",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588212",
"Name": "召唤卡",
"Icon": "http://47.109.132.12/uploads/40006.png",
"Type": 3,
"Count": 3,
"OtherId": "40006",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588213",
"Name": "召唤卡",
"Icon": "http://47.109.132.12/uploads/40006.png",
"Type": 3,
"Count": 3,
"OtherId": "40006",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588214",
"Name": "召唤卡",
"Icon": "http://47.109.132.12/uploads/40006.png",
"Type": 3,
"Count": 3,
"OtherId": "40006",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588215",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588216",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588217",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588218",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588219",
"Name": "刮刮卡",
"Icon": "http://47.109.132.12/uploads/40005.png",
"Type": 3,
"Count": 1,
"OtherId": "40005",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588220",
"Name": "刮刮卡",
"Icon": "http://47.109.132.12/uploads/40005.png",
"Type": 3,
"Count": 1,
"OtherId": "40005",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588221",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 50000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588222",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 50000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588223",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 50000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "588224",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 50000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
}
]
},
{
"PoolType": 3,
"Level": "A",
"Count": 2,
"Weight": 1,
"PrizeList": [
{
"PrizeId": "589001",
"Name": "堕天使之翼",
"Icon": "",
"Type": 3,
"Count": 1,
"OtherId": "12007",
"Weight": 1,
"TotalCount": 0
},
{
"PrizeId": "589002",
"Name": "流火之焰",
"Icon": "",
"Type": 3,
"Count": 1,
"OtherId": "12029",
"Weight": 2,
"TotalCount": 0
}
]
},
{
"PoolType": 3,
"Level": "B",
"Count": 10,
"Weight": 10,
"PrizeList": [
{
"PrizeId": "589101",
"Name": "穹渊核弹",
"Icon": "http://47.109.132.12/uploads/48004.png?timespan=1788935361",
"Type": 3,
"Count": 1,
"OtherId": "48004",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "589102",
"Name": "穹渊核弹",
"Icon": "http://47.109.132.12/uploads/48004.png?timespan=1788935361",
"Type": 3,
"Count": 1,
"OtherId": "48004",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "589103",
"Name": "噬浪核弹",
"Icon": "http://47.109.132.12/uploads/48003.png?timespan=1788935284",
"Type": 3,
"Count": 1,
"OtherId": "48003",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "589104",
"Name": "噬浪核弹",
"Icon": "http://47.109.132.12/uploads/48003.png?timespan=1788935284",
"Type": 3,
"Count": 1,
"OtherId": "48003",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "589105",
"Name": "霜雷核弹",
"Icon": "http://47.109.132.12/uploads/48002.png?timespan=1788935210",
"Type": 3,
"Count": 1,
"OtherId": "48002",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "589106",
"Name": "霜雷核弹",
"Icon": "http://47.109.132.12/uploads/48002.png?timespan=1788935210",
"Type": 3,
"Count": 1,
"OtherId": "48002",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "589107",
"Name": "涌爆核弹",
"Icon": "http://47.109.132.12/uploads/48001.png?timespan=1788935117",
"Type": 3,
"Count": 1,
"OtherId": "48001",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "589108",
"Name": "涌爆核弹",
"Icon": "http://47.109.132.12/uploads/48001.png?timespan=1788935117",
"Type": 3,
"Count": 1,
"OtherId": "48001",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "589109",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 100000,
"OtherId": "",
"Weight": 10,
"TotalCount": 0
},
{
"PrizeId": "589110",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 100000,
"OtherId": "",
"Weight": 10,
"TotalCount": 0
}
]
},
{
"PoolType": 3,
"Level": "C",
"Count": 18,
"Weight": 20,
"PrizeList": [
{
"PrizeId": "589201",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589202",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589203",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589204",
"Name": "狂暴卡",
"Icon": "http://47.109.132.12/uploads/40004.png?v=1784285974",
"Type": 3,
"Count": 10,
"OtherId": "40004",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589205",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 100000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589206",
"Name": "金币",
"Icon": "",
"Type": 1,
"Count": 100000,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589207",
"Name": "钻石",
"Icon": "",
"Type": 2,
"Count": 5,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589208",
"Name": "钻石",
"Icon": "",
"Type": 2,
"Count": 5,
"OtherId": "",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589209",
"Name": "召唤卡",
"Icon": "http://47.109.132.12/uploads/40006.png",
"Type": 3,
"Count": 3,
"OtherId": "40006",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589210",
"Name": "召唤卡",
"Icon": "http://47.109.132.12/uploads/40006.png",
"Type": 3,
"Count": 3,
"OtherId": "40006",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589211",
"Name": "召唤卡",
"Icon": "http://47.109.132.12/uploads/40006.png",
"Type": 3,
"Count": 3,
"OtherId": "40006",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589212",
"Name": "召唤卡",
"Icon": "http://47.109.132.12/uploads/40006.png",
"Type": 3,
"Count": 3,
"OtherId": "40006",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589213",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589214",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589215",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589216",
"Name": "冰冻卡",
"Icon": "http://47.109.132.12/uploads/40003.png?v=1784285984",
"Type": 3,
"Count": 3,
"OtherId": "40003",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589217",
"Name": "刮刮卡",
"Icon": "http://47.109.132.12/uploads/40005.png",
"Type": 3,
"Count": 1,
"OtherId": "40005",
"Weight": 20,
"TotalCount": 0
},
{
"PrizeId": "589218",
"Name": "刮刮卡",
"Icon": "http://47.109.132.12/uploads/40005.png",
"Type": 3,
"Count": 1,
"OtherId": "40005",
"Weight": 20,
"TotalCount": 0
}
]
}
],
"CostsByPool": {
"1": [100, 200, 300, 500, 800, 1000],
"2": [100, 200, 300, 500, 800, 1000],
"3": [100, 200, 300, 500, 800, 1000]
},
"UserMaxDraw": 100
}
+53
View File
@@ -0,0 +1,53 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"JwtConfig": {
"TokenKey": "token",
"Issuer": "admin",
"Audience": "",
"SecretKey": "henanhuashengchongwuyouxiangongsikejizhongxin",
"ValidateIssuer": false,
"ValidateAudience": false,
"ValidateIssuerSigningKey": false,
"ValidateLifetime": false
},
"ConnectionStrings": {
"PgSqlConnection": "host=pgsql;database=data_0524;user id=data_0524;password=123456;"
},
"CapConfig": {
"Host": "rabbitmq",
"UserName": "admin",
"Password": "123456",
"VirtualHost": "/",
"PGLogHost": "host=pgsql;database=data_0524;user id=data_0524;password=123456;"
},
"RedisConfig": {
"Host": "redis",
"Port": 6379,
"UserName": "",
"Password": "123456",
"Database": 1,
"ConnectionString": "redis:6379,password=123456,defaultDatabase=2,abortConnect=false"
},
"MongoDbSettings": {
"username": "root",
"password": "root23456",
"ServerUrl": "mongodb://mongo:27017"
},
"WXConfig": {
"AppId": "wxceab9ce5b4474219",
"Secret": "5a291d7b295c76da579611da3f83e059",
"Url": "https://api.weixin.qq.com"
},
"BulletConfig": {
"SignKey": "63215f0afa3f5b25",
"MaxBulletPerUser": 100,
"TimeWindowSeconds": 500000
}
}
@@ -0,0 +1,50 @@
using DotNetCore.CAP.Messages;
using AdminService.Tasks;
namespace AdminHost.Extension
{
public static class CapExtension
{
public static IServiceCollection AddCap(this IServiceCollection services, IConfiguration configuration)
{
services.AddCap(x =>
{
x.UseDashboard();
x.UseRabbitMQ(rb =>
{
rb.HostName = configuration["CapConfig:Host"];
rb.UserName = configuration["CapConfig:UserName"];
rb.Password = configuration["CapConfig:Password"];
rb.VirtualHost = configuration["CapConfig:VirtualHost"];
rb.ExchangeName = "cap.exchange";
});
x.UsePostgreSql(op =>
{
op.ConnectionString = configuration["CapConfig:PGLogHost"];
op.Schema = "public";
});
x.SucceedMessageExpiredAfter = 7 * 24 * 3600;
x.FailedRetryCount = 5;
x.FailedRetryInterval = 10;
x.FailedThresholdCallback = ObjFailedThresholdCallback;
});
return services;
}
private static async void ObjFailedThresholdCallback(FailedInfo failed)
{
var exception = failed.Message.Headers["cap-exception"] ?? string.Empty;
var name = failed.Message.GetName();
if (failed.MessageType == MessageType.Publish)
{
}
else
{
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
using Microsoft.Extensions.Logging;
using ScheduledService;
using UserService.Tasks;
namespace AdminHost.Jobs;
public class DailyTaskJob : CronJobService
{
private readonly ITaskService _taskService;
public DailyTaskJob(
IScheduleConfig<DailyTaskJob> config,
ITaskService taskService,
ILogger<CronJobService> baseLogger)
: base(config.CronExpression, config.TimeZoneInfo, baseLogger)
{
_taskService = taskService;
}
public override async Task DoWork(CancellationToken cancellationToken)
{
await _taskService.DayTaskReward();
}
}
@@ -0,0 +1,156 @@
using System.Text;
using Common;
using CommonDomain;
using CommonService;
using Newtonsoft.Json;
namespace AdminHost.Middleware
{
public class ApiLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ApiLoggingMiddleware> _logger;
private readonly IApiLogService _apiLogService;
public ApiLoggingMiddleware(RequestDelegate next, ILogger<ApiLoggingMiddleware> logger, IApiLogService apiLogService)
{
_next = next;
_logger = logger;
_apiLogService = apiLogService;
}
public async Task Invoke(HttpContext httpContext)
{
var request = httpContext.Request;
if (request.Path.StartsWithSegments(new PathString("/api")) && request.Method.ToLower() != "options")
{
var requestTime = DateTime.Now;
var requestBodyContent = string.Empty;
var originalRequest = httpContext.Response.Body;
if (request.Method == "POST")
{
request.EnableBuffering();
requestBodyContent = await new StreamReader(request.Body).ReadToEndAsync();
request.Body.Position = 0;
}
try
{
using (var responseBody = new MemoryStream())
{
var response = httpContext.Response;
response.Body = responseBody;
await _next(httpContext);
string responseBodyContent = null;
responseBodyContent = await FormatResponse(response);
await responseBody.CopyToAsync(originalRequest);
string token = string.Empty, version = string.Empty, userAgent = string.Empty, source = string.Empty, deviceId = string.Empty, device = string.Empty, ip = string.Empty,sign=string.Empty;
if (httpContext.Request != null && httpContext.Request.Headers != null)
{
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Token"]))
{
token = httpContext.Request.Headers["Token"] + ""; }
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Version"]))
{
version = httpContext.Request.Headers["Version"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["User-Agent"]))
{
userAgent = httpContext.Request.Headers["User-Agent"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Source"]))
{
source = httpContext.Request.Headers["Source"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["DeviceId"]))
{
deviceId = httpContext.Request.Headers["DeviceId"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Device"]))
{
device = httpContext.Request.Headers["Device"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Ip"]))
{
ip = httpContext.Request.Headers["Ip"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Sign"]))
{
sign = httpContext.Request.Headers["Sign"] + "";
}
}
try
{
if (!string.IsNullOrEmpty(responseBodyContent))
{
BaseResponse<object> baseResponse = JsonConvert.DeserializeObject<BaseResponse<object>>(responseBodyContent);
if (baseResponse != null && baseResponse.Data != null)
{
baseResponse.Data = null;
}
responseBodyContent = JsonConvert.SerializeObject(baseResponse);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "API响应日志脱敏序列化失败 path={Path}", request.Path);
}
var now = DateTime.Now;
var apilog = new ApiLog()
{
requestTime = now,
accessToken = token,
clientIP = httpContext.Connection.RemoteIpAddress.ToString(),
consumeTime = (DateTime.Now - requestTime).Milliseconds,
requestHead= JsonConvert.SerializeObject(httpContext.Request.Headers),
requestUrl = request.Path,
requestMethod = request.Method.ToLower(),
requestQuery = request.QueryString.ToString(),
requestBody = requestBodyContent,
httpStatus = httpContext.Response.StatusCode,
responseBody = responseBodyContent,
userAgent = userAgent,
device= device,
deviceId= deviceId,
ip=ip,
source=source,
version=version,
sign=sign
};
await _apiLogService.InsertOneAsync(apilog);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "API日志记录失败 path={Path}", request.Path);
}
finally
{
httpContext.Response.Body = originalRequest;
}
}
else
{
await _next(httpContext);
}
}
private async Task<string> FormatResponse(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
string text = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
return text;
}
}
public static class ApiLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseApiLogging(this IApplicationBuilder app)
{
return app.UseMiddleware<ApiLoggingMiddleware>();
}
}
}
@@ -0,0 +1,47 @@
using AdminService.System;
using AdminService.Customer;
using AdminService.Admin;
using Autofac;
using CommonService;
using MongoRepository;
using UserService;
using UserService.BeautifulNumber;
namespace AdminHost.Middleware
{
public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterGeneric(typeof(MongoBaseRepository<>)).As(typeof(IMongoBaseRepository<>)).InstancePerLifetimeScope();
builder.RegisterType<RedisService.RedisService>()
.As<RedisService.IRedisService>()
.InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(typeof(ApiLogService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(typeof(AdminOperationLogService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(typeof(SystemService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(typeof(UserFeedbackService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterType<CommonService.BackgroundOperationService>()
.As<CommonService.IBackgroundOperationService>()
.InstancePerLifetimeScope();
}
}
}
@@ -0,0 +1,234 @@
using System.Text;
using Domain.Admin.Admin;
using Domain.Admin.Admin.ENum;
namespace AdminHost.Middleware
{
public class OperationLogMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<OperationLogMiddleware> _logger;
public OperationLogMiddleware(RequestDelegate next, ILogger<OperationLogMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, Domain.Admin.Admin.IAdminOperationLogService logService)
{
var path = context.Request.Path.Value ?? "";
var method = context.Request.Method;
bool shouldLog = path.StartsWith("/adminapi", StringComparison.OrdinalIgnoreCase) ||
path.StartsWith("/api/Admin", StringComparison.OrdinalIgnoreCase);
if (!shouldLog)
{
await _next(context);
return;
}
if (path.Contains("/login", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/GetAccessToken", StringComparison.OrdinalIgnoreCase))
{
await _next(context);
return;
}
string? requestBody = null;
if (method == "POST" || method == "PUT")
{
context.Request.EnableBuffering();
using var reader = new StreamReader(context.Request.Body, Encoding.UTF8, leaveOpen: true);
requestBody = await reader.ReadToEndAsync();
context.Request.Body.Position = 0;
}
var startTime = DateTime.Now;
string? ip = GetClientIp(context);
string? adminId = null;
string? adminName = null;
if (context.User.Identity?.IsAuthenticated == true)
{
adminId = context.User.FindFirst("uid")?.Value;
adminName = context.User.FindFirst("dname")?.Value
?? context.User.FindFirst(System.Security.Claims.ClaimTypes.Name)?.Value;
}
if (adminId == "administrator" || adminName == "administrator")
{
await _next(context);
return;
}
int statusCode = 200;
try
{
await _next(context);
statusCode = context.Response.StatusCode;
}
catch (Exception ex)
{
statusCode = 500;
_logger.LogError(ex, "请求处理异常");
throw;
}
_ = Task.Run(async () =>
{
try
{
var log = new AdminOperationLog
{
AdminId = adminId,
AdminName = adminName,
Module = (int)GetModuleFromPath(path),
ActionType = GetActionType(method, path),
Description = $"{GetActionType(method, path)} - {path}",
HttpMethod = method,
RequestPath = path,
RequestParams = requestBody?.Length > 4000 ? requestBody[..4000] : requestBody,
Response = null,
Ip = ip,
Status = statusCode >= 200 && statusCode < 400 ? 1 : 0,
OperateTime = startTime
};
await logService.AddLog(log);
}
catch (Exception ex)
{
_logger.LogError(ex, "记录操作日志失败");
}
});
}
private static string? GetClientIp(HttpContext context)
{
var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (string.IsNullOrEmpty(ip))
{
ip = context.Connection.RemoteIpAddress?.ToString();
}
return ip;
}
private static EnumOperationModule GetModuleFromPath(string path)
{
if (path.Contains("/AdminUser", StringComparison.OrdinalIgnoreCase))
return EnumOperationModule.User;
if (path.Contains("/Customer", StringComparison.OrdinalIgnoreCase))
return EnumOperationModule.Customer;
if (path.Contains("/BeautifulNumber", StringComparison.OrdinalIgnoreCase))
return EnumOperationModule.BeautifulNumber;
if (path.Contains("/Notice", StringComparison.OrdinalIgnoreCase))
return EnumOperationModule.Notice;
if (path.Contains("/Task", StringComparison.OrdinalIgnoreCase))
return EnumOperationModule.Task;
if (path.Contains("/GameConfig", StringComparison.OrdinalIgnoreCase))
return EnumOperationModule.GameConfig;
if (path.Contains("/Room", StringComparison.OrdinalIgnoreCase))
return EnumOperationModule.Room;
if (path.Contains("/System/user", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/system/user", StringComparison.OrdinalIgnoreCase))
return EnumOperationModule.SystemUser;
if (path.Contains("/System/role", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/system/role", StringComparison.OrdinalIgnoreCase))
return EnumOperationModule.Role;
if (path.Contains("/System/nav", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/system/nav", StringComparison.OrdinalIgnoreCase))
return EnumOperationModule.Navigation;
if (path.Contains("/System/dic", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/system/dic", StringComparison.OrdinalIgnoreCase))
return EnumOperationModule.Dictionary;
return EnumOperationModule.Other;
}
private static string GetActionType(string method, string path)
{
if (path.Contains("/Ban", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/ban", StringComparison.OrdinalIgnoreCase))
return "封号";
if (path.Contains("/Unban", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/unban", StringComparison.OrdinalIgnoreCase))
return "解封";
if (path.Contains("/Batch", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/batch", StringComparison.OrdinalIgnoreCase))
return "批量操作";
if (path.Contains("/Email", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/email", StringComparison.OrdinalIgnoreCase))
return "发送邮件";
if (path.Contains("/Init", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/init", StringComparison.OrdinalIgnoreCase))
return "初始化";
if (path.Contains("/Freeze", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/freeze", StringComparison.OrdinalIgnoreCase))
return "冻结";
if (path.Contains("/Unfreeze", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/unfreeze", StringComparison.OrdinalIgnoreCase))
return "解冻";
if (path.Contains("/Give", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/give", StringComparison.OrdinalIgnoreCase))
return "赠送";
if (path.Contains("/Clear", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/clear", StringComparison.OrdinalIgnoreCase))
return "清空";
if (path.Contains("/Robot", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/robot", StringComparison.OrdinalIgnoreCase))
return "机器人管理";
if (path.Contains("/state", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/State", StringComparison.OrdinalIgnoreCase))
return "设置状态";
if (path.Contains("/icon", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/Icon", StringComparison.OrdinalIgnoreCase))
return "修改头像";
if (path.Contains("/list", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/List", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/GetAll", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/getall", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/Get", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/get", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/check", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/Check", StringComparison.OrdinalIgnoreCase))
return "查询";
if (path.Contains("/add", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/create", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/Add", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/Create", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/send", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/Send", StringComparison.OrdinalIgnoreCase))
return "新增";
if (path.Contains("/update", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/edit", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/Update", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/Edit", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/set-", StringComparison.OrdinalIgnoreCase))
return "修改";
if (path.Contains("/delete", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/Delete", StringComparison.OrdinalIgnoreCase))
return "删除";
return method switch
{
"GET" => "查询",
"POST" => "新增",
"PUT" => "修改",
"DELETE" => "删除",
_ => "操作"
};
}
}
public static class OperationLogMiddlewareExtensions
{
public static IApplicationBuilder UseOperationLog(this IApplicationBuilder builder)
{
return builder.UseMiddleware<OperationLogMiddleware>();
}
}
}
+150
View File
@@ -0,0 +1,150 @@
using System.Text.Json;
using AdminApi;
using AdminHost.Extension;
using AdminHost.Jobs;
using AdminHost.Middleware;
using Microsoft.AspNetCore.SignalR;
using AdminService;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Common.Request;
using JWTHelper;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using MongoRepository.Config;
using Newtonsoft.Json;
using NLog.Extensions.Logging;
using NLog.Web;
using RedisHelper;
using ScheduledService;
using SharpAbp.Infrastructure.Api.Middleware;
Environment.SetEnvironmentVariable("SERVICE_NAME",
Environment.GetEnvironmentVariable("SERVICE_NAME")
?? System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name
?? "AdminHost");
Environment.SetEnvironmentVariable("INSTANCE_ID",
Environment.GetEnvironmentVariable("INSTANCE_ID") ?? "default");
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers()
.AddApplicationPart(typeof(AssemblyMarker).Assembly)
.AddControllersAsServices()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
builder.Services.AddControllers();
builder.Logging.AddNLog();
builder.Host.UseNLog();
builder.Services.AddControllers().AddNewtonsoftJson(
option => {
option.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
option.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
option.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
option.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
}
);
builder.Configuration.AddJsonFile(Path.Combine("Config", "system.json"), optional: true);
builder.Services.Configure<MongoDbSettings>(builder.Configuration.GetSection(nameof(MongoDbSettings)));
builder.Services.AddSingleton<IMongoDbSettings>(sp => sp.GetRequiredService<IOptions<MongoDbSettings>>().Value);
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddSingleton<IRequestApi, RequestApi>();
builder.Services.AddAutoMapper(typeof(AutoMapperConfig));
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: new[] { "self" });
builder.Services.AddCors(options =>
{
options.AddPolicy("Cors", policy =>
{
policy
.SetIsOriginAllowed(_ => true)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
builder.Services.AddSignalR();
#region jwt
builder.Services.AddCustomJwt(builder.Configuration);
#endregion
#region redis
builder.Services.AddRedisCache(builder.Configuration);
#endregion
#region Cap
builder.Services.AddCap(builder.Configuration);
#endregion
builder.Services.AddCronJob<DailyTaskJob>(c =>
{
c.TimeZoneInfo = TimeZoneInfo.Local;
c.CronExpression = @"0 0 0 * * *";
});
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterModule<AutofacModule>();
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("Cors");
app.UseRouting();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseApiLogging();
app.UseOperationLog();
app.MapControllers();
app.UseGlobalExceptionHandler();
app.MapHealthChecks("/health", new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
var result = new
{
status = report.Status.ToString(),
checks = report.Entries.Select(e => new
{
name = e.Key,
status = e.Value.Status.ToString(),
description = e.Value.Description,
duration = e.Value.Duration.TotalMilliseconds
}),
totalDuration = report.TotalDuration.TotalMilliseconds
};
await context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(result));
}
});
app.Run();
@@ -0,0 +1,29 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57330",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:8002/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"CapConfig": {
"Host": "rabbitmq",
"UserName": "admin",
"Password": "123456",
"VirtualHost": "/",
"PGLogHost": "host=pgsql;database=data_0524;user id=data_0524;password=123456;"
}
}
+8
View File
@@ -0,0 +1,8 @@
name: webapi
services:
0524admin:
image: 0524admin
container_name: '0524admin'
restart: always
ports:
- '5241:80'
View File
+135
View File
@@ -0,0 +1,135 @@
layui.define(['laytpl', 'layer', 'element', 'util'], function(exports){
exports('setter', {
baseurl:'http://localhost:8002/',
customerApiUrl: 'http://localhost:8007/',
container: 'LAY_app' //容器ID
,base: layui.cache.base //记录layuiAdmin文件夹所在路径
,views: layui.cache.base + 'views/' //视图所在目录
, entry: 'user/administrators/list' //默认视图文件名
,engine: '.html' //视图文件后缀名
,pageTabs: true //是否开启页面选项卡功能
,type: 'tab' //tab切换模式,不设置则每次点击都刷新
, name: '朋客捕鱼后台管理'
,tableName: 'layuiAdmin' //本地存储表名
,MOD_NAME: 'admin' //模块事件名
,debug: false //是否开启调试模式。如开启,接口异常时会抛出异常 URL 等信息
,interceptor: true //是否开启未登入拦截
//自定义请求字段
,request: {
tokenName: 'token' //自动携带 token 的字段名。可设置 false 不携带。
}
//自定义响应字段
,response: {
statusName: 'code' //数据状态的字段名称
,statusCode: {
ok: 0 //数据状态一切正常的状态码
, logout: 401 //登录状态失效的状态码
}
,msgName: 'info' //状态信息的字段名称
,dataName: 'data' //数据详情的字段名称
}
//独立页面路由,可随意添加(无需写参数)
,indPage: [
'/user/login' //登入页
,'/user/reg' //注册页
,'/user/forget' //找回密码
,'/template/tips/test' //独立页的一个测试 demo
]
//扩展的第三方模块
,extend: [
'echarts', //echarts 核心包
'echartsTheme' //echarts 主题
]
//主题配置
,theme: {
//内置主题配色方案
color: [{
main: '#20222A' //主题色
,selected: '#009688' //选中色
,alias: 'default' //默认别名
},{
main: '#03152A'
,selected: '#3B91FF'
,alias: 'dark-blue' //藏蓝
},{
main: '#2E241B'
,selected: '#A48566'
,alias: 'coffee' //咖啡
},{
main: '#50314F'
,selected: '#7A4D7B'
,alias: 'purple-red' //紫红
},{
main: '#344058'
,logo: '#1E9FFF'
,selected: '#1E9FFF'
,alias: 'ocean' //海洋
},{
main: '#3A3D49'
,logo: '#2F9688'
,selected: '#5FB878'
,alias: 'green' //墨绿
},{
main: '#20222A'
,logo: '#F78400'
,selected: '#F78400'
,alias: 'red' //橙色
},{
main: '#28333E'
,logo: '#AA3130'
,selected: '#AA3130'
,alias: 'fashion-red' //时尚红
},{
main: '#24262F'
,logo: '#3A3D49'
,selected: '#009688'
,alias: 'classic-black' //经典黑
},{
logo: '#226A62'
,header: '#2F9688'
,alias: 'green-header' //墨绿头
},{
main: '#344058'
,logo: '#0085E8'
,selected: '#1E9FFF'
,header: '#1E9FFF'
,alias: 'ocean-header' //海洋头
},{
header: '#393D49'
,alias: 'classic-black-header' //经典黑
},{
main: '#50314F'
,logo: '#50314F'
,selected: '#7A4D7B'
,header: '#50314F'
,alias: 'purple-red-header' //紫红头
},{
main: '#28333E'
,logo: '#28333E'
,selected: '#AA3130'
,header: '#AA3130'
,alias: 'fashion-red-header' //时尚红头
},{
main: '#28333E'
,logo: '#009688'
,selected: '#009688'
,header: '#009688'
,alias: 'green-header' //墨绿头
}]
//初始的颜色索引,对应上面的配色方案数组索引
//如果本地已经有主题色记录,则以本地记录为优先,除非请求本地数据(localStorage)
,initColorIndex: 0
}
});
});
@@ -0,0 +1,131 @@
layui.define(["table", "admin", "form", 'user', 'tree'], function (e) {
var i = (layui.$, layui.admin),
$ = layui.$,
t = layui.view,
l = layui.table,
r = layui.form,
tree = layui.tree,
admin = layui.admin,
setter = layui.setter;
var treedata = [];
//提交数据
l.render({
elem: "#LAY-user-back-manage",
url: setter.baseurl + "adminapi/banner/list",
method: "post",
contentType: "application/json",
page: true,
where: {
keyWord: "",
state: -1
},
request: {
pageName: 'pageIndex' //页码的参数名称,默认:page
, limitName: 'pageSize' //每页数据量的参数名,默认:limit
},
limit: 10,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
cols: [
[
{
field: "title",
title: "标题",
}, {
field: "url",
title: "链接"
}
, {
field: "times",
title: "时长(秒)"
}, {
field: "seeCount",
title: "浏览次数"
}, {
field: "state",
title: "状态",
templet: function (d) {
if (d.state == 1)
return "正常";
else
return "停止"
}
}, {
field: "position",
title: "位置",
templet: function (d) {
var str = "";
console.log(d)
d.position.forEach(function (currentValue, index, array) {
str += currentValue.title + ","
});
return str;
}
}, {
field: "createTime",
title: "加入时间",
sort: !0
}, {
title: "操作",
align: "center",
fixed: "right",
toolbar: "#table-banneradmin-admin"
}]
],
text: "对不起,加载出现异常!",
response: {
statusName: 'code', //数据状态的字段名称,默认:code
statusCode: 0, //成功的状态码,默认:0
msgName: 'info', //状态信息的字段名称,默认:msg
countName: 'count', //数据总数的字段名称,默认:count
dataName: 'data' //数据列表的字段名称,默认:data
}
}),
l.on("tool(LAY-user-back-manage)", function (e) {
var l = e.data;
console.log(e.event)
if ("del" === e.event) {
layer.confirm("确定删除此广告?", function (i) {
//提交数据
admin.req({
method: "get",
url: setter.baseurl + "adminapi/banner/delete?id=" + e.data.id,//实际使用请改成服务端真实接口
async: true,
done: function (res) {
e.del(), layer.close(i)
}
})
})
}
if ("edit" === e.event) {
i.popup({
title: "编辑广告",
area: ["500px", "600px"],
id: "LAY-popup-user-add",
success: function (e, i) {
t(this.id).render("user/administrators/bannerform", l).done(function () {
r.render(null, "layuiadmin-form-admin"),
r.on("submit(LAY-user-back-submit)", function (e) {
})
})
}
})
}
}), e("banneradmin", {})
});
@@ -0,0 +1,142 @@
/** layuiAdmin.pro-v1.4.0 LPPL License By https://www.layui.com/admin/ */;
layui.define(function (exports) {
var $ = layui.$;
var i = (layui.$, layui.layer, layui.laytpl, layui.setter, layui.view, layui.admin);
// Helper: convert PascalCase or any case keys to camelCase recursively
function toCamelKey(s) {
if (!s || typeof s !== 'string') return s;
// handle underscores and PascalCase
var str = s.replace(/_([a-z])/g, function (m, p1) { return p1.toUpperCase(); });
// if contains uppercase letters after first char, convert first char to lower
return str.charAt(0).toLowerCase() + str.slice(1);
}
function convertKeysToCamel(obj) {
if (obj === null || obj === undefined) return obj;
if (Array.isArray(obj)) return obj.map(convertKeysToCamel);
if (typeof obj === 'object') {
var res = {};
for (var k in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, k)) continue;
var nk = toCamelKey(k);
res[nk] = convertKeysToCamel(obj[k]);
}
return res;
}
return obj;
}
// Wrap admin.req so that all responses have their .data keys converted to camelCase
if (i && i.req) {
var _origReq = i.req.bind(i);
i.req = function (options) {
if (!options) return _origReq(options);
var userDone = options.done;
options.done = function (res) {
try {
if (res && res.data) {
res.data = convertKeysToCamel(res.data);
}
} catch (e) {
console && console.error && console.error('convertKeysToCamel error', e);
}
if (typeof userDone === 'function') userDone(res);
};
return _origReq(options);
};
}
// Enum definitions: populate as needed. Keys are field names in camelCase, values are arrays of {value,text}
window.enumDefinitions = window.enumDefinitions || {
fishType: [{ value: 1, text: '普通鱼' }, { value: 2, text: 'Boss鱼' }, { value: 3, text: '黄金鱼' }, { value: 4, text: '特殊鱼' }, { value: 5, text: '特殊Boss' }],
saleType: [{ value: 1, text: '钻石' }, { value: 2, text: '房卡' }, { value: 4, text: '金币' }, { value: 5, text: '道具' }]
};
function replaceInputWithSelect($form, name, options, currentValue) {
var $input = $form.find('[name="' + name + '"]');
if ($input.length === 0) return;
// create select
var $select = $('<select></select>');
$select.attr('name', name);
$options = options || [];
$select.append($('<option value=""></option>'));
$options.forEach(function (opt) {
var $opt = $('<option></option>').attr('value', opt.value).text(opt.text);
if (String(opt.value) === String(currentValue)) $opt.attr('selected', 'selected');
$select.append($opt);
});
$input.replaceWith($select);
}
// Normalize input/select/textarea name attributes to camelCase
function normalizeFormInputNames($container) {
try {
var $root = $($container || document);
$root.find('input[name],select[name],textarea[name]').each(function () {
var $el = $(this);
var name = $el.attr('name');
if (!name) return;
var newName = toCamelKey(name);
if (newName !== name) {
$el.attr('name', newName);
}
});
} catch (e) {
console && console.error && console.error('normalizeFormInputNames error', e);
}
}
window.renderEnumControls = function ($container) {
try {
var $ = layui.$;
var $form = $($container || document);
// normalize names first
normalizeFormInputNames($form);
Object.keys(window.enumDefinitions).forEach(function (field) {
var options = window.enumDefinitions[field];
// find any input/select with this name and replace if it's not already a select
var $els = $form.find('[name="' + field + '"]');
$els.each(function () {
var $el = $(this);
if ($el.is('select')) return; // already select
var currentVal = $el.val();
replaceInputWithSelect($form, field, options, currentVal);
});
});
} catch (e) {
console && console.error && console.error('renderEnumControls error', e);
}
};
// Wrap form.render to invoke enum rendering after original render
if (layui && layui.form && layui.form.render) {
var _origFormRender = layui.form.render.bind(layui.form);
layui.form.render = function () {
var res = _origFormRender.apply(null, arguments);
try {
// arguments may include filter name; find forms with that lay-filter
var filter = arguments && arguments.length ? arguments[0] : null;
if (typeof filter === 'string') {
// find forms with lay-filter
var $forms = layui.$("[lay-filter='" + filter + "']");
$forms.each(function () { window.renderEnumControls(this); });
} else {
window.renderEnumControls(document);
}
} catch (e) { }
return res;
};
}
i.events.logout = function () {
i.req({
url: "./json/user/logout.js",
type: "get",
data: {},
done: function (e) {
i.exit()
}
})
}, exports("common", {})
});
+93
View File
@@ -0,0 +1,93 @@
layui.define(["table", "admin", "form", 'user', 'tree'], function (e) {
var i = (layui.$, layui.admin),
$ = layui.$,
t = layui.view,
l = layui.treeTable,
r = layui.form,
tree = layui.tree,
admin = layui.admin,
setter = layui.setter;
var treedata = [];
//提交数据
l.render({
elem: "#LAY-user-back-dic",
url: setter.baseurl + "adminapi/System/dic/list",
method: "get",
page: false,
limit: 0,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
treeNode:true,
cols: [
[
{
field: "name",
title: "名称"
}, {
field: "order",
title: "序号"
}, {
title: "操作",
width: 300,
align: "center",
fixed: "right",
toolbar: "#table-role-admin"
}]
],
text: "对不起,加载出现异常!"
}), l.on("tool(LAY-user-back-dic)", function (e) {
var l = e.data;
console.log(e.event)
if ("del" === e.event) {
layer.confirm("确定删除此菜单?", function (i) {
//提交数据
admin.req({
method: "get",
url: setter.baseurl + "adminapi/System/dic/delete?id=" + e.data.id,//实际使用请改成服务端真实接口
async: true,
done: function (res) {
e.del(), layer.close(i)
}
})
})
}
if ("edit" === e.event) {
i.popup({
title: "编辑菜单",
area: ["500px", "480px"],
id: "LAY-popup-user-add",
success: function (e, i) {
t(this.id).render("user/administrators/dicform", l).done(function () {
r.render(null, "layuiadmin-form-dic"), r.on("submit(LAY-user-dic-submit)", function (e) {
var param = e.field;
param.dicid
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/dic/update",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(param),
done: function (res) {
layui.table.reload("LAY-user-back-dic"), layer.close(i)
}
})
})
})
}
})
}
}), e("dic", {})
});
+93
View File
@@ -0,0 +1,93 @@
layui.define(["table", "admin", "form", 'user', 'tree'], function (e) {
var i = (layui.$, layui.admin),
$ = layui.$,
t = layui.view,
l = layui.treeTable,
r = layui.form,
tree = layui.tree,
admin = layui.admin,
setter = layui.setter;
var treedata = [];
//提交数据
l.render({
elem: "#LAY-user-back-nav",
url: setter.baseurl + "adminapi/System/nav/list",
method: "get",
page: false,
limit: 0,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
treeNode:true,
cols: [
[
{
field: "name",
title: "名称"
}, {
field: "order",
title: "序号"
}, {
title: "操作",
width: 300,
align: "center",
fixed: "right",
toolbar: "#table-role-admin"
}]
],
text: "对不起,加载出现异常!"
}), l.on("tool(LAY-user-back-nav)", function (e) {
var l = e.data;
console.log(e.event)
if ("del" === e.event) {
layer.confirm("确定删除此菜单?", function (i) {
//提交数据
admin.req({
method: "get",
url: setter.baseurl + "adminapi/System/nav/delete?id=" + e.data.id,//实际使用请改成服务端真实接口
async: true,
done: function (res) {
e.del(), layer.close(i)
}
})
})
}
if ("edit" === e.event) {
i.popup({
title: "编辑菜单",
area: ["500px", "480px"],
id: "LAY-popup-user-add",
success: function (e, i) {
t(this.id).render("user/administrators/navform", l).done(function () {
r.render(null, "layuiadmin-form-nav"), r.on("submit(LAY-user-nav-submit)", function (e) {
var param = e.field;
param.navid
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/nav/update",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(param),
done: function (res) {
layui.table.reload("LAY-user-back-nav"), layer.close(i)
}
})
})
})
}
})
}
}), e("nav", {})
});
@@ -0,0 +1,175 @@
layui.define(["table", "admin", "form"], function (e) {
var i = (layui.$, layui.admin),
$ = layui.$,
t = layui.view,
l = layui.table,
r = layui.form,
admin = layui.admin,
setter = layui.setter;
//提交数据
console.log(setter);
l.render({
elem: "#LAY-user-back-manage",
url: setter.baseurl + "adminapi/Mall/GetAllProductList",
method: "get",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
cols: [
[
{
field: "number",
title: "编号",
templet: function (d) {
return d.productInfo?.number??"";
}
},
{
field: "name",
title: "名称",
}, {
field: "productName",
title: "产品名称",
templet: function (d) {
return d.productInfo?.name ?? "";
}
},
{
field: "saleInfo",
title: "售价信息",
templet: function (d) {
// 1 钻石 2 房卡 4 金币 5 道具
var tempName = "";
if (d.moneySaleInfo != null && d.moneySaleInfo.price > 0) {
tempName += d.moneySaleInfo.price + "元 " + (d.moneySaleInfo.limitCount == 0 ? "不限购" : " 限购"+d.moneySaleInfo.limitCount + "个 </br>");
}
if (d.diamondSaleInfo != null && d.diamondSaleInfo.price > 0) {
tempName += (d.diamondSaleInfo.price + "钻石 " + (d.diamondSaleInfo.limitCount == 0 ? "不限购" : "限购"+d.diamondSaleInfo.limitCount + "个</br>"));
}
if (d.scoreSaleInfo != null && d.scoreSaleInfo.price > 0) {
tempName += d.scoreSaleInfo.price + "积分" + (d.scoreSaleInfo.limitCount == 0 ? "不限购" : "限购"+d.scoreSaleInfo.limitCount + "个");
}
return tempName
}
}, {
field: "state",
title: "状态",
templet: function (d) {
if (d.state == 0)
return "下架";
else
return "上架"
}
}, {
title: "操作",
align: "center",
fixed: "right",
toolbar: "#table-product-admin"
}]
],
text: "对不起,加载出现异常!",
response: {
statusName: 'code', //数据状态的字段名称,默认:code
statusCode: 0, //成功的状态码,默认:0
msgName: 'info', //状态信息的字段名称,默认:msg
countName: 'count', //数据总数的字段名称,默认:count
dataName: 'data' //数据列表的字段名称,默认:data
}
}),
l.on("tool(LAY-user-back-manage)", function (e) {
var l = e.data;
if ("del" === e.event) {
layer.confirm("确定删除此产品?", function (i) {
//提交数据
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Mall/DeleteProduct?id=" + e.data.id,//实际使用请改成服务端真实接口
async: true,
done: function (res) {
e.del(), layer.close(i)
}
})
})
}
if ("state" === e.event) {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Mall/UpdateProductState?id=" + e.data.id + "&state=" + (e.data.state == 0 ? 1 : 0),//实际使用请改成服务端真实接口
async: true,
done: function (res) {
layer.msg("操作成功");
layui.table.reload("LAY-user-back-manage");
}
})
}
if ("edit" === e.event) {
i.popup({
title: "编辑产品",
area: ["500px", "600px"],
id: "LAY-popup-product-add",
success: function (e, i) {
t(this.id).render("mall/productform", l).done(function () {
r.render(null, "layuiadmin-form-admin"), r.on("submit(LAY-product-back-submit)", function (data) {
console.log(data)
var param = {
id: data.field.id,
name: data.field.name,
imageUrl: data.field.imageUrl,
detailType: parseInt(data.field.detailType),
number: data.field.baseProduct
};
param.moneySaleInfo = { limitCount: data.field.moneyLimitCount, price: parseFloat(data.field.moneyPrice) }
param.diamondSaleInfo = { limitCount: data.field.diamondLimitCount, price: parseFloat(data.field.diamondPrice) }
param.scoreSaleInfo = { limitCount: data.field.scoreLimitCount, price: parseFloat(data.field.scorePrice) }
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/mall/UpdateProduct",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(param),
done: function (res) {
layui.table.reload("LAY-user-back-manage"), layer.close(i)
}
})
})
})
}
})
}
}), e("productlistjs", {})
});
+33
View File
@@ -0,0 +1,33 @@
/** layuiAdmin.pro-v1.4.0 LPPL License By https://www.layui.com/admin/ */;
layui.define(["form", "upload"], function (t) {
var i = layui.$,
e = layui.layer,
n = (layui.laytpl, layui.setter, layui.view, layui.admin),
a = layui.form,
s = layui.upload;
i("body");
a.render(), a.verify({
nickname: function (t, i) {
return new RegExp("^[a-zA-Z0-9_一-龥\\s·]+$").test(t) ? /(^\_)|(\__)|(\_+$)/.test(t) ? "用户名首尾不能出现下划线'_'" : /^\d+\d+\d$/.test(t) ? "用户名不能全为数字" : void 0 : "用户名不能有特殊字符"
},
pass: [/^[\S]{6,12}$/, "密码必须6到12位,且不能出现空格"],
repass: function (t) {
if (t !== i("#LAY_password").val()) return "两次密码输入不一致"
}
});
a.on("submit(setmypass)", function (t) {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/System/user/delete?id=" + e.data.id,//实际使用请改成服务端真实接口
async: true,
done: function (res) {
return e.msg("修改成功"), !1
}
})
}), t("set", {})
});
+175
View File
@@ -0,0 +1,175 @@
layui.define(["table", "admin", "form", 'user', 'tree'], function (e) {
var i = (layui.$, layui.admin),
$ = layui.$,
t = layui.view,
l = layui.table,
r = layui.form,
admin = layui.admin,
setter = layui.setter;
//提交数据
l.render({
elem: "#LAY-task-back-manage",
url: setter.baseurl + "adminapi/task/GetTaskList",
method: "post",
contentType: "application/json",
page: true,
where: {
keyWord: "",
state: -1
},
request: {
pageName: 'pageIndex' //页码的参数名称,默认:page
, limitName: 'pageSize' //每页数据量的参数名,默认:limit
},
limit: 10,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
cols: [
[
{
field: "number",
title: "任务编号",
}, {
field: "remark",
title: "任务描述"
}, {
field: "taskType",
title: "任务类型",
templet: function (d) {
if (d.taskType == 1)
return "每日任务";
if (d.taskType == 2)
return "在线时长任务";
}
}, {
field: "taskEvent",
title: "事件",
templet: function (d) {
var tem = "";
switch (d.taskEvent) {
case 1: tem = "整点在线奖励"; break;
case 2: tem = "在线时长";break;
case 3: tem = "捕获任意鱼";break;
case 4: tem = "捕获黄金鱼";break;
case 5: tem = "捕获Boss鱼"; break;
case 6: tem = "赢取金币"; break;
case 7: tem = "使用狂暴"; break;
case 8: tem = "开炮任务"; break;
case 9: tem = "登录任务"; break;
case 10: tem = "充值任务"; break;
default:tem = "未知";break;
}
return tem;
}
}, {
field: "taskCount",
title: "任务条件"
}
, {
field: "exp",
title: "战令经验"
}
, {
field: "state",
title: "状态",
templet: function (d) {
if (d.state == 1)
return "启用";
else
return "禁用"
}
}
, {
width: 240,
title: "操作",
align: "center",
fixed: "right",
toolbar: "#table-taskadmin-admin"
}]
],
text: "对不起,加载出现异常!",
response: {
statusName: 'code', //数据状态的字段名称,默认:code
statusCode: 0, //成功的状态码,默认:0
msgName: 'info', //状态信息的字段名称,默认:msg
countName: 'count', //数据总数的字段名称,默认:count
dataName: 'data' //数据列表的字段名称,默认:data
}
}),
l.on("tool(LAY-task-back-manage)", function (e) {
var l = e.data;
console.log(e.event)
if ("del" === e.event) {
layer.confirm("确定删除此任务?", function (i) {
//提交数据
admin.req({
method: "get",
url: setter.baseurl + "adminapi/task/DeleteTask?id=" + e.data.id,//实际使用请改成服务端真实接口
async: true,
done: function (res) {
e.del(), layer.close(i)
}
})
})
}
if ("state" === e.event) {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Task/UpdateTaskState?id=" + e.data.id + "&state=" + (e.data.state == 1 ? 2 :1),//实际使用请改成服务端真实接口
async: true,
done: function (res) {
layer.msg("操作成功");
layui.table.reload("LAY-task-back-manage");
}
})
}
if ("edit" === e.event) {
i.popup({
title: "编辑任务",
area: ["500px", "600px"],
id: "LAY-popup-user-add",
success: function (e, i) {
t(this.id).render("config/taskform", l).done(function () {
r.render(null, "layuiadmin-form-admin"), r.on("submit(LAY-user-back-submit)", function (e) {
console.log(e.field)
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/task/AddTask",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(e.field),
done: function (res) {
layui.table.reload("LAY-task-back-manage"), layer.close(i)
}
})
})
})
}
})
}
}), e("task", {})
});
@@ -0,0 +1,15 @@
/** layuiAdmin.pro-v1.4.0 LPPL License By https://www.layui.com/admin/ */;
layui.define("form", function (e) {
var t = layui.$,
i = (layui.layer, layui.laytpl, layui.setter, layui.view, layui.admin, layui.form),
a = t("body");
i.verify({
nickname: function (e, t) {
return new RegExp("^[a-zA-Z0-9_一-龥\\s·]+$").test(e) ? /(^\_)|(\__)|(\_+$)/.test(e) ? "用户名首尾不能出现下划线'_'" : /^\d+\d+\d$/.test(e) ? "用户名不能全为数字" : void 0 : "用户名不能有特殊字符"
},
pass: [/^[\S]{6,12}$/, "密码必须6到12位,且不能出现空格"]
}), a.on("click", "#LAY-user-get-vercode", function () {
t(this);
this.src = "https://www.oschina.net/action/user/captcha?t=" + (new Date).getTime()
}), e("user", {})
});
@@ -0,0 +1,243 @@
layui.define(["table", "admin", "form", 'user', 'tree'], function (e) {
var i = (layui.$, layui.admin),
$ = layui.$,
t = layui.view,
l = layui.table,
r = layui.form,
tree = layui.tree,
admin = layui.admin,
setter = layui.setter;
var treedata = [];
//提交数据
l.render({
elem: "#LAY-user-back-manage",
url: setter.baseurl + "adminapi/System/user/list",
method: "post",
contentType: "application/json",
page: true,
where: {
keyWord: "",
state: -1
},
request: {
pageName: 'pageIndex' //页码的参数名称,默认:page
, limitName: 'pageSize' //每页数据量的参数名,默认:limit
},
limit: 10,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
cols: [
[
{
field: "userName",
title: "用户名",
}, {
field: "icon",
title: "头像",
templet: function (d) {
if (d.icon == null || d.icon == "")
return "";
return "<image src='"+d.icon+"'>"
}
}, {
field: "displayName",
title: "名字"
}, {
field: "state",
title: "状态",
templet: function (d) {
if (d.state == 0)
return "禁用";
else
return "正常"
}
}, {
field: "systemRole",
title: "角色",
templet: function (d) {
if (d.systemRole == null)
return "未选择"
return d.systemRole.name
}
}, {
field: "registerTime",
title: "加入时间",
sort: !0
}, {
title: "操作",
align: "center",
fixed: "right",
toolbar: "#table-useradmin-admin"
}]
],
text: "对不起,加载出现异常!",
response: {
statusName: 'code', //数据状态的字段名称,默认:code
statusCode: 0, //成功的状态码,默认:0
msgName: 'info', //状态信息的字段名称,默认:msg
countName: 'count', //数据总数的字段名称,默认:count
dataName: 'data' //数据列表的字段名称,默认:data
}
}),
l.on("tool(LAY-user-back-manage)", function (e) {
var l = e.data;
console.log(e.event)
if ("del" === e.event) {
layer.confirm("确定删除此管理员?", function (i) {
//提交数据
admin.req({
method: "get",
url: setter.baseurl + "adminapi/System/user/delete?id=" + e.data.id,//实际使用请改成服务端真实接口
async: true,
done: function (res) {
e.del(), layer.close(i)
}
})
})
}
if ("edit" === e.event) {
i.popup({
title: "编辑管理员",
area: ["500px", "600px"],
id: "LAY-popup-user-add",
success: function (e, i) {
t(this.id).render("user/administrators/adminform", l).done(function () {
r.render(null, "layuiadmin-form-admin"), r.on("submit(LAY-user-back-submit)", function (e) {
var param = {
id: e.field.id,
userName: e.field.userName,
displayName: e.field.displayName,
icon: e.field.icon,
password: e.field.password,
systemRole: {
id: e.field.systemRole,
name: $('#systemRole option:selected').text()
}
};
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/user/update",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(param),
done: function (res) {
layui.table.reload("LAY-user-back-manage"), layer.close(i)
}
})
})
})
}
})
}
}), l.render({
elem: "#LAY-user-back-role",
url: setter.baseurl + "adminapi/System/role/list",
method: "get",
page: false,
limit: 0,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
cols: [
[
{
field: "roleName",
title: "角色名"
}, {
field: "description",
title: "具体描述"
}, {
title: "操作",
width: 300,
align: "center",
fixed: "right",
toolbar: "#table-useradmin-admin"
}]
],
text: "对不起,加载出现异常!"
}), l.on("tool(LAY-user-back-role)", function (e) {
var l = e.data;
console.log(e.event)
if ("del" === e.event) {
layer.confirm("确定删除此角色?", function (i) {
//提交数据
admin.req({
method: "get",
url: setter.baseurl + "adminapi/System/role/delete?id=" + e.data.id,//实际使用请改成服务端真实接口
async: true,
done: function (res) {
e.del(), layer.close(i)
}
})
})
}
if ("edit" === e.event) {
i.popup({
title: "编辑角色",
area: ["500px", "480px"],
id: "LAY-popup-user-add",
success: function (e, i) {
t(this.id).render("user/administrators/roleform", l).done(function () {
r.render(null, "layuiadmin-form-role"), r.on("submit(LAY-user-role-submit)", function (e) {
var param = e.field;
param.navid
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/role/update",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(param),
done: function (res) {
layui.table.reload("LAY-user-back-role"), layer.close(i)
}
})
})
})
}
})
}
if ("role" === e.event) {
i.popup({
title: "编辑权限",
area: ["500px", "600px"],
id: "LAY-popup-user-tree-add",
success: function (e, i) {
t(this.id).render("user/administrators/roletree", l).done(function () {
})
}
})
}
}), e("useradmin", {})
});
+89
View File
@@ -0,0 +1,89 @@
/** layuiAdmin.pro-v1.4.0 LPPL License By https://www.layui.com/admin/ */;
layui.extend({
setter: "config",
admin: "lib/admin",
view: "lib/view"
}).define(["setter", "admin"], function (e) {
var a = layui.setter,
n = layui.element,
i = layui.admin,
t = i.tabsPage,
l = layui.view,
o = function () {
var e = layui.router(),
r = e.path,
y = i.correctRouter(e.path.join("/"));
r.length || (r = [""]), "" === r[r.length - 1] && (r[r.length - 1] = a.entry);
var h = function (e) {
o.haveInit && d(".layui-layer").each(function () {
var e = d(this),
a = e.attr("times");
e.hasClass("layui-layim") || layer.close(a)
}), o.haveInit = !0, d(s).scrollTop(0), delete t.type
};
return "tab" === t.type && ("/" !== y || "/" === y && i.tabsBody().html()) ? (i.tabsBodyChange(t.index), h(t.type)) : (l().render(r.join("/")).then(function (l) {
var o, r = d("#LAY_app_tabsheader>li");
r.each(function (e) {
var a = d(this),
n = a.attr("lay-id");
n === y && (o = !0, t.index = e)
}), a.pageTabs && "/" !== y && (o || (d(s).append('<div class="layadmin-tabsbody-item layui-show"></div>'), t.index = r.length, n.tabAdd(u, {
title: "<span>" + (l.title || "新标签页") + "</span>",
id: y,
attr: e.href
}))), this.container = i.tabsBody(t.index), a.pageTabs || this.container.scrollTop(0), n.tabChange(u, y), i.tabsBodyChange(t.index)
}).done(function () {
layui.use("common", layui.cache.callback.common), c.on("resize", layui.data.resize), n.render("breadcrumb", "breadcrumb"), i.tabsBody(t.index).on("scroll", function () {
var e = d(this),
a = d(".layui-laydate"),
n = d(".layui-layer")[0];
a[0] && (a.each(function () {
var e = d(this);
e.hasClass("layui-laydate-static") || e.remove()
}), e.find("input").blur()), n && layer.closeAll("tips")
})
}), void h())
},
r = function (e) {
var n, t = layui.router(),
r = l(a.container),
s = i.correctRouter(t.path.join("/"));
if (layui.each(a.indPage, function (e, a) {
if (s === a) return n = !0
}), layui.config({
base: a.base + "controller/"
}), n || "/user/login" === s) r.render(t.path.join("/")).done(function () {
i.pageType = "alone"
});
else {
if (a.interceptor) {
var u = layui.data(a.tableName);
if (!u[a.request.tokenName]) return location.hash = "/user/login/redirect=" + encodeURIComponent(s)
}
"console" === i.pageType ? o() : r.render("layout").done(function () {
o(), layui.element.render(), i.screen() < 2 && i.sideFlexible(), i.pageType = "console"
})
}
},
s = "#LAY_app_body",
u = "layadmin-layout-tabs",
d = layui.$,
c = d(window);
layui.link(a.base + "style/admin.css?v=" + (i.v + "-1"), function () {
r()
}, "layuiAdmin"), window.onhashchange = function () {
// 只在首次加载时渲染layout,之后只切换tab
if (!o.haveInit) {
r();
} else {
// 已经初始化,只切换到对应tab
o();
}
layui.event.call(this, a.MOD_NAME, "hash({*})", layui.router())
}, layui.each(a.extend, function (e, n) {
var i = {};
i[n] = "{/}" + a.base + "lib/extend/" + n, layui.extend(i)
}), e("index", {
render: o
})
});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
/** layuiAdmin.pro-v1.4.0 LPPL License By https://www.layui.com/admin/ */
;layui.define(function(e){e("echartsTheme",{color:["#009688","#1E9FFF","#5FB878","#FFB980","#D87A80","#8d98b3","#e5cf0d","#97b552","#95706d","#dc69aa","#07a2a4","#9a7fd1","#588dd5","#f5994e","#c05050","#59678c","#c9ab00","#7eb00a","#6f5553","#c14089"],title:{textStyle:{fontWeight:"normal",color:"#666"}},dataRange:{itemWidth:15,color:["#009688","#e0ffff"]},toolbox:{color:["#1e90ff","#1e90ff","#1e90ff","#1e90ff"],effectiveColor:"#ff4500"},tooltip:{backgroundColor:"rgba(50,50,50,0.5)",axisPointer:{type:"line",lineStyle:{color:"#009688"},crossStyle:{color:"#008acd"},shadowStyle:{color:"rgba(200,200,200,0.2)"}}},dataZoom:{dataBackgroundColor:"#efefff",fillerColor:"rgba(182,162,222,0.2)",handleColor:"#008acd"},grid:{borderColor:"#eee"},categoryAxis:{axisLine:{lineStyle:{color:"#009688"}},axisTick:{show:!1},splitLine:{lineStyle:{color:["#eee"]}}},valueAxis:{axisLine:{lineStyle:{color:"#009688"}},splitArea:{show:!0,areaStyle:{color:["rgba(250,250,250,0.1)","rgba(200,200,200,0.1)"]}},splitLine:{lineStyle:{color:["#eee"]}}},polar:{axisLine:{lineStyle:{color:"#ddd"}},splitArea:{show:!0,areaStyle:{color:["rgba(250,250,250,0.2)","rgba(200,200,200,0.2)"]}},splitLine:{lineStyle:{color:"#ddd"}}},timeline:{lineStyle:{color:"#009688"},controlStyle:{normal:{color:"#009688"},emphasis:{color:"#009688"}},symbol:"emptyCircle",symbolSize:3},bar:{itemStyle:{normal:{barBorderRadius:2},emphasis:{barBorderRadius:2}}},line:{smooth:!0,symbol:"emptyCircle",symbolSize:3},k:{itemStyle:{normal:{color:"#d87a80",color0:"#2ec7c9",lineStyle:{color:"#d87a80",color0:"#2ec7c9"}}}},scatter:{symbol:"circle",symbolSize:4},radar:{symbol:"emptyCircle",symbolSize:3},map:{itemStyle:{normal:{areaStyle:{color:"#ddd"},label:{textStyle:{color:"#d87a80"}}},emphasis:{areaStyle:{color:"#fe994e"}}}},force:{itemStyle:{normal:{linkStyle:{color:"#1e90ff"}}}},chord:{itemStyle:{normal:{borderWidth:1,borderColor:"rgba(128, 128, 128, 0.5)",chordStyle:{lineStyle:{color:"rgba(128, 128, 128, 0.5)"}}},emphasis:{borderWidth:1,borderColor:"rgba(128, 128, 128, 0.5)",chordStyle:{lineStyle:{color:"rgba(128, 128, 128, 0.5)"}}}}},gauge:{axisLine:{lineStyle:{color:[[.2,"#2ec7c9"],[.8,"#5ab1ef"],[1,"#d87a80"]],width:10}},axisTick:{splitNumber:10,length:15,lineStyle:{color:"auto"}},splitLine:{length:22,lineStyle:{color:"auto"}},pointer:{width:5}},textStyle:{fontFamily:"微软雅黑, Arial, Verdana, sans-serif"}})});
+2
View File
@@ -0,0 +1,2 @@
/** layuiAdmin.pro-v1.4.0 LPPL License By https://www.layui.com/admin/ */
;layui.define(["laytpl","layer"],function(e){var t=layui.jquery,a=layui.laytpl,n=layui.layer,r=layui.setter,o=(layui.device(),layui.hint()),i=function(e){return new d(e)},s="LAY_app_body",d=function(e){this.id=e,this.container=t("#"+(e||s))};i.loading=function(e){e.append(this.elemLoad=t('<i class="layui-anim layui-anim-rotate layui-anim-loop layui-icon layui-icon-loading layadmin-loading"></i>'))},i.removeLoad=function(){this.elemLoad&&this.elemLoad.remove()},i.exit=function(){layui.data(r.tableName,{key:r.request.tokenName,remove:!0}),location.hash="/user/login"},i.req=function(e){var a=e.success,n=e.error,o=r.request,s=r.response,d=function(){return r.debug?"<br><cite>URL:</cite>"+e.url:""};if(e.data=e.data||{},e.headers=e.headers||{},o.tokenName){var l="string"==typeof e.data?JSON.parse(e.data):e.data;e.data[o.tokenName]=o.tokenName in l?e.data[o.tokenName]:layui.data(r.tableName)[o.tokenName]||"",e.headers[o.tokenName]=o.tokenName in e.headers?e.headers[o.tokenName]:layui.data(r.tableName)[o.tokenName]||""}return delete e.success,delete e.error,t.ajax(t.extend({type:"get",dataType:"json",success:function(t){var n=s.statusCode;if(t[s.statusName]==n.ok)"function"==typeof e.done&&e.done(t);else if(t[s.statusName]==n.logout)i.exit();else{var r=["<cite>Error:</cite> "+(t[s.msgName]||"返回状态码异常"),d()].join("");i.error(r)}"function"==typeof a&&a(t)},error:function(e,t){var a=["请求异常,请重试<br><cite>错误信息:</cite>"+t,d()].join("");i.error(a),"function"==typeof n&&n(res)}},e))},i.popup=function(e){var a=e.success,r=e.skin;return delete e.success,delete e.skin,n.open(t.extend({type:1,title:"提示",content:"",id:"LAY-system-view-popup",skin:"layui-layer-admin"+(r?" "+r:""),shadeClose:!0,closeBtn:!1,success:function(e,r){var o=t('<i class="layui-icon" close>&#x1006;</i>');e.append(o),o.on("click",function(){n.close(r)}),"function"==typeof a&&a.apply(this,arguments)}},e))},i.error=function(e,a){return i.popup(t.extend({content:e,maxWidth:300,offset:"t",anim:6,id:"LAY_adminError"},a))},d.prototype.render=function(e,a){var n=this;layui.router();return e=r.views+e+r.engine,t("#"+s).children(".layadmin-loading").remove(),i.loading(n.container),t.ajax({url:e,type:"get",dataType:"html",data:{v:layui.cache.version},success:function(e){e="<div>"+e+"</div>";var r=t(e).find("title"),o=r.text()||(e.match(/\<title\>([\s\S]*)\<\/title>/)||[])[1],s={title:o,body:e};r.remove(),n.params=a||{},n.then&&(n.then(s),delete n.then),n.parse(e),i.removeLoad(),n.done&&(n.done(s),delete n.done)},error:function(e){return i.removeLoad(),n.render.isError?i.error("请求视图文件异常,状态:"+e.status):(404===e.status?n.render("template/tips/404"):n.render("template/tips/error"),void(n.render.isError=!0))}}),n},d.prototype.parse=function(e,n,r){var s=this,d="object"==typeof e,l=d?e:t(e),u=d?e:l.find("*[template]"),c=function(e){var n=a(e.dataElem.html()),o=t.extend({params:y.params},e.res);e.dataElem.after(n.render(o)),"function"==typeof r&&r();try{e.done&&new Function("d",e.done)(o)}catch(i){console.error(e.dataElem[0],"\n存在错误回调脚本\n\n",i)}},y=layui.router();l.find("title").remove(),s.container[n?"after":"html"](l.children()),y.params=s.params||{};for(var p=u.length;p>0;p--)!function(){var e=u.eq(p-1),t=e.attr("lay-done")||e.attr("lay-then"),n=a(e.attr("lay-url")||"").render(y),r=a(e.attr("lay-data")||"").render(y),s=a(e.attr("lay-headers")||"").render(y);try{r=new Function("return "+r+";")()}catch(d){o.error("lay-data: "+d.message),r={}}try{s=new Function("return "+s+";")()}catch(d){o.error("lay-headers: "+d.message),s=s||{}}n?i.req({type:e.attr("lay-type")||"get",url:n,data:r,dataType:"json",headers:s,success:function(a){c({dataElem:e,res:a,done:t})}}):c({dataElem:e,done:t})}();return s},d.prototype.send=function(e,t){var n=a(e||this.container.html()).render(t||{});return this.container.html(n),this},d.prototype.refresh=function(e){var t=this,a=t.container.next(),n=a.attr("lay-templateid");return t.id!=n?t:(t.parse(t.container,"refresh",function(){t.container.siblings('[lay-templateid="'+t.id+'"]:last').remove(),"function"==typeof e&&e()}),t)},d.prototype.then=function(e){return this.then=e,this},d.prototype.done=function(e){return this.done=e,this},e("view",i)});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
/** layuiAdmin.pro-v1.4.0 LPPL License By https://www.layui.com/admin/ */
#LAY_app,body,html{height:100%}.layui-layout-body{overflow:auto}#LAY-user-login,.layadmin-user-display-show{display:block!important}.layadmin-user-login{position:relative;left:0;top:0;padding:110px 0;min-height:100%;box-sizing:border-box}.layadmin-user-login-main{width:375px;margin:0 auto;box-sizing:border-box}.layadmin-user-login-box{padding:20px}.layadmin-user-login-header{text-align:center}.layadmin-user-login-header h2{margin-bottom:10px;font-weight:300;font-size:30px;color:#000}.layadmin-user-login-header p{font-weight:300;color:#999}.layadmin-user-login-body .layui-form-item{position:relative}.layadmin-user-login-icon{position:absolute;left:1px;top:1px;width:38px;line-height:36px;text-align:center;color:#d2d2d2}.layadmin-user-login-body .layui-form-item .layui-input{padding-left:38px}.layadmin-user-login-codeimg{max-height:38px;width:100%;cursor:pointer;box-sizing:border-box}.layadmin-user-login-other{position:relative;font-size:0;line-height:38px;padding-top:20px}.layadmin-user-login-other>*{display:inline-block;vertical-align:middle;margin-right:10px;font-size:14px}.layadmin-user-login-other .layui-icon{position:relative;top:2px;font-size:26px}.layadmin-user-login-other a:hover{opacity:.8}.layadmin-user-jump-change{float:right}.layadmin-user-login-footer{position:absolute;left:0;bottom:0;width:100%;line-height:30px;padding:20px;text-align:center;box-sizing:border-box;color:rgba(0,0,0,.5)}.layadmin-user-login-footer span{padding:0 5px}.layadmin-user-login-footer a{padding:0 5px;color:rgba(0,0,0,.5)}.layadmin-user-login-footer a:hover{color:rgba(0,0,0,1)}.layadmin-user-login-main[bgimg]{background-color:#fff;box-shadow:0 0 5px rgba(0,0,0,.05)}.ladmin-user-login-theme{position:fixed;bottom:0;left:0;width:100%;text-align:center}.ladmin-user-login-theme ul{display:inline-block;padding:5px;background-color:#fff}.ladmin-user-login-theme ul li{display:inline-block;vertical-align:top;width:64px;height:43px;cursor:pointer;transition:all .3s;-webkit-transition:all .3s;background-color:#f2f2f2}.ladmin-user-login-theme ul li:hover{opacity:.9}@media screen and (max-width:768px){.layadmin-user-login{padding-top:60px}.layadmin-user-login-main{width:300px}.layadmin-user-login-box{padding:10px}}
Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
<div style="text-align:center;padding:40px;color:#999;">此页面已废弃,奖池配置已整合到列表页</div>
@@ -0,0 +1,708 @@
<title>奖池抽奖活动</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>活动</cite></a>
<a><cite>奖池抽奖活动</cite></a>
</div>
</div>
<div class="layui-fluid">
<!-- 活动配置 -->
<div class="layui-card">
<div class="layui-card-header">活动配置</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="lottery-form">
<input type="hidden" name="activityId" id="activityId">
<div class="layui-form-item">
<label class="layui-form-label">活动名称</label>
<div class="layui-input-inline" style="width:400px;">
<input type="text" name="activityName" lay-verify="required" placeholder="请输入活动名称" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-inline">
<select name="isOpen">
<option value="false">关闭</option>
<option value="true">开启</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">开始时间</label>
<div class="layui-input-inline">
<input type="text" name="startTime" id="startTime" placeholder="yyyy-MM-dd HH:mm:ss" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">留空=立即开始</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">结束时间</label>
<div class="layui-input-inline">
<input type="text" name="endTime" id="endTime" placeholder="yyyy-MM-dd HH:mm:ss" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">留空=不限制</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">炮台价格</label>
<div class="layui-input-inline" style="width:400px;"><input type="text" name="costsCannon" class="layui-input" placeholder="炮台奖池每次抽取消耗(必填)"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">战神价格</label>
<div class="layui-input-inline" style="width:400px;"><input type="text" name="costsGodWar" class="layui-input" placeholder="战神奖池每次抽取消耗(必填)"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">翅膀价格</label>
<div class="layui-input-inline" style="width:400px;"><input type="text" name="costsWing" class="layui-input" placeholder="翅膀奖池每次抽取消耗(必填)"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">每人上限</label>
<div class="layui-input-inline" style="width:120px;">
<input type="number" name="userMaxDraw" value="100" placeholder="次数" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">单用户最大抽奖次数(0=不限制)</div>
</div>
</form>
</div>
</div>
<!-- 奖池等级 -->
<div class="layui-card">
<div class="layui-card-header">奖池等级</div>
<div class="layui-card-body">
<div class="layui-tab layui-tab-brief" lay-filter="lottery-pool-type">
<ul class="layui-tab-title">
<li class="layui-this" lay-id="1">炮台奖池</li>
<li lay-id="2">战神奖池</li>
<li lay-id="3">翅膀奖池</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<div class="layui-btn-container" style="margin-bottom:10px;">
<button class="layui-btn layui-btn-sm" id="btn-add-pool"><i class="layui-icon layui-icon-add-1"></i> 添加等级</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" id="btn-init-lottery"><i class="layui-icon layui-icon-refresh"></i> 初始化整个配置数据</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" id="btn-save"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" id="btn-reload"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
</div>
<table id="table-pool" lay-filter="table-pool"></table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 奖池等级操作栏 -->
<script type="text/html" id="bar-pool">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="prize">奖项</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<!-- 奖项管理对话框模板 -->
<script type="text/html" id="prize-dialog-tpl">
<div class="prize-container">
<div class="prize-left">
<div class="prize-toolbar">
<button class="layui-btn layui-btn-sm" id="btn-add-prize"><i class="layui-icon layui-icon-add-1"></i> 新增</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" id="btn-delete-prize"><i class="layui-icon layui-icon-delete"></i> 删除</button>
</div>
<div class="prize-table-wrap">
<table id="table-prize-list"></table>
</div>
</div>
<div class="prize-right">
<h4>奖项详情</h4>
<div class="layui-form" lay-filter="prize-form">
<div class="layui-form-item">
<label class="layui-form-label">奖项ID</label>
<div class="layui-input-block">
<input type="text" name="prizeId" id="prizeId" class="layui-input" placeholder="唯一标识">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">奖项名称</label>
<div class="layui-input-block">
<input type="text" name="prizeName" id="prizeName" class="layui-input" placeholder="如:1000金币">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">图标</label>
<div class="layui-input-block">
<input type="text" name="prizeIcon" id="prizeIcon" class="layui-input" placeholder="图标URL">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<select name="prizeType" id="prizeType" lay-filter="prizeType">
<option value="1">金币</option>
<option value="2">钻石</option>
<option value="3">道具</option>
<option value="4">道具碎片</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数量</label>
<div class="layui-input-block">
<input type="number" name="prizeCount" id="prizeCount" class="layui-input" placeholder="奖励数量">
</div>
</div>
<div class="layui-form-item prize-prop-select" style="display:none;">
<label class="layui-form-label">道具编号</label>
<div class="layui-input-block">
<select name="prizeOtherId" id="prizeOtherId" lay-filter="prizeOtherId">
<option value="">请选择道具/碎片</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">权重</label>
<div class="layui-input-block">
<input type="number" name="prizeWeight" id="prizeWeight" class="layui-input" placeholder="抽中权重,越大越易中">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">总库存</label>
<div class="layui-input-block">
<input type="number" name="prizeTotalCount" id="prizeTotalCount" class="layui-input" placeholder="0=无限">
</div>
</div>
<div class="layui-form-item" style="margin-top:20px;">
<button class="layui-btn layui-btn-normal" id="btn-save-prize" style="width:100%;">保存</button>
</div>
</div>
</div>
</div>
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer', 'laydate', 'util', 'element'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
laydate = layui.laydate,
util = layui.util,
element = layui.element,
setter = layui.setter;
// ============ 数据 ============
var activityData = null; // 完整活动数据(camelCase keys,来自服务端)
var poolsData = []; // 奖池等级列表(本地编辑,camelCase keys)
var currentPoolIndex = -1; // 当前编辑的奖池等级索引
var currentPrizeIndex = -1; // 当前编辑的奖项索引
var currentPoolType = 1; // 1=炮台,2=战神,3=翅膀
var propList = []; // 道具列表
var propFragmentList = []; // 碎片单独存于 PropFragments,不在 PropConfig 中
function propIcon(number) {
var key = String(number || '').trim();
var prop = propList.find(function(p) { return String(p.number) === key; });
if (prop) return prop.imageUrl || prop.icon || '';
var fragment = propFragmentList.find(function(p) { return String(p.number) === key; });
if (!fragment) return '';
var parent = propList.find(function(p) { return String(p.number) === String(fragment.productNumber); });
return fragment.imageUrl || (parent && parent.imageUrl) || '';
}
function prizeIcon(prize) {
// 道具和碎片使用最新配置图片,兼容历史奖项保存的失效 icon。
var type = parseInt(prize.type, 10);
return ((type === 3 || type === 4) ? propIcon(prize.otherId) : '') || prize.icon || (prize.product && prize.product.imageUrl) || '';
}
function newClientId() {
return 'pool_' + Date.now() + '_' + Math.random().toString(16).slice(2);
}
function findPoolIndex(row) {
if (!row) return -1;
return poolsData.findIndex(function(p) { return p._clientId === row._clientId; });
}
// 初始化:防止loading残影
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
// ============ 数据加载 ============
function loadActivity() {
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/Lottery/list',
done: function (res) {
if (res.code === 0) {
if (res.data && res.data.length > 0) {
activityData = res.data[0];
} else {
activityData = {
activityId: '',
activityName: '',
isOpen: false,
startTime: 0,
endTime: 0,
costsByPool: {},
userMaxDraw: 100,
pools: []
};
}
poolsData = (activityData.pools || []).map(function(p) {
var item = $.extend({}, p);
item.poolType = parseInt(item.poolType) || 1;
item._clientId = newClientId();
return item;
});
fillForm();
renderPoolTable();
}
}
});
}
function loadProps() {
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/GameConfig',
done: function (res) {
if (res.code === 0 && res.data) {
propList = res.data.propConfig || [];
propFragmentList = res.data.propFragments || [];
renderPoolTable();
}
}
});
}
// ============ 表单回填 ============
function fillForm() {
if (!activityData) return;
// 先用 jQuery 清空再赋值,避免 layui form.val 累积
var costsByPool = activityData.costsByPool || {};
$('input[name="costsCannon"]').val((costsByPool['1'] || []).join(','));
$('input[name="costsGodWar"]').val((costsByPool['2'] || []).join(','));
$('input[name="costsWing"]').val((costsByPool['3'] || []).join(','));
$('input[name="activityName"]').val(activityData.activityName || '');
$('input[name="userMaxDraw"]').val(activityData.userMaxDraw || 100);
$('select[name="isOpen"]').val(activityData.isOpen ? 'true' : 'false');
$('#activityId').val(activityData.activityId || '');
if (activityData.startTime) {
var sd = new Date(activityData.startTime * 1000);
$('#startTime').val(util.toDateString(sd, 'yyyy-MM-dd HH:mm:ss'));
}
if (activityData.endTime) {
var ed = new Date(activityData.endTime * 1000);
$('#endTime').val(util.toDateString(ed, 'yyyy-MM-dd HH:mm:ss'));
}
form.render();
}
// ============ 日期选择器 ============
laydate.render({ elem: '#startTime', type: 'datetime' });
laydate.render({ elem: '#endTime', type: 'datetime' });
// ============ 奖池等级表格 ============
function renderPoolTable() {
table.render({
elem: '#table-pool',
data: poolsData.filter(function(p) { return (parseInt(p.poolType) || 1) === currentPoolType; }),
page: false,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'level', title: '等级', width: 100 },
{ field: 'count', title: '数量', width: 80 },
{ field: 'weight', title: '权重', width: 80 },
{ title: '奖项数', width: 80, templet: function(d) {
return (d.prizeList && d.prizeList.length) ? d.prizeList.length + '项' : '无';
}},
{ title: '操作', width: 200, fixed: 'right', toolbar: '#bar-pool' }
]]
});
}
// ============ 奖池等级事件 ============
table.on('tool(table-pool)', function(obj) {
var idx = findPoolIndex(obj.data);
if (idx < 0) {
layer.msg('未找到当前奖池,请刷新页面后重试');
return;
}
var data = poolsData[idx];
if (obj.event === 'del') {
layer.confirm('确定删除等级「' + (data.level || '') + '」吗?', function(ix) {
poolsData.splice(idx, 1);
renderPoolTable();
layer.close(ix);
});
} else if (obj.event === 'edit') {
showPoolEdit(data, false);
} else if (obj.event === 'prize') {
currentPoolIndex = idx;
showPrizeDialog(data);
}
});
// 添加等级
$('#btn-add-pool').on('click', function() {
var newItem = { _clientId: newClientId(), poolType: currentPoolType, level: '', count: 0, weight: 10, prizeList: [] };
showPoolEdit(newItem, true);
});
// 保存配置
$('#btn-save').on('click', function() {
saveActivity();
});
$('#btn-init-lottery').on('click', function() {
layer.confirm('将用当前线上奖池制作的模板覆盖整个奖池活动配置(3组奖池、等级、奖项、权重和价格)。活动会保持关闭,玩家抽奖历史不清除。确定继续吗?', function(index) {
layer.close(index);
admin.req({
method: 'post',
url: setter.baseurl + 'adminapi/Lottery/initialize',
done: function(res) {
if (res.code === 0) {
layer.msg('初始化成功,活动保持关闭');
loadActivity();
} else {
layer.msg(res.info || '初始化失败');
}
}
});
});
});
// 刷新
$('#btn-reload').on('click', function() {
loadActivity();
layer.msg('刷新成功');
});
element.on('tab(lottery-pool-type)', function() {
currentPoolType = parseInt($(this).attr('lay-id')) || 1;
currentPoolIndex = -1;
currentPrizeIndex = -1;
renderPoolTable();
});
// ============ 奖池等级编辑弹窗 ============
function showPoolEdit(data, isAdd) {
var title = isAdd ? '添加等级' : '编辑等级';
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="level" value="' + (data.level || '') + '" placeholder="如: A, B, C" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">数量</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="count" value="' + (data.count || 0) + '" placeholder="奖项总数量" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">权重</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="weight" value="' + (data.weight || 10) + '" placeholder="等级权重,越大越易中" class="layui-input">' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: title,
area: ['450px', '300px'],
content: html,
btn: ['保存', '取消'],
success: function(layero) { form.render(); },
yes: function(idx, layero) {
var level = layero.find('input[name="level"]').val();
var count = parseInt(layero.find('input[name="count"]').val()) || 0;
var weight = parseInt(layero.find('input[name="weight"]').val()) || 10;
if (!level) { layer.msg('等级不能为空'); return; }
if (isAdd) {
poolsData.push({ _clientId: data._clientId || newClientId(), poolType: currentPoolType, level: level, count: count, weight: weight, prizeList: [] });
} else {
data.level = level;
data.count = count;
data.weight = weight;
}
layer.close(idx);
renderPoolTable();
}
});
}
// ============ 奖项管理弹窗 ============
function showPrizeDialog(poolData) {
if (!poolData.prizeList) poolData.prizeList = [];
currentPrizeIndex = -1;
var dialogContent = $('#prize-dialog-tpl').html();
layer.open({
type: 1,
title: '等级「' + (poolData.level || '') + '」- 奖项管理',
area: ['1000px', '620px'],
content: dialogContent,
shadeClose: true,
success: function(layero) {
// 必须先渲染 form,否则 select 不显示
form.render(null, 'prize-form');
renderPrizeTable(layero, poolData);
// 绑定按钮事件
layero.find('#btn-add-prize').on('click', function() {
poolData.prizeList.push({
prizeId: '', name: '', icon: '', type: 1, count: 0,
otherId: '', weight: 10, totalCount: 0, drawnCount: 0
});
currentPrizeIndex = poolData.prizeList.length - 1;
renderPrizeTable(layero, poolData, currentPrizeIndex);
loadPrizeForm(poolData.prizeList[currentPrizeIndex], layero);
});
layero.find('#btn-delete-prize').on('click', function() {
if (currentPrizeIndex < 0) return layer.msg('请先选择要删除的奖项');
poolData.prizeList.splice(currentPrizeIndex, 1);
currentPrizeIndex = -1;
clearPrizeForm(layero);
renderPrizeTable(layero, poolData, -1);
});
layero.find('#btn-save-prize').on('click', function() {
savePrizeItem(layero, poolData);
});
// 类型切换(filter 名必须与 HTML lay-filter 一致)
form.on('select(prizeType)', function(obj) {
if (obj.value === '3' || obj.value === '4') {
layero.find('.prize-prop-select').show();
renderPrizeProductOptions(layero, parseInt(obj.value, 10), '');
} else {
layero.find('.prize-prop-select').hide();
}
});
form.on('select(prizeOtherId)', function(obj) {
var type = parseInt(layero.find('#prizeType').val(), 10);
var source = type === 4 ? propFragmentList : propList;
var product = source.find(function(item) { return String(item.number) === String(obj.value); });
if (!product) return;
layero.find('#prizeName').val(product.name || '');
layero.find('#prizeIcon').val(propIcon(product.number));
});
}
});
}
function renderPrizeTable(layero, poolData, selectIndex) {
var list = poolData.prizeList || [];
table.render({
elem: layero.find('#table-prize-list'),
data: list,
page: false,
height: 380,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'prizeId', title: 'ID', width: 80 },
{ field: 'name', title: '名称', minWidth: 100 },
{ title: '图标', width: 70, templet: function(d) {
var src = prizeIcon(d);
return src ? '<img src="' + src + '" style="width:32px;height:32px;object-fit:contain" alt="图标">' : '';
}},
{ field: 'type', title: '类型', width: 80, templet: function(d) {
var map = {1: '金币', 2: '钻石', 3: '道具', 4: '碎片'};
return map[d.type] || '金币';
}},
{ field: 'count', title: '数量', width: 80 },
{ field: 'weight', title: '权重', width: 60 },
{ field: 'totalCount', title: '库存', width: 60, templet: function(d) {
return d.totalCount || '∞';
}}
]],
done: function() {
if (list.length > 0 && (selectIndex === undefined || selectIndex < 0)) {
selectIndex = 0;
}
if (selectIndex >= 0 && selectIndex < list.length) {
currentPrizeIndex = selectIndex;
var $tr = layero.find('#table-prize-list + .layui-table-view tr[data-index="' + selectIndex + '"]');
if ($tr.length) {
$tr.addClass('layui-table-click');
loadPrizeForm(list[selectIndex], layero);
}
}
}
});
// 行点击切换
layero.find('#table-prize-list + .layui-table-view').off('click').on('click', 'tr[data-index]', function() {
var idx = parseInt($(this).attr('data-index'));
if (idx >= 0 && idx < list.length) {
currentPrizeIndex = idx;
layero.find('tr[data-index]').removeClass('layui-table-click');
$(this).addClass('layui-table-click');
loadPrizeForm(list[idx], layero);
}
});
}
function loadPrizeForm(data, layero) {
layero.find('#prizeId').val(data.prizeId || '');
layero.find('#prizeName').val(data.name || '');
layero.find('#prizeIcon').val(prizeIcon(data));
layero.find('#prizeType').val(data.type || 1);
layero.find('#prizeCount').val(data.count || 0);
renderPrizeProductOptions(layero, parseInt(data.type, 10) || 1, data.otherId || '');
layero.find('#prizeWeight').val(data.weight || 10);
layero.find('#prizeTotalCount').val(data.totalCount || 0);
if (data.type === 3 || data.type === 4) {
layero.find('.prize-prop-select').show();
} else {
layero.find('.prize-prop-select').hide();
}
form.render('select');
}
function renderPrizeProductOptions(layero, type, selected) {
var select = layero.find('#prizeOtherId');
var source = type === 4 ? propFragmentList : propList;
select.empty().append('<option value="">请选择道具/碎片</option>');
source.forEach(function(item) {
var number = String(item.number || '');
select.append($('<option></option>').val(number).text(number + ' - ' + (item.name || '')));
});
select.val(String(selected || ''));
form.render('select', 'prize-form');
}
function clearPrizeForm(layero) {
layero.find('#prizeId').val('');
layero.find('#prizeName').val('');
layero.find('#prizeIcon').val('');
layero.find('#prizeType').val('1');
layero.find('#prizeCount').val('0');
layero.find('#prizeOtherId').val('');
layero.find('#prizeWeight').val('10');
layero.find('#prizeTotalCount').val('0');
layero.find('.prize-prop-select').hide();
form.render('select');
}
function savePrizeItem(layero, poolData) {
if (currentPrizeIndex < 0) return layer.msg('请先选择要编辑的奖项');
var list = poolData.prizeList;
list[currentPrizeIndex] = {
prizeId: layero.find('#prizeId').val() || '',
name: layero.find('#prizeName').val() || '',
icon: layero.find('#prizeIcon').val() || propIcon(layero.find('#prizeOtherId').val()) || '',
type: parseInt(layero.find('#prizeType').val()) || 1,
count: parseInt(layero.find('#prizeCount').val()) || 0,
otherId: layero.find('#prizeOtherId').val() || '',
weight: parseInt(layero.find('#prizeWeight').val()) || 10,
totalCount: parseInt(layero.find('#prizeTotalCount').val()) || 0,
drawnCount: 0
};
renderPrizeTable(layero, poolData, currentPrizeIndex);
renderPoolTable();
layer.msg('保存成功');
}
// ============ 保存活动 ============
function saveActivity() {
var activityName = $('input[name="activityName"]').val();
if (!activityName) { layer.msg('活动名称不能为空'); return; }
var startTimeStr = $('input[name="startTime"]').val();
var endTimeStr = $('input[name="endTime"]').val();
function parseCosts(name) {
var value = $('input[name="' + name + '"]').val() || '';
return value.split(',').map(function(s) { return parseInt(s.trim()) || 0; }).filter(function(n) { return n > 0; });
}
var costsCannon = parseCosts('costsCannon');
var costsGodWar = parseCosts('costsGodWar');
var costsWing = parseCosts('costsWing');
if (costsCannon.length === 0) { layer.msg('请配置炮台奖池消耗金币'); return; }
if (costsGodWar.length === 0) { layer.msg('请配置战神奖池消耗金币'); return; }
if (costsWing.length === 0) { layer.msg('请配置翅膀奖池消耗金币'); return; }
var startTs = startTimeStr ? Math.floor(new Date(startTimeStr).getTime() / 1000) : 0;
var endTs = endTimeStr ? Math.floor(new Date(endTimeStr).getTime() / 1000) : 0;
var param = {
activityId: $('#activityId').val() || '',
activityName: activityName,
isOpen: $('select[name="isOpen"]').val() === 'true',
startTime: startTs,
endTime: endTs,
userMaxDraw: parseInt($('input[name="userMaxDraw"]').val()) || 100,
costsByPool: {
1: costsCannon,
2: costsGodWar,
3: costsWing
},
// _clientId 仅用于解决 Layui 行副本定位问题,不提交到服务端。
pools: poolsData.map(function(p) {
var copy = $.extend(true, {}, p);
delete copy._clientId;
(copy.prizeList || []).forEach(function(prize) {
var type = parseInt(prize.type, 10);
if (type === 3 || type === 4) {
prize.icon = prize.icon || propIcon(prize.otherId) || '';
}
});
return copy;
})
};
admin.req({
method: 'post',
contentType: 'application/json;charset=UTF-8',
url: setter.baseurl + 'adminapi/Lottery/save',
data: JSON.stringify(param),
done: function(res) {
if (res.code === 0) {
layer.msg('保存成功');
loadActivity();
} else {
layer.msg(res.info || '保存失败');
}
}
});
}
// ============ 启动 ============
loadProps();
loadActivity();
});
</script>
<style>
.prize-container { display: flex; height: 480px; padding: 10px; }
.prize-left { flex: 1; margin-right: 15px; }
.prize-table-wrap { height: 400px; overflow: auto; }
.prize-right { width: 300px; border-left: 1px solid #e6e6e6; padding-left: 15px; }
.prize-right h4 { margin-bottom: 15px; color: #333; font-size: 16px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
.prize-toolbar { margin-bottom: 10px; }
.prize-toolbar .layui-btn { margin-right: 5px; }
.prize-right .layui-form-item { margin-bottom: 10px; }
.prize-right .layui-form-label { padding: 9px 0; width: 60px; }
.prize-right .layui-input-block { margin-left: 70px; }
</style>
@@ -0,0 +1,298 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>后台充值/扣除</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="../../../layui/css/layui.css">
<script src="../../../layui/layui.js"></script>
<script src="../../../layui/xm-select.js"></script>
</head>
<body>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">后台充值/扣除</div>
<div class="layui-card-body">
<!-- 操作表单 -->
<form class="layui-form" lay-filter="operation-form" style="margin-bottom:20px;">
<div class="layui-form-item">
<label class="layui-form-label">目标用户</label>
<div class="layui-input-inline" style="width:300px;">
<input type="text" name="targetUserId" id="targetUser" required lay-verify="required"
placeholder="请输入用户ID或用户名" class="layui-input">
<input type="hidden" id="searchedUserId" value="">
<div class="layui-form-mid layui-word-aux">支持ID或用户名搜索</div>
</div>
<button type="button" class="layui-btn layui-btn-normal" id="btn-search-user">
<i class="layui-icon layui-icon-search"></i> 搜索
</button>
</div>
<div class="layui-form-item">
<label class="layui-form-label">操作类型</label>
<div class="layui-input-inline">
<input type="radio" name="operationType" value="1" title="充值" checked>
<input type="radio" name="operationType" value="2" title="扣除">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">货币类型</label>
<div class="layui-input-inline">
<select name="currencyType" lay-verify="required">
<option value="1">金币</option>
<option value="2">钻石</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">操作数量</label>
<div class="layui-input-inline">
<input type="number" name="amount" required lay-verify="required|number"
placeholder="请输入数量" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-inline" style="width:400px;">
<input type="text" name="remark" placeholder="备注信息" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-danger" lay-submit lay-filter="btn-deduct">扣除</button>
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="btn-charge">充值</button>
</div>
</div>
</form>
<!-- 操作日志查询 -->
<div class="layui-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="adminUser" id="adminUser-search"
placeholder="后台操作者ID/用户名" class="layui-input">
</div>
<div class="layui-inline">
<input type="text" name="targetUserSearch" id="targetUser-search"
placeholder="目标用户ID/用户名" class="layui-input">
</div>
<div class="layui-inline">
<select name="operationTypeSearch" id="operationType-search">
<option value="">全部</option>
<option value="1">充值</option>
<option value="2">扣除</option>
</select>
</div>
<div class="layui-inline">
<button class="layui-btn" id="btn-search-logs">
<i class="layui-icon layui-icon-search"></i> 查询
</button>
</div>
</div>
</div>
<!-- 操作日志表格 -->
<table id="table-logs" lay-filter="table-logs"></table>
</div>
</div>
</div>
<script>
layui.use(['table', 'form', 'layer'], function(){
var table = layui.table;
var form = layui.form;
var layer = layui.layer;
var $ = layui.$;
var setter = layui.setter;
// 渲染表单元素
form.render();
// 货币类型名称映射
var currencyNames = { 1: '金币', 2: '钻石' };
// 初始化操作日志表格
var tableIns = table.render({
elem: '#table-logs',
url: setter.baseurl + 'adminapi/BackgroundOperation/logs',
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
method: 'get',
page: true,
limit: 20,
limits: [20, 50, 100],
where: {
adminUserId: '',
targetUserId: '',
operationType: ''
},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'OperationTime', title: '操作时间', width: 160, templet: function(d) {
return formatDateTime(d.operationTime);
}},
{ field: 'adminUserName', title: '操作者', width: 120 },
{ field: 'targetUserName', title: '目标用户', width: 120 },
{ field: 'operationType', title: '操作类型', width: 100, templet: function(d) {
return d.operationType === 1 ?
'<span class="layui-badge layui-bg-green">充值</span>' :
'<span class="layui-badge layui-bg-red">扣除</span>';
}},
{ field: 'currencyType', title: '货币类型', width: 100, templet: function(d) {
return currencyNames[d.currencyType] || '未知(' + d.currencyType + ')';
}},
{ field: 'amount', title: '数量', width: 100 },
{ field: 'remark', title: '备注', minWidth: 200 }
]],
response: {
statusCode: 0, // 规定成功的状态码,默认:0
msgName: 'info', // 规定状态信息的字段名称,默认:msg
countName: 'count', // 规定数据总数的字段名称,默认:count
dataName: 'data' // 规定数据列表的字段名称,默认:data
}
});
// 搜索用户(用于绑定目标用户)
$('#btn-search-user').click(function(){
var keyword = $('#targetUser').val().trim();
if (!keyword) {
layer.msg('请输入用户ID或用户名');
return;
}
// 显示加载中
var loading = layer.load(1);
$.ajax({
url: setter.baseurl + 'adminapi/BackgroundOperation/search-user',
type: 'get',
data: { keyword: keyword },
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
dataType: 'json',
success: function(res) {
layer.close(loading);
if (res.code === 0) {
// 搜索成功,显示用户信息并保存 userId
var user = res.data;
$('#searchedUserId').val(user.userId);
layer.alert(
'用户信息:<br>' +
'用户ID:' + user.userId + '<br>' +
'用户名:' + user.userName + '<br>' +
'手机号:' + (user.userPhone || '未绑定') + '<br>' +
'金币:' + user.gold + '<br>' +
'钻石:' + user.diamond + '<br>' +
'注册时间:' + formatDateTime(user.registerTime),
{
icon: 1,
title: '用户信息'
}
);
} else {
layer.msg(res.info || '用户不存在');
}
},
error: function(xhr, status, error) {
layer.close(loading);
console.error('搜索用户失败:', error);
layer.msg('搜索用户失败,请检查网络连接');
}
});
});
// 充值按钮
form.on('submit(btn-charge)', function(data){
var field = data.field;
field.operationType = 1; // 充值
$.ajax({
url: setter.baseurl + 'adminapi/BackgroundOperation/charge',
type: 'post',
contentType: 'application/json',
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
data: JSON.stringify({
targetUserId: $('#searchedUserId').val(),
currencyType: parseInt(field.currencyType),
amount: parseFloat(field.amount),
remark: field.remark || ''
}),
success: function(res) {
if (res.code === 0) {
layer.msg('充值成功', {icon: 1});
tableIns.reload();
} else {
layer.msg(res.info || '充值失败');
}
}
});
return false;
});
// 扣除按钮
form.on('submit(btn-deduct)', function(data){
var field = data.field;
field.operationType = 2; // 扣除
layer.confirm('确认要扣除 ' + field.amount + ' ' +
(field.currencyType == 1 ? '金币' : '钻石') + '?', function(idx) {
$.ajax({
url: setter.baseurl + 'adminapi/BackgroundOperation/deduct',
type: 'post',
contentType: 'application/json',
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
data: JSON.stringify({
targetUserId: $('#searchedUserId').val(),
currencyType: parseInt(field.currencyType),
amount: parseFloat(field.amount),
remark: field.remark || ''
}),
success: function(res) {
if (res.code === 0) {
layer.msg('扣除成功', {icon: 1});
tableIns.reload();
} else {
layer.msg(res.info || '扣除失败');
}
}
});
layer.close(idx);
});
return false;
});
// 查询日志
$('#btn-search-logs').click(function(){
var where = {};
var adminUser = $('#adminUser-search').val().trim();
var targetUser = $('#targetUser-search').val().trim();
var operationType = $('#operationType-search').val();
if (adminUser) where.adminUserId = adminUser;
if (targetUser) where.targetUserId = targetUser;
if (operationType) where.operationType = parseInt(operationType);
tableIns.reload({
where: where,
page: { curr: 1 }
});
});
function formatDateTime(timestamp) {
if (!timestamp) return '';
var date = new Date(timestamp * 1000);
return date.toLocaleString('zh-CN');
}
});
</script>
</body>
</html>
@@ -0,0 +1,715 @@
<title>靓号管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>靓号管理</cite></a>
<a><cite>靓号列表</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-beautifulnumber-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">靓号</label>
<div class="layui-input-block">
<input type="text" name="numberSearch" placeholder="靓号搜索" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">等级</label>
<div class="layui-input-block">
<select name="level">
<option value="">全部</option>
<option value="S">S级</option>
<option value="A">A级</option>
<option value="B">B级</option>
<option value="C">C级</option>
<option value="D">D级</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="status">
<option value="">全部</option>
<option value="0">未售</option>
<option value="1">已售</option>
<option value="2">冻结</option>
<option value="3">普通号</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-beautifulnumber" lay-submit lay-filter="LAY-beautifulnumber-front-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-beautifulnumber-front-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-btn-group">
<button class="layui-btn layui-btn-sm" lay-event="addNumber">
<i class="layui-icon layui-icon-add-circle"></i> 添加靓号
</button>
<button class="layui-btn layui-btn-sm" lay-event="batchAddNumbers">
<i class="layui-icon layui-icon-add-circle"></i> 批量添加
</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-event="initRules">
<i class="layui-icon layui-icon-refresh"></i> 初始化规则
</button>
<button class="layui-btn layui-btn-sm" lay-event="initNumbers">
<i class="layui-icon layui-icon-refresh"></i> 根据规则生成靓号
</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
<br><br>
<table id="LAY-beautifulnumber-back-manage" lay-filter="LAY-beautifulnumber-back-manage"></table>
<script type="text/html" id="table-beautifulnumber-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="give"><i class="layui-icon layui-icon-gift"></i>赠送</a>
<!--<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>-->
<a class="layui-btn layui-btn-xs" lay-event="editPrice"><i class="layui-icon layui-icon-edit"></i>改价</a>
{{# if(d.status === 0){ }}
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="freeze"><i class="layui-icon layui-icon-close"></i>冻结</a>
{{# } else if(d.status === 2){ }}
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="unfreeze"><i class="layui-icon layui-icon-ok"></i>解冻</a>
{{# } }}
{{# if(d.status !== 3){ }}
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="setNormal"><i class="layui-icon layui-icon-set"></i>普通号</a>
{{# } }}
{{# if(d.status !== 1){ }}
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
{{# } }}
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form'], function () {
var i = (layui.$, layui.admin),
$ = layui.$,
admin = layui.admin,
view = layui.view,
table = layui.table,
setter = layui.setter,
form = layui.form;
form.render(null, 'layadmin-beautifulnumber-formlist');
//监听搜索
form.on('submit(LAY-beautifulnumber-front-search)', function (data) {
var field = data.field;
// 转换状态为数字
if (field.status) {
field.status = parseInt(field.status);
}
//执行重载
table.reload('LAY-beautifulnumber-back-manage', {
where: field,
page: { curr: 1 }
});
});
// 监听重置
$('.layui-btn-group').on('click', '[lay-event="refresh"]', function () {
table.reload('LAY-beautifulnumber-back-manage');
});
// 添加单个靓号
$('.layui-btn-group').on('click', '[lay-event="addNumber"]', function () {
var content =
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-add-number">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">靓号</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="number" class="layui-input" placeholder="输入靓号" required>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<select name="level">' +
'<option value="S">S级</option>' +
'<option value="A">A级</option>' +
'<option value="B">B级</option>' +
'<option value="C">C级</option>' +
'<option value="D" selected>D级</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" class="layui-input" placeholder="金币数量" value="0">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-add-number-submit">添加</button>' +
'</div>' +
'</div>' +
'</form>';
layer.open({
type: 1,
title: '添加靓号',
area: ['600px', '450px'],
content: content
});
layui.form.render();
layui.form.on('submit(LAY-add-number-submit)', function(dataField) {
var param = {
number: dataField.field.number,
level: dataField.field.level,
price: parseInt(dataField.field.price) || 0
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/add-number",
data: JSON.stringify(param),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.closeAll();
layer.msg('添加成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '添加失败');
}
}
});
return false;
});
});
// 批量添加靓号
$('.layui-btn-group').on('click', '[lay-event="batchAddNumbers"]', function () {
var content =
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-batch-add">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">靓号列表</label>' +
'<div class="layui-input-block">' +
'<textarea name="numbers" class="layui-textarea" placeholder="每行一个号码" style="height: 200px;" required></textarea>' +
'<div style="color:#999;font-size:12px;margin-top:5px;">每行输入一个靓号,系统会自动过滤已存在的号码</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<select name="level">' +
'<option value="S">S级</option>' +
'<option value="A">A级</option>' +
'<option value="B">B级</option>' +
'<option value="C">C级</option>' +
'<option value="D" selected>D级</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" class="layui-input" placeholder="金币数量" value="0">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-batch-add-submit">批量添加</button>' +
'</div>' +
'</div>' +
'</form>';
layer.open({
type: 1,
title: '批量添加靓号',
area: ['500px', '500px'],
content: content
});
layui.form.render();
layui.form.on('submit(LAY-batch-add-submit)', function(dataField) {
var numbersText = dataField.field.numbers.trim();
if (!numbersText) {
layer.msg('请输入靓号列表');
return false;
}
var numbers = numbersText.split('\n').map(function(n) { return n.trim(); }).filter(function(n) { return n; });
var param = {
numbers: numbers,
level: dataField.field.level,
price: parseInt(dataField.field.price) || 0
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/batch-add-numbers",
data: JSON.stringify(param),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.closeAll();
layer.msg(res.data ? res.data.message : '添加成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '添加失败');
}
}
});
return false;
});
});
// 监听初始化规则
$('.layui-btn-group').on('click', '[lay-event="initRules"]', function () {
layer.confirm("确定初始化靓号规则吗?", function (idx) {
admin.req({
method: "post",
url: setter.baseurl + "adminapi/BeautifulNumber/init-rules",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
} else {
layer.msg(res.info || '初始化失败');
}
layer.close(idx);
}
})
})
});
// 监听根据规则生成靓号
$('.layui-btn-group').on('click', '[lay-event="initNumbers"]', function () {
layer.confirm("确定根据规则生成靓号吗?已售出的靓号不会被删除,未售出的靓号将按规则重新生成。", function (idx) {
admin.req({
method: "post",
url: setter.baseurl + "adminapi/BeautifulNumber/init-numbers",
done: function (res) {
layer.close(idx);
if (res.code === 0 || res.code === 200) {
layer.msg(res.info || '初始化成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '初始化失败');
}
}
})
})
});
table.render({
elem: "#LAY-beautifulnumber-back-manage",
url: setter.baseurl + "adminapi/BeautifulNumber/list",
method: "get",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
where: {},
request: {
pageName: 'page',
limitName: 'size'
},
limit: 10,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "number", title: "靓号", width: 150 },
{ field: "ruleName", title: "规则", width: 120 },
{
field: "level", title: "等级", width: 80,
templet: function (d) {
var colors = { 'S': '#ff4d4f', 'A': '#faad14', 'B': '#722ed1', 'C': '#1890ff', 'D': '#8c8c8c' };
return '<span style="color:' + (colors[d.level] || '#8c8c8c') + ';font-weight:bold;">' + d.level + '</span>';
}
},
{ field: "price", title: "价格", width: 120, templet: function (d) { return formatNumber(d.price); } },
{
field: "status", title: "状态", width: 100,
templet: function (d) {
var statusMap = { 0: '<span class="layui-badge">未售</span>', 1: '<span class="layui-badge layui-bg-green">已售</span>', 2: '<span class="layui-badge layui-bg-orange">冻结</span>', 3: '<span class="layui-badge layui-bg-gray">普通号</span>' };
return statusMap[d.status] || '-';
}
},
{
field: "saleTime", title: "售出时间", width: 160,
templet: function (d) {
return d.saleTime ? formatTimestamp(d.saleTime) : '-';
}
},
{ field: "userId", title: "购买者ID", width: 150 },
{ width: 450, title: "操作", align: "center", fixed: "right", toolbar: "#table-beautifulnumber-admin" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
table.on("tool(LAY-beautifulnumber-back-manage)", function (e) {
var l = e.data;
if ("del" === e.event) {
layer.confirm("确定删除此靓号么?", function (i) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/delete",
data: JSON.stringify({ id: l.id }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
e.del(), layer.close(i);
layer.msg('删除成功');
} else {
layer.msg(res.info || '删除失败');
}
}
})
})
}
if ("editPrice" === e.event) {
i.popup({
title: "修改价格",
area: ["400px", "200px"],
id: "LAY-popup-edit-price",
success: function (div, idx) {
$(div).html(
'<form class="layui-form" style="padding: 20px;" lay-filter="layadmin-form-edit-price">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">靓号</label>' +
'<div class="layui-input-block">' +
'<input type="text" class="layui-input" value="' + (l.number || '') + '" disabled>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">新价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" class="layui-input" value="' + (l.price || 0) + '" required>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-edit-price-submit">修改</button>' +
'</div>' +
'</div>' +
'</form>'
);
form.render(null, "layadmin-form-edit-price");
form.on("submit(LAY-edit-price-submit)", function (data) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/update-price",
data: JSON.stringify({ id: l.id, price: parseInt(data.field.price) }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.close(idx);
layer.msg('修改成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '修改失败');
}
}
})
return false;
});
}
})
}
if ("freeze" === e.event) {
layer.confirm("确定冻结此靓号么?", function (i) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/freeze",
data: JSON.stringify({ id: l.id }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('冻结成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '冻结失败');
}
layer.close(i);
}
})
})
}
if ("unfreeze" === e.event) {
layer.confirm("确定解冻此靓号么?", function (i) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/unfreeze",
data: JSON.stringify({ id: l.id }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('解冻成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '解冻失败');
}
layer.close(i);
}
})
})
}
if ("setNormal" === e.event) {
layer.confirm("确定将此靓号设为普通号么?", function (i) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/set-normal",
data: JSON.stringify({ id: l.id }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('设置成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '设置失败');
}
layer.close(i);
}
})
})
}
if ("give" === e.event) {
i.popup({
title: "赠送靓号给用户",
area: ["600px", "380px"],
id: "LAY-popup-give-number",
success: function (div, idx) {
$(div).html(
'<div style="padding:20px;">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label" style="width:70px;">靓号</label>' +
'<div class="layui-input-block" style="left:90px;">' +
'<span class="layui-badge layui-bg-blue" style="padding:6px 15px;font-size:14px;">' + (l.number || '') + '</span>' +
'</div>' +
'</div>' +
'<div class="layui-form-item" style="margin-bottom:5px;">' +
'<label class="layui-form-label" style="width:70px;">选择用户</label>' +
'<div class="layui-input-block" style="left:90px;position:relative;">' +
'<input type="text" id="user-search-input" class="layui-input" style="padding-right:35px;" placeholder="输入用户名、昵称、用户编号或手机号搜索">' +
'<i class="layui-icon layui-icon-loading layui-anim layui-anim-loop" id="search-loading" style="position:absolute;right:10px;top:10px;display:none;"></i>' +
'</div>' +
'</div>' +
'<div id="user-dropdown" class="user-dropdown-list" style="display:none;position:absolute;left:90px;width:calc(100% - 90px);max-height:250px;overflow-y:auto;background:#fff;border:1px solid #e6e6e6;border-radius:2px;box-shadow:0 2px 4px rgba(0,0,0,.1);z-index:99999;"></div>' +
'<div style="margin-top:10px;color:#999;font-size:12px;margin-left:70px;">提示:可按用户名、昵称、用户编号、手机号进行模糊搜索</div>' +
'<div id="selected-user-info" style="margin-top:15px;padding:15px;background:#e8f4ff;border-radius:6px;border:1px solid #b8d6f0;display:none;margin-left:70px;">' +
'<div style="display:flex;align-items:center;justify-content:space-between;">' +
'<div>' +
'<span style="color:#666;">已选择用户:</span>' +
'<span id="selected-user-text" style="color:#1e88e5;font-weight:bold;font-size:15px;"></span>' +
'</div>' +
'<button type="button" class="layui-btn layui-btn-sm layui-btn-danger" id="btn-clear-user"><i class="layui-icon layui-icon-close"></i> 清除</button>' +
'</div>' +
'</div>' +
'<div style="margin-top:20px;text-align:center;">' +
'<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" id="btn-cancel-give" style="margin-right:15px;">取消</button>' +
'<button type="button" class="layui-btn layui-btn-sm" id="btn-confirm-give"><i class="layui-icon layui-icon-ok"></i> 确认赠送</button>' +
'</div>' +
'</div>'
);
var selectedUser = null;
var searchTimer = null;
var $dropdown = $('#user-dropdown');
var $input = $('#user-search-input');
function renderDropdown(users) {
if (!users || users.length === 0) {
$dropdown.html('<div style="padding:15px;color:#999;text-align:center;">未找到匹配的用户</div>').show();
return;
}
var html = '';
users.forEach(function(user) {
var number = user.baseInfo ? user.baseInfo.number : '';
var name = user.baseInfo ? user.baseInfo.userName : '';
var mobile = user.baseInfo ? user.baseInfo.mobile : '';
html += '<div class="user-dropdown-item" data-id="' + user.id + '" data-number="' + number + '" data-name="' + name + '" data-mobile="' + mobile + '" style="padding:12px 15px;cursor:pointer;border-bottom:1px solid #f0f0f0;">' +
'<div style="display:flex;justify-content:space-between;align-items:center;">' +
'<div><span style="font-weight:bold;color:#333;font-size:14px;">' + name + '</span> <span style="color:#666;font-size:12px;">[' + number + ']</span></div>' +
(mobile ? '<span style="color:#999;font-size:12px;">' + mobile + '</span>' : '<span style="color:#999;font-size:12px;">无手机号</span>') +
'</div></div>';
});
$dropdown.html(html).show();
}
function searchUsers(keyword) {
if (!keyword || keyword.length < 1) {
$dropdown.hide();
return;
}
$('#search-loading').show();
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/AdminUser/GetUserList",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
data: JSON.stringify({ keyWord: keyword, pageIndex: 1, pageSize: 20 }),
done: function (res) {
$('#search-loading').hide();
if (res.data && res.data.length > 0) {
renderDropdown(res.data);
} else {
renderDropdown([]);
}
},
fail: function() {
$('#search-loading').hide();
renderDropdown([]);
}
});
}
$input.on('input', function() {
var keyword = $(this).val().trim();
clearTimeout(searchTimer);
if (keyword.length < 1) {
$dropdown.hide();
return;
}
searchTimer = setTimeout(function() {
searchUsers(keyword);
}, 300);
});
$input.on('focus', function() {
var keyword = $(this).val().trim();
if (keyword.length >= 1 && $dropdown.find('.user-dropdown-item').length > 0) {
$dropdown.show();
}
});
$(document).off('click', '.user-dropdown-item').on('click', '.user-dropdown-item', function() {
var userId = $(this).data('id');
var userNumber = $(this).data('number');
var userName = $(this).data('name');
var userMobile = $(this).data('mobile');
selectedUser = { id: userId, number: userNumber, name: userName, mobile: userMobile };
$input.val(name + ' [' + userNumber + ']' + (userMobile ? ' ' + userMobile : ''));
$dropdown.hide();
$('#selected-user-text').text(userName + ' [' + userNumber + ']' + (userMobile ? ' ' + userMobile : ''));
$('#selected-user-info').show();
});
$(document).on('click', function(e) {
if (!$(e.target).closest('#user-search-input, #user-dropdown').length) {
$dropdown.hide();
}
});
$('#btn-clear-user').on('click', function() {
selectedUser = null;
$input.val('').focus();
$('#selected-user-info').hide();
});
$('#btn-cancel-give').on('click', function() {
layer.close(idx);
});
$('#btn-confirm-give').on('click', function() {
if (!selectedUser) {
layer.msg('请先选择用户');
return;
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/give-to-user",
data: JSON.stringify({ number: l.number, userId: selectedUser.id }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.close(idx);
layer.msg('赠送成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '赠送失败');
}
}
});
});
}
})
}
});
// 数字格式化
function formatNumber(num) {
if (!num) return '0';
return num.toString().replace(/\B(?=(\d{4})+(?!\d))/g, ",");
}
// 时间戳格式化
function formatTimestamp(timestamp, format = 'yyyy-MM-dd hh:mm:ss') {
if (!timestamp) return '-';
const date = new Date(timestamp * 1000);
const o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
};
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (const k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return format;
}
// e("beautifulnumber", {})
});
</script>
@@ -0,0 +1,520 @@
<title>靓号规则配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>靓号管理</cite></a>
<a><cite>规则配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-group">
<button class="layui-btn layui-btn-sm" id="btn-add">
<i class="layui-icon layui-icon-add-circle"></i> 添加规则
</button>
<button class="layui-btn layui-btn-sm" id="btn-save">
<i class="layui-icon layui-icon-ok"></i> 保存规则
</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" id="btn-init">
<i class="layui-icon layui-icon-refresh"></i> 初始化规则
</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" id="btn-reload">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
</div>
<div class="layui-card-body">
<table id="LAY-rules-manage" lay-filter="LAY-rules-manage"></table>
<script type="text/html" id="table-rule-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script type="text/html" id="toolbar-rule">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add">添加规则</button>
</div>
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var rules = [];
function loadRules() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/BeautifulNumber/rules",
done: function (res) {
if (res.code === 0 && res.data) {
rules = res.data;
renderTable();
}
}
});
}
function renderTable() {
table.render({
elem: '#LAY-rules-manage',
data: rules,
cols: [[
{ field: "ruleName", title: "规则名称", width: 150, edit: 'text' },
{ field: "rulePattern", title: "生成模式", width: 150 },
{ field: "level", title: "等级", width: 70, edit: 'text' },
{ field: "price", title: "价格(金币)", width: 100, edit: 'text' },
{ field: "sort", title: "排序", width: 70, edit: 'text' },
{
field: "isEnable",
title: "启用",
width: 80,
templet: function(d) {
return d.isEnable ? '<span class="layui-badge">是</span>' : '<span class="layui-badge layui-bg-gray">否</span>';
}
},
{ width: 250, title: "操作", align: "center", fixed: "right", toolbar: "#table-rule-admin" }
]],
toolbar: '#toolbar-rule'
});
}
$('#btn-reload').on('click', function() {
loadRules();
layer.msg('刷新成功');
});
$('#btn-add').on('click', function() {
showAddDialog();
});
$('#btn-init').on('click', function() {
layer.confirm('确定初始化靓号规则吗?这将覆盖现有规则!', function(idx) {
admin.req({
method: "post",
url: setter.baseurl + "adminapi/BeautifulNumber/init-rules",
done: function (res) {
layer.close(idx);
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadRules();
} else {
layer.msg(res.info || '初始化失败');
}
}
});
});
});
$('#btn-save').on('click', function() {
layer.confirm('确定保存所有规则吗?', function(idx) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/update-rules",
data: JSON.stringify(rules),
done: function (res) {
layer.close(idx);
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadRules();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
});
table.on("tool(LAY-rules-manage)", function (obj) {
var data = obj.data;
if ("del" === obj.event) {
layer.confirm("确定删除此规则么?", function (i) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/delete-rule",
data: JSON.stringify({ id: data.id }),
done: function (res) {
layer.close(i);
if (res.code === 0 || res.code === 200) {
layer.msg('删除成功');
loadRules();
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
if ("edit" === obj.event) {
showEditDialog(data, obj);
}
});
table.on('toolbar(LAY-rules-manage)', function(obj){
if(obj.event === 'add'){
showAddDialog();
}
});
function showAddDialog() {
var content =
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-add-rule">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">规则名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="ruleName" class="layui-input" placeholder="如:AAAA、ABBB、ABCDEFGH、AAAABBBX">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<select name="level">' +
'<option value="S">S级</option>' +
'<option value="A">A级</option>' +
'<option value="B">B级</option>' +
'<option value="C">C级</option>' +
'<option value="D">D级</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" class="layui-input" placeholder="金币数量">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">排序</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="sort" class="layui-input" placeholder="数字越小越靠前" value="0">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-add-rule-submit">添加</button>' +
'</div>' +
'</div>' +
'</form>';
layer.open({
type: 1,
title: '添加规则',
area: ['650px', '500px'],
content: content
});
layui.form.render();
layui.form.on('submit(LAY-add-rule-submit)', function(dataField) {
var ruleName = dataField.field.ruleName.toUpperCase().trim();
if (!ruleName) {
layer.msg('请输入规则名称');
return false;
}
var newRule = {
ruleName: ruleName,
rulePattern: ruleName,
level: dataField.field.level,
price: parseInt(dataField.field.price) || 0,
sort: parseInt(dataField.field.sort) || 0,
isEnable: true
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/add-rule",
data: JSON.stringify(newRule),
done: function (res) {
layer.closeAll();
if (res.code === 0 || res.code === 200) {
layer.msg('添加成功');
loadRules();
} else {
layer.msg(res.info || '添加失败');
}
}
});
return false;
});
}
function showEditDialog(data, obj) {
var level = data.level || 'D';
var isEnabled = data.isEnable ? 'checked' : '';
var content =
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-edit-rule">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">规则名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="ruleName" class="layui-input" value="' + (data.ruleName || '') + '" placeholder="如:KKKKKKKK KKKKTTTT ABCDEFGH KKKKXXXX">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<select name="level">' +
'<option value="S" ' + (level === 'S' ? 'selected' : '') + '>S级</option>' +
'<option value="A" ' + (level === 'A' ? 'selected' : '') + '>A级</option>' +
'<option value="B" ' + (level === 'B' ? 'selected' : '') + '>B级</option>' +
'<option value="C" ' + (level === 'C' ? 'selected' : '') + '>C级</option>' +
'<option value="D" ' + (level === 'D' ? 'selected' : '') + '>D级</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" class="layui-input" value="' + (data.price || 0) + '" placeholder="金币数量">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">排序</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="sort" class="layui-input" value="' + (data.sort || 0) + '" placeholder="数字越小越靠前">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">启用</label>' +
'<div class="layui-input-block">' +
'<input type="checkbox" name="isEnable" lay-skin="switch" lay-text="开启|关闭" ' + isEnabled + '>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-edit-rule-submit">保存</button>' +
'</div>' +
'</div>' +
'</form>';
layer.open({
type: 1,
title: '编辑规则',
area: ['500px', '450px'],
content: content
});
layui.form.render();
layui.form.on('submit(LAY-edit-rule-submit)', function(formData) {
var isEnable = $(formData.form).find('input[name="isEnable"]').prop('checked');
var ruleName = formData.field.ruleName.toUpperCase().trim();
if (!ruleName) {
layer.msg('请输入规则名称');
return false;
}
var updatedRule = {
id: data.id,
ruleName: ruleName,
level: formData.field.level,
price: parseInt(formData.field.price) || 0,
sort: parseInt(formData.field.sort) || 0,
isEnable: isEnable
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/update-rule",
data: JSON.stringify(updatedRule),
done: function (res) {
layer.closeAll();
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadRules();
} else {
layer.msg(res.info || '保存失败');
}
}
});
return false;
});
}
function generateRegexPattern(ruleName) {
if (!ruleName) {
return { valid: false, message: '请输入规则名称' };
}
ruleName = ruleName.toUpperCase().trim();
if (ruleName.length!=8) {
return { valid: false, message: '规则名称长度必须是8位' };
}
// 检查是否全部是连续字母A-H (如 ABCDEFGH 表示8连号)
var isAllConsecutive = true;
var expectedChar = 'A';
for (var i = 0; i < ruleName.length; i++) {
if (ruleName[i] !== expectedChar) {
isAllConsecutive = false;
break;
}
expectedChar = String.fromCharCode(expectedChar.charCodeAt(0) + 1);
}
// 如果是 ABCDEFGH 这种连续字母,表示全部是连续数字
if (isAllConsecutive) {
var len = ruleName.length;
// 生成匹配连续数字的正则: 01234567, 12345678, 23456789
var patterns = [];
for (var start = 0; start <= 9 - len; start++) {
var seq = '';
for (var k = 0; k < len; k++) {
seq += (start + k);
}
if (patterns.length > 0 && patterns.length < 10) {
patterns.push(seq);
}
}
if (patterns.length > 0) {
return {
valid: true,
rulePattern: "^(" + patterns.join('|') + ")$"
};
}
}
var regex = "^";
var prevGroup = '';
for (var i = 0; i < ruleName.length; i++) {
var char = ruleName[i];
if (char === 'A') {
if (prevGroup !== 'A') {
var aLen = 0;
var j = i;
while (j < ruleName.length && ruleName[j] === 'A') {
aLen++;
j++;
}
if (aLen === 1) {
regex += "(\\d)";
} else {
regex += "(\\d)\\1{" + (aLen - 1) + "}";
}
prevGroup = 'A';
}
} else if (char === 'B') {
if (prevGroup !== 'B') {
var bLen = 0;
var j = i;
while (j < ruleName.length && ruleName[j] === 'B') {
bLen++;
j++;
}
regex += "(\\d{" + bLen + "})";
prevGroup = 'B';
}
} else if (char === 'X') {
regex += "\\d";
prevGroup = 'X';
} else if (char >= '0' && char <= '9') {
regex += char;
prevGroup = 'N';
} else if (char >= 'A' && char <= 'H') {
// C-H 也当作连续数字处理
if (prevGroup !== 'CONSECUTIVE') {
var cLen = 0;
var j = i;
while (j < ruleName.length) {
var c = ruleName[j];
if (c >= 'A' && c <= 'H') {
cLen++;
j++;
} else {
break;
}
}
regex += "(\\d{" + cLen + "})";
prevGroup = 'CONSECUTIVE';
i = j - 1;
}
} else {
return { valid: false, message: '规则名称只能包含A-H、X和数字' };
}
}
regex += "$";
try {
new RegExp(regex);
} catch(e) {
return { valid: false, message: '生成的正则表达式无效: ' + regex };
}
return {
valid: true,
rulePattern: regex
};
}
window.generateRegex = function() {
var ruleName = $('#ruleNameInput').val().toUpperCase().trim();
if (ruleName) {
var result = generateRegexPattern(ruleName);
if (result.valid) {
$('#rulePatternInput').val(result.rulePattern);
}
} else {
$('#rulePatternInput').val('');
}
};
window.generateEditRegex = function() {
var ruleName = $('#editRuleNameInput').val().toUpperCase().trim();
if (ruleName) {
var result = generateRegexPattern(ruleName);
if (result.valid) {
$('#editRulePatternInput').val(result.rulePattern);
}
} else {
$('#editRulePatternInput').val('');
}
};
table.on('edit(LAY-rules-manage)', function(obj){
var field = obj.field;
var value = obj.value;
var data = obj.data;
if (!isNaN(value) && value !== '') {
if (field === 'price' || field === 'sort') {
data[field] = parseInt(value);
}
}
if (field === 'ruleName') {
data.rulePattern = value;
renderTable();
}
});
loadRules();
});
</script>
@@ -0,0 +1,59 @@
<div class="layui-form" lay-filter="layuiadmin-form-admin" style="padding: 20px 30px 0 0;">
<input type="hidden" name="id">
<div class="layui-form-item">
<label class="layui-form-label">任务编号</label>
<div class="layui-input-inline">
<input type="text" name="number" placeholder="" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务描述</label>
<div class="layui-input-inline">
<input type="text" name="remark" placeholder="" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务类型</label>
<div class="layui-input-inline">
<select name="taskType">
<option value="1">每日任务</option>
<option value="2">在线时长</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务事件</label>
<div class="layui-input-inline">
<select name="taskEvent">
<option value="1">整点在线奖励</option>
<option value="2">在线时长</option>
<option value="3">捕获任意鱼</option>
<option value="4">捕获黄金鱼</option>
<option value="5">捕获Boss鱼</option>
<option value="6">赢取金币</option>
<option value="7">使用狂暴</option>
<option value="8">开炮任务</option>
<option value="9">登录任务</option>
<option value="10">充值任务</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务条件</label>
<div class="layui-input-inline">
<input type="number" name="taskCount" placeholder="" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">战令经验</label>
<div class="layui-input-inline">
<input type="number" name="exp" placeholder="" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="button" lay-submit lay-filter="LAY-user-back-submit" value="确认" class="layui-btn">
</div>
</div>
</div>
@@ -0,0 +1,231 @@
<title>任务管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏配置</cite></a>
<a><cite>任务管理</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-taskadmin-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="state">
<option value="-1">全部</option>
<option value="1">启用</option>
<option value="2">禁用</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">任务类型</label>
<div class="layui-input-block">
<select name="taskType">
<option value="-1">全部</option>
<option value="1">每日任务</option>
<option value="2">在线时长</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">任务事件</label>
<div class="layui-input-block">
<select name="taskEvent">
<option value="-1">全部</option>
<option value="1">整点在线奖励</option>
<option value="2">在线时长</option>
<option value="3">捕获任意鱼</option>
<option value="4">捕获黄金鱼</option>
<option value="5">捕获Boss鱼</option>
<option value="6">赢取金币</option>
<option value="7">使用狂暴</option>
<option value="8">开炮任务</option>
<option value="9">登录任务</option>
<option value="10">充值任务</option>
</select>
</div>
</div>
</div>
</div>
<div class="layui-card-body">
<div style="padding-bottom: 10px;">
<button class="layui-btn layuiadmin-btn-admin" data-type="add">添加</button>
</div>
<table id="LAY-task-back-manage" lay-filter="LAY-task-back-manage"></table>
<script type="text/html" id="table-taskadmin-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="state"><i class="layui-icon layui-icon-edit"></i>状态</a>
</script>
</div>
</div>
</div>
<script>
layui.use('task', layui.factory('task')).use(['admin','task', 'table'], function () {
var $ = layui.$
, admin = layui.admin
, view = layui.view
, table = layui.table
, setter = layui.setter
, form = layui.form;
form.render('select'); //刷新select选择框渲染
form.on('select', function (data) {
var data1 = form.val("layadmin-taskadmin-formlist");
data1.state = parseInt(data1.state);
data1.taskType = parseInt(data1.taskType);
data1.taskEvent = parseInt(data1.taskEvent);
table.reload('LAY-task-back-manage', {
where: data1
});
});
//监听搜索
form.on('submit(LAY-user-front-search)', function (data) {
var field = data.field;
//执行重载
table.reload('LAY-task-back-manage', {
where: field
});
});
table.on('tool(LAY-task-back-manage)', function (obj) {
var data = obj.data;
if (obj.event === 'edit') {
admin.popup({
title: '编辑任务'
, area: ["500px", "600px"]
, id: 'LAY-popup-user-edit'
, success: function (layero, index) {
view(this.id).render('config/TaskForm').done(function () {
form.val('layuiadmin-form-admin', {
id: data.id || '',
number: data.number || '',
remark: data.remark || '',
taskType: data.taskType || 1,
taskEvent: data.taskEvent || 1,
taskCount: data.taskCount || 0,
exp: data.exp || 0,
state: data.state || 1
});
form.render(null, 'layuiadmin-form-admin');
form.on('submit(LAY-user-back-submit)', function (formData) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/task/UpdateTask",
async: false,
data: JSON.stringify(formData.field),
done: function (res) {
if (res.code === 0) {
layui.table.reload('LAY-task-back-manage');
layer.close(index);
} else {
layer.msg(res.info || '操作失败');
}
}
});
});
});
}
});
} else if (obj.event === 'del') {
layer.confirm('确定删除吗?', function (index) {
admin.req({
url: setter.baseurl + "adminapi/task/DeleteTask",
data: { id: data.id },
done: function (res) {
if (res.code === 0) {
obj.del();
layer.close(index);
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
} else if (obj.event === 'state') {
var newState = data.state === 1 ? 2 : 1;
admin.req({
url: setter.baseurl + "adminapi/task/UpdateTaskState",
data: { id: data.id, state: newState },
done: function (res) {
if (res.code === 0) {
obj.update({ state: newState });
} else {
layer.msg(res.info || '状态更新失败');
}
}
});
}
});
//事件
var active = {
add: function () {
admin.popup({
title: '添加任务'
, area: ["500px", "600px"]
, id: 'LAY-popup-user-add'
, success: function (layero, index) {
view(this.id).render('config/TaskForm').done(function () {
form.render(null, 'layuiadmin-form-admin');
//监听提交
form.on('submit(LAY-user-back-submit)', function (data) {
console.log(data.field)
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/task/AddTask",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(data.field),
done: function (res) {
//$.ajax({});
layui.table.reload('LAY-task-back-manage'); //重载表格
layer.close(index); //执行关闭
}
})
//提交 Ajax 成功后,关闭当前弹层并重载表格
});
});
}
});
}
};
$('.layui-btn.layuiadmin-btn-admin').on('click', function () {
var type = $(this).data('type');
active[type] ? active[type].call(this) : '';
});
});
</script>
@@ -0,0 +1,254 @@
<title>聊天记录</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a lay-href="customer/conversation"><cite>会话列表</cite></a>
<a><cite>聊天记录</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header" style="display: flex; justify-content: space-between; align-items: center;">
<span id="conversation-title">聊天记录</span>
<div>
<button class="layui-btn layui-btn-sm layui-btn-danger" onclick="deleteAllMessages()">清空记录</button>
</div>
</div>
<div class="layui-card-body">
<table id="LAY-chatlog-manage" lay-filter="LAY-chatlog-manage"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-chatlog">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="deleteSelected">批量删除</button>
</div>
</script>
<script type="text/html" id="table-chatlog-toolbar">
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="delete"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script>
function getParameterByName(name) {
// 优先从 hash 中查找
var hash = window.location.hash;
if (hash && hash.includes('?')) {
var searchPart = hash.substring(hash.indexOf('?'));
var params = new URLSearchParams(searchPart);
return params.get(name);
}
// 如果 hash 中没有,再从主 URL 查找
var params = new URLSearchParams(window.location.search);
return params.get(name);
}
layui.use(['admin', 'table'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
setter = layui.setter;
// 获取URL参数
var conversationId = getParameterByName('conversationId') || '';
var userName = getParameterByName('userName') || '';
var customerName = getParameterByName('customerName') || '';
console.log(conversationId)
// 设置标题
document.getElementById('conversation-title').textContent = '聊天记录 - ' + userName + ' 与 ' + customerName;
console.log('conversationId:', conversationId);
table.render({
elem: "#LAY-chatlog-manage",
url: setter.baseurl + "adminapi/customer/chatlog/" + conversationId,
method: "get",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: false,
toolbar: '#toolbar-chatlog',
defaultToolbar: [],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{
field: "senderType",
title: "发送者",
width: 100,
templet: function(d) {
return d.senderType == 1 ? '<span class="layui-badge layui-bg-blue">用户</span>' : '<span class="layui-badge">客服</span>';
}
},
{ field: "senderName", title: "发送者名称", width: 150 },
{
field: "contentType",
title: "类型",
width: 100,
templet: function(d) {
return d.contentType == 1 ? '<span class="layui-badge layui-bg-green">文字</span>' : '<span class="layui-badge">图片</span>';
}
},
{
field: "content",
title: "内容",
templet: function(d) {
if (d.contentType == 2) {
return '<img src="' + d.content + '" style="max-width:200px;max-height:100px;cursor:pointer;" lay-event="viewImage">';
}
return d.content || '-';
}
},
{
field: "createTime",
title: "发送时间",
width: 180,
templet: function(d) {
return d.createTime ? formatTimestamp(d.createTime) : '-';
}
},
{ title: "操作", width: 100, align: "center", fixed: "right", toolbar: "#table-chatlog-toolbar" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
// 点击图片查看大图
table.on('tool(LAY-chatlog-manage)', function(e) {
if (e.event === 'viewImage') {
layer.open({
type: 1,
title: '图片预览',
area: ['800px', '600px'],
content: '<div style="text-align:center;padding:20px;"><img src="' + e.data.content + '" style="max-width:100%;max-height:100%;"></div>'
});
}
if (e.event === 'delete') {
layer.confirm("确定删除此消息吗?", function(index) {
admin.req({
method: "delete",
url: setter.baseurl + "adminapi/customer/message/" + e.data.id,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
done: function (res) {
if (res.code === 0) {
layer.msg('删除成功');
table.reload("LAY-chatlog-manage");
} else {
layer.msg(res.info || '删除失败');
}
layer.close(index);
}
});
});
}
});
// 批量删除
table.on('toolbar(LAY-chatlog-manage)', function(obj) {
var checkStatus = table.checkStatus(obj.config.id);
if (obj.event === 'deleteSelected') {
var data = checkStatus.data;
if (data.length === 0) {
layer.msg('请选择要删除的消息');
return;
}
layer.confirm("确定删除选中的 " + data.length + " 条消息吗?", function(index) {
var ids = data.map(function(d) { return d.id; });
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/customer/messages/delete-batch",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
data: JSON.stringify({ ids: ids }),
done: function (res) {
if (res.code === 0) {
layer.msg('删除成功');
table.reload("LAY-chatlog-manage");
} else {
layer.msg(res.info || '删除失败');
}
layer.close(index);
}
});
});
}
});
// 清空所有记录
window.deleteAllMessages = function() {
if (!conversationId) {
layer.msg('无效的会话ID');
return;
}
layer.confirm("确定清空此会话的所有聊天记录吗?此操作不可恢复!", function(index) {
admin.req({
method: "delete",
url: setter.baseurl + "adminapi/customer/messages/" + conversationId,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
done: function (res) {
if (res.code === 0) {
layer.msg('清空成功');
table.reload("LAY-chatlog-manage");
} else {
layer.msg(res.info || '清空失败');
}
layer.close(index);
}
});
});
};
// 时间戳格式化
function formatTimestamp(timestamp) {
if (!timestamp) return '-';
// 处理时间戳,可能是字符串或数字
var date;
if (typeof timestamp === 'string') {
// 如果是ISO格式字符串,直接解析
date = new Date(timestamp);
} else {
// 如果是时间戳,假设是毫秒级别
date = new Date(timestamp);
}
// 转换为北京时间 (UTC+8)
var utc = date.getTime() + (date.getTimezoneOffset() * 60000);
var beijingDate = new Date(utc + (3600000 * 8));
var o = {
'M+': beijingDate.getMonth() + 1,
'd+': beijingDate.getDate(),
'h+': beijingDate.getHours(),
'm+': beijingDate.getMinutes(),
's+': beijingDate.getSeconds()
};
var format = 'yyyy-MM-dd hh:mm:ss';
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (beijingDate.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return format;
}
});
</script>
@@ -0,0 +1,215 @@
<title>会话管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>客服管理</cite></a>
<a><cite>会话列表</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-conversation-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">用户ID</label>
<div class="layui-input-block">
<input type="text" name="userId" placeholder="请输入用户ID" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">客服ID</label>
<div class="layui-input-block">
<input type="text" name="customerId" placeholder="请输入客服ID" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="status">
<option value="">全部</option>
<option value="0">等待中</option>
<option value="1">进行中</option>
<option value="2">已结束</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-conversation" lay-submit lay-filter="LAY-conversation-front-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-conversation-front-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-btn-group">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
<br><br>
<table id="LAY-conversation-manage" lay-filter="LAY-conversation-manage"></table>
<script type="text/html" id="table-conversation-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="chatlog"><i class="layui-icon layui-icon-log"></i>聊天记录</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="delete"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
view = layui.view,
table = layui.table,
layer = layui.layer,
setter = layui.setter,
form = layui.form,
customerApiUrl = setter.customerApiUrl || setter.baseurl;
form.render(null, 'layadmin-conversation-formlist');
//监听搜索
form.on('submit(LAY-conversation-front-search)', function (data) {
var field = data.field;
//执行重载
table.reload('LAY-conversation-manage', {
where: field,
page: { curr: 1 }
});
});
// 监听重置
$('.layui-btn-group').on('click', '[lay-event="refresh"]', function () {
table.reload('LAY-conversation-manage');
});
table.render({
elem: "#LAY-conversation-manage",
url: customerApiUrl + "customerapi/customer/conversations",
method: "get",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
where: {
userId: "",
customerId: "",
status: ""
},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 10,
cols: [[
{ field: "userName", title: "用户名称", width: 150 },
{ field: "userId", title: "用户ID", width: 200 },
{ field: "customerName", title: "客服名称", width: 150 },
{ field: "customerId", title: "客服ID", width: 200 },
{
field: "status",
title: "状态",
width: 100,
templet: function(d) {
var statusMap = {
0: '<span class="layui-badge">等待中</span>',
1: '<span class="layui-badge layui-bg-green">进行中</span>',
2: '<span class="layui-badge layui-bg-gray">已结束</span>'
};
return statusMap[d.status] || '-';
}
},
{
field: "createTime",
title: "创建时间",
width: 180,
templet: function(d) {
return d.createTime ? formatTimestamp(d.createTime) : '-';
}
},
{
field: "endTime",
title: "结束时间",
width: 180,
templet: function(d) {
return d.endTime ? formatTimestamp(d.endTime) : '-';
}
},
{ width: 190, title: "操作", align: "center", fixed: "right", toolbar: "#table-conversation-admin" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
// 监听工具条
table.on("tool(LAY-conversation-manage)", function (e) {
var l = e.data;
if ("chatlog" === e.event) {
// 打开新页面查看聊天记录
var url = 'customer/chatlog.html?conversationId=' + l.id + '&userName=' + encodeURIComponent(l.userName || '') + '&customerName=' + encodeURIComponent(l.customerName || '');
window.location.hash = '/' + url;
} else if ("delete" === e.event) {
layer.confirm('确定删除该会话及全部聊天记录吗?此操作不可恢复。', function(index) {
admin.req({
method: 'delete',
// 删除属于后台管理操作,必须走带鉴权的 AdminApi。
url: setter.baseurl + 'adminapi/Customer/conversation/' + encodeURIComponent(l.id),
done: function(res) {
if (res.code === 0 || res.code === 200) {
e.del();
layer.close(index);
layer.msg(res.info || '删除成功');
table.reload('LAY-conversation-manage');
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
});
// 时间戳格式化
function formatTimestamp(timestamp) {
if (!timestamp) return '-';
var date = new Date(timestamp);
var o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
};
var format = 'yyyy-MM-dd hh:mm:ss';
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return format;
}
});
</script>
@@ -0,0 +1,452 @@
<title>客服管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>客服管理</cite></a>
<a><cite>客服列表</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-customer-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">客服名称</label>
<div class="layui-input-block">
<input type="text" name="userName" placeholder="请输入客服名称" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="status">
<option value="">全部</option>
<option value="0">离线</option>
<option value="1">在线</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-customer" lay-submit lay-filter="LAY-customer-front-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-customer-front-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-btn-group">
<button class="layui-btn layui-btn-sm" lay-event="add">
<i class="layui-icon layui-icon-add-1"></i> 添加客服
</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
<br><br>
<table id="LAY-customer-manage" lay-filter="LAY-customer-manage"></table>
<script type="text/html" id="table-customer-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
</div>
</div>
</div>
<style>
.avatar-upload-box { position: relative; display: inline-block; }
.avatar-upload-box .upload-btn { position: absolute; top: 0; left: 0; width: 80px; height: 80px; opacity: 0; cursor: pointer; }
.avatar-upload-preview { width: 80px; height: 80px; border: 1px dashed #ddd; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #fafafa; cursor: pointer; overflow: hidden; }
.avatar-upload-preview:hover { border-color: #009688; }
.avatar-upload-preview img { width: 100%; height: 100%; object-fit: cover; }
.avatar-upload-preview .placeholder { color: #999; font-size: 12px; text-align: center; }
</style>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
view = layui.view,
table = layui.table,
setter = layui.setter,
form = layui.form,
layer = layui.layer;
form.render(null, 'layadmin-customer-formlist');
//监听搜索
form.on('submit(LAY-customer-front-search)', function (data) {
var field = data.field;
//执行重载
table.reload('LAY-customer-manage', {
where: field,
page: { curr: 1 }
});
});
// 监听重置
$('.layui-btn-group').on('click', '[lay-event="refresh"]', function () {
table.reload('LAY-customer-manage');
});
table.render({
elem: "#LAY-customer-manage",
url: setter.baseurl + "adminapi/customer/list",
method: "get",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
where: {
userName: "",
status: ""
},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 10,
cols: [[
{ field: "userName", title: "客服名称", width: 150 },
{ field: "userId", title: "客服ID", width: 200 },
{
field: "avatar",
title: "头像",
width: 100,
templet: function(d) {
return d.avatar ? '<img src="' + d.avatar + '" style="width:40px;height:40px;">' : '-';
}
},
{
field: "status",
title: "状态",
width: 100,
templet: function(d) {
return d.status == 1 ? '<span class="layui-badge layui-bg-green">在线</span>' : '<span class="layui-badge">离线</span>';
}
},
{
field: "createTime",
title: "创建时间",
width: 180,
templet: function(d) {
return d.createTime ? formatTimestamp(d.createTime) : '-';
}
},
{ width: 180, title: "操作", align: "center", fixed: "right", toolbar: "#table-customer-admin" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
// 监听工具条
table.on("tool(LAY-customer-manage)", function (e) {
var l = e.data;
if ("del" === e.event) {
layer.confirm("确定删除此客服吗?", function (i) {
admin.req({
method: "delete",
url: setter.baseurl + "adminapi/customer/" + e.data.id,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
done: function (res) {
if (res.code === 0) {
e.del();
layer.close(i);
layer.msg('删除成功');
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
if ("edit" === e.event) {
var avatarHtml = l.avatar
? '<img src="' + l.avatar + '" style="width:100%;height:100%;">'
: '<div class="placeholder">点击上传</div>';
admin.popup({
title: "编辑客服",
area: ["500px", "450px"],
id: "LAY-popup-customer-edit",
success: function (div, idx) {
$(div).html(
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-customer">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">客服名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="userName" class="layui-input" value="' + (l.userName || '') + '" required>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">头像</label>' +
'<div class="layui-input-block">' +
'<div class="avatar-upload-box">' +
'<div class="avatar-upload-preview" id="avatarPreview">' + avatarHtml + '</div>' +
'<input type="file" class="upload-btn" accept="image/*" id="avatarUpload">' +
'</div>' +
'<input type="hidden" name="avatar" id="avatarInput" value="' + (l.avatar || '') + '">' +
'<div style="margin-top:8px;color:#999;font-size:12px;">点击图片可预览,支持jpg、png格式</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">状态</label>' +
'<div class="layui-input-block">' +
'<select name="status">' +
'<option value="0" ' + (l.status == 0 ? 'selected' : '') + '>离线</option>' +
'<option value="1" ' + (l.status == 1 ? 'selected' : '') + '>在线</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-customer-edit-submit">提交</button>' +
'</div>' +
'</div>' +
'</form>'
);
form.render(null, "layuiadmin-form-customer");
var $preview = $('#avatarPreview');
var $input = $('#avatarInput');
var $fileInput = $('#avatarUpload');
$preview.on('click', function() {
var url = $input.val();
if (url) {
layer.open({
type: 1,
title: '头像预览',
area: ['auto', 'auto'],
shadeClose: true,
content: '<div style="padding:20px;text-align:center;"><img src="' + url + '" style="max-width:300px;max-height:300px;border-radius:50%;"></div>'
});
}
});
$fileInput.on('change', function() {
var file = this.files[0];
if (file) {
var formData = new FormData();
formData.append('file', file);
$.ajax({
url: setter.baseurl + 'adminapi/ImageUpload/upload/temp',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
success: function(res) {
if (res.code === 0 || res.code === 200) {
var path = res.data.path;
$input.val(path);
$preview.html('<img src="' + path + '" style="width:100%;height:100%;">');
layer.msg('上传成功');
} else {
layer.msg(res.info || '上传失败');
}
},
error: function() {
layer.msg('上传失败');
}
});
}
});
form.on("submit(LAY-customer-edit-submit)", function (data) {
var param = {
id: l.id,
userName: data.field.userName,
avatar: data.field.avatar,
status: parseInt(data.field.status)
};
admin.req({
method: "put",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/customer",
data: JSON.stringify(param),
done: function (res) {
if (res.code === 0) {
layer.close(idx);
layer.msg('修改成功');
table.reload("LAY-customer-manage");
} else {
layer.msg(res.info || '修改失败');
}
}
});
return false;
});
}
});
}
});
// 添加客服
$('.layui-btn-group').on('click', '[lay-event="add"]', function () {
admin.popup({
title: "添加客服",
area: ["500px", "450px"],
id: "LAY-popup-customer-add",
success: function (div, idx) {
$(div).html(
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-customer-add">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">客服ID</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="userId" id="userIdInput" class="layui-input" placeholder="请输入客服用户ID" required>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">客服名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="userName" class="layui-input" placeholder="请输入客服名称" required>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">头像</label>' +
'<div class="layui-input-block">' +
'<div class="avatar-upload-box">' +
'<div class="avatar-upload-preview" id="avatarPreviewAdd">' +
'<div class="placeholder">点击上传</div>' +
'</div>' +
'<input type="file" class="upload-btn" accept="image/*" id="avatarUploadAdd">' +
'</div>' +
'<input type="hidden" name="avatar" id="avatarInputAdd" value="">' +
'<div style="margin-top:8px;color:#999;font-size:12px;">点击图片可预览,支持jpg、png格式</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-customer-add-submit">添加</button>' +
'</div>' +
'</div>' +
'</form>'
);
form.render(null, "layuiadmin-form-customer-add");
var $preview = $('#avatarPreviewAdd');
var $input = $('#avatarInputAdd');
var $fileInput = $('#avatarUploadAdd');
$preview.on('click', function() {
var url = $input.val();
if (url) {
layer.open({
type: 1,
title: '头像预览',
area: ['auto', 'auto'],
shadeClose: true,
content: '<div style="padding:20px;text-align:center;"><img src="' + url + '" style="max-width:300px;max-height:300px;border-radius:50%;"></div>'
});
}
});
$fileInput.on('change', function() {
var file = this.files[0];
if (file) {
var userId = $('#userIdInput').val() || ('new_' + Date.now());
var formData = new FormData();
formData.append('file', file);
$.ajax({
url: setter.baseurl + 'adminapi/ImageUpload/upload/temp',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
success: function(res) {
if (res.code === 0 || res.code === 200) {
var path = res.data.path;
$input.val(path);
$preview.html('<img src="' + path + '" style="width:100%;height:100%;">');
layer.msg('上传成功');
} else {
layer.msg(res.info || '上传失败');
}
},
error: function() {
layer.msg('上传失败');
}
});
}
});
form.on("submit(LAY-customer-add-submit)", function (data) {
var param = {
userId: data.field.userId,
userName: data.field.userName,
avatar: data.field.avatar
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/customer",
data: JSON.stringify(param),
done: function (res) {
if (res.code === 0) {
layer.close(idx);
layer.msg('添加成功');
table.reload("LAY-customer-manage");
} else {
layer.msg(res.info || '添加失败');
}
}
});
return false;
});
}
});
});
// 时间戳格式化
function formatTimestamp(timestamp) {
if (!timestamp) return '-';
var date = new Date(timestamp);
var o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
};
var format = 'yyyy-MM-dd hh:mm:ss';
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return format;
}
});
</script>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,238 @@
<title>邮件管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>用户</cite></a>
<a><cite>邮件管理</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-emaillist-filter">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">关键词</label>
<div class="layui-input-inline">
<input type="text" name="userNumber" placeholder="编号/昵称/标题/内容" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">已读状态</label>
<div class="layui-input-inline">
<select name="readStatus">
<option value="">全部</option>
<option value="1">已读</option>
<option value="2">未读</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">领取状态</label>
<div class="layui-input-inline">
<select name="rewardStatus">
<option value="">全部</option>
<option value="1">已领取</option>
<option value="2">未领取</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">发送时间</label>
<div class="layui-input-inline">
<input type="text" name="timeRange" id="timeRange" placeholder="选择时间范围" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-list" lay-submit lay-filter="LAY-emaillist-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-emaillist-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<table id="LAY-emaillist-manage" lay-filter="LAY-emaillist-manage"></table>
<script type="text/html" id="table-emaillist-toolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="batchdel">
<i class="layui-icon layui-icon-delete"></i> 批量删除
</button>
</div>
</script>
<script type="text/html" id="table-emaillist-bar">
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script type="text/html" id="isReadTpl">
{{# if(d.isRead){ }}
<span class="layui-badge layui-bg-blue">已读</span>
{{# } else { }}
<span class="layui-badge layui-bg-gray">未读</span>
{{# } }}
</script>
<script type="text/html" id="isRewardTpl">
{{# if(d.isReward){ }}
<span class="layui-badge layui-bg-green">已领取</span>
{{# } else { }}
<span class="layui-badge layui-bg-orange">未领取</span>
{{# } }}
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'laydate'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
laydate = layui.laydate,
setter = layui.setter;
laydate.render({
elem: '#timeRange',
type: 'datetime',
range: '~'
});
form.render(null, 'layadmin-emaillist-filter');
form.on('submit(LAY-emaillist-search)', function (data) {
var field = data.field;
if (field.timeRange) {
var times = field.timeRange.split('~');
field.startTime = new Date(times[0].trim()).getTime();
field.endTime = new Date(times[1].trim()).getTime();
}
delete field.timeRange;
if (field.readStatus) field.readStatus = parseInt(field.readStatus);
if (field.rewardStatus) field.rewardStatus = parseInt(field.rewardStatus);
table.reload('LAY-emaillist-manage', {
where: field,
page: { curr: 1 }
});
});
form.on('submit(LAY-emaillist-reset)', function () {
$('input[name="userNumber"]').val('');
$('select[name="readStatus"]').val('');
$('select[name="rewardStatus"]').val('');
$('input[name="timeRange"]').val('');
form.render('select');
table.reload('LAY-emaillist-manage', {
where: {},
page: { curr: 1 }
});
});
table.render({
elem: "#LAY-emaillist-manage",
url: setter.baseurl + "adminapi/AdminUser/GetEmailList",
method: "post",
contentType: "application/json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
toolbar: '#table-emaillist-toolbar',
defaultToolbar: ['filter', 'exports', 'print'],
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 10,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ type: 'numbers', title: '序号', width: 60 },
{ field: "userId", title: "用户ID", width: 280 },
{ field: "userName", title: "用户昵称", width: 120, templet: function(d) { return d.userName || '-'; } },
{ field: "userNumber", title: "用户编号", width: 120, templet: function(d) { return d.userNumber || '-'; } },
{ field: "title", title: "邮件标题", minWidth: 200 },
{ field: "rewardText", title: "奖励内容", width: 180 },
{ field: "isRead", title: "已读", width: 70, templet: '#isReadTpl' },
{ field: "isReward", title: "领取", width: 80, templet: '#isRewardTpl' },
{ field: "sendTime", title: "发送时间", width: 160, templet: function(d) {
if (!d.sendTime) return '-';
var dt = new Date(d.sendTime * 1000);
return dt.getFullYear() + '-' + pad(dt.getMonth()+1) + '-' + pad(dt.getDate()) + ' ' + pad(dt.getHours()) + ':' + pad(dt.getMinutes()) + ':' + pad(dt.getSeconds());
}},
{ width: 80, title: "操作", align: "center", fixed: "right", toolbar: "#table-emaillist-bar" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
function pad(n) { return (n < 10 ? '0' : '') + n; }
table.on("tool(LAY-emaillist-manage)", function (e) {
var l = e.data;
if ("del" === e.event) {
layer.confirm('确定删除该邮件吗?', function (idx) {
admin.req({
url: setter.baseurl + 'adminapi/AdminUser/DeleteEmail?id=' + l.id,
method: 'get',
done: function (res) {
layer.close(idx);
if (res.code === 0) {
layer.msg('删除成功');
} else {
layer.msg(res.info || '删除失败');
}
table.reload('LAY-emaillist-manage');
}
});
});
}
});
table.on('toolbar(LAY-emaillist-manage)', function (obj) {
if (obj.event === 'batchdel') {
var checkStatus = table.checkStatus('LAY-emaillist-manage');
var data = checkStatus.data;
if (data.length === 0) return layer.msg('请选择要删除的数据');
layer.confirm('确定删除选中的 ' + data.length + ' 条邮件吗?', function (idx) {
var ids = data.map(function (item) { return item.id; });
admin.req({
url: setter.baseurl + 'adminapi/AdminUser/BatchDeleteEmail',
contentType: 'application/json',
method: 'post',
data: JSON.stringify(ids),
done: function (res) {
layer.close(idx);
if (res.code === 0) {
layer.msg(res.info || '删除成功');
} else {
layer.msg(res.info || '删除失败');
}
table.reload('LAY-emaillist-manage');
}
});
});
}
});
});
</script>
@@ -0,0 +1,646 @@
<title>战令配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>战令配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">基础配置</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="bo-form">
<div class="layui-form-item">
<label class="layui-form-label">是否开启</label>
<div class="layui-input-inline">
<input type="checkbox" name="isOpen" lay-skin="switch" lay-text="开启|关闭">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">结束时间</label>
<div class="layui-input-inline">
<input type="text" name="endTime" id="endTime" class="layui-input" placeholder="选择日期时间">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">规则描述</label>
<div class="layui-input-inline" style="width:400px;">
<input type="text" name="ruleDesc" class="layui-input" placeholder="规则描述">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">购买描述</label>
<div class="layui-input-inline" style="width:400px;">
<input type="text" name="buyDesc" class="layui-input" placeholder="购买描述">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">进阶描述</label>
<div class="layui-input-inline" style="width:400px;">
<input type="text" name="advanceGiftDesc" class="layui-input" placeholder="进阶奖励描述">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="save-base">保存基础配置</button>
</div>
</div>
</form>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">等级配置</div>
<div class="layui-card-body">
<div class="layui-btn-container" style="margin-bottom:10px;">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-level"><i class="layui-icon layui-icon-refresh"></i> 初始化</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-level"><i class="layui-icon layui-icon-add-1"></i> 添加等级</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-levels"><i class="layui-icon layui-icon-ok"></i> 保存</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveAct('BattleOrderConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
<table id="table-level" lay-filter="table-level"></table>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">进阶配置</div>
<div class="layui-card-body">
<div class="layui-btn-container" style="margin-bottom:10px;">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-advance"><i class="layui-icon layui-icon-refresh"></i> 初始化</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-advance"><i class="layui-icon layui-icon-add-1"></i> 添加进阶</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-advances"><i class="layui-icon layui-icon-ok"></i> 保存</button>
</div>
<table id="table-advance" lay-filter="table-advance"></table>
</div>
</div>
</div>
<script type="text/html" id="bar-level">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script type="text/html" id="bar-advance">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="reward">奖励</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script type="text/html" id="tpl-payType">
{{# var m = {1:'金币',2:'钻石',3:'现金'}; }}
{{= m[d.payType] || '金币' }}
</script>
<script type="text/html" id="tpl-advance-type">
{{# var m = {1:'豪华',2:'至尊',3:'豪华+至尊'}; }}
{{= m[d.type] || '豪华' }}
</script>
<script type="text/html" id="reward-dialog-tpl">
<div class="reward-tabs" style="padding:10px;">
<div class="layui-tab" lay-filter="reward-tab">
<ul class="layui-tab-title">
<li class="layui-this">普通奖励</li>
<li>豪华奖励</li>
<li>至尊奖励</li>
</ul>
<div class="layui-tab-content" style="height:480px;">
<div class="layui-tab-item layui-show" data-key="normalGiftList"></div>
<div class="layui-tab-item" data-key="luxuryGiftList"></div>
<div class="layui-tab-item" data-key="supremeGiftList"></div>
</div>
</div>
</div>
</script>
<script type="text/html" id="gift-editor-tpl">
<div class="gift-container">
<div class="gift-left">
<div class="gift-toolbar">
<button class="layui-btn layui-btn-sm" id="btn-add-gift"><i class="layui-icon layui-icon-add-1"></i> 新增</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" id="btn-delete-gift"><i class="layui-icon layui-icon-delete"></i> 删除</button>
</div>
<div class="gift-table-wrap"><table id="table-gift-list"></table></div>
</div>
<div class="gift-right">
<h4>礼包项详情</h4>
<div class="layui-form">
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<select id="giftType" lay-filter="giftType">
<option value="1">金币</option>
<option value="2">钻石</option>
<option value="3">道具</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数量</label>
<div class="layui-input-block"><input type="number" id="giftCount" class="layui-input" placeholder="数量"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">随机</label>
<div class="layui-input-block"><input type="checkbox" id="giftIsRandom" lay-skin="switch" lay-text="是|否"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最小值</label>
<div class="layui-input-block"><input type="number" id="giftMinCount" class="layui-input" placeholder="最小值"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最大值</label>
<div class="layui-input-block"><input type="number" id="giftMaxCount" class="layui-input" placeholder="最大值"></div>
</div>
<div class="layui-form-item gift-prop-select" style="display:none;">
<label class="layui-form-label">道具编号</label>
<div class="layui-input-block"><input type="text" id="giftProductNumber" class="layui-input" placeholder="输入道具编号"></div>
</div>
<div class="layui-form-item" style="margin-top:10px;">
<button class="layui-btn layui-btn-normal" id="btn-save-gift" style="width:100%;">保存</button>
</div>
</div>
</div>
</div>
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer', 'laydate', 'element', 'upload'], function () {
var $ = layui.$, admin = layui.admin, table = layui.table, form = layui.form,
layer = layui.layer, laydate = layui.laydate, setter = layui.setter, element = layui.element, upload = layui.upload;
var activityConfig = null;
var rewardCtx = null; // { giftList, title }
setTimeout(function() {
$('.layui-layer-shade').remove(); layer.closeAll();
}, 100);
laydate.render({ elem: '#endTime', type: 'datetime' });
function loadConfig() {
admin.req({ method: 'get', url: setter.baseurl + 'adminapi/ActivityConfig/Get',
done: function(r2) {
activityConfig = (r2.code === 0 && r2.data) ? r2.data : { battleOrderConfig: {} };
initForm();
}
});
}
function bo() {
if (!activityConfig.battleOrderConfig) activityConfig.battleOrderConfig = {};
return activityConfig.battleOrderConfig;
}
function initForm() {
var b = bo();
$('input[name="isOpen"]').prop('checked', b.isOpen);
$('input[name="endTime"]').val(b.endTime ? new Date(b.endTime * 1000).toLocaleString() : '');
$('input[name="ruleDesc"]').val(b.ruleDesc || '');
$('input[name="buyDesc"]').val(b.buyDesc || '');
$('input[name="advanceGiftDesc"]').val(b.advanceGiftDesc || '');
form.render();
renderLevelTable();
renderAdvanceTable();
}
function save(msg) {
admin.req({
method: 'post', contentType: 'application/json;charset=UTF-8',
url: setter.baseurl + 'adminapi/ActivityConfig/UpdateBattleOrder',
data: JSON.stringify(activityConfig.battleOrderConfig),
done: function(r) {
r.code === 0 ? (layer.msg(msg || '保存成功'), loadConfig()) : layer.msg(r.info || '保存失败');
}
});
}
form.on('submit(save-base)', function(d) {
var b = bo();
b.isOpen = d.field.isOpen === 'on';
b.ruleDesc = d.field.ruleDesc || '';
b.buyDesc = d.field.buyDesc || '';
b.advanceGiftDesc = d.field.advanceGiftDesc || '';
if (d.field.endTime) b.endTime = Math.floor(new Date(d.field.endTime).getTime() / 1000);
save('保存成功'); return false;
});
// ==================== 等级表格 ====================
function renderLevelTable() {
var levels = bo().levelConfig || [];
table.render({ elem: '#table-level', data: levels, page: true,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'level', title: '等级', width: 70, sort: true },
{ field: 'exp', title: '升级经验', width: 90 },
{ title: '普通奖励', width: 90, templet: function(d) { return (d.normalGiftList||[]).length + '项'; } },
{ title: '豪华奖励', width: 90, templet: function(d) { return (d.luxuryGiftList||[]).length + '项'; } },
{ title: '至尊奖励', width: 90, templet: function(d) { return (d.supremeGiftList||[]).length + '项'; } },
{ field: 'payType', title: '付费', width: 70, templet: '#tpl-payType' },
{ field: 'payCount', title: '费用', width: 80 },
{ title: '操作', width: 200, fixed: 'right', toolbar: '#bar-level' }
]]
});
}
// ==================== 进阶表格 ====================
function renderAdvanceTable() {
var list = bo().advanceGift || [];
table.render({ elem: '#table-advance', data: list, page: false,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'type', title: '类型', width: 80, templet: '#tpl-advance-type' },
{ field: 'name', title: '名称', width: 120 },
{ field: 'count', title: '数量', width: 80 },
{ field: 'payType', title: '付费', width: 70, templet: '#tpl-payType' },
{ field: 'payCount', title: '费用', width: 80 },
{ title: '奖励', width: 80, templet: function(d) { return (d.giftList||[]).length + '项'; } },
{ title: '操作', width: 180, fixed: 'right', toolbar: '#bar-advance' }
]]
});
}
// ==================== 等级 CRUD ====================
function showLevelEdit(data, isAdd) {
var readonly = isAdd ? '' : 'readonly';
var html = '<div style="padding:15px;"><form class="layui-form">' +
'<div class="layui-row"><div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">等级</label><div class="layui-input-block"><input type="number" id="dlg-level" value="'+(data.level||'')+'" class="layui-input" '+readonly+'></div></div></div>' +
'<div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">升级经验</label><div class="layui-input-block"><input type="number" id="dlg-exp" value="'+(data.exp||0)+'" class="layui-input"></div></div></div></div>' +
'<div class="layui-row"><div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">付费类型</label><div class="layui-input-block"><select id="dlg-payType"><option value="1"'+(data.payType===1?' selected':'')+'>金币</option><option value="2"'+(data.payType===2?' selected':'')+'>钻石</option><option value="3"'+(data.payType===3?' selected':'')+'>现金</option></select></div></div></div>' +
'<div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">付费金额</label><div class="layui-input-block"><input type="number" id="dlg-payCount" value="'+(data.payCount||0)+'" class="layui-input"></div></div></div></div>' +
'<div style="margin-top:10px;border-top:1px solid #eee;padding-top:10px;"><div class="layui-tab" lay-filter="dlg-reward-tab"><ul class="layui-tab-title"><li class="layui-this">普通奖励</li><li>豪华奖励</li><li>至尊奖励</li></ul>' +
'<div class="layui-tab-content" style="height:300px;padding:5px 0;">' +
'<div class="layui-tab-item layui-show"><div class="inplace-gift" data-key="normalGiftList"></div></div>' +
'<div class="layui-tab-item"><div class="inplace-gift" data-key="luxuryGiftList"></div></div>' +
'<div class="layui-tab-item"><div class="inplace-gift" data-key="supremeGiftList"></div></div>' +
'</div></div></div></form></div>';
layer.open({ type:1, title:isAdd?'添加等级':'编辑等级', area:['850px','580px'], content:html, btn:['保存','取消'],
success:function(l){
form.render('select');
element.render('tab');
var keys = ['normalGiftList','luxuryGiftList','supremeGiftList'];
var tgt = {};
keys.forEach(function(k){ tgt[k] = data[k] || []; });
renderInplaceGift(l.find('.inplace-gift[data-key="normalGiftList"]'), tgt.normalGiftList);
element.on('tab(dlg-reward-tab)', function(o){
var k = keys[o.index];
var ph = l.find('.inplace-gift[data-key="'+k+'"]');
if (ph.is(':empty')) renderInplaceGift(ph, tgt[k]);
});
window._dlgGiftData = tgt;
},
yes:function(idx,l){
var tgt = window._dlgGiftData || {};
var item = {
level: parseInt(l.find('#dlg-level').val()) || data.level,
exp: parseInt(l.find('#dlg-exp').val()) || 0,
normalGiftList: tgt.normalGiftList || [],
luxuryGiftList: tgt.luxuryGiftList || [],
supremeGiftList: tgt.supremeGiftList || [],
payType: parseInt(l.find('#dlg-payType').val()) || 1,
payCount: parseInt(l.find('#dlg-payCount').val()) || 0
};
var b = bo();
if (!b.levelConfig) b.levelConfig = [];
if (isAdd) {
if (b.levelConfig.some(function(x) { return x.level === item.level; }))
{ layer.msg('等级已存在'); return; }
b.levelConfig.push(item);
} else {
var i = b.levelConfig.findIndex(function(x) { return x.level === data.level; });
if (i > -1) b.levelConfig[i] = item;
}
delete window._dlgGiftData;
layer.close(idx); save(isAdd?'添加成功':'保存成功');
}
});
}
function renderInplaceGift(container, giftList) {
if (!giftList) giftList = [];
var html = '<div style="margin-bottom:8px;"><button class="layui-btn layui-btn-xs layui-btn-normal btn-ip-add"><i class="layui-icon layui-icon-add-1"></i> 新增奖励项</button></div>' +
'<table class="ip-gift-table"><thead><tr><th style="width:60px">类型</th><th style="width:60px">数量</th><th style="width:60px">最小值</th><th style="width:60px">最大值</th><th style="width:60px">随机</th><th style="width:100px">道具ID</th><th style="width:60px">操作</th></tr></thead><tbody></tbody></table>';
container.html(html);
var tbody = container.find('tbody');
function renderRows(){
tbody.empty();
giftList.forEach(function(g,idx){
var typeMap = {1:'金币',2:'钻石',3:'道具'};
var tr = $('<tr><td>'+ (typeMap[g.type]||'金币') +'</td><td>'+ (g.count||0) +'</td><td>'+ (g.minCount||0) +'</td><td>'+ (g.maxCount||0) +'</td><td>'+ (g.isRandom?'是':'否') +'</td><td>'+ (g.otherId||'') +'</td><td><button class="layui-btn layui-btn-xs btn-ip-edit" data-idx="'+idx+'">编辑</button><button class="layui-btn layui-btn-xs layui-btn-danger btn-ip-del" data-idx="'+idx+'">删除</button></td></tr>');
tbody.append(tr);
});
}
renderRows();
container.find('.btn-ip-add').off('click').on('click', function(){
giftList.push({type:1,count:100,isRandom:false,minCount:0,maxCount:0,otherId:''});
renderRows();
});
container.off('click','.btn-ip-del').on('click','.btn-ip-del', function(){
var idx = parseInt($(this).data('idx'));
giftList.splice(idx,1);
renderRows();
});
container.off('click','.btn-ip-edit').on('click','.btn-ip-edit', function(){
var idx = parseInt($(this).data('idx'));
var g = giftList[idx];
if (!g) return;
var ehtml = '<div style="padding:15px;"><form class="layui-form"><div class="layui-form-item"><label class="layui-form-label">类型</label><div class="layui-input-block"><select id="ip-type"><option value="1"'+(g.type===1?' selected':'')+'>金币</option><option value="2"'+(g.type===2?' selected':'')+'>钻石</option><option value="3"'+(g.type===3?' selected':'')+'>道具</option></select></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">数量</label><div class="layui-input-block"><input type="number" id="ip-count" value="'+(g.count||0)+'" class="layui-input"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">随机</label><div class="layui-input-block"><input type="checkbox" id="ip-random" lay-skin="switch" lay-text="是|否"'+(g.isRandom?' checked':'')+'></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">最小值</label><div class="layui-input-block"><input type="number" id="ip-min" value="'+(g.minCount||0)+'" class="layui-input"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">最大值</label><div class="layui-input-block"><input type="number" id="ip-max" value="'+(g.maxCount||0)+'" class="layui-input"></div></div>' +
'<div class="layui-form-item gift-prop-select" style="'+(g.type===3?'':'display:none;')+'"><label class="layui-form-label">道具编号</label><div class="layui-input-block"><input type="text" id="ip-oid" class="layui-input" value="'+(g.otherId||'')+'" placeholder="输入道具编号"></div></div></form></div>';
layer.open({type:1,title:'编辑奖励项',area:['420px','420px'],content:ehtml,btn:['保存','取消'],
success:function(el){ form.render(); form.on('select(ip-type)', function(o){ el.find('.gift-prop-select').toggle(o.value==='3'); }); },
yes:function(ei,el){
var t = parseInt(el.find('#ip-type').val())||1;
var propNum = t===3 ? (el.find('#ip-oid').val()||'') : '';
giftList[idx] = {
type:t, count: parseInt(el.find('#ip-count').val())||0,
isRandom: el.find('#ip-random')[0].checked,
minCount: parseInt(el.find('#ip-min').val())||0,
maxCount: parseInt(el.find('#ip-max').val())||0,
otherId: propNum
};
layer.close(ei);
renderRows();
}
});
});
}
// ==================== 进阶 CRUD ====================
function showAdvanceEdit(data, isAdd) {
var readonly = isAdd ? '' : 'readonly';
var html = '<div style="padding:20px;"><form class="layui-form">' +
'<div class="layui-row"><div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">类型</label><div class="layui-input-block"><select id="dlg-type" '+readonly+'><option value="1"'+(data.type===1?' selected':'')+'>豪华</option><option value="2"'+(data.type===2?' selected':'')+'>至尊</option><option value="3"'+(data.type===3?' selected':'')+'>豪华+至尊</option></select></div></div></div>' +
'<div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">名称</label><div class="layui-input-block"><input type="text" id="dlg-name" value="'+(data.name||'')+'" class="layui-input"></div></div></div></div>' +
'<div class="layui-row"><div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">奖励数量</label><div class="layui-input-block"><input type="number" id="dlg-count" value="'+(data.count||0)+'" class="layui-input"></div></div></div>' +
'<div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">图片</label><div class="layui-input-block" style="display:flex;"><input type="text" id="dlg-image" value="'+(data.imageUrl||'')+'" class="layui-input" placeholder="图片URL" style="flex:1;margin-right:5px;"><button type="button" id="btn-upload-advance-img" class="layui-btn layui-btn-sm"><i class="layui-icon layui-icon-upload"></i></button></div></div></div></div>' +
'<div class="layui-row"><div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">付费类型</label><div class="layui-input-block"><select id="dlg-payType"><option value="1"'+(data.payType===1?' selected':'')+'>金币</option><option value="2"'+(data.payType===2?' selected':'')+'>钻石</option><option value="3"'+(data.payType===3?' selected':'')+'>现金</option></select></div></div></div>' +
'<div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">付费金额</label><div class="layui-input-block"><input type="number" id="dlg-payCount" value="'+(data.payCount||0)+'" class="layui-input"></div></div></div></div>' +
'</form></div>';
layer.open({ type:1, title:isAdd?'添加进阶':'编辑进阶', area:['550px','380px'], content:html, btn:['保存','取消'],
success:function(l){
form.render('select');
layui.upload.render({
elem: l.find('#btn-upload-advance-img'),
url: (layui.setter.baseurl||'/')+'adminapi/ImageUpload/upload/temp',
done: function(r) {
if (r.code === 0) l.find('#dlg-image').val(r.data.path);
}
});
},
yes:function(idx,l){
var item = {
type: parseInt(l.find('#dlg-type').val()) || 1,
name: l.find('#dlg-name').val() || '',
count: parseInt(l.find('#dlg-count').val()) || 0,
imageUrl: l.find('#dlg-image').val() || '',
payType: parseInt(l.find('#dlg-payType').val()) || 1,
payCount: parseInt(l.find('#dlg-payCount').val()) || 0,
giftList: data.giftList || []
};
var b = bo();
if (!b.advanceGift) b.advanceGift = [];
if (isAdd) {
if (b.advanceGift.some(function(x) { return x.type === item.type; }))
{ layer.msg('类型已存在'); return; }
b.advanceGift.push(item);
} else {
var i = b.advanceGift.findIndex(function(x) { return x.type === data.type; });
if (i > -1) b.advanceGift[i] = item;
}
layer.close(idx); save(isAdd?'添加成功':'保存成功');
}
});
}
// ==================== 奖励编辑弹窗(三层tab,与奖池页面一致) ====================
function showRewardDialog(title, levelData) {
rewardCtx = { normal: levelData.normalGiftList || [], luxury: levelData.luxuryGiftList || [], supreme: levelData.supremeGiftList || [] };
var html = '<div class="reward-tabs" style="padding:10px;"><div class="layui-tab" lay-filter="reward-tab">' +
'<ul class="layui-tab-title"><li class="layui-this">普通奖励</li><li>豪华奖励</li><li>至尊奖励</li></ul>' +
'<div class="layui-tab-content" style="height:480px;">' +
'<div class="layui-tab-item layui-show"><div class="gift-placeholder" data-key="normalGiftList"></div></div>' +
'<div class="layui-tab-item"><div class="gift-placeholder" data-key="luxuryGiftList"></div></div>' +
'<div class="layui-tab-item"><div class="gift-placeholder" data-key="supremeGiftList"></div></div>' +
'</div></div></div>';
layer.open({
type: 1, title: title, area: ['1000px', '620px'],
content: html, shade: 0.1, shadeClose: true,
success: function(layero) {
// 初始化第一个tab的编辑器
initGiftEditor(layero, rewardCtx.normal);
// tab切换时重新初始化编辑器
form.on('tab(reward-tab)', function(o) {
var key = o.index === 0 ? 'normalGiftList' : o.index === 1 ? 'luxuryGiftList' : 'supremeGiftList';
var list = rewardCtx[key];
var ph = layero.find('.gift-placeholder[data-key="' + key + '"]');
ph.html($('#gift-editor-tpl').html());
var propOpts = '<option value="">请选择道具</option>';
propList.forEach(function(p) { propOpts += '<option value="'+p.number+'">'+p.number+' - '+p.name+'</option>'; });
ph.find('#giftProductNumber').html(propOpts);
form.render();
bindGiftEvents(ph, list);
});
},
end: function() { renderLevelTable(); renderAdvanceTable(); }
});
}
function initGiftEditor(layero, giftList) {
var ph = layero.find('.gift-placeholder[data-key="normalGiftList"]');
ph.html($('#gift-editor-tpl').html());
var propOpts = '<option value="">请选择道具</option>';
propList.forEach(function(p) { propOpts += '<option value="'+p.number+'">'+p.number+' - '+p.name+'</option>'; });
ph.find('#giftProductNumber').html(propOpts);
form.render();
bindGiftEvents(ph, giftList);
}
function bindGiftEvents(container, giftList) {
var $c = $(container);
currentGiftIndex = -1;
renderGiftTable($c, giftList);
$c.find('#btn-add-gift').on('click', function() {
giftList.push({ type:1, count:100, isRandom:false, minCount:0, maxCount:0, otherId:'' });
currentGiftIndex = giftList.length - 1;
renderGiftTable($c, giftList, currentGiftIndex);
loadGiftForm(giftList[giftList.length-1], $c);
});
$c.find('#btn-delete-gift').on('click', function() {
if (currentGiftIndex < 0) return layer.msg('请先选择要删除的项');
giftList.splice(currentGiftIndex, 1);
currentGiftIndex = -1; clearGiftForm($c);
renderGiftTable($c, giftList, -1);
});
$c.find('#btn-save-gift').on('click', function() {
if (currentGiftIndex < 0) return layer.msg('请先选择要编辑的项');
var t = parseInt($c.find('#giftType').val()) || 1;
var oid = t === 3 ? ($c.find('#giftProductNumber').val()||'') : '';
giftList[currentGiftIndex] = {
type: t, count: parseInt($c.find('#giftCount').val()) || 0,
isRandom: $c.find('#giftIsRandom')[0].checked,
minCount: parseInt($c.find('#giftMinCount').val()) || 0,
maxCount: parseInt($c.find('#giftMaxCount').val()) || 0,
otherId: oid
};
renderGiftTable($c, giftList, currentGiftIndex);
layer.msg('保存成功');
});
form.on('select(giftType)', function(o) {
$c.find('.gift-prop-select').toggle(o.value === '3');
$c.find('#giftCount').val(0); $c.find('#giftIsRandom')[0].checked = false; form.render('checkbox');
});
$c.find('#giftCount').on('input', function() { $c.find('#giftIsRandom')[0].checked = false; form.render('checkbox'); });
$c.find('#giftIsRandom').on('click', function() {
if (this.checked) setTimeout(function() { $c.find('#giftCount').val(0); }, 10);
});
}
var currentGiftIndex = -1;
function renderGiftTable(container, giftList, selectIndex) {
table.render({ elem: container.find('#table-gift-list'), data: giftList, page: false, height: 380,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'type', title: '类型', width: 80, templet: function(d) { return ({1:'金币',2:'钻石',3:'道具'})[d.type]||'金币'; }},
{ field: 'count', title: '数量', width: 80 },
{ field: 'minCount', title: '最小值', width: 80 },
{ field: 'maxCount', title: '最大值', width: 80 },
{ field: 'isRandom', title: '随机', width: 70, templet: function(d) { return d.isRandom ? '是' : '否'; }},
{ field: 'otherId', title: '道具Id', width: 120 }
]],
done: function() {
if (selectIndex === undefined) selectIndex = giftList.length > 0 ? 0 : -1;
if (selectIndex >= 0 && selectIndex < giftList.length) {
currentGiftIndex = selectIndex;
var tr = container.find('#table-gift-list + .layui-table-view tr[data-index="'+selectIndex+'"]');
if (tr.length) { tr.addClass('layui-table-click'); loadGiftForm(giftList[selectIndex], container); }
}
}
});
container.find('#table-gift-list + .layui-table-view').off('click').on('click', 'tr[data-index]', function() {
var idx = parseInt($(this).attr('data-index'));
if (idx >= 0 && idx < giftList.length) {
currentGiftIndex = idx;
container.find('tr[data-index]').removeClass('layui-table-click');
$(this).addClass('layui-table-click');
loadGiftForm(giftList[idx], container);
}
});
}
function loadGiftForm(data, container) {
container.find('#giftType').val(data.type || 1);
container.find('#giftCount').val(data.count || 0);
container.find('#giftIsRandom')[0].checked = data.isRandom || false;
container.find('#giftMinCount').val(data.minCount || 0);
container.find('#giftMaxCount').val(data.maxCount || 0);
var oid = data.otherId || '';
container.find('#giftProductNumber').val(oid);
container.find('.gift-prop-select').toggle(data.type === 3);
form.render('checkbox'); form.render('select');
}
function clearGiftForm(container) {
container.find('#giftType').val('1');
container.find('#giftCount').val('0');
container.find('#giftIsRandom')[0].checked = false;
container.find('#giftMinCount').val('0');
container.find('#giftMaxCount').val('0');
container.find('#giftProductNumber').val('');
container.find('.gift-prop-select').hide();
form.render('checkbox'); form.render('select');
}
// ==================== 初始化按钮(从 JSON 还原) ====================
$(document).on('click', '[lay-filter="init-level"],[lay-filter="init-advance"]', function() {
layer.confirm('确定要从配置文件还原战令数据吗?', function(i) {
layer.close(i);
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/GameConfig/InitActivitySection/BattleOrderConfig',
done: function(r) {
if (r.code === 0 || r.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(r.info || '初始化失败');
}
}
});
});
});
$(document).on('click', '[lay-filter="add-level"]', function() {
var b = bo(), levels = b.levelConfig || [];
var max = levels.length ? Math.max.apply(null, levels.map(function(l){return l.level;})) : 0;
showLevelEdit({ level:max+1, exp:(max+1)*100, normalGiftList:[], luxuryGiftList:[], supremeGiftList:[], payType:1, payCount:(max+1)*100 }, true);
});
$(document).on('click', '[lay-filter="add-advance"]', function() {
var b = bo(), list = b.advanceGift || [];
var used = list.map(function(a){return a.type;});
var nt = 1; while (used.indexOf(nt) > -1) nt++;
showAdvanceEdit({ type:nt, name:({1:'豪华',2:'至尊',3:'豪华+至尊'})[nt]||'进阶', count:100, payType:1, payCount:10000, giftList:[] }, true);
});
$(document).on('click', '[lay-filter="save-levels"],[lay-filter="save-advances"]', function() { save('保存成功'); });
// ==================== 表格操作事件 ====================
table.on('tool(table-level)', function(obj) {
var d = obj.data, b = bo(), i = b.levelConfig.findIndex(function(x){return x.level===d.level;});
if (i < 0) return;
if (obj.event === 'del') {
layer.confirm('确定删除等级'+d.level+'?', function(idx) { b.levelConfig.splice(i,1); layer.close(idx); save('删除成功'); });
} else if (obj.event === 'edit') {
showLevelEdit(d, false);
}
});
table.on('tool(table-advance)', function(obj) {
var d = obj.data, b = bo(), i = b.advanceGift.findIndex(function(x){return String(x.type)===String(d.type);});
if (i < 0) return;
if (obj.event === 'del') {
layer.confirm('确定删除进阶'+d.name+'?', function(idx) { b.advanceGift.splice(i,1); layer.close(idx); save('删除成功'); });
} else if (obj.event === 'edit') {
showAdvanceEdit(d);
} else if (obj.event === 'reward') {
if (!b.advanceGift[i].giftList) b.advanceGift[i].giftList = [];
var html = '<div class="gift-placeholder">'+$('#gift-editor-tpl').html()+'</div>';
layer.open({
type:1, title:'进阶 - '+d.name+' 奖励列表', area:['1000px','620px'],
content: html, shade: 0.1, shadeClose: true,
success: function(layero) {
// 奖励类型下拉与道具编号输入是两个不同字段,不能把道具选项塞进类型下拉。
form.render();
bindGiftEvents(layero.find('.gift-placeholder'), b.advanceGift[i].giftList);
},
end: function() { renderLevelTable(); renderAdvanceTable(); }
});
}
});
window.saveAct=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/ActivityConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
<style>
.gift-container { display: flex; height: 460px; padding: 10px; }
.gift-left { flex: 1; margin-right: 15px; }
.gift-table-wrap { height: 380px; overflow: auto; }
.gift-right { width: 280px; border-left: 1px solid #e6e6e6; padding-left: 15px; }
.gift-right h4 { margin-bottom: 12px; color: #333; font-size: 15px; padding-bottom: 8px; border-bottom: 1px solid #eee; }
.gift-toolbar { margin-bottom: 8px; }
.gift-toolbar .layui-btn { margin-right: 5px; }
.gift-right .layui-form-item { margin-bottom: 10px; }
.gift-right .layui-form-label { padding: 9px 0; width: 60px; }
.gift-right .layui-input-block { margin-left: 70px; }
.reward-tabs .layui-tab-title { height: 42px; }
.reward-tabs .layui-tab-title li { line-height: 42px; }
.reward-tabs .layui-tab-content { padding: 10px 0; }
</style>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,179 @@
<title>Boss道具掉落配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>Boss道具掉落配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-bossdrop"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-bossdrop"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-bossdrop"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('BossDropConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="bd-form">
<div class="layui-form-item">
<label class="layui-form-label">不掉落权重</label>
<div class="layui-input-inline" style="width:200px;">
<input type="number" name="noDropWeight" class="layui-input" placeholder="不掉落权重(值越大越难掉落)">
</div>
<div class="layui-form-mid layui-word-aux">总权重 = 不掉落权重 + 各掉落项权重之和</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="save-base">保存基础配置</button>
</div>
</div>
</form>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">掉落道具列表(所有Boss通用)</div>
<div class="layui-card-body">
<div class="layui-btn-container" style="margin-bottom:10px;">
<button class="layui-btn layui-btn-sm" lay-filter="add-item"><i class="layui-icon layui-icon-add-1"></i> 添加掉落项</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-items"><i class="layui-icon layui-icon-ok"></i> 保存</button>
</div>
<table id="table-items" lay-filter="table-items"></table>
</div>
</div>
</div>
<script type="text/html" id="bar-item">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$, admin = layui.admin, table = layui.table, form = layui.form, layer = layui.layer, setter = layui.setter;
var gameConfig = null, propList = [];
setTimeout(function() { $('.layui-layer-shade').remove(); layer.closeAll(); }, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
if (!gameConfig.bossDropConfig) gameConfig.bossDropConfig = { noDropWeight: 5000, dropItems: [] };
propList = (gameConfig.propConfig || []).filter(function(p){ return p.isSale; }).map(function(p){ return { number: p.number, name: p.name }; });
initForm();
renderTable();
}
}
});
}
function bd() { return gameConfig.bossDropConfig; }
function initForm() {
var cfg = bd();
$('input[name="noDropWeight"]').val(cfg.noDropWeight || 5000);
form.render();
}
function saveBase() {
var cfg = bd();
cfg.noDropWeight = parseInt($('input[name="noDropWeight"]').val()) || 5000;
save();
}
function save() {
admin.req({
method: "post", contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/UpdateBossDrop",
data: JSON.stringify(bd()),
done: function (r) {
r.code === 0 ? (layer.msg('保存成功'), loadConfig()) : layer.msg(r.info || '保存失败');
}
});
}
function renderTable() {
var items = bd().dropItems || [];
table.render({ elem: '#table-items', data: items, page: false,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'propName', title: '道具名称', width: 150 },
{ field: 'count', title: '数量', width: 80 },
{ field: 'weight', title: '权重', width: 80 },
{ title: '操作', width: 160, fixed: 'right', toolbar: '#bar-item' }
]]
});
}
form.on('submit(save-base)', function() { saveBase(); return false; });
function showItemEdit(data, idx) {
var items = bd().dropItems || [];
var opts = '<option value="">请选择道具</option>';
(gameConfig.propConfig || []).forEach(function(p) { opts += '<option value="'+p.number+'"'+(p.number===data.propNumber?' selected':'')+'>'+p.number+' - '+p.name+'</option>'; });
var html = '<div style="padding:20px;"><form class="layui-form">' +
'<div class="layui-form-item"><label class="layui-form-label">道具</label><div class="layui-input-block"><select id="dlg-prop" lay-search>'+opts+'</select></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">数量</label><div class="layui-input-block"><input type="number" id="dlg-count" value="'+(data.count||1)+'" class="layui-input"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">权重</label><div class="layui-input-block"><input type="number" id="dlg-weight" value="'+(data.weight||100)+'" class="layui-input"></div></div>' +
'</form></div>';
layer.open({ type:1, title: idx < 0 ? '添加掉落项' : '编辑掉落项', area:['420px','320px'], content:html, btn:['保存','取消'],
success:function(l){ form.render('select'); },
yes:function(i,l){
var item = {
propNumber: l.find('#dlg-prop').val() || '',
propName: l.find('#dlg-prop option:selected').text().replace(/^[^-]+ - /, ''),
count: parseInt(l.find('#dlg-count').val()) || 1,
weight: parseInt(l.find('#dlg-weight').val()) || 0
};
if (idx < 0) items.push(item); else items[idx] = item;
layer.close(i); renderTable(); save();
}
});
}
$(document).on('click', '[lay-filter="add-item"]', function() { showItemEdit({ propNumber:'', propName:'', count:1, weight:100 }, -1); });
$(document).on('click', '[lay-filter="save-items"]', function() { save(); });
table.on('tool(table-items)', function(obj) {
var items = bd().dropItems || [];
if (obj.event === 'del') {
layer.confirm('确定删除?', function(i) { items.splice(items.indexOf(obj.data), 1); layer.close(i); renderTable(); save(); });
} else if (obj.event === 'edit') {
var idx = items.indexOf(obj.data);
showItemEdit(obj.data, idx);
}
});
$(document).on('click', '[lay-filter="init-bossdrop"]', function() {
layer.confirm('确定要从配置文件还原Boss掉落配置吗?', function(i) {
layer.close(i);
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/GameConfig/InitSection/BossDropConfig',
done: function(r) {
if (r.code === 0 || r.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(r.info || '初始化失败');
}
}
});
});
});
$(document).on('click', '[lay-filter="reload-bossdrop"]', function() { loadConfig(); layer.msg('刷新成功'); });
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,393 @@
<title>机器人AI配置</title>
<style>
.layui-form-label { width: 200px; }
.bot-row { display: flex; flex-wrap: wrap; gap: 8px; }
.bot-row .layui-form-item { display: flex; align-items: center; margin: 0; white-space: nowrap; flex: 1; min-width: 300px; }
.bot-row .layui-form-label { width: auto; text-align: right; padding-right: 6px; font-size: 13px; }
.bot-row .layui-input-inline { width: 70px; }
.bot-row .layui-form-mid { font-size: 11px; color: #999; margin-left: 4px; }
</style>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>机器人AI配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-botai"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-botai"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-botai"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('BotConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</div>
<div class="layui-card-body">
<form class="layui-form" id="form-botai" style="padding: 20px;">
<fieldset class="layui-elem-field"><legend>全局设置</legend>
<div class="layui-field-box">
<div class="layui-form-item"><label class="layui-form-label">总开关</label><div class="layui-input-inline"><input type="checkbox" name="enabled" lay-skin="switch" lay-text="开启|关闭"></div><div class="layui-form-mid">机器人总开关</div></div>
<div class="layui-form-item"><label class="layui-form-label">AI状态</label><div class="layui-input-inline"><input type="text" name="aiState" class="layui-input" style="width:120px;"></div><div class="layui-form-mid">active=正常 | idle=待机 | aggressive=激进</div></div>
<div class="layui-form-item"><label class="layui-form-label">爆率因子</label><div class="layui-input-inline"><input type="number" name="luckFactor" class="layui-input" step="0.1"></div><div class="layui-form-mid">1.0=正常, >1容易杀, &lt;1难杀</div></div>
<div class="layui-form-item"><label class="layui-form-label">状态刷新间隔(秒)</label><div class="layui-input-inline"><input type="number" name="stateRefreshInterval" class="layui-input" step="0.1"></div><div class="layui-form-mid">0=不自动刷新</div></div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>技能配置</legend>
<div class="layui-field-box">
<div class="layui-form-item"><label class="layui-form-label">技能刷新间隔(秒)</label><div class="layui-input-inline"><input type="number" name="skillRefreshInterval" class="layui-input" step="0.1"></div><div class="layui-form-mid">每隔多少秒重新掷骰子决定技能组合</div></div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">锁定概率</label><div class="layui-input-inline"><input type="number" name="lockProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">自动瞄准最近鱼</div></div>
<div class="layui-form-item"><label class="layui-form-label">锁定Min</label><div class="layui-input-inline"><input type="number" name="lockMinTime" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">锁定Max</label><div class="layui-input-inline"><input type="number" name="lockMaxTime" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">狂暴概率</label><div class="layui-input-inline"><input type="number" name="frenzyProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">开火速度翻倍</div></div>
<div class="layui-form-item"><label class="layui-form-label">狂暴Min</label><div class="layui-input-inline"><input type="number" name="frenzyMinTime" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">狂暴Max</label><div class="layui-input-inline"><input type="number" name="frenzyMaxTime" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">三发散弹概率</label><div class="layui-input-inline"><input type="number" name="tripleProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">一次3发</div></div>
<div class="layui-form-item"><label class="layui-form-label">三发Min</label><div class="layui-input-inline"><input type="number" name="tripleMinTime" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">三发Max</label><div class="layui-input-inline"><input type="number" name="tripleMaxTime" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">双倍炮概率</label><div class="layui-input-inline"><input type="number" name="doubleProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">消耗翻倍</div></div>
<div class="layui-form-item"><label class="layui-form-label">双倍Min</label><div class="layui-input-inline"><input type="number" name="doubleMinTime" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">双倍Max</label><div class="layui-input-inline"><input type="number" name="doubleMaxTime" class="layui-input" step="0.1"></div></div>
</div>
</div>
</fieldset>
<!-- 行为模板 -->
<!-- 三个行为模式共用同一套字段,使用前缀区分 -->
<fieldset class="layui-elem-field"><legend>Active 行为参数</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">开火间隔Min(秒)</label><div class="layui-input-inline"><input type="number" name="activeFireIntervalMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火间隔Max(秒)</label><div class="layui-input-inline"><input type="number" name="activeFireIntervalMax" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火概率</label><div class="layui-input-inline"><input type="number" name="activeFireProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">0.9=经常开火偶尔停顿</div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">旋转速度(度/秒)</label><div class="layui-input-inline"><input type="number" name="activeRotateSpeed" class="layui-input" step="1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Min(秒)</label><div class="layui-input-inline"><input type="number" name="activeTargetHoldTimeMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Max(秒)</label><div class="layui-input-inline"><input type="number" name="activeTargetHoldTimeMax" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">切换目标概率</label><div class="layui-input-inline"><input type="number" name="activeTargetSwitchChance" class="layui-input" step="0.01"></div></div>
<div class="layui-form-item"><label class="layui-form-label">高分偏好</label><div class="layui-input-inline"><input type="number" name="activeTargetPreferHighScore" class="layui-input" step="0.1"></div><div class="layui-form-mid">0=纯距离, 1=纯分数</div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">允许打Boss</label><div class="layui-input-inline"><input type="checkbox" name="activeCanTargetBoss" lay-skin="switch" lay-text="是|否"></div></div>
<div class="layui-form-item"><label class="layui-form-label">允许打特殊鱼</label><div class="layui-input-inline"><input type="checkbox" name="activeCanTargetSpecial" lay-skin="switch" lay-text="是|否"></div></div>
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>Idle 行为参数</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">开火间隔Min(秒)</label><div class="layui-input-inline"><input type="number" name="idleFireIntervalMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火间隔Max(秒)</label><div class="layui-input-inline"><input type="number" name="idleFireIntervalMax" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火概率</label><div class="layui-input-inline"><input type="number" name="idleFireProb" class="layui-input" step="0.01"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">旋转速度(度/秒)</label><div class="layui-input-inline"><input type="number" name="idleRotateSpeed" class="layui-input" step="1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Min(秒)</label><div class="layui-input-inline"><input type="number" name="idleTargetHoldTimeMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Max(秒)</label><div class="layui-input-inline"><input type="number" name="idleTargetHoldTimeMax" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">切换目标概率</label><div class="layui-input-inline"><input type="number" name="idleTargetSwitchChance" class="layui-input" step="0.01"></div></div>
<div class="layui-form-item"><label class="layui-form-label">高分偏好</label><div class="layui-input-inline"><input type="number" name="idleTargetPreferHighScore" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">允许打Boss</label><div class="layui-input-inline"><input type="checkbox" name="idleCanTargetBoss" lay-skin="switch" lay-text="是|否"></div></div>
<div class="layui-form-item"><label class="layui-form-label">允许打特殊鱼</label><div class="layui-input-inline"><input type="checkbox" name="idleCanTargetSpecial" lay-skin="switch" lay-text="是|否"></div></div>
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>Aggressive 行为参数</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">开火间隔Min(秒)</label><div class="layui-input-inline"><input type="number" name="aggFireIntervalMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火间隔Max(秒)</label><div class="layui-input-inline"><input type="number" name="aggFireIntervalMax" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火概率</label><div class="layui-input-inline"><input type="number" name="aggFireProb" class="layui-input" step="0.01"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">旋转速度(度/秒)</label><div class="layui-input-inline"><input type="number" name="aggRotateSpeed" class="layui-input" step="1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Min(秒)</label><div class="layui-input-inline"><input type="number" name="aggTargetHoldTimeMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Max(秒)</label><div class="layui-input-inline"><input type="number" name="aggTargetHoldTimeMax" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">切换目标概率</label><div class="layui-input-inline"><input type="number" name="aggTargetSwitchChance" class="layui-input" step="0.01"></div></div>
<div class="layui-form-item"><label class="layui-form-label">高分偏好</label><div class="layui-input-inline"><input type="number" name="aggTargetPreferHighScore" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">允许打Boss</label><div class="layui-input-inline"><input type="checkbox" name="aggCanTargetBoss" lay-skin="switch" lay-text="是|否"></div></div>
<div class="layui-form-item"><label class="layui-form-label">允许打特殊鱼</label><div class="layui-input-inline"><input type="checkbox" name="aggCanTargetSpecial" lay-skin="switch" lay-text="是|否"></div></div>
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>倍数策略</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">初始档位索引</label><div class="layui-input-inline"><input type="number" name="multiplierInitialLevelIndex" class="layui-input"></div><div class="layui-form-mid">对应炮倍列表下标,0=最低档</div></div>
<div class="layui-form-item"><label class="layui-form-label">降档阈值</label><div class="layui-input-inline"><input type="number" name="multiplierDecreaseThreshold" class="layui-input" step="0.1"></div><div class="layui-form-mid">当前金币/进场金币 &lt; 此值</div></div>
<div class="layui-form-item"><label class="layui-form-label">升档阈值</label><div class="layui-input-inline"><input type="number" name="multiplierIncreaseThreshold" class="layui-input" step="0.1"></div><div class="layui-form-mid">当前金币/进场金币 > 此值</div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">调整档位步数</label><div class="layui-input-inline"><input type="number" name="multiplierStep" class="layui-input"></div></div>
<div class="layui-form-item"><label class="layui-form-label">检查间隔(秒)</label><div class="layui-input-inline"><input type="number" name="multiplierCheckInterval" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">最低档位</label><div class="layui-input-inline"><input type="number" name="multiplierMinLevelIndex" class="layui-input"></div><div class="layui-form-mid">不会降到低于此值</div></div>
<div class="layui-form-item"><label class="layui-form-label">最高档位(-1不限)</label><div class="layui-input-inline"><input type="number" name="multiplierMaxLevelIndex" class="layui-input"></div></div>
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>Boss策略</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">Boss鱼类型</label><div class="layui-input-inline"><input type="text" name="bossFishTypes" class="layui-input" style="width:120px;" placeholder="2,5"></div><div class="layui-form-mid">FishTypeEnum 的 int 值,逗号分隔</div></div>
<div class="layui-form-item"><label class="layui-form-label">无Boss散射</label><div class="layui-input-inline"><input type="checkbox" name="scatterWhenNoBoss" lay-skin="switch" lay-text="开启|关闭"></div></div>
<div class="layui-form-item"><label class="layui-form-label">三发概率</label><div class="layui-input-inline"><input type="number" name="tripleShotProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">无Boss时定点三发概率</div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">优先最大倍率Boss</label><div class="layui-input-inline"><input type="checkbox" name="preferMaxMultiplierBoss" lay-skin="switch" lay-text="是|否"></div></div>
<div class="layui-form-item"><label class="layui-form-label">散射阈值</label><div class="layui-input-inline"><input type="number" name="scatterModeThreshold" class="layui-input"></div><div class="layui-form-mid">Boss数 > 此值开启散射</div></div>
<div class="layui-form-item"><label class="layui-form-label">狂暴概率</label><div class="layui-input-inline"><input type="number" name="bossFrenzyProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">Boss数 ≤ 散射阈值时的概率</div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">狂暴Min</label><div class="layui-input-inline"><input type="number" name="bossFrenzyMinTime" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">狂暴Max</label><div class="layui-input-inline"><input type="number" name="bossFrenzyMaxTime" class="layui-input" step="0.1"></div></div>
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>退出条件</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">盈利退出(金币)</label><div class="layui-input-inline"><input type="number" name="exitWhenGoldAbove" class="layui-input"></div><div class="layui-form-mid">超过此值时退出(盈利离场),0=不检测</div></div>
<div class="layui-form-item"><label class="layui-form-label">亏损退出(金币)</label><div class="layui-input-inline"><input type="number" name="exitWhenGoldBelow" class="layui-input"></div><div class="layui-form-mid">低于此值时退出(输完离场)</div></div>
<div class="layui-form-item"><label class="layui-form-label">检查间隔(秒)</label><div class="layui-input-inline"><input type="number" name="exitCheckInterval" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">发送离房消息</label><div class="layui-input-inline"><input type="checkbox" name="sendLeaveRoom" lay-skin="switch" lay-text="是|否"></div></div>
<div class="layui-form-item"><label class="layui-form-label">退出后显示等待</label><div class="layui-input-inline"><input type="checkbox" name="showWaitingAfterExit" lay-skin="switch" lay-text="是|否"></div></div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
<script>
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})};
</script>
<script>
layui.use(['admin', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var gameConfig = null;
setTimeout(function() { $('.layui-layer-shade').remove(); layer.closeAll(); }, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) { gameConfig = res.data; fillForm(); }
}
});
}
function bc() { if (!gameConfig.botConfig) gameConfig.botConfig = {}; return gameConfig.botConfig; }
function val(name, def) { var v = $('#form-botai input[name="'+name+'"]').val(); return v !== undefined && v !== '' ? v : def; }
function chk(name) { return $('#form-botai input[name="'+name+'"]').prop('checked'); }
function num(name, def) { return parseFloat(val(name, def)) || def; }
function intv(name, def) { return parseInt(val(name, def)) || def; }
function fillBehavior(prefix, data) {
if (!data) return;
$('#form-botai input[name="'+prefix+'FireIntervalMin"]').val(data.fireIntervalMin ?? 0.3);
$('#form-botai input[name="'+prefix+'FireIntervalMax"]').val(data.fireIntervalMax ?? 1.2);
$('#form-botai input[name="'+prefix+'FireProb"]').val(data.fireProb ?? 0.9);
$('#form-botai input[name="'+prefix+'RotateSpeed"]').val(data.rotateSpeed ?? 80);
$('#form-botai input[name="'+prefix+'TargetHoldTimeMin"]').val(data.targetHoldTimeMin ?? 2);
$('#form-botai input[name="'+prefix+'TargetHoldTimeMax"]').val(data.targetHoldTimeMax ?? 5);
$('#form-botai input[name="'+prefix+'TargetSwitchChance"]').val(data.targetSwitchChance ?? 0.7);
$('#form-botai input[name="'+prefix+'TargetPreferHighScore"]').val(data.targetPreferHighScore ?? 0);
$('#form-botai input[name="'+prefix+'CanTargetBoss"]').prop('checked', data.canTargetBoss !== false);
$('#form-botai input[name="'+prefix+'CanTargetSpecial"]').prop('checked', data.canTargetSpecial !== false);
}
function readBehavior(prefix) {
return {
fireIntervalMin: num(prefix+'FireIntervalMin', 0.3),
fireIntervalMax: num(prefix+'FireIntervalMax', 1.2),
fireProb: num(prefix+'FireProb', 0.9),
rotateSpeed: num(prefix+'RotateSpeed', 80),
targetHoldTimeMin: num(prefix+'TargetHoldTimeMin', 2),
targetHoldTimeMax: num(prefix+'TargetHoldTimeMax', 5),
targetSwitchChance: num(prefix+'TargetSwitchChance', 0.7),
targetPreferHighScore: num(prefix+'TargetPreferHighScore', 0),
canTargetBoss: chk(prefix+'CanTargetBoss'),
canTargetSpecial: chk(prefix+'CanTargetSpecial')
};
}
function fillForm() {
var c = bc();
$('#form-botai input[name="enabled"]').prop('checked', c.enabled !== false);
$('#form-botai input[name="aiState"]').val(c.aiState || 'active');
$('#form-botai input[name="luckFactor"]').val(c.luckFactor ?? 1);
$('#form-botai input[name="stateRefreshInterval"]').val(c.stateRefreshInterval ?? 0);
var sk = c.skills || {};
$('#form-botai input[name="skillRefreshInterval"]').val(sk.skillRefreshInterval ?? 10);
$('#form-botai input[name="lockProb"]').val(sk.lockProb ?? 0);
$('#form-botai input[name="lockMinTime"]').val(sk.lockMinTime ?? 5);
$('#form-botai input[name="lockMaxTime"]').val(sk.lockMaxTime ?? 20);
$('#form-botai input[name="frenzyProb"]').val(sk.frenzyProb ?? 0.3);
$('#form-botai input[name="frenzyMinTime"]').val(sk.frenzyMinTime ?? 3);
$('#form-botai input[name="frenzyMaxTime"]').val(sk.frenzyMaxTime ?? 8);
$('#form-botai input[name="tripleProb"]').val(sk.tripleProb ?? 0.5);
$('#form-botai input[name="tripleMinTime"]').val(sk.tripleMinTime ?? 4);
$('#form-botai input[name="tripleMaxTime"]').val(sk.tripleMaxTime ?? 12);
$('#form-botai input[name="doubleProb"]').val(sk.doubleProb ?? 0);
$('#form-botai input[name="doubleMinTime"]').val(sk.doubleMinTime ?? 3);
$('#form-botai input[name="doubleMaxTime"]').val(sk.doubleMaxTime ?? 6);
var mp = c.multiplier || {};
$('#form-botai input[name="multiplierInitialLevelIndex"]').val(mp.initialLevelIndex ?? 0);
$('#form-botai input[name="multiplierDecreaseThreshold"]').val(mp.decreaseThreshold ?? 0.5);
$('#form-botai input[name="multiplierIncreaseThreshold"]').val(mp.increaseThreshold ?? 1.5);
$('#form-botai input[name="multiplierStep"]').val(mp.step ?? 1);
$('#form-botai input[name="multiplierCheckInterval"]').val(mp.checkInterval ?? 3);
$('#form-botai input[name="multiplierMinLevelIndex"]').val(mp.minLevelIndex ?? 0);
$('#form-botai input[name="multiplierMaxLevelIndex"]').val(mp.maxLevelIndex ?? -1);
var bs = c.bossStrategy || {};
$('#form-botai input[name="bossFishTypes"]').val((bs.bossFishTypes || [2,5]).join(','));
$('#form-botai input[name="scatterWhenNoBoss"]').prop('checked', bs.scatterWhenNoBoss !== false);
$('#form-botai input[name="tripleShotProb"]').val(bs.tripleShotProb ?? 0.5);
$('#form-botai input[name="preferMaxMultiplierBoss"]').prop('checked', bs.preferMaxMultiplierBoss !== false);
$('#form-botai input[name="scatterModeThreshold"]').val(bs.scatterModeThreshold ?? 3);
$('#form-botai input[name="bossFrenzyProb"]').val(bs.frenzyProb ?? 0.6);
$('#form-botai input[name="bossFrenzyMinTime"]').val(bs.frenzyMinTime ?? 3);
$('#form-botai input[name="bossFrenzyMaxTime"]').val(bs.frenzyMaxTime ?? 8);
var ex = c.exitCondition || {};
$('#form-botai input[name="exitWhenGoldAbove"]').val(ex.exitWhenGoldAbove ?? 0);
$('#form-botai input[name="exitWhenGoldBelow"]').val(ex.exitWhenGoldBelow ?? 100);
$('#form-botai input[name="exitCheckInterval"]').val(ex.checkInterval ?? 5);
$('#form-botai input[name="sendLeaveRoom"]').prop('checked', ex.sendLeaveRoom !== false);
$('#form-botai input[name="showWaitingAfterExit"]').prop('checked', ex.showWaitingAfterExit !== false);
fillBehavior('active', c.activeBehavior);
fillBehavior('idle', c.idleBehavior);
fillBehavior('agg', c.aggressiveBehavior);
form.render();
}
function collectConfig() {
var c = bc();
c.enabled = chk('enabled');
c.aiState = val('aiState', 'active');
c.luckFactor = num('luckFactor', 1);
c.stateRefreshInterval = num('stateRefreshInterval', 0);
c.skills = {
skillRefreshInterval: num('skillRefreshInterval', 10),
lockProb: num('lockProb', 0), lockMinTime: num('lockMinTime', 5), lockMaxTime: num('lockMaxTime', 20),
frenzyProb: num('frenzyProb', 0.3), frenzyMinTime: num('frenzyMinTime', 3), frenzyMaxTime: num('frenzyMaxTime', 8),
tripleProb: num('tripleProb', 0.5), tripleMinTime: num('tripleMinTime', 4), tripleMaxTime: num('tripleMaxTime', 12),
doubleProb: num('doubleProb', 0), doubleMinTime: num('doubleMinTime', 3), doubleMaxTime: num('doubleMaxTime', 6)
};
c.multiplier = {
initialLevelIndex: intv('multiplierInitialLevelIndex', 0),
decreaseThreshold: num('multiplierDecreaseThreshold', 0.5),
increaseThreshold: num('multiplierIncreaseThreshold', 1.5),
step: intv('multiplierStep', 1),
checkInterval: num('multiplierCheckInterval', 3),
minLevelIndex: intv('multiplierMinLevelIndex', 0),
maxLevelIndex: intv('multiplierMaxLevelIndex', -1)
};
var bft = val('bossFishTypes', '2,5').split(',').map(function(s) { return parseInt(s.trim()) || 2; }).filter(function(n) { return n > 0; });
c.bossStrategy = {
bossFishTypes: bft.length > 0 ? bft : [2, 5],
scatterWhenNoBoss: chk('scatterWhenNoBoss'),
tripleShotProb: num('tripleShotProb', 0.5),
preferMaxMultiplierBoss: chk('preferMaxMultiplierBoss'),
scatterModeThreshold: intv('scatterModeThreshold', 3),
frenzyProb: num('bossFrenzyProb', 0.6),
frenzyMinTime: num('bossFrenzyMinTime', 3),
frenzyMaxTime: num('bossFrenzyMaxTime', 8)
};
c.exitCondition = {
exitWhenGoldAbove: intv('exitWhenGoldAbove', 0),
exitWhenGoldBelow: intv('exitWhenGoldBelow', 100),
checkInterval: num('exitCheckInterval', 5),
sendLeaveRoom: chk('sendLeaveRoom'),
showWaitingAfterExit: chk('showWaitingAfterExit')
};
c.activeBehavior = readBehavior('active');
c.idleBehavior = readBehavior('idle');
c.aggressiveBehavior = readBehavior('agg');
return c;
}
$(document).on('click', '[lay-filter="init-botai"]', function () {
layer.confirm('确定要初始化机器人AI配置吗?此操作将覆盖现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/BotConfig",
done: function (res) {
layer.msg(res.info || (res.code === 0 ? '初始化成功' : '初始化失败'));
if (res.code === 0 || res.code === 200) loadConfig();
}
});
});
});
$(document).on('click', '[lay-filter="reload-botai"]', function () { loadConfig(); layer.msg('刷新成功'); });
$(document).on('click', '[lay-filter="save-botai"]', function () {
collectConfig();
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) { layer.msg('保存成功'); loadConfig(); }
else { layer.msg(res.info || '保存失败'); }
}
});
});
loadConfig();
});
</script>

Some files were not shown because too many files have changed in this diff Show More