Browse Source

合并代码

master
秦汉 4 weeks ago
parent
commit
c4022f044e
  1. 2
      Build_God_Admin_Frontend/Frontend/.env.development
  2. 2
      Build_God_Admin_Frontend/Frontend/src/api/bag.ts
  3. 13
      Build_God_Api/Build_God_Api/Controllers/BagController.cs
  4. 31
      Build_God_Api/Build_God_Api/Controllers/MissionController.cs
  5. 10
      Build_God_Api/Build_God_Api/DB/Mission.cs
  6. 14
      Build_God_Api/Build_God_Api/Dto/MissionDtos.cs
  7. 35
      Build_God_Api/Build_God_Api/Services/BagService.cs
  8. 39
      Build_God_Api/Build_God_Api/Services/DailyMissionService.cs
  9. 83
      Build_God_Api/Build_God_Api/Services/MissionProgressService.cs
  10. 2
      Build_God_Game/.env.development
  11. 7
      Build_God_Game/src/api/bag.ts
  12. 38
      Build_God_Game/src/api/dailyMission.ts
  13. 1
      Build_God_Game/src/assets/images/collection.svg
  14. 1
      Build_God_Game/src/assets/images/hunting.svg
  15. 42
      Build_God_Game/src/composables/useMissionProgress.ts
  16. 569
      Build_God_Game/src/views/DailyMissionView.vue
  17. 134
      docs/superpowers/specs/2026-04-15-mission-system-redesign.md

2
Build_God_Admin_Frontend/Frontend/.env.development

@ -1,2 +1,2 @@
# 开发环境配置 # 开发环境配置
VITE_API_URL=https://localhost:59447/api/god/ VITE_API_URL=http://localhost:5091/api/god/

2
Build_God_Admin_Frontend/Frontend/src/api/bag.ts

