Browse Source

增加了一个怪兽模块的后台配置

master
秦汉 3 days ago
parent
commit
35c7d34af4
  1. 91
      Build_God_Admin_Frontend/Frontend/src/api/monster.ts
  2. 5
      Build_God_Admin_Frontend/Frontend/src/components/Sidebar.vue
  3. 1
      Build_God_Admin_Frontend/Frontend/src/constants/theme.ts
  4. 6
      Build_God_Admin_Frontend/Frontend/src/router/index.ts
  5. 658
      Build_God_Admin_Frontend/Frontend/src/views/admin/MonstersView.vue
  6. 100
      Build_God_Api/Build_God_Api/Controllers/MonsterController.cs
  7. 52
      Build_God_Api/Build_God_Api/DB/Monster.cs
  8. 10
      Build_God_Api/Build_God_Api/Dto/MonsterDtos.cs
  9. 3
      Build_God_Api/Build_God_Api/Program.cs
  10. 129
      Build_God_Api/Build_God_Api/Services/MonsterService.cs

91
Build_God_Admin_Frontend/Frontend/src/api/monster.ts

@ -0,0 +1,91 @@
import http, { type EnumInfoDto } from "@/api/index";
export interface Monster {
id: number;
name: string;
description: string;
health: number;
attack: number;
defense: number;
criticalRate: number;
level: number;
type: number;
rewards?: MonsterReward[];
}
export interface MonsterReward {
id: number;
monsterId: number;
rewardType: number;
itemId: number;
itemName: string;
count: number;
}
export interface PagedResult<T> {
items: T[];
totalCount: number;
pageNumber?: number;
}
interface SearchMonsterDto {
pageNumber: number | undefined;
pageSize: number | undefined;
monsterType: number | undefined;
level: number | undefined;
}
export const GetMonsterList = (
monsterType?: number,
level?: number,
pageNumber?: number,
pageSize?: number
): Promise<PagedResult<Monster> | Monster[]> => {
var dto: SearchMonsterDto = {
pageNumber: pageNumber,
pageSize: pageSize,
monsterType: monsterType,
level: level,
};
return http.post("/monster/all", dto);
};
export const GetMonsterById = (id: number): Promise<Monster> => {
return http.get(`/monster/${id}`);
};
export const AddMonster = (data: Monster): Promise<boolean> => {
return http.post("/monster", data);
};
export const UpdateMonster = (data: Monster): Promise<boolean> => {
return http.put(`/monster/${data.id}`, data);
};
export const DeleteMonster = (id: number): Promise<boolean> => {
return http.delete(`/monster/${id}`);
};
export const GetMonsterTypes = (): Promise<EnumInfoDto[]> => {
return http.get("/monster/types");
};
export const GetMonsterRewards = (monsterId: number): Promise<MonsterReward[]> => {
return http.get(`/monster/rewards/${monsterId}`);
};
export const GetRewardTypes = (): Promise<EnumInfoDto[]> => {
return http.get("/monster/reward-types");
};
export const AddMonsterReward = (data: MonsterReward): Promise<boolean> => {
return http.post("/monster/reward", data);
};
export const UpdateMonsterReward = (data: MonsterReward): Promise<boolean> => {
return http.put(`/monster/reward/${data.id}`, data);
};
export const DeleteMonsterReward = (id: number): Promise<boolean> => {
return http.delete(`/monster/reward/${id}`);
};

5
Build_God_Admin_Frontend/Frontend/src/components/Sidebar.vue

@ -50,6 +50,11 @@ const menuItems = [
icon: ICONS.scrap, icon: ICONS.scrap,
label: '垃圾管理', label: '垃圾管理',
path: '/admin/scraps' path: '/admin/scraps'
},
{
icon: ICONS.monster,
label: '怪兽管理',
path: '/admin/monsters'
} }
] ]

1
Build_God_Admin_Frontend/Frontend/src/constants/theme.ts

