From 414b8cdb344e4001dcae499830c61d22b16e6bc7 Mon Sep 17 00:00:00 2001 From: qinhan Date: Sat, 21 Mar 2026 17:36:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8D=A1=E5=9E=83=E5=9C=BE?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Frontend/src/api/scrap.ts | 38 ++ .../Frontend/src/views/admin/ScrapView.vue | 364 ++++++++++ Build_God_Api/Build_God_Api/Build_God_Api.sln | 24 + .../Controllers/ScrapController.cs | 104 +++ .../Build_God_Api/DB/CharacterScrap.cs | 11 + Build_God_Api/Build_God_Api/DB/Scrap.cs | 44 ++ Build_God_Api/Build_God_Api/Dto/ScrapDto.cs | 36 + .../Build_God_Api/Services/ScrapService.cs | 226 +++++++ Build_God_Game/src/api/scrap.ts | 76 +++ Build_God_Game/src/views/ScrapView.vue | 625 ++++++++++++++++++ 10 files changed, 1548 insertions(+) create mode 100644 Build_God_Admin_Frontend/Frontend/src/api/scrap.ts create mode 100644 Build_God_Admin_Frontend/Frontend/src/views/admin/ScrapView.vue create mode 100644 Build_God_Api/Build_God_Api/Build_God_Api.sln create mode 100644 Build_God_Api/Build_God_Api/Controllers/ScrapController.cs create mode 100644 Build_God_Api/Build_God_Api/DB/CharacterScrap.cs create mode 100644 Build_God_Api/Build_God_Api/DB/Scrap.cs create mode 100644 Build_God_Api/Build_God_Api/Dto/ScrapDto.cs create mode 100644 Build_God_Api/Build_God_Api/Services/ScrapService.cs create mode 100644 Build_God_Game/src/api/scrap.ts create mode 100644 Build_God_Game/src/views/ScrapView.vue diff --git a/Build_God_Admin_Frontend/Frontend/src/api/scrap.ts b/Build_God_Admin_Frontend/Frontend/src/api/scrap.ts new file mode 100644 index 0000000..7d519e4 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/scrap.ts @@ -0,0 +1,38 @@ +import http from "../api/index"; + +export interface Scrap { + id: number; + name: string; + description: string; + story: string; + level: number; + levelName: string; + levelColor: string; + attackBonus: number; + defenseBonus: number; + hpBonus: number; + magicBonus: number; + isActive: boolean; +} + +export interface ScrapLevel { + id: number; + name: string; + displayName: string; +} + +export const GetAllScraps = (): Promise => { + return http.get("scrap/all"); +}; + +export const AddScrap = (data: Scrap): Promise => { + return http.post("scrap", data); +}; + +export const UpdateScrap = (data: Scrap): Promise => { + return http.put("scrap", data); +}; + +export const DeleteScrap = (id: number): Promise => { + return http.delete(`scrap/${id}`); +}; diff --git a/Build_God_Admin_Frontend/Frontend/src/views/admin/ScrapView.vue b/Build_God_Admin_Frontend/Frontend/src/views/admin/ScrapView.vue new file mode 100644 index 0000000..6cb1146 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/admin/ScrapView.vue @@ -0,0 +1,364 @@ + + + + + diff --git a/Build_God_Api/Build_God_Api/Build_God_Api.sln b/Build_God_Api/Build_God_Api/Build_God_Api.sln new file mode 100644 index 0000000..6667f92 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Build_God_Api.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build_God_Api", "Build_God_Api.csproj", "{47C83A69-1184-B652-B317-1E0D4236A04D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {47C83A69-1184-B652-B317-1E0D4236A04D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47C83A69-1184-B652-B317-1E0D4236A04D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47C83A69-1184-B652-B317-1E0D4236A04D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47C83A69-1184-B652-B317-1E0D4236A04D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E4CD997F-31FC-4563-B882-DA1CF44946AC} + EndGlobalSection +EndGlobal diff --git a/Build_God_Api/Build_God_Api/Controllers/ScrapController.cs b/Build_God_Api/Build_God_Api/Controllers/ScrapController.cs new file mode 100644 index 0000000..37d274d --- /dev/null +++ b/Build_God_Api/Build_God_Api/Controllers/ScrapController.cs @@ -0,0 +1,104 @@ +using Build_God_Api.DB; +using Build_God_Api.Dto; +using Build_God_Api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Build_God_Api.Controllers +{ + [ApiController] + [Route("api/god/[controller]")] + public class ScrapController(IScrapService scrapService, ICurrentUserService currentUserService) : ControllerBase + { + private readonly IScrapService scrapService = scrapService; + private readonly ICurrentUserService currentUserService = currentUserService; + + [HttpGet("list")] + [Authorize] + public async Task>> GetScrapList() + { + return await scrapService.GetAllActiveScrapsAsync(); + } + + [HttpPost("scan")] + [Authorize] + public async Task> ScanScrap([FromBody] ScrapScanRequest request) + { + if (request.CharacterId <= 0) + { + return BadRequest("无效的角色ID"); + } + + var result = await scrapService.ScanAndAssignScrapAsync(request.CharacterId); + return result; + } + + [HttpGet("history/{characterId}")] + [Authorize] + public async Task>> GetScrapHistory(int characterId) + { + if (characterId <= 0) + { + return BadRequest("无效的角色ID"); + } + + return await scrapService.GetCharacterScrapHistoryAsync(characterId); + } + + [HttpGet("all")] + [Authorize(Roles = "admin")] + public async Task>> GetAllScraps() + { + return await scrapService.GetAllScrapsAsync(); + } + + [HttpPost] + [Authorize(Roles = "admin")] + public async Task> AddScrap([FromBody] ScrapDto dto) + { + if (string.IsNullOrWhiteSpace(dto.Name)) + { + return BadRequest("名称不能为空"); + } + + var result = await scrapService.AddScrapAsync(dto); + return result; + } + + [HttpPut] + [Authorize(Roles = "admin")] + public async Task> UpdateScrap([FromBody] ScrapDto dto) + { + if (dto.Id <= 0) + { + return BadRequest("无效的ID"); + } + + if (string.IsNullOrWhiteSpace(dto.Name)) + { + return BadRequest("名称不能为空"); + } + + var result = await scrapService.UpdateScrapAsync(dto); + return result; + } + + [HttpDelete("{id}")] + [Authorize(Roles = "admin")] + public async Task> DeleteScrap(int id) + { + if (id <= 0) + { + return BadRequest("无效的ID"); + } + + var result = await scrapService.DeleteScrapAsync(id); + return result; + } + } + + public class ScrapScanRequest + { + public int CharacterId { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/DB/CharacterScrap.cs b/Build_God_Api/Build_God_Api/DB/CharacterScrap.cs new file mode 100644 index 0000000..604529a --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/CharacterScrap.cs @@ -0,0 +1,11 @@ +namespace Build_God_Api.DB +{ + public class CharacterScrap : BaseEntity + { + public int CharacterId { get; set; } + + public int ScrapId { get; set; } + + public DateTime ObtainedAt { get; set; } = DateTime.UtcNow; + } +} diff --git a/Build_God_Api/Build_God_Api/DB/Scrap.cs b/Build_God_Api/Build_God_Api/DB/Scrap.cs new file mode 100644 index 0000000..81aa0ac --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/Scrap.cs @@ -0,0 +1,44 @@ +using SqlSugar; +using System.ComponentModel; + +namespace Build_God_Api.DB +{ + public class Scrap : BaseEntity + { + public string Name { get; set; } = string.Empty; + + public string Description { get; set; } = string.Empty; + + public string Story { get; set; } = string.Empty; + + public ScrapLevel Level { get; set; } + + public int AttackBonus { get; set; } + + public int DefenseBonus { get; set; } + + public int HpBonus { get; set; } + + public int MagicBonus { get; set; } + + public bool IsActive { get; set; } = true; + } + + public enum ScrapLevel + { + [Description("普通")] + White = 1, + + [Description("优秀")] + Green = 2, + + [Description("精良")] + Blue = 3, + + [Description("史诗")] + Purple = 4, + + [Description("传说")] + Orange = 5 + } +} diff --git a/Build_God_Api/Build_God_Api/Dto/ScrapDto.cs b/Build_God_Api/Build_God_Api/Dto/ScrapDto.cs new file mode 100644 index 0000000..db489f3 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Dto/ScrapDto.cs @@ -0,0 +1,36 @@ +using Build_God_Api.DB; + +namespace Build_God_Api.Dto +{ + public class ScrapDto + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string Story { get; set; } = string.Empty; + public int Level { get; set; } + public string LevelName { get; set; } = string.Empty; + public string LevelColor { get; set; } = string.Empty; + public int AttackBonus { get; set; } + public int DefenseBonus { get; set; } + public int HpBonus { get; set; } + public int MagicBonus { get; set; } + public bool IsActive { get; set; } = true; + } + + public class ScrapScanResultDto + { + public ScrapDto Scrap { get; set; } = null!; + public int AttackGain { get; set; } + public int DefenseGain { get; set; } + public int HpGain { get; set; } + public int MagicGain { get; set; } + } + + public class ScrapHistoryDto + { + public int Id { get; set; } + public ScrapDto Scrap { get; set; } = null!; + public DateTime ObtainedAt { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/ScrapService.cs b/Build_God_Api/Build_God_Api/Services/ScrapService.cs new file mode 100644 index 0000000..b261b77 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/ScrapService.cs @@ -0,0 +1,226 @@ +using Build_God_Api.DB; +using Build_God_Api.Dto; +using SqlSugar; +using System.ComponentModel; + +namespace Build_God_Api.Services +{ + public interface IScrapService + { + Task> GetAllActiveScrapsAsync(); + Task ScanAndAssignScrapAsync(int characterId); + Task> GetCharacterScrapHistoryAsync(int characterId); + Task> GetAllScrapsAsync(); + Task AddScrapAsync(ScrapDto dto); + Task UpdateScrapAsync(ScrapDto dto); + Task DeleteScrapAsync(int id); + } + + public class ScrapService(ISqlSugarClient db) : IScrapService + { + private readonly ISqlSugarClient db = db; + + private static readonly Dictionary LevelColors = new() + { + { ScrapLevel.White, "#FFFFFF" }, + { ScrapLevel.Green, "#00FF00" }, + { ScrapLevel.Blue, "#0077FF" }, + { ScrapLevel.Purple, "#9932CC" }, + { ScrapLevel.Orange, "#FF8C00" } + }; + + private static readonly Dictionary LevelWeights = new() + { + { ScrapLevel.White, 50 }, + { ScrapLevel.Green, 30 }, + { ScrapLevel.Blue, 15 }, + { ScrapLevel.Purple, 4 }, + { ScrapLevel.Orange, 1 } + }; + + public async Task> GetAllActiveScrapsAsync() + { + var scraps = await db.Queryable() + .Where(x => x.IsActive) + .OrderBy(x => x.Level) + .ToListAsync(); + + return scraps.Select(MapToDto).ToList(); + } + + public async Task ScanAndAssignScrapAsync(int characterId) + { + var character = await db.Queryable().FirstAsync(x => x.Id == characterId) + ?? throw new Exception("角色不存在"); + + var scraps = await db.Queryable() + .Where(x => x.IsActive) + .ToListAsync(); + + if (scraps.Count == 0) + { + throw new Exception("目前没有可捡的垃圾"); + } + + var selectedLevel = SelectRandomLevel(); + var levelScraps = scraps.Where(x => x.Level == selectedLevel).ToList(); + + if (levelScraps.Count == 0) + { + levelScraps = scraps; + } + + var random = new Random(); + var selectedScrap = levelScraps[random.Next(levelScraps.Count)]; + + var characterScrap = new CharacterScrap + { + CharacterId = characterId, + ScrapId = selectedScrap.Id, + CreatedBy = characterId, + UpdatedBy = characterId + }; + await db.Insertable(characterScrap).ExecuteCommandAsync(); + + character.Attack += selectedScrap.AttackBonus; + character.MaxHP += selectedScrap.HpBonus; + await db.Updateable(character).ExecuteCommandAsync(); + + return new ScrapScanResultDto + { + Scrap = MapToDto(selectedScrap), + AttackGain = selectedScrap.AttackBonus, + DefenseGain = selectedScrap.DefenseBonus, + HpGain = selectedScrap.HpBonus, + MagicGain = selectedScrap.MagicBonus + }; + } + + public async Task> GetCharacterScrapHistoryAsync(int characterId) + { + var history = await db.Queryable() + .Where(x => x.CharacterId == characterId) + .OrderByDescending(x => x.ObtainedAt) + .ToListAsync(); + + var result = new List(); + foreach (var item in history) + { + var scrap = await db.Queryable().FirstAsync(x => x.Id == item.ScrapId); + if (scrap != null) + { + result.Add(new ScrapHistoryDto + { + Id = item.Id, + Scrap = MapToDto(scrap), + ObtainedAt = item.ObtainedAt + }); + } + } + + return result; + } + + private ScrapLevel SelectRandomLevel() + { + var random = new Random(); + int totalWeight = LevelWeights.Values.Sum(); + int randomValue = random.Next(totalWeight); + + int cumulativeWeight = 0; + foreach (var kvp in LevelWeights.OrderBy(x => x.Key)) + { + cumulativeWeight += kvp.Value; + if (randomValue < cumulativeWeight) + { + return kvp.Key; + } + } + + return ScrapLevel.White; + } + + private static ScrapDto MapToDto(Scrap scrap) + { + var levelEnum = scrap.Level; + var description = GetEnumDescription(levelEnum); + + return new ScrapDto + { + Id = scrap.Id, + Name = scrap.Name, + Description = scrap.Description, + Story = scrap.Story, + Level = (int)scrap.Level, + LevelName = description, + LevelColor = LevelColors.GetValueOrDefault(scrap.Level, "#FFFFFF"), + AttackBonus = scrap.AttackBonus, + DefenseBonus = scrap.DefenseBonus, + HpBonus = scrap.HpBonus, + MagicBonus = scrap.MagicBonus + }; + } + + private static string GetEnumDescription(ScrapLevel level) + { + var field = level.GetType().GetField(level.ToString()); + var attribute = field?.GetCustomAttributes(typeof(DescriptionAttribute), false) + .FirstOrDefault() as DescriptionAttribute; + return attribute?.Description ?? level.ToString(); + } + + public async Task> GetAllScrapsAsync() + { + return await db.Queryable() + .OrderBy(x => x.Level) + .ToListAsync(); + } + + public async Task AddScrapAsync(ScrapDto dto) + { + var scrap = new Scrap + { + Name = dto.Name, + Description = dto.Description, + Story = dto.Story, + Level = (ScrapLevel)dto.Level, + AttackBonus = dto.AttackBonus, + DefenseBonus = dto.DefenseBonus, + HpBonus = dto.HpBonus, + MagicBonus = dto.MagicBonus, + IsActive = dto.IsActive + }; + + await db.Insertable(scrap).ExecuteCommandAsync(); + return true; + } + + public async Task UpdateScrapAsync(ScrapDto dto) + { + var scrap = await db.Queryable().FirstAsync(x => x.Id == dto.Id); + if (scrap == null) + { + throw new Exception("垃圾不存在"); + } + + scrap.Name = dto.Name; + scrap.Description = dto.Description; + scrap.Story = dto.Story; + scrap.Level = (ScrapLevel)dto.Level; + scrap.AttackBonus = dto.AttackBonus; + scrap.DefenseBonus = dto.DefenseBonus; + scrap.HpBonus = dto.HpBonus; + scrap.MagicBonus = dto.MagicBonus; + scrap.IsActive = dto.IsActive; + + await db.Updateable(scrap).ExecuteCommandAsync(); + return true; + } + + public async Task DeleteScrapAsync(int id) + { + await db.Deleteable().Where(x => x.Id == id).ExecuteCommandAsync(); + return true; + } + } +} diff --git a/Build_God_Game/src/api/scrap.ts b/Build_God_Game/src/api/scrap.ts new file mode 100644 index 0000000..c4f465f --- /dev/null +++ b/Build_God_Game/src/api/scrap.ts @@ -0,0 +1,76 @@ +import axios from 'axios' + +const instance = axios.create({ + baseURL: '/api', + timeout: 10000, + headers: { + 'Content-Type': 'application/json' + } +}) + +instance.interceptors.request.use( + (config) => { + const token = localStorage.getItem('auth_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => Promise.reject(error) +) + +instance.interceptors.response.use( + (response) => response.data, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('auth_token') + localStorage.removeItem('user') + window.location.href = '/login' + } + return Promise.reject(error.response?.data || error.message) + } +) + +export interface ScrapDto { + id: number + name: string + description: string + story: string + level: number + levelName: string + levelColor: string + attackBonus: number + defenseBonus: number + hpBonus: number + magicBonus: number +} + +export interface ScrapScanResultDto { + scrap: ScrapDto + attackGain: number + defenseGain: number + hpGain: number + magicGain: number +} + +export interface ScrapHistoryDto { + id: number + scrap: ScrapDto + obtainedAt: string +} + +export const scrapApi = { + getScrapList: (): Promise => { + return instance.get('/scrap/list') + }, + + scanScrap: (characterId: number): Promise => { + return instance.post('/scrap/scan', { characterId }) + }, + + getScrapHistory: (characterId: number): Promise => { + return instance.get(`/scrap/history/${characterId}`) + } +} + +export default instance diff --git a/Build_God_Game/src/views/ScrapView.vue b/Build_God_Game/src/views/ScrapView.vue new file mode 100644 index 0000000..bd13d5b --- /dev/null +++ b/Build_God_Game/src/views/ScrapView.vue @@ -0,0 +1,625 @@ + + + + +