@ -73,7 +73,7 @@ export const GetBagItems = (characterBagId: number): Promise<BagItem[]> => {
} }
export const AddItemToBag = (characterBagId: number, data: AddBagItemDto): Promise<boolean> => { export const AddItemToBag = (characterBagId: number, data: AddBagItemDto): Promise<boolean> => {
return http.post(`bag/${characterBagId}/items`, data) return http.post(`bag/${characterBagId}/items/with-mission`, data)
} }
export const RemoveItemFromBag = (characterBagId: number, itemId: number): Promise<boolean> => { export const RemoveItemFromBag = (characterBagId: number, itemId: number): Promise<boolean> => {

13
Build_God_Api/Build_God_Api/Controllers/BagController.cs

@ -8,9 +8,13 @@ namespace Build_God_Api.Controllers
{ {
[ApiController] [ApiController]
[Route("api/god/[controller]")] [Route("api/god/[controller]")]
public class BagController(IBagService service) : ControllerBase public class BagController(
IBagService service,
IMissionProgressService missionProgressService
) : ControllerBase
{ {
private readonly IBagService service = service; private readonly IBagService service = service;
private readonly IMissionProgressService missionProgressService = missionProgressService;
// ============ Bag配置管理 ============ // ============ Bag配置管理 ============
@ -82,6 +86,13 @@ namespace Build_God_Api.Controllers
return await service.AddItemToBag(characterBagId, dto.ItemType, dto.ItemId, dto.Quantity); return await service.AddItemToBag(characterBagId, dto.ItemType, dto.ItemId, dto.Quantity);
} }
[HttpPost("{characterBagId}/items/with-mission")]
[Authorize]
public async Task<ActionResult<bool>> AddItemToBagWithMission(int characterBagId, [FromBody] AddBagItemDto dto)
{
return await service.AddItemToBagWithMissionProgress(characterBagId, dto.ItemType, dto.ItemId, dto.Quantity, missionProgressService);
}
[HttpDelete("{characterBagId}/items/{itemId}")] [HttpDelete("{characterBagId}/items/{itemId}")]
[Authorize(Roles = "admin")] [Authorize(Roles = "admin")]
public async Task<ActionResult<bool>> RemoveItemFromBag(int characterBagId, int itemId) public async Task<ActionResult<bool>> RemoveItemFromBag(int characterBagId, int itemId)

31
Build_God_Api/Build_God_Api/Controllers/MissionController.cs

@ -11,11 +11,15 @@ namespace Build_God_Api.Controllers
[Route("api/god/[controller]")] [Route("api/god/[controller]")]
public class MissionController( public class MissionController(
IMissionService service, IMissionService service,
IMissionProgressService progressService IMissionProgressService progressService,
ICharacterService characterService,
ICurrentUserService currentUserService
) : ControllerBase ) : ControllerBase
{ {
private readonly IMissionService service = service; private readonly IMissionService service = service;
private readonly IMissionProgressService progressService = progressService; private readonly IMissionProgressService progressService = progressService;
private readonly ICharacterService characterService = characterService;
private readonly ICurrentUserService currentUserService = currentUserService;
// ============ Mission ============ // ============ Mission ============
@ -93,6 +97,31 @@ namespace Build_God_Api.Controllers
return await progressService.Delete(id); return await progressService.Delete(id);
} }
[HttpPost("progress/update")]
[Authorize]
public async Task<ActionResult<UpdateProgressResultDto>> UpdateProgress([FromBody] UpdateProgressCmd cmd)
{
try
{
var character = await characterService.GetCharacterByAccountId(currentUserService.UserId);
if (character == null)
return BadRequest("角色不存在");
var (success, missionCompleted) = await progressService.UpdateProgressByTargetType(
character.Id, cmd.ProgressType, cmd.ItemId, cmd.ItemName, cmd.Count);
return Ok(new UpdateProgressResultDto
{
Success = success,
MissionCompleted = missionCompleted
});
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
// ============ Enums ============ // ============ Enums ============
[HttpGet("types")] [HttpGet("types")]

10
Build_God_Api/Build_God_Api/DB/Mission.cs

@ -20,7 +20,7 @@ namespace Build_God_Api.DB
/// 任务类型 /// 任务类型
/// </summary> /// </summary>
[SugarColumn(IsNullable = false)] [SugarColumn(IsNullable = false)]
public MissionType Type { get; set; } = MissionType.MainStory; public MissionType Type { get; set; } = MissionType.Collection;
/// <summary> /// <summary>
/// 任务标题(展示用,与Name不同) /// 任务标题(展示用,与Name不同)
@ -91,11 +91,11 @@ namespace Build_God_Api.DB
/// </summary> /// </summary>
public enum MissionType public enum MissionType
{ {
[Description("悬赏任务")] [Description("收集任务")]
MainStory = 1, Collection = 1,
[Description("日常任务")] [Description("狩猎任务")]
DailyTask = 2 Hunting = 2
} }
/// <summary> /// <summary>

14
Build_God_Api/Build_God_Api/Dto/MissionDtos.cs

@ -8,4 +8,18 @@ namespace Build_God_Api.Dto
public int PageSize { get; set; } = 0; public int PageSize { get; set; } = 0;
public int? MissionType { get; set; } public int? MissionType { get; set; }
} }
public class UpdateProgressCmd
{
public ProgressTargetType ProgressType { get; set; }
public int? ItemId { get; set; }
public string? ItemName { get; set; }
public int Count { get; set; } = 1;
}
public class UpdateProgressResultDto
{
public bool Success { get; set; }
public bool MissionCompleted { get; set; }
}
} }

35
Build_God_Api/Build_God_Api/Services/BagService.cs

@ -1,4 +1,5 @@
using Build_God_Api.DB; using Build_God_Api.DB;
using Build_God_Api.Dto;
using SqlSugar; using SqlSugar;
namespace Build_God_Api.Services namespace Build_God_Api.Services
@ -19,6 +20,7 @@ namespace Build_God_Api.Services
// 背包物品管理 // 背包物品管理
Task<List<BagItemDto>> GetBagItems(int characterBagId); Task<List<BagItemDto>> GetBagItems(int characterBagId);
Task<bool> AddItemToBag(int characterBagId, int itemType, int itemId, int quantity); Task<bool> AddItemToBag(int characterBagId, int itemType, int itemId, int quantity);
Task<bool> AddItemToBagWithMissionProgress(int characterBagId, int itemType, int itemId, int quantity, IMissionProgressService? missionProgressService = null);
Task<bool> RemoveItemFromBag(int characterBagId, int bagItemId); Task<bool> RemoveItemFromBag(int characterBagId, int bagItemId);
// 物品数量管理 // 物品数量管理
@ -266,6 +268,39 @@ namespace Build_God_Api.Services
return true; return true;
} }
public async Task<bool> AddItemToBagWithMissionProgress(int characterBagId, int itemType, int itemId, int quantity, IMissionProgressService? missionProgressService = null)
{
var result = await AddItemToBag(characterBagId, itemType, itemId, quantity);
if (result && missionProgressService != null)
{
var characterBag = await db.Queryable<CharacterBag>()
.FirstAsync(x => x.Id == characterBagId);
if (characterBag != null)
{
var pill = await db.Queryable<Pill>().FirstAsync(x => x.Id == itemId);
var itemName = pill?.Name ?? string.Empty;
try
{
await missionProgressService.UpdateProgressByTargetType(
characterBag.CharacterId,
ProgressTargetType.CollectItem,
itemId,
itemName,
quantity);
}
catch
{
// 忽略任务进度更新错误,不影响物品添加
}
}
}
return result;
}
public async Task<bool> RemoveItemFromBag(int characterBagId, int bagItemId) public async Task<bool> RemoveItemFromBag(int characterBagId, int bagItemId)
{ {
var item = await db.Queryable<BagItem>() var item = await db.Queryable<BagItem>()

39
Build_God_Api/Build_God_Api/Services/DailyMissionService.cs

@ -50,6 +50,9 @@ namespace Build_God_Api.Services
public int TodayClaimedCount { get; set; } public int TodayClaimedCount { get; set; }
public int TodayTotalCount { get; set; } public int TodayTotalCount { get; set; }
public List<MissionRewardDto> Rewards { get; set; } = new(); public List<MissionRewardDto> Rewards { get; set; } = new();
public MissionType MissionType { get; set; }
public MissionDifficulty Difficulty { get; set; }
public List<MissionProgressDto> Progresses { get; set; } = new();
} }
public class MissionRewardDto public class MissionRewardDto
@ -61,6 +64,16 @@ namespace Build_God_Api.Services
public int Count { get; set; } public int Count { get; set; }
} }
public class MissionProgressDto
{
public int MissionProgressId { get; set; }
public ProgressTargetType TargetType { get; set; }
public int? TargetItemId { get; set; }
public string? TargetItemName { get; set; }
public int TargetCount { get; set; }
public int CurrentCount { get; set; }
}
public class DailyMissionService( public class DailyMissionService(
ISqlSugarClient db, ISqlSugarClient db,
IBagService bagService, IBagService bagService,
@ -125,6 +138,7 @@ namespace Build_God_Api.Services
{ {
var mission = await _db.Queryable<Mission>() var mission = await _db.Queryable<Mission>()
.Includes(x => x.Rewards) .Includes(x => x.Rewards)
.Includes(x => x.Progresses)
.FirstAsync(x => x.Id == dm.MissionId); .FirstAsync(x => x.Id == dm.MissionId);
if (mission == null) continue; if (mission == null) continue;
@ -145,7 +159,9 @@ namespace Build_God_Api.Services
AssignedDate = dm.AssignedDate, AssignedDate = dm.AssignedDate,
ExpReward = CalculateExpReward(mission.Difficulty, character.LevelId), ExpReward = CalculateExpReward(mission.Difficulty, character.LevelId),
TodayClaimedCount = todayClaimedCount, TodayClaimedCount = todayClaimedCount,
TodayTotalCount = todayTotalCount TodayTotalCount = todayTotalCount,
MissionType = mission.Type,
Difficulty = mission.Difficulty
}; };
if (mission.Rewards != null) if (mission.Rewards != null)
@ -163,6 +179,25 @@ namespace Build_God_Api.Services
} }
} }
if (mission.Progresses != null)
{
foreach (var progress in mission.Progresses)
{
var characterProgress = await _db.Queryable<CharacterMissionProgress>()
.FirstAsync(x => x.CharacterId == characterId && x.MissionId == dm.MissionId && x.MissionProgressId == progress.Id);
dto.Progresses.Add(new MissionProgressDto
{
MissionProgressId = progress.Id,
TargetType = progress.TargetType,
TargetItemId = progress.TargetItemId,
TargetItemName = progress.TargetItemName,
TargetCount = progress.TargetCount,
CurrentCount = characterProgress?.CurrentCount ?? 0
});
}
}
result.Add(dto); result.Add(dto);
} }
@ -312,7 +347,7 @@ namespace Build_God_Api.Services
} }
var availableMissions = await _db.Queryable<Mission>() var availableMissions = await _db.Queryable<Mission>()
.Where(x => x.Type == MissionType.DailyTask) .Where(x => x.Type == MissionType.Collection || x.Type == MissionType.Hunting)
.Where(x => x.IsAvailable == true) .Where(x => x.IsAvailable == true)
.Where(x => x.RequiredLevelId <= character.LevelId) .Where(x => x.RequiredLevelId <= character.LevelId)
.ToListAsync(); .ToListAsync();