@ -7,6 +7,7 @@ export const ICONS = {
bag:'🎒', bag:'🎒',
mission: '📜', mission: '📜',
scrap: '📜', scrap: '📜',
monster: '👹',
reward: { reward: {
pill: '💊', pill: '💊',

6
Build_God_Admin_Frontend/Frontend/src/router/index.ts

@ -69,6 +69,12 @@ const routes: RouteRecordRaw[] = [
component: () => import('../views/admin/BagsView.vue'), component: () => import('../views/admin/BagsView.vue'),
meta: { title: '背包管理' } meta: { title: '背包管理' }
}, },
{
path: 'monsters',
name: 'monsters',
component: () => import('../views/admin/MonstersView.vue'),
meta: { title: '怪兽管理' }
},
{ {
path: 'settings', path: 'settings',
name: 'settings', name: 'settings',

658
Build_God_Admin_Frontend/Frontend/src/views/admin/MonstersView.vue

@ -0,0 +1,658 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
AddMonster,
DeleteMonster,
GetMonsterList,
GetMonsterTypes,
UpdateMonster,
type Monster,
type MonsterReward,
GetRewardTypes,
AddMonsterReward,
UpdateMonsterReward,
DeleteMonsterReward,
GetMonsterRewards
} from '@/api/monster'
import type { EnumInfoDto } from '@/api'
import { GetPillList, type Pill } from '@/api/pill'
import { GetEquipmentTemplateList, type EquipmentTemplate } from '@/api/equipment'
import { Plus, Edit, Delete, Close } from '@element-plus/icons-vue'
import { ICONS } from '@/constants/theme'
const monsters = ref<Monster[]>([])
const monsterTypes = ref<EnumInfoDto[]>([])
const rewardTypes = ref<EnumInfoDto[]>([])
const pillData = ref<Pill[]>([])
const equipmentData = ref<EquipmentTemplate[]>([])
const currentPage = ref(1)
const pageSize = ref(10)
const totalCount = ref(0)
const monsterTypeFilter = ref<number | undefined>(undefined)
const levelFilter = ref<number | undefined>(undefined)
const showDialog = ref(false)
const showRewardDialog = ref(false)
const isEditing = ref(false)
const isEditingReward = ref(false)
const searchQuery = ref('')
const formData = ref<Monster>({
id: 0,
name: '',
description: '',
health: 0,
attack: 0,
defense: 0,
criticalRate: 0,
level: 1,
type: 1
})
const rewardFormData = ref<MonsterReward>({
id: 0,
monsterId: 0,
rewardType: 0,
itemId: 0,
itemName: '',
count: 0
})
const rewardSelectData = ref<{ id: number; name: string }[]>([])
const filteredMonsters = computed(() => {
return monsters.value.filter(m =>
m.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
})
const translateMonsterType = (typeId: number) => {
const type = monsterTypes.value.find(t => t.id === typeId)
return type ? type.description : '未知类型'
}
const getMonsterTypeClass = (typeId: number) => {
switch (typeId) {
case 1:
return 'type-normal'
case 2:
return 'type-elite'
case 3:
return 'type-boss'
default:
return ''
}
}
const openDialog = (monster?: Monster) => {
if (monster !== undefined) {
isEditing.value = true
formData.value = { ...monster }
} else {
isEditing.value = false
formData.value = {
id: 0,
name: '',
description: '',
health: 0,
attack: 0,
defense: 0,
criticalRate: 0,
level: 1,
type: 1
}
}
showDialog.value = true
}
const closeDialog = () => {
showDialog.value = false
}
const openRewardDialog = async (monsterId?: number, reward?: MonsterReward) => {
if (reward !== undefined) {
isEditingReward.value = true
if (reward.rewardType) await rewardTypeChange(reward.rewardType)
rewardFormData.value = { ...reward }
rewardFormData.value.monsterId = monsterId ?? 0
if (rewardFormData.value.itemId) onRewardItemChange(rewardFormData.value.itemId)
} else {
isEditingReward.value = false
rewardFormData.value = {
id: 0,
monsterId: monsterId ?? 0,
rewardType: 0,
itemId: 0,
itemName: '',
count: 0
}
rewardSelectData.value = []
}
showRewardDialog.value = true
}
const closeRewardDialog = () => {
showRewardDialog.value = false
}
const rewardTypeChange = async (id: number) => {
// RewardType: 1=, 2=, 3=, 4=
if (id === 1) {
const pills = await GetPillList()
rewardSelectData.value = (Array.isArray(pills) ? pills : []).map(p => ({ id: p.id, name: p.name }))
rewardFormData.value.itemId = rewardSelectData.value.length ? rewardSelectData.value[0]!.id : 0
if (rewardFormData.value.itemId) onRewardItemChange(rewardFormData.value.itemId)
} else if (id === 2) {
const eqs = await fetchEquipments()
rewardSelectData.value = (Array.isArray(eqs) ? eqs : []).map(e => ({ id: e.id, name: e.name }))
rewardFormData.value.itemId = rewardSelectData.value.length ? rewardSelectData.value[0]!.id : 0
if (rewardFormData.value.itemId) onRewardItemChange(rewardFormData.value.itemId)
} else if (id === 3) {
rewardSelectData.value = []
rewardFormData.value.itemId = 0
rewardFormData.value.itemName = '经验'
} else if (id === 4) {
rewardSelectData.value = []
rewardFormData.value.itemId = 0
rewardFormData.value.itemName = '灵石'
} else {
rewardSelectData.value = []
rewardFormData.value.itemId = 0
}
}
const onRewardItemChange = (id: number) => {
const found = rewardSelectData.value.find(x => x.id === id)
if (found) {
rewardFormData.value.itemName = found.name
}
}
const saveReward = async () => {
if (isEditingReward.value) {
var result = await UpdateMonsterReward(rewardFormData.value)
if (result) {
ElMessage.success('奖励修改成功')
closeRewardDialog()
await refreshMonsters()
} else {
ElMessage.error('修改失败')
}
} else {
var result = await AddMonsterReward(rewardFormData.value)
if (result) {
ElMessage.success('奖励添加成功')
closeRewardDialog()
await refreshMonsters()
} else {
ElMessage.error('添加失败')
}
}
}
const deleteReward = (reward: MonsterReward) => {
ElMessageBox.confirm(`确定删除此奖励吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
var result = await DeleteMonsterReward(reward.id)
if (result) {
ElMessage.success('奖励删除成功')
await refreshMonsters()
} else {
ElMessage.error('删除失败')
}
}).catch(() => { })
}
const getRewardTypeLabel = (id: number) => {
const t = rewardTypes.value.find(x => x.id === id)
if (t) return t.description
const map: Record<number, string> = { 1: '丹药', 2: '装备', 3: '经验', 4: '灵石' }
return map[id] || '未知'
}
const getRewardEmoji = (id: number) => {
switch (id) {
case 1:
return ICONS.reward.pill
case 2:
return ICONS.reward.equipment
case 3:
return ICONS.reward.experience
case 4:
return ICONS.reward.money
default:
return ICONS.reward.default
}
}
const formatRewardDisplay = (r: MonsterReward) => {
if (!r) return ''
if (r.rewardType === 3) return `${r.count} 经验`
if (r.rewardType === 4) return `${r.count} 灵石`
return r.itemName ? `${r.itemName} ×${r.count}` : `×${r.count}`
}
const saveMonster = async () => {
if (formData.value.name == undefined
|| formData.value.description == undefined
|| formData.value.type == undefined
|| formData.value.level == undefined
) {
ElMessage.error('请填写必填项')
return
}
if (isEditing.value) {
var result = await UpdateMonster(formData.value)
if (result) {
ElMessage.success('怪兽更新成功')
closeDialog()
await refreshMonsters()
} else {
ElMessage.error('怪兽更新失败')
}
} else {
var result = await AddMonster(formData.value)
if (result) {
ElMessage.success('怪兽添加成功')
closeDialog()
await refreshMonsters()
} else {
ElMessage.error('添加失败')
}
}
}
const deleteMonster = (monster: Monster) => {
ElMessageBox.confirm(`确定删除怪兽 "${monster.name}" 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
var result = await DeleteMonster(monster.id)
if (result) {
ElMessage.success('怪兽删除成功')
await refreshMonsters()
} else {
ElMessage.error('删除失败')
}
}).catch(() => { })
}
const loadMonsterRewards = async (monster: Monster) => {
if (!monster.rewards) {
const rewards = await GetMonsterRewards(monster.id)
monster.rewards = rewards
}
}
const fetchEquipments = async (): Promise<EquipmentTemplate[]> => {
var res = await GetEquipmentTemplateList(undefined, 1, 100)
if (Array.isArray(res)) {
return res
} else {
return res.items || []
}
}
onMounted(async () => {
await refreshMonsters()
await fetchTypes()
await fetchRewardTypes()
})
const refreshMonsters = async (page?: number) => {
if (page !== undefined) currentPage.value = page
const res = await GetMonsterList(
monsterTypeFilter.value,
levelFilter.value,
currentPage.value,
pageSize.value
)
if (Array.isArray(res)) {
monsters.value = res
totalCount.value = res.length
} else {
monsters.value = res.items || []
totalCount.value = res.totalCount || (res.items ? res.items.length : 0)
}
for (const monster of monsters.value) {
await loadMonsterRewards(monster)
}
}
const fetchTypes = async () => {
var data = await GetMonsterTypes()
monsterTypes.value = data
}
const fetchRewardTypes = async () => {
var data = await GetRewardTypes()
rewardTypes.value = data
}
</script>
<template>
<div class="monsters-container">
<div class="header">
<h2>怪兽管理</h2>
</div>
<div class="search-bar">
<el-input v-model="searchQuery" placeholder="搜索怪兽名称..." style="max-width: 300px;"></el-input>
<el-select v-model="monsterTypeFilter" style="max-width: 200px;" @change="refreshMonsters(undefined)"
placeholder="搜索怪兽类型..." clearable>
<el-option v-for="(value, index) in monsterTypes" :key="index" :value="value.id" :label="value.description" />
</el-select>
<el-button type="primary" @click="openDialog(undefined)">
<el-icon class="el-icon--left">
<Plus />
</el-icon>
</el-button>
</div>
<el-table :data="filteredMonsters" style="width: 100%;" stripe :preserve-expanded-content="true">
<el-table-column type="expand">
<template #default="scoped">
<div style="display:flex; gap:8px; align-items:center; margin-bottom:8px;">
<el-button @click="openRewardDialog(scoped.row.id, undefined)">添加奖励</el-button>
</div>
<div style="display:flex; gap:8px; align-items:center; margin: 16px 0 8px 0;">
<h3 style="margin:0;">击杀奖励</h3>
</div>
<div class="reward-cards">
<div v-if="(!scoped.row.rewards || scoped.row.rewards.length === 0)" class="no-rewards">暂无奖励</div>
<div v-for="(r, idx) in scoped.row.rewards" :key="idx" class="reward-card">
<div class="reward-icon">{{ getRewardEmoji(r.rewardType) }}</div>
<div class="reward-body">
<div class="reward-type">{{ getRewardTypeLabel(r.rewardType) }}</div>
<div class="reward-name">{{ formatRewardDisplay(r) }}</div>
</div>
<div class="reward-actions">
<el-button type="primary" @click="openRewardDialog(scoped.row.id, r)">编辑</el-button>
<el-button type="danger" @click="deleteReward(r)">删除</el-button>
</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="名称" prop="name" width="150"></el-table-column>
<el-table-column label="等级" prop="level" width="80"></el-table-column>
<el-table-column label="类型">
<template #default="scoped">
<span class="monster-type" :class="getMonsterTypeClass(scoped.row.type)">
{{ translateMonsterType(scoped.row.type) }}
</span>
</template>
</el-table-column>
<el-table-column label="生命值" prop="health" width="100"></el-table-column>
<el-table-column label="攻击力" prop="attack" width="100"></el-table-column>
<el-table-column label="防御力" prop="defense" width="100"></el-table-column>
<el-table-column label="暴击率" width="100">
<template #default="scoped">
{{ scoped.row.criticalRate }}%
</template>
</el-table-column>
<el-table-column label="描述" prop="description" show-overflow-tooltip></el-table-column>
<el-table-column label="编辑" fixed="right">
<template #default="scoped">
<el-button type="primary" @click="openDialog(scoped.row)" :icon="Edit" circle />
<el-button type="danger" @click="deleteMonster(scoped.row)" :icon="Delete" circle />
</template>
</el-table-column>
</el-table>
<div class="pagination" v-if="totalCount > 0">
<el-pagination layout="prev, pager, next" :total="totalCount" :current-page="currentPage"
@current-change="refreshMonsters" />
</div>
<!-- Reward Dialog -->
<div v-if="showRewardDialog" class="dialog-overlay">
<el-form :model="rewardFormData" class="dialog" label-position="top">
<div class="dialog-header">
<h3>{{ isEditingReward ? '编辑奖励' : '添加奖励' }}</h3>
<el-button @click="closeRewardDialog" :icon="Close" circle />
</div>
<el-form-item label="类型">
<el-select v-model="rewardFormData.rewardType" @change="rewardTypeChange" :disabled="isEditingReward">
<el-option v-for="(value, index) in rewardTypes" :key="index" :value="value.id">
{{ value.description }}
</el-option>
</el-select>
</el-form-item>
<el-form-item v-if="rewardFormData.rewardType === 1 || rewardFormData.rewardType === 2" label="物品">
<el-select v-model="rewardFormData.itemId" @change="onRewardItemChange">
<el-option v-for="(value, index) in rewardSelectData" :key="index" :value="value.id" :label="value.name" />
</el-select>
</el-form-item>
<el-form-item v-else-if="rewardFormData.rewardType === 3 || rewardFormData.rewardType === 4" label="说明">
<el-input v-model="rewardFormData.itemName" disabled />
</el-form-item>
<el-form-item label="数量">
<el-input-number v-model="rewardFormData.count" :min="1" />
</el-form-item>
<div class="dialog-footer">
<el-button type="info" @click="closeRewardDialog">取消</el-button>
<el-button type="primary" @click="saveReward">保存</el-button>
</div>
</el-form>
</div>
<!-- Monster Dialog -->
<div v-if="showDialog" class="dialog-overlay">
<el-form :inline="true" :model="formData" class="dialog" label-position="top">
<div class="dialog-header">
<h3>{{ isEditing ? '编辑怪兽' : '添加怪兽' }}</h3>
<el-button @click="closeDialog" :icon="Close" circle />
</div>
<el-form-item label="名称">
<el-input v-model="formData.name" placeholder="怪兽名称" clearable />
</el-form-item>
<el-form-item label="等级">
<el-input-number v-model="formData.level" />
</el-form-item>
<el-form-item label="类型">
<el-select v-model="formData.type">
<el-option v-for="(value, index) in monsterTypes" :key="index" :value="value.id"
:label="value.description" />
</el-select>
</el-form-item>
<el-form-item label="生命值">
<el-input-number v-model="formData.health" :min="0" />
</el-form-item>
<el-form-item label="攻击力">
<el-input-number v-model="formData.attack" :min="0" />
</el-form-item>
<el-form-item label="防御力">
<el-input-number v-model="formData.defense" :min="0" />
</el-form-item>
<el-form-item label="暴击率(%)">
<el-input-number v-model="formData.criticalRate" :min="0" :max="100" />
</el-form-item>
<el-form-item label="描述" style="width: 100%;">
<el-input v-model="formData.description" placeholder="描述" type="textarea" style="width: 100%;" />
</el-form-item>
<div class="dialog-footer">
<el-button type="info" @click="closeDialog">取消</el-button>
<el-button type="primary" @click="saveMonster">保存</el-button>
</div>
</el-form>
</div>
</div>
</template>
<style scoped lang="css">
.monsters-container {
background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
padding: 20px;
border-radius: 8px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.header h2 {
margin: 0;
color: #e5e7eb;
font-size: 20px;
}
.search-bar {
display: flex;
margin-bottom: 20px;
gap: 12px;
}
.monster-type {
display: inline-block;
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
}
.type-normal {
background-color: #6b7280;
}
.type-elite {
background-color: #8b5cf6;
}
.type-boss {
background-color: #dc2626;
}
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.dialog {
background: #1f2937;
border: 1px solid #374151;
border-radius: 8px;
width: 100%;
max-width: 500px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
padding: 10px;
}
.dialog .el-input,
.dialog .el-select,
.dialog .el-input-number,
.dialog .el-input-number .el-input {
width: 200px !important;
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #374151;
width: 100%;
margin-bottom: 10px;
padding: 10px 10px 10px 0px;
}
.dialog-header h3 {
margin: 0;
color: #e5e7eb;
font-size: 16px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
border-top: 1px solid #374151;
width: 100%;
padding: 10px 10px 0 0;
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
.reward-cards {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 8px;
}
.reward-card {
display: flex;
align-items: center;
gap: 12px;
background: linear-gradient(180deg, #111827, #0f172a);
border: 1px solid rgba(255, 255, 255, 0.04);
padding: 12px;
border-radius: 8px;
width: 260px;
box-shadow: 0 6px 18px rgba(2, 6, 23, 0.6);
}
.reward-icon {
font-size: 28px;
width: 42px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.03);
border-radius: 8px;
}
.reward-body {
flex: 1 1 auto;
}
.reward-type {
color: #9ca3af;
font-size: 12px;
margin-bottom: 4px;
}
.reward-name {
color: #e5e7eb;
font-weight: 600;
}
.no-rewards {
color: #9ca3af;
}
.reward-actions {
display: flex;
align-items: center;
gap: 4px;
}
</style>

100
Build_God_Api/Build_God_Api/Controllers/MonsterController.cs

@ -0,0 +1,100 @@
using Build_God_Api.Common;
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 MonsterController(IMonsterService service) : ControllerBase
{
private readonly IMonsterService _service = service;
[HttpPost]
[Authorize(Roles = "admin")]
public async Task<ActionResult<bool>> Add([FromBody] Monster item)
{
return await _service.Add(item);
}
[HttpPut("{id}")]
[Authorize(Roles = "admin")]
public async Task<ActionResult<bool>> Update(int id, [FromBody] Monster item)
{
item.Id = id;
return await _service.Update(item);
}
[HttpDelete("{id}")]
[Authorize(Roles = "admin")]
public async Task<ActionResult<bool>> Delete(int id)
{
return await _service.Delete(id);
}
[HttpGet("{id}")]
[Authorize]
public async Task<ActionResult<Monster>> GetById(int id)
{
var monster = await _service.GetById(id);
if (monster == null)
{
return NotFound();
}
return monster;
}
[HttpPost("all")]
[Authorize]
public async Task<ActionResult<PagedResult<Monster>>> GetAll([FromBody] SearchMonsterDto dto)
{
return await _service.GetAll(dto);
}
[HttpGet("types")]
[Authorize]
public ActionResult<List<EnumInfoDto>> GetMonsterTypes()
{
return EnumHelper.GetEnumList<MonsterType>();
}
[HttpGet("rewards/{monsterId}")]
[Authorize]
public async Task<ActionResult<List<MonsterReward>>> GetRewards(int monsterId)
{
return await _service.GetRewards(monsterId);
}
[HttpPost("reward")]
[Authorize(Roles = "admin")]
public async Task<ActionResult<bool>> AddReward([FromBody] MonsterReward reward)
{
return await _service.AddReward(reward);
}
[HttpPut("reward/{id}")]
[Authorize(Roles = "admin")]
public async Task<ActionResult<bool>> UpdateReward(int id, [FromBody] MonsterReward reward)
{
reward.Id = id;
return await _service.UpdateReward(reward);
}
[HttpDelete("reward/{id}")]
[Authorize(Roles = "admin")]
public async Task<ActionResult<bool>> DeleteReward(int id)
{
return await _service.DeleteReward(id);
}
[HttpGet("reward-types")]
[Authorize]
public ActionResult<List<EnumInfoDto>> GetRewardTypes()
{
return EnumHelper.GetEnumList<RewardType>();
}
}
}

52
Build_God_Api/Build_God_Api/DB/Monster.cs

@ -0,0 +1,52 @@
using SqlSugar;
using System.ComponentModel;
namespace Build_God_Api.DB
{
public class Monster : BaseEntity
{
[SugarColumn(Length = 100)]
public string Name { get; set; } = string.Empty;
[SugarColumn(Length = 500)]
public string Description { get; set; } = string.Empty;
public int Health { get; set; }
public int Attack { get; set; }
public int Defense { get; set; }
public decimal CriticalRate { get; set; }
public int Level { get; set; }
public MonsterType Type { get; set; }
}
public class MonsterReward : BaseEntity
{
public int MonsterId { get; set; }
public RewardType Type { get; set; }
public int ItemId { get; set; }
[SugarColumn(Length = 100)]
public string ItemName { get; set; } = string.Empty;
public int Count { get; set; }
}
public enum MonsterType
{
[Description("普通")]
Normal = 1,
[Description("精英")]
Elite = 2,
[Description("首领")]
Boss = 3
}
}

10
Build_God_Api/Build_God_Api/Dto/MonsterDtos.cs

@ -0,0 +1,10 @@
namespace Build_God_Api.Dto
{
public class SearchMonsterDto
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
public int? MonsterType { get; set; }
public int? Level { get; set; }
}
}

3
Build_God_Api/Build_God_Api/Program.cs

@ -97,6 +97,8 @@ namespace Build_God_Api
sqlSugarClient.CodeFirst.InitTables(typeof(CharacterDailyMission)); sqlSugarClient.CodeFirst.InitTables(typeof(CharacterDailyMission));
sqlSugarClient.CodeFirst.InitTables(typeof(Scrap)); sqlSugarClient.CodeFirst.InitTables(typeof(Scrap));
sqlSugarClient.CodeFirst.InitTables(typeof(CharacterScrap)); sqlSugarClient.CodeFirst.InitTables(typeof(CharacterScrap));
sqlSugarClient.CodeFirst.InitTables(typeof(Monster));
sqlSugarClient.CodeFirst.InitTables(typeof(MonsterReward));
return sqlSugarClient; return sqlSugarClient;
}); });
@ -156,6 +158,7 @@ namespace Build_God_Api
builder.Services.AddHostedService<DailyMissionHostedService>(); builder.Services.AddHostedService<DailyMissionHostedService>();
builder.Services.AddScoped<IChatService, ChatService>(); builder.Services.AddScoped<IChatService, ChatService>();
builder.Services.AddScoped<IScrapService, ScrapService>(); builder.Services.AddScoped<IScrapService, ScrapService>();
builder.Services.AddScoped<IMonsterService, MonsterService>();
builder.Services.AddCors(options => builder.Services.AddCors(options =>
{ {

129
Build_God_Api/Build_God_Api/Services/MonsterService.cs

@ -0,0 +1,129 @@
using Build_God_Api.DB;
using Build_God_Api.Dto;
using SqlSugar;
namespace Build_God_Api.Services
{
public interface IMonsterService
{
Task<bool> Add(Monster item);
Task<bool> Delete(int id);
Task<bool> Update(Monster item);
Task<Monster?> GetById(int id);
Task<PagedResult<Monster>> GetAll(SearchMonsterDto dto);
Task<bool> ExistsByNameAsync(string name);
Task<bool> AddReward(MonsterReward reward);
Task<bool> UpdateReward(MonsterReward reward);
Task<bool> DeleteReward(int id);
Task<List<MonsterReward>> GetRewards(int monsterId);
}
public class MonsterService(ISqlSugarClient db, ICurrentUserService currentUserService) : IMonsterService
{
private readonly ISqlSugarClient _db = db;
private readonly ICurrentUserService _currentUserService = currentUserService;
public async Task<bool> Add(Monster item)
{
var exists = await ExistsByNameAsync(item.Name);
if (exists)
{
throw new Exception($"已存在名为 {item.Name} 的怪兽");
}
item.CreatedOn = DateTime.UtcNow;
item.CreatedBy = _currentUserService.UserId;
await _db.Insertable(item).ExecuteCommandAsync();
return true;
}
public async Task<bool> Delete(int id)
{
var monster = await _db.Queryable<Monster>().FirstAsync(x => x.Id == id)
?? throw new Exception("没找到对应的怪兽");
await _db.Deleteable<MonsterReward>().Where(x => x.MonsterId == id).ExecuteCommandAsync();
await _db.Deleteable(monster).ExecuteCommandAsync();
return true;
}
public async Task<bool> ExistsByNameAsync(string name)
{
return await _db.Queryable<Monster>().AnyAsync(x => x.Name == name);
}
public async Task<PagedResult<Monster>> GetAll(SearchMonsterDto dto)
{
var query = _db.Queryable<Monster>()
.WhereIF(dto.MonsterType != null, x => (int)x.Type == dto.MonsterType)
.WhereIF(dto.Level != null, x => x.Level == dto.Level)
.OrderBy(x => x.CreatedOn, OrderByType.Desc);
var list = await query.Skip((dto.PageNumber - 1) * dto.PageSize).Take(dto.PageSize).ToListAsync();
var total = await query.CountAsync();
return new PagedResult<Monster>
{
PageNumber = dto.PageNumber,
TotalCount = total,
Items = list
};
}
public async Task<Monster?> GetById(int id)
{
return await _db.Queryable<Monster>().FirstAsync(x => x.Id == id);
}
public async Task<bool> Update(Monster item)
{
var monster = await _db.Queryable<Monster>().FirstAsync(x => x.Id == item.Id)
?? throw new Exception("没找到对应的怪兽");
item.UpdatedOn = DateTime.UtcNow;
item.UpdatedBy = _currentUserService.UserId;
await _db.Updateable(item).ExecuteCommandAsync();
return true;
}
public async Task<bool> AddReward(MonsterReward reward)
{
reward.CreatedOn = DateTime.UtcNow;
reward.CreatedBy = _currentUserService.UserId;
await _db.Insertable(reward).ExecuteCommandAsync();
return true;
}
public async Task<bool> UpdateReward(MonsterReward reward)
{
var existing = await _db.Queryable<MonsterReward>().FirstAsync(x => x.Id == reward.Id)
?? throw new Exception("没找到对应的奖励");
reward.UpdatedOn = DateTime.UtcNow;
reward.UpdatedBy = _currentUserService.UserId;
await _db.Updateable(reward).ExecuteCommandAsync();
return true;
}
public async Task<bool> DeleteReward(int id)
{
var reward = await _db.Queryable<MonsterReward>().FirstAsync(x => x.Id == id)
?? throw new Exception("没找到对应的奖励");
await _db.Deleteable(reward).ExecuteCommandAsync();
return true;
}
public async Task<List<MonsterReward>> GetRewards(int monsterId)
{
return await _db.Queryable<MonsterReward>()
.Where(x => x.MonsterId == monsterId)
.ToListAsync();
}
}
}
Loading…
Cancel
Save