83
Build_God_Api/Build_God_Api/Services/MissionProgressService.cs

@ -1,4 +1,5 @@
using Build_God_Api.DB; using Build_God_Api.DB;
using Build_God_Api.Dto;
using SqlSugar; using SqlSugar;
namespace Build_God_Api.Services namespace Build_God_Api.Services
@ -16,6 +17,9 @@ namespace Build_God_Api.Services
Task<List<CharacterMissionProgress>> GetCharacterProgress(int characterId, int missionId); Task<List<CharacterMissionProgress>> GetCharacterProgress(int characterId, int missionId);
Task<bool> UpdateCharacterProgress(int characterId, int missionProgressId, int count); Task<bool> UpdateCharacterProgress(int characterId, int missionProgressId, int count);
Task<bool> InitCharacterProgress(int characterId, int missionId); Task<bool> InitCharacterProgress(int characterId, int missionId);
// 更新角色任务进度(根据目标类型自动匹配)
Task<(bool success, bool missionCompleted)> UpdateProgressByTargetType(int characterId, ProgressTargetType targetType, int? itemId, string? itemName, int count);
} }
public class MissionProgressService(ISqlSugarClient db) : IMissionProgressService public class MissionProgressService(ISqlSugarClient db) : IMissionProgressService
@ -125,5 +129,84 @@ namespace Build_God_Api.Services
return true; return true;
} }
public async Task<(bool success, bool missionCompleted)> UpdateProgressByTargetType(int characterId, ProgressTargetType targetType, int? itemId, string? itemName, int count)
{
// 查找该角色所有进行中的每日任务
var dailyMissions = await db.Queryable<CharacterDailyMission>()
.Where(x => x.CharacterId == characterId && x.Status == DailyMissionStatus.InProgress)
.ToListAsync();
if (!dailyMissions.Any())
return (false, false);
bool anyCompleted = false;
foreach (var dailyMission in dailyMissions)
{
// 获取任务配置
var mission = await db.Queryable<Mission>()
.Includes(x => x.Progresses)
.FirstAsync(x => x.Id == dailyMission.MissionId);
if (mission?.Progresses == null || !mission.Progresses.Any())
continue;
// 查找匹配的目标进度
var matchingProgress = mission.Progresses.FirstOrDefault(p =>
p.TargetType == targetType &&
(targetType != ProgressTargetType.CollectItem || p.TargetItemId == itemId));
if (matchingProgress == null)
continue;
// 获取角色进度
var characterProgress = await db.Queryable<CharacterMissionProgress>()
.FirstAsync(x =>
x.CharacterId == characterId &&
x.MissionId == dailyMission.MissionId &&
x.MissionProgressId == matchingProgress.Id);
if (characterProgress == null)
{
characterProgress = new CharacterMissionProgress
{
CharacterId = characterId,
MissionId = dailyMission.MissionId,
MissionProgressId = matchingProgress.Id,
CurrentCount = 0,
IsCompleted = false,
UpdatedOn = DateTime.UtcNow
};
await db.Insertable(characterProgress).ExecuteCommandAsync();
}
if (characterProgress.IsCompleted)
continue;
// 更新进度
characterProgress.CurrentCount = Math.Min(characterProgress.CurrentCount + count, matchingProgress.TargetCount);
characterProgress.IsCompleted = characterProgress.CurrentCount >= matchingProgress.TargetCount;
characterProgress.UpdatedOn = DateTime.UtcNow;
await db.Updateable(characterProgress).ExecuteCommandAsync();
// 检查任务是否全部完成
var allProgresses = await db.Queryable<CharacterMissionProgress>()
.Where(x => x.CharacterId == characterId && x.MissionId == dailyMission.MissionId)
.ToListAsync();
bool allCompleted = allProgresses.All(p => p.IsCompleted);
if (allCompleted)
{
dailyMission.Status = DailyMissionStatus.Completed;
await db.Updateable(dailyMission).ExecuteCommandAsync();
anyCompleted = true;
}
}
return (true, anyCompleted);
}
} }
} }

2
Build_God_Game/.env.development

@ -1,2 +1,2 @@
# 开发环境配置 # 开发环境配置
VITE_API_URL=https://localhost:59447/api/god/ VITE_API_URL=http://localhost:5091/api/god/

7
Build_God_Game/src/api/bag.ts

@ -39,4 +39,11 @@ export const getCharacterBag = (characterId: number): Promise<CharacterBag | nul
export const getBagItems = (characterBagId: number): Promise<BagItem[]> => { export const getBagItems = (characterBagId: number): Promise<BagItem[]> => {
return http.get(`bag/${characterBagId}/items`) return http.get(`bag/${characterBagId}/items`)
}
export const addItemToBag = (characterBagId: number, itemType: number, itemId: number, quantity: number, withMissionProgress: boolean = false): Promise<boolean> => {
if (withMissionProgress) {
return http.post(`bag/${characterBagId}/items/with-mission`, { itemType, itemId, quantity })
}
return http.post(`bag/${characterBagId}/items`, { itemType, itemId, quantity })
} }

38
Build_God_Game/src/api/dailyMission.ts

@ -12,6 +12,25 @@ export const RewardType = {
Money: 3 Money: 3
} as const } as const
export type RewardType = typeof RewardType[keyof typeof RewardType] export type RewardType = typeof RewardType[keyof typeof RewardType]
export const MissionType = {
Collection: 1,
Hunting: 2
} as const
export type MissionType = typeof MissionType[keyof typeof MissionType]
export const MissionDifficulty = {
Normal: 1,
Hard: 2,
Purgatory: 3
} as const
export type MissionDifficulty = typeof MissionDifficulty[keyof typeof MissionDifficulty]
export const ProgressTargetType = {
CollectItem: 1,
Fish: 2,
KillMonster: 3,
ConsumeItem: 4,
Custom: 5
} as const
export type ProgressTargetType = typeof ProgressTargetType[keyof typeof ProgressTargetType]
export interface MissionReward { export interface MissionReward {
rewardType: RewardType rewardType: RewardType
rewardTypeName: string rewardTypeName: string
@ -19,6 +38,14 @@ export interface MissionReward {
itemName: string itemName: string
count: number count: number
} }
export interface MissionProgressDto {
missionProgressId: number
targetType: ProgressTargetType
targetItemId: number | null
targetItemName: string | null
targetCount: number
currentCount: number
}
export interface DailyMission { export interface DailyMission {
id: number id: number
characterId: number characterId: number
@ -36,6 +63,9 @@ export interface DailyMission {
todayClaimedCount: number todayClaimedCount: number
todayTotalCount: number todayTotalCount: number
rewards: MissionReward[] rewards: MissionReward[]
missionType: MissionType
difficulty: MissionDifficulty
progresses: MissionProgressDto[]
} }
export const dailyMissionApi = { export const dailyMissionApi = {
getList: (): Promise<DailyMission[]> => { getList: (): Promise<DailyMission[]> => {
@ -46,5 +76,13 @@ export const dailyMissionApi = {
}, },
claim: (dailyMissionId: number): Promise<boolean> => { claim: (dailyMissionId: number): Promise<boolean> => {
return http.post(`/dailyMission/${dailyMissionId}/claim`) return http.post(`/dailyMission/${dailyMissionId}/claim`)
},
updateProgress: (progressType: ProgressTargetType, itemId: number | null, itemName: string | null, count: number): Promise<{ success: boolean; missionCompleted: boolean }> => {
return http.post('/mission/progress/update', {
progressType,
itemId,
itemName,
count
})
} }
} }

1
Build_God_Game/src/assets/images/collection.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

1
Build_God_Game/src/assets/images/hunting.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1776231671376" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17660" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M329.142857 671.869388c-83.591837 0-151.510204-67.918367-151.510204-151.510204s67.918367-151.510204 151.510204-151.510204 151.510204 67.918367 151.510204 151.510204-67.918367 151.510204-151.510204 151.510204z m0-261.22449c-60.604082 0-109.714286 49.110204-109.714286 109.714286s49.110204 109.714286 109.714286 109.714285 109.714286-49.110204 109.714286-109.714285-49.110204-109.714286-109.714286-109.714286zM694.857143 671.869388c-83.591837 0-151.510204-67.918367-151.510204-151.510204s67.918367-151.510204 151.510204-151.510204 151.510204 67.918367 151.510204 151.510204-67.918367 151.510204-151.510204 151.510204z m0-261.22449c-60.604082 0-109.714286 49.110204-109.714286 109.714286s49.110204 109.714286 109.714286 109.714285 109.714286-49.110204 109.714286-109.714285-49.110204-109.714286-109.714286-109.714286z" fill="#d81e06" p-id="17661" data-spm-anchor-id="a313x.search_index.0.i1.f3e53a81C26W9f" class="selected"></path><path d="M644.179592 913.240816c-26.644898 0-50.155102-12.016327-66.351021-30.30204-16.195918 18.808163-39.706122 30.302041-66.35102 30.30204s-50.155102-12.016327-66.35102-30.30204c-16.195918 18.808163-39.706122 30.302041-66.351021 30.30204-48.065306 0-87.24898-39.183673-87.248979-87.248979v-36.571429c-133.746939-38.138776-198.530612-123.820408-198.530613-261.22449 0-230.4 187.559184-417.959184 417.959184-417.959183s417.959184 187.559184 417.959184 417.959183c0 137.404082-64.783673 223.085714-198.530613 261.22449v36.571429c1.044898 48.587755-38.138776 87.24898-86.204081 87.248979z m-86.726531-86.72653h41.795919c0 25.077551 20.37551 45.453061 45.453061 45.453061 25.077551 0 45.453061-20.37551 45.453061-45.453061V757.55102l15.673469-4.179591c124.865306-30.82449 182.857143-102.4 182.857143-225.175511 0-207.412245-168.75102-376.163265-376.163265-376.163265s-376.163265 168.75102-376.163265 376.163265c0 122.77551 57.991837 193.828571 182.857143 225.175511l15.673469 4.179591v68.963266c0 25.077551 20.37551 45.453061 45.453061 45.453061 25.077551 0 45.453061-20.37551 45.453061-45.453061h41.795919c0 25.077551 20.37551 45.453061 45.453061 45.453061s44.408163-20.897959 44.408163-45.453061z" fill="#d81e06" p-id="17662" data-spm-anchor-id="a313x.search_index.0.i0.f3e53a81C26W9f" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

42
Build_God_Game/src/composables/useMissionProgress.ts

@ -0,0 +1,42 @@
import { dailyMissionApi, ProgressTargetType } from '@/api/dailyMission'
export const useMissionProgress = () => {
const updateProgress = async (
progressType: ProgressTargetType,
itemId: number | null = null,
itemName: string | null = null,
count: number = 1
) => {
try {
const result = await dailyMissionApi.updateProgress(progressType, itemId, itemName, count)
return result
} catch (error) {
console.error('更新任务进度失败:', error)
return null
}
}
const onCollectItem = async (itemId: number, itemName: string, count: number = 1) => {
return updateProgress(ProgressTargetType.CollectItem, itemId, itemName, count)
}
const onKillMonster = async (monsterId: number, monsterName: string, count: number = 1) => {
return updateProgress(ProgressTargetType.KillMonster, monsterId, monsterName, count)
}
const onFish = async (count: number = 1) => {
return updateProgress(ProgressTargetType.Fish, null, '钓鱼', count)
}
const onConsumeItem = async (itemId: number, itemName: string, count: number = 1) => {
return updateProgress(ProgressTargetType.ConsumeItem, itemId, itemName, count)
}
return {
updateProgress,
onCollectItem,
onKillMonster,
onFish,
onConsumeItem
}
}

569
Build_God_Game/src/views/DailyMissionView.vue

@ -1,30 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted, defineComponent, type PropType, h } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { dailyMissionApi, type DailyMission, DailyMissionStatus, RewardType } from '@/api/dailyMission' import { dailyMissionApi, type DailyMission, DailyMissionStatus, RewardType, MissionType, MissionDifficulty, ProgressTargetType } from '@/api/dailyMission'
import Particles from '@/components/Particles/Particles.vue' import Particles from '@/components/Particles/Particles.vue'
import MissionCarousel from '@/components/MissionCarousel/MissionCarousel.vue' import ElectricBorder from '@/components/ElectricBorder/ElectricBorder.vue'
import Carousel from '@/components/Carousel/Carousel.vue' import collectionIcon from '@/assets/images/collection.svg?raw'
import type { CarouselItem } from '@/components/Carousel/Carousel.vue' import huntingNormalIcon from '@/assets/images/hunting.svg?raw'
const router = useRouter() const router = useRouter()
const carouselItems: CarouselItem[] = [
{
title: "Custom Item",
description: "A custom carousel item.",
id: 1,
icon: "circle",
},
{
title: "Another Item",
description: "Another carousel item.",
id: 2,
icon: "layers",
},
// Add more items as needed
];
const missions = ref<DailyMission[]>([]) const missions = ref<DailyMission[]>([])
const todayStats = ref({ claimed: 0, total: 0 }) const todayStats = ref({ claimed: 0, total: 0 })
const loading = ref(false) const loading = ref(false)
@ -39,71 +23,6 @@ const showMsg = (text: string, type: 'success' | 'error' = 'success') => {
}, 2000) }, 2000)
} }
const getStatusText = (status: DailyMissionStatus) => {
switch (status) {
case DailyMissionStatus.Pending:
return '待接取'
case DailyMissionStatus.InProgress:
return '进行中'
case DailyMissionStatus.Completed:
return '待领取'
case DailyMissionStatus.Claimed:
return '已领取'
default:
return '未知'
}
}
const getStatusClass = (status: DailyMissionStatus) => {
switch (status) {
case DailyMissionStatus.Pending:
return 'status-pending'
case DailyMissionStatus.InProgress:
return 'status-progress'
case DailyMissionStatus.Completed:
return 'status-completed'
case DailyMissionStatus.Claimed:
return 'status-claimed'
default:
return ''
}
}
const getRewardIcon = (type: RewardType) => {
switch (type) {
case RewardType.Money:
return '💰'
case RewardType.Pill:
return '💊'
case RewardType.Equipment:
return '⚔️'
default:
return '🎁'
}
}
const formatRemainingTime = (endTime?: string) => {
if (!endTime) return ''
const end = new Date(endTime)
const now = new Date()
const diff = end.getTime() - now.getTime()
if (diff <= 0) return '已完成'
const minutes = Math.floor(diff / 60000)
const seconds = Math.floor((diff % 60000) / 1000)
return `${minutes}${seconds}`
}
const remainingTimes = ref<Record<number, string>>({})
let timer: number | null = null
const updateRemainingTimes = () => {
missions.value.forEach(mission => {
if (mission.status === DailyMissionStatus.InProgress) {
remainingTimes.value[mission.id] = formatRemainingTime(mission.endTime)
}
})
}
const canAccept = (mission: DailyMission) => mission.status === DailyMissionStatus.Pending const canAccept = (mission: DailyMission) => mission.status === DailyMissionStatus.Pending
const canClaim = (mission: DailyMission) => mission.status === DailyMissionStatus.Completed const canClaim = (mission: DailyMission) => mission.status === DailyMissionStatus.Completed
@ -118,7 +37,6 @@ const loadMissions = async () => {
total: data[0].todayTotalCount total: data[0].todayTotalCount
} }
} }
updateRemainingTimes()
} catch (error: any) { } catch (error: any) {
showMsg(error?.message || '加载任务失败', 'error') showMsg(error?.message || '加载任务失败', 'error')
} finally { } finally {
@ -152,30 +70,161 @@ const handleGoBack = () => {
router.push('/game') router.push('/game')
} }
const handleMissionSelect = (mission: DailyMission) => {
selectedMission.value = mission
}
onMounted(() => { onMounted(() => {
loadMissions() loadMissions()
timer = window.setInterval(updateRemainingTimes, 1000)
}) })
const selectedMission = ref<DailyMission | null>(null) const activeMissions = computed(() => missions.value.filter(m => !m.isFromYesterday))
const hasAnyMissions = computed(() => activeMissions.value.length > 0)
const yesterdayMissions = computed(() => missions.value.filter(m => m.isFromYesterday && m.status === DailyMissionStatus.Completed)) const getMissionTypeClass = (type: MissionType, difficulty: MissionDifficulty) => {
const todayMissions = computed(() => missions.value.filter(m => !m.isFromYesterday)) if (type === MissionType.Collection) {
return 'mission-collection'
}
if (difficulty === MissionDifficulty.Purgatory) {
return 'mission-hunting-purgatory'
}
if (difficulty === MissionDifficulty.Hard) {
return 'mission-hunting-hard'
}
return 'mission-hunting-normal'
}
const getStatusText = (status: DailyMissionStatus) => {
switch (status) {
case DailyMissionStatus.Pending: return '待接取'
case DailyMissionStatus.InProgress: return '进行中'
case DailyMissionStatus.Completed: return '待领取'
case DailyMissionStatus.Claimed: return '已领取'
default: return '未知'
}
}
const getStatusClass = (status: DailyMissionStatus) => {
switch (status) {
case DailyMissionStatus.Pending: return 'status-pending'
case DailyMissionStatus.InProgress: return 'status-progress'
case DailyMissionStatus.Completed: return 'status-completed'
case DailyMissionStatus.Claimed: return 'status-claimed'
default: return ''
}
}
const hasAnyMissions = computed(() => yesterdayMissions.value.length > 0 || todayMissions.value.length > 0) const getDifficultyLabel = (difficulty: MissionDifficulty) => {
switch (difficulty) {
case MissionDifficulty.Normal: return '普通'
case MissionDifficulty.Hard: return '困难'
case MissionDifficulty.Purgatory: return '炼狱'
default: return ''
}
}
const getDifficultyClass = (difficulty: MissionDifficulty) => {
switch (difficulty) {
case MissionDifficulty.Normal: return 'difficulty-normal'
case MissionDifficulty.Hard: return 'difficulty-hard'
case MissionDifficulty.Purgatory: return 'difficulty-purgatory'
default: return ''
}
}
const getRewardIcon = (type: RewardType) => {
switch (type) {
case RewardType.Money: return '💰'
case RewardType.Pill: return '💊'
case RewardType.Equipment: return '⚔️'
default: return '🎁'
}
}
const getProgressText = (mission: DailyMission) => {
if (!mission.progresses || mission.progresses.length === 0) return ''
const progress = mission.progresses[0]
const targetType = progress.targetType === ProgressTargetType.CollectItem ? '收集' : '狩猎'
const targetName = progress.targetItemName || (progress.targetType === ProgressTargetType.KillMonster ? '怪物' : '物品')
return `${targetType} ${progress.currentCount}/${progress.targetCount} ${targetName}`
}
const getMissionIcon = (type: MissionType, difficulty: MissionDifficulty) => {
if (type === MissionType.Collection) {
return collectionIcon
}
// For hunting, use different filters based on difficulty
return huntingNormalIcon
}
const getMissionIconFilter = (type: MissionType, difficulty: MissionDifficulty) => {
if (type === MissionType.Collection) {
return 'none'
}
switch (difficulty) {
case MissionDifficulty.Purgatory:
return 'hue-rotate(-10deg) saturate(1.5) brightness(0.8)'
case MissionDifficulty.Hard:
return 'hue-rotate(-30deg) saturate(1.2)'
default:
return 'none'
}
}
const MissionCardContent = defineComponent({
name: 'MissionCardContent',
props: {
mission: {
type: Object as PropType<DailyMission>,
required: true
}
},
emits: ['accept', 'claim'],
setup(props, { emit }) {
return () => h('div', { class: 'mission-card-content' }, [
h('div', { class: 'card-header' }, [
h('div', { class: 'card-title-row' }, [
h('div', { class: 'card-icon', style: `filter: ${getMissionIconFilter(props.mission.missionType, props.mission.difficulty)}`, innerHTML: getMissionIcon(props.mission.missionType, props.mission.difficulty) }),
h('span', { class: 'card-title' }, props.mission.missionTitle),
]),
h('span', { class: ['card-status', getStatusClass(props.mission.status)] }, getStatusText(props.mission.status)),
]),
h('div', { class: 'card-desc' }, props.mission.missionDescription),
h('div', { class: 'card-difficulty' }, [
h('span', { class: ['difficulty-badge', getDifficultyClass(props.mission.difficulty)] }, getDifficultyLabel(props.mission.difficulty)),
]),
props.mission.progresses && props.mission.progresses.length > 0 && props.mission.status !== DailyMissionStatus.Pending
? h('div', { class: 'card-progress' }, [
h('span', { class: 'progress-label' }, '进度:'),
h('span', { class: 'progress-value' }, getProgressText(props.mission)),
])
: null,
h('div', { class: 'card-rewards' }, [
h('span', { class: 'reward-item exp-reward' }, `${props.mission.expReward}`),
...props.mission.rewards.slice(0, 2).map((reward: any) =>
h('span', { class: 'reward-item' }, `${getRewardIcon(reward.rewardType)}${reward.count}`)
),
props.mission.rewards.length > 2
? h('span', { class: 'reward-more' }, `+${props.mission.rewards.length - 2}`)
: null,
]),
h('div', { class: 'card-actions' }, [
canAccept(props.mission)
? h('button', { class: 'card-btn accept-btn', onClick: () => emit('accept') }, '接取')
: canClaim(props.mission)
? h('button', { class: 'card-btn claim-btn', onClick: () => emit('claim') }, '领取')
: props.mission.status === DailyMissionStatus.InProgress
? h('button', { class: 'card-btn disabled-btn', disabled: true }, '进行中')
: props.mission.status === DailyMissionStatus.Claimed
? h('button', { class: 'card-btn disabled-btn', disabled: true }, '已完成')
: null,
]),
])
}
})
</script> </script>
<template> <template>
<div class="daily-mission-page"> <div class="daily-mission-page">
<Particles :particle-count="50" :particle-colors="['#ffffff', '#aaaaaa']" class="particles-bg" /> <Particles :particle-count="50" :particle-colors="['#ffffff', '#aaaaaa']" class="particles-bg" />
<div class="page-container"> <div class="page-container">
<div class="page-header"> <div class="page-header">
<span class="back-btn" @click="handleGoBack"> 返回</span> <span class="back-btn" @click="handleGoBack"> 返回</span>
<span class="title">每日任务</span> <span class="title">每日任务</span>
@ -191,109 +240,44 @@ const hasAnyMissions = computed(() => yesterdayMissions.value.length > 0 || toda
</div> </div>
<div v-else class="missions-content"> <div v-else class="missions-content">
<div v-if="activeMissions.length > 0" class="stats-bar">
<span class="stats-text">今日进度: {{ todayStats.claimed }}/{{ todayStats.total }}</span>
</div>
<!-- <div v-if="yesterdayMissions.length > 0" class="mission-section yesterday-section"> <div v-if="hasAnyMissions" class="mission-list">
<div class="section-header"> <template v-for="mission in activeMissions" :key="mission.id">
<span class="section-title">昨日任务</span> <ElectricBorder
<span class="section-badge highlight">待领取</span> v-if="mission.difficulty === MissionDifficulty.Purgatory"
</div> color="#dc2626"
<div class="carousel-wrapper"> :speed="1.2"
<MissionCarousel :items="yesterdayMissions" :base-width="340" :card-height="220" :chaos="1.5"
@select="handleMissionSelect"> :thickness="2"
<template #default="{ item }"> class="mission-card purgatory-card"
<div class="mission-card-content"> >
<div class="card-header"> <div class="card-inner">
<span class="card-title">{{ item.missionTitle }}</span> <component
<span class="card-status" :class="getStatusClass(item.status)"> :is="MissionCardContent"
{{ getStatusText(item.status) }} :mission="mission"
</span> @accept="handleAccept(mission)"
</div> @claim="handleClaim(mission)"
<div class="card-desc">{{ item.missionDescription }}</div> />
<div class="card-time"> </div>
<span v-if="item.status === DailyMissionStatus.InProgress"> </ElectricBorder>
剩余: {{ remainingTimes[item.id] || formatRemainingTime(item.endTime) }} <div
</span> v-else
<span v-else> class="mission-card"
{{ item.spendTimeMinutes }}分钟 :class="getMissionTypeClass(mission.missionType, mission.difficulty)"
</span> >
</div> <div class="card-inner">
<div class="card-rewards"> <component
<span class="reward-item exp-reward">{{ item.expReward }}</span> :is="MissionCardContent"
<span v-for="(reward, idx) in item.rewards.slice(0, 2)" :key="idx" class="reward-item"> :mission="mission"
{{ getRewardIcon(reward.rewardType) }}{{ reward.count }} @accept="handleAccept(mission)"
</span> @claim="handleClaim(mission)"
<span v-if="item.rewards.length > 2" class="reward-more">+{{ item.rewards.length - 2 }}</span> />
</div> </div>
<div class="card-actions"> </div>
<button v-if="canAccept(item)" class="card-btn accept-btn" @click.stop="handleAccept(item)"> </template>
接取
</button>
<button v-if="canClaim(item)" class="card-btn claim-btn" @click.stop="handleClaim(item)">
领取
</button>
<button v-if="item.status === DailyMissionStatus.InProgress" class="card-btn disabled-btn" disabled>
挂机中
</button>
<button v-if="item.status === DailyMissionStatus.Claimed" class="card-btn disabled-btn" disabled>
已完成
</button>
</div>
</div>
</template>
</MissionCarousel>
</div>
</div> -->
<div v-if="todayMissions.length > 0" class="mission-section">
<div class="section-header">
<span class="section-title">今日任务</span>
<span class="section-badge">{{ todayStats.claimed }}/{{ todayStats.total }}</span>
</div>
<div class="carousel-wrapper">
<MissionCarousel :items="todayMissions" :base-width="340" :card-height="180" @select="handleMissionSelect"
:round="true" :loop="true">
<template #default="{ item }">
<div class="mission-card-content">
<div class="card-header">
<span class="card-title">{{ item.missionTitle }}</span>
<span class="card-status" :class="getStatusClass(item.status)">
{{ getStatusText(item.status) }}
</span>
</div>
<div class="card-desc">{{ item.missionDescription }}</div>
<div class="card-time">
<span v-if="item.status === DailyMissionStatus.InProgress">
剩余: {{ remainingTimes[item.id] || formatRemainingTime(item.endTime) }}
</span>
<span v-else>
{{ item.spendTimeMinutes }}分钟
</span>
</div>
<div class="card-rewards">
<span class="reward-item exp-reward">{{ item.expReward }}</span>
<span v-for="(reward, idx) in item.rewards.slice(0, 2)" :key="idx" class="reward-item">
{{ getRewardIcon(reward.rewardType) }}{{ reward.count }}
</span>
<span v-if="item.rewards.length > 2" class="reward-more">+{{ item.rewards.length - 2 }}</span>
</div>
<div class="card-actions">
<button v-if="canAccept(item)" class="card-btn accept-btn" @click.stop="handleAccept(item)">
接取
</button>
<button v-if="canClaim(item)" class="card-btn claim-btn" @click.stop="handleClaim(item)">
领取
</button>
<button v-if="item.status === DailyMissionStatus.InProgress" class="card-btn disabled-btn" disabled>
挂机中
</button>
<button v-if="item.status === DailyMissionStatus.Claimed" class="card-btn disabled-btn" disabled>
已完成
</button>
</div>
</div>
</template>
</MissionCarousel>
</div>
</div> </div>
<div v-if="!hasAnyMissions" class="empty-state"> <div v-if="!hasAnyMissions" class="empty-state">
@ -377,7 +361,6 @@ const hasAnyMissions = computed(() => yesterdayMissions.value.length > 0 || toda
opacity: 0; opacity: 0;
transform: translateX(-50%) translateY(-10px); transform: translateX(-50%) translateY(-10px);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateX(-50%) translateY(0); transform: translateX(-50%) translateY(0);
@ -393,124 +376,185 @@ const hasAnyMissions = computed(() => yesterdayMissions.value.length > 0 || toda
.missions-content { .missions-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 24px; gap: 16px;
} }
.mission-section { .stats-bar {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 12px 16px;
text-align: center;
}
.stats-text {
color: #888888;
font-size: 0.9rem;
}
.mission-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.mission-card {
background: rgba(255, 255, 255, 0.02); background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.08); border: 1px solid;
border-radius: 12px; border-radius: 12px;
padding: 16px; overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
} }
.yesterday-section { .mission-card:hover {
background: rgba(255, 68, 68, 0.05); transform: translateY(-2px);
border-color: rgba(255, 68, 68, 0.2);
} }
.section-header { .purgatory-card {
display: flex; border: none;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
} }
.section-title { .card-inner {
color: #ffffff; padding: 16px;
font-size: 1rem;
font-weight: 500;
} }
.section-badge { .mission-collection {
background: rgba(255, 255, 255, 0.1); border-color: rgba(34, 197, 94, 0.3);
color: #888888;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.8rem;
} }
.section-badge.highlight { .mission-hunting-normal {
background: rgba(255, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.3);
color: #ff4444;
} }
.carousel-wrapper { .mission-hunting-hard {
display: flex; border-color: rgba(249, 115, 22, 0.4);
justify-content: center; }
.mission-hunting-purgatory {
border-color: rgba(220, 38, 38, 0.5);
} }
.mission-card-content { .mission-card-content {
width: 100%;
height: 100%;
padding: 12px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-sizing: border-box; gap: 10px;
} }
.card-header { .card-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 6px; padding-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.card-title-row {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.card-icon {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.card-icon :deep(svg) {
width: 100%;
height: 100%;
} }
.card-title { .card-title {
color: #ffffff; color: #ffffff;
font-size: 0.9rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
flex: 1;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
margin-right: 8px;
} }
.card-status { .card-status {
padding: 2px 8px; padding: 2px 8px;
border-radius: 8px; border-radius: 8px;
font-size: 0.65rem; font-size: 0.7rem;
font-weight: 500; font-weight: 500;
white-space: nowrap; white-space: nowrap;
} }
.card-desc { .card-desc {
color: #888888; color: #888888;
font-size: 0.7rem; font-size: 0.8rem;
line-height: 1.3; line-height: 1.4;
margin-bottom: 6px;
flex: 1;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
} }
.card-time { .card-difficulty {
color: #ff8844; display: flex;
align-items: center;
}
.difficulty-badge {
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7rem; font-size: 0.7rem;
margin-bottom: 8px; font-weight: 500;
}
.difficulty-normal {
background: rgba(100, 100, 100, 0.3);
color: #888888;
}
.difficulty-hard {
background: rgba(249, 115, 22, 0.2);
color: #f97316;
}
.difficulty-purgatory {
background: rgba(220, 38, 38, 0.3);
color: #fca5a5;
}
.card-progress {
display: flex;
align-items: center;
gap: 6px;
background: rgba(255, 255, 255, 0.03);
padding: 8px 10px;
border-radius: 6px;
}
.progress-label {
color: #666666;
font-size: 0.75rem;
}
.progress-value {
color: #22c55e;
font-size: 0.8rem;
font-weight: 500;
} }
.card-rewards { .card-rewards {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 3px; gap: 4px;
margin-bottom: 8px;
} }
.card-rewards .reward-item { .reward-item {
background: rgba(255, 136, 68, 0.1); background: rgba(255, 136, 68, 0.1);
border: 1px solid rgba(255, 136, 68, 0.2); border: 1px solid rgba(255, 136, 68, 0.2);
color: #ff8844; color: #ff8844;
padding: 1px 5px; padding: 2px 6px;
border-radius: 4px; border-radius: 4px;
font-size: 0.65rem; font-size: 0.7rem;
} }
.card-rewards .exp-reward { .exp-reward {
background: rgba(255, 215, 0, 0.1); background: rgba(255, 215, 0, 0.1);
border-color: rgba(255, 215, 0, 0.3); border-color: rgba(255, 215, 0, 0.3);
color: #ffd700; color: #ffd700;
@ -519,21 +563,22 @@ const hasAnyMissions = computed(() => yesterdayMissions.value.length > 0 || toda
.reward-more { .reward-more {
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
color: #888888; color: #888888;
padding: 1px 5px; padding: 2px 6px;
border-radius: 4px; border-radius: 4px;
font-size: 0.65rem; font-size: 0.7rem;
} }
.card-actions { .card-actions {
display: flex; display: flex;
gap: 6px; gap: 8px;
margin-top: 4px;
} }
.card-btn { .card-btn {
flex: 1; flex: 1;
padding: 6px 10px; padding: 8px 12px;
border-radius: 6px; border-radius: 6px;
font-size: 0.75rem; font-size: 0.8rem;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;

134
docs/superpowers/specs/2026-04-15-mission-system-redesign.md

@ -0,0 +1,134 @@
# 任务系统改版设计 - 收集与狩猎任务
**日期**: 2026-04-15
**状态**: 已批准
## 概述
将每日任务从"挂机等待完成"改为"主动收集/狩猎完成"模式,收集任务收集指定数量物品完成,狩猎任务狩猎指定数量怪物完成。
## 后端改动
### 1. DailyMissionDto 新增字段
```csharp
public class DailyMissionDto
{
// 现有字段...
public MissionType MissionType { get; set; } // 任务类型:收集/狩猎
public MissionDifficulty Difficulty { get; set; } // 难度
public List<MissionProgressDto> Progresses { get; set; } // 任务目标列表
public int CurrentProgress { get; set; } // 当前完成进度
public int TargetProgress { get; set; } // 目标进度
}
public class MissionProgressDto
{
public int MissionProgressId { get; set; }
public ProgressTargetType TargetType { get; set; }
public int? TargetItemId { get; set; }
public string? TargetItemName { get; set; }
public int TargetCount { get; set; }
public int CurrentCount { get; set; }
}
```
### 2. 任务进度更新API
**API**: `POST /api/god/mission/{missionId}/progress`
**请求体**:
```json
{
"progressType": "CollectItem" | "KillMonster",
"itemId": 1, // 可选,收集物品时需要
"monsterId": 1, // 可选,狩猎怪物时需要
"count": 1
}
```
**逻辑**:
- 检查角色是否有进行中的任务需要该类型进度
- 更新 CharacterMissionProgress 表
- 如果目标达成,自动标记进度完成
- 如果所有进度完成,标记任务完成(状态变为待领取)
### 3. 每日任务生成逻辑调整
现有逻辑已包含难度分配(Normal/Hard/Purgatory),只需确保:
- 生成任务时同时生成 MissionProgress 记录
- 收集任务的 TargetType = CollectItem
- 狩猎任务的 TargetType = KillMonster
---
## 前端改动
### 1. 页面结构
移除轮播图组件,改为卡片网格布局:
- 单列卡片列表,最大宽度 480px
- 卡片高度自适应
### 2. 卡片样式
**普通/困难难度**:
- 背景: `rgba(255,255,255,0.02)`
- 边框: 1px solid,颜色根据任务类型
- 圆角: 12px
**炼狱难度**:
- 使用 ElectricBorder 组件
- 边框颜色: #dc2626
- 带发光效果
### 3. 颜色方案
| 任务类型 | 难度 | 边框颜色 |
|---------|------|---------|
| 收集任务 | 普通 | #22c55e (绿色) |
| 收集任务 | 困难 | #16a34a (深绿) |
| 收集任务 | 炼狱 | ElectricBorder #15803d |
| 狩猎任务 | 普通 | #ef4444 (红色) |
| 狩猎任务 | 困难 | #f97316 (橙色) |
| 狩猎任务 | 炼狱 | ElectricBorder #dc2626 |
### 4. 卡片内容
```
┌─────────────────────────────────────┐
│ [图标] 任务名称 [难度标签] │
│ 任务描述... │
│ ────────────────────────────────── │
│ 目标: 收集 5/10 灵草 │
│ ────────────────────────────────── │
│ 奖励: 💰100 ✨50 │
│ ────────────────────────────────── │
│ [接取] / [进行中] / [领取] │
└─────────────────────────────────────┘
```
### 5. 图标
- 收集任务: 用户提供的 SVG 图标
- 狩猎任务: 按难度区分
- 普通: 绿色怪物图标
- 困难: 橙色怪物图标
- 炼狱: 红色怪物图标(带发光)
### 6. 状态按钮
- 待接取: "接取" 按钮
- 进行中: "进行中" 按钮(禁用状态)
- 待领取: "领取" 按钮
- 已领取: "已完成" 按钮(禁用状态)
---
## 实施顺序
1. 后端: 修改 DailyMissionDto,添加新字段
2. 后端: 添加任务进度更新 API
3. 后端: 调整每日任务生成逻辑,关联 MissionProgress
4. 前端: 更新 API 类型定义
5. 前端: 重写 DailyMissionView,移除轮播图,使用卡片布局
6. 前端: 添加 ElectricBorder 应用于炼狱难度卡片
Loading…
Cancel
Save