Browse Source

装备配置去掉多余的部位,暂时只考虑武器、防具、饰品。

属性改成固定属性,名字可重复
master
hanqin 1 week ago
parent
commit
f55c3fe9fd
  1. 24
      Build_God_Admin_Frontend/Frontend/src/api/equipment.ts
  2. 121
      Build_God_Admin_Frontend/Frontend/src/views/admin/EquipmentsView.vue
  3. 18
      Build_God_Admin_Frontend/Frontend/src/views/admin/MissionView.vue
  4. 37
      Build_God_Api/Build_God_Api/DB/Equipment.cs
  5. 7
      Build_God_Api/Build_God_Api/Scripts/equipment_simplify_postgresql.sql
  6. 51
      Build_God_Api/Build_God_Api/Services/EquipmentService.cs
  7. 6
      Build_God_Api/Build_God_Api/Services/ShopService.cs
  8. 5
      Build_God_Game/src/views/CatalogView.vue
  9. BIN
      Build_God_Game/模拟导图.xls

24
Build_God_Admin_Frontend/Frontend/src/api/equipment.ts

@ -8,14 +8,17 @@ export interface EquipmentTemplate {
rarity: number;
requirdLevelId: number;
setId: number | null;
attributePool: string;
randomAttrCount: number;
/** 固定属性 JSON;空则用 [] */
defaultAttributes?: string | null;
/** @deprecated 旧版随机池,仅兼容 */
attributePool?: string | null;
randomAttrCount?: number;
maxEnhanceLevel: number;
icon: string | null;
}
export interface EquipmentAttribute {
type: string;
type: number;
value: number;
}
@ -42,14 +45,6 @@ export interface EnhanceConfig {
bonusPercent: number;
}
export interface EquipmentAttributePool {
type: number;
min: number;
max: number;
weight: number;
}
//获取所有的equipment
export interface PagedResult<T> {
items: T[];
totalCount: number;
@ -74,37 +69,30 @@ export const GetEquipmentTemplateList = (
return http.post("equipment/all", dto);
};
//获取装备类型
export const GetEquipmentTypes = (): Promise<EnumInfoDto[]> => {
return http.get("equipment/types");
};
//获取装备稀有度
export const GetEquipmentRarities = (): Promise<EnumInfoDto[]> => {
return http.get("equipment/rarities");
};
//获取装备属性类型
export const GetEquipmentAttributeTypes = (): Promise<EnumInfoDto[]> => {
return http.get("equipment/attribute-types");
};
//添加装备模板
export const AddEquipmentTemplate = (data: EquipmentTemplate): Promise<boolean> => {
return http.post("equipment", data);
};
//修改装备模板
export const UpdateEquipmentTemplate = (data: EquipmentTemplate): Promise<boolean> => {
return http.put("equipment", data);
};
//删除装备模板
export const DeleteEquipmentTemplate = (id: number): Promise<boolean> => {
return http.delete(`equipment/${id}`);
};
//获取装备实例列表
export const GetEquipmentInstanceList = (
characterBagId?: number,
pageNumber?: number,

121
Build_God_Admin_Frontend/Frontend/src/views/admin/EquipmentsView.vue

@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { AddEquipmentTemplate, DeleteEquipmentTemplate, GetEquipmentTemplateList, GetEquipmentRarities, GetEquipmentTypes, GetEquipmentAttributeTypes, UpdateEquipmentTemplate, type EquipmentTemplate, type EquipmentAttributePool } from '@/api/equipment'
import { AddEquipmentTemplate, DeleteEquipmentTemplate, GetEquipmentTemplateList, GetEquipmentRarities, GetEquipmentTypes, GetEquipmentAttributeTypes, UpdateEquipmentTemplate, type EquipmentTemplate, type EquipmentAttribute } from '@/api/equipment'
import type { EnumInfoDto } from '@/api'
import { GetLevelList, type Level } from '@/api/level'
import { Plus, Edit, Delete, Close } from '@element-plus/icons-vue'
@ -37,13 +37,12 @@ const formData = ref<Partial<EquipmentTemplate>>({
rarity: 0,
requirdLevelId: 0,
setId: null,
attributePool: '[]',
randomAttrCount: 4,
defaultAttributes: '[]',
maxEnhanceLevel: 10,
icon: null
})
const attributePoolList = ref<EquipmentAttributePool[]>([])
const fixedAttrsList = ref<EquipmentAttribute[]>([])
const filteredEquipments = computed(() => {
return equipmentTemplates.value.filter(eq =>
@ -70,28 +69,25 @@ const translateLevel = (levelId: number) => {
return item ? item.name : '未知';
}
const addAttributePool = () => {
attributePoolList.value.push({
type: 1,
min: 0,
max: 100,
weight: 1
})
updateAttributePool()
const addFixedAttr = () => {
fixedAttrsList.value.push({ type: 1, value: 0 })
updateDefaultAttributes()
}
const removeAttributePool = (index: number) => {
attributePoolList.value.splice(index, 1)
updateAttributePool()
const removeFixedAttr = (index: number) => {
fixedAttrsList.value.splice(index, 1)
updateDefaultAttributes()
}
const updateAttributePool = () => {
formData.value.attributePool = JSON.stringify(attributePoolList.value)
const updateDefaultAttributes = () => {
formData.value.defaultAttributes = JSON.stringify(fixedAttrsList.value)
}
const parseAttributePool = (json: string) => {
const parseDefaultAttributes = (json: string | undefined | null) => {
if (!json || json === '[]') return []
try {
return JSON.parse(json) as EquipmentAttributePool[]
const arr = JSON.parse(json) as EquipmentAttribute[]
return Array.isArray(arr) ? arr.map(x => ({ type: Number(x.type), value: Number(x.value) })) : []
} catch {
return []
}
@ -101,7 +97,7 @@ const openDialog = (eq?: EquipmentTemplate) => {
if (eq != undefined) {
isEditing.value = true
formData.value = { ...eq }
attributePoolList.value = parseAttributePool(eq.attributePool || '[]')
fixedAttrsList.value = parseDefaultAttributes(eq.defaultAttributes ?? '[]')
} else {
isEditing.value = false
formData.value = {
@ -111,13 +107,13 @@ const openDialog = (eq?: EquipmentTemplate) => {
rarity: undefined,
requirdLevelId: undefined,
setId: null,
attributePool: '[]',
randomAttrCount: 4,
defaultAttributes: '[]',
maxEnhanceLevel: 10,
icon: null
}
attributePoolList.value = []
fixedAttrsList.value = []
}
updateDefaultAttributes()
showDialog.value = true
}
@ -138,7 +134,7 @@ const saveEquipment = async () => {
return;
}
updateAttributePool()
updateDefaultAttributes()
if (isEditing.value) {
const index = equipmentTemplates.value.findIndex(p => p.id === formData.value.id)
@ -157,15 +153,14 @@ const saveEquipment = async () => {
}
} else {
const newOne: EquipmentTemplate = {
id: 1,
id: 0,
name: formData.value.name || '',
description: formData.value.description || '',
type: formData.value.type || 1,
rarity: formData.value.rarity || 1,
requirdLevelId: formData.value.requirdLevelId || 1,
setId: formData.value.setId ?? null,
attributePool: formData.value.attributePool || '[]',
randomAttrCount: formData.value.randomAttrCount || 4,
defaultAttributes: formData.value.defaultAttributes || '[]',
maxEnhanceLevel: formData.value.maxEnhanceLevel || 10,
icon: formData.value.icon || null,
}
@ -205,17 +200,11 @@ const getEqTypeClass = (eqType: number) => {
case 1:
return 'eqType-weapon'
case 2:
return 'eqType-Armor'
return 'eqType-armor'
case 3:
return 'eqType-Helmet'
case 4:
return 'eqType-Necklace'
case 5:
return 'eqType-Ring'
case 6:
return 'eqType-Boots'
return 'eqType-accessory'
default:
return ''
return 'eqType-unknown'
}
}
@ -228,8 +217,6 @@ onMounted(async () => {
})
const refreshEquipments = async (page?: number) => {
console.log('current-page', page)
if (page !== undefined) currentPage.value = page
const res = await GetEquipmentTemplateList(eqFileterType.value, currentPage.value, pageSize.value)
@ -311,7 +298,11 @@ const fetchAttributeTypes = async () => {
</span>
</template>
</el-table-column>
<el-table-column label="随机属性数" prop="randomAttrCount" width="100"></el-table-column>
<el-table-column label="固定属性条数" width="110">
<template #default="scoped">
{{ parseDefaultAttributes(scoped.row.defaultAttributes ?? '[]').length }}
</template>
</el-table-column>
<el-table-column label="最大强化" prop="maxEnhanceLevel" width="100"></el-table-column>
<el-table-column label="编辑" fixed="right">
<template #default="scoped">
@ -357,45 +348,31 @@ const fetchAttributeTypes = async () => {
<el-form-item label="图标文件名">
<el-input v-model="formData.icon" placeholder="如: sword.png" clearable />
</el-form-item>
<el-form-item label="随机属性数量">
<el-input-number v-model="formData.randomAttrCount" :min="1" :max="10" />
</el-form-item>
<el-form-item label="最大强化等级">
<el-input-number v-model="formData.maxEnhanceLevel" :min="1" :max="20" />
</el-form-item>
<!-- 属性池配置 -->
<div class="attribute-pool-section">
<div class="pool-header">
<span>属性池配置</span>
<el-button type="primary" size="small" @click="addAttributePool">添加属性</el-button>
<span>固定属性与角色装备实例 JSON 一致购买时写入实例</span>
<el-button type="primary" size="small" @click="addFixedAttr">添加一条</el-button>
</div>
<el-table :data="attributePoolList" border size="small">
<el-table-column label="属性类型" width="350">
<el-table :data="fixedAttrsList" border size="small">
<el-table-column label="属性类型" min-width="280">
<template #default="scoped">
<el-select v-model="scoped.row.type" size="small" @change="updateAttributePool">
<el-select v-model="scoped.row.type" size="small" filterable @change="updateDefaultAttributes">
<el-option v-for="(value, index) in attributeTypes" :key="index" :value="value.id" :label="value.description"/>
</el-select>
</template>
</el-table-column>
<el-table-column label="最小值" width="150">
<el-table-column label="数值" width="180">
<template #default="scoped">
<el-input-number v-model="scoped.row.min" :min="0" size="small" controls-position="right" />
</template>
</el-table-column>
<el-table-column label="最大值" width="150">
<template #default="scoped">
<el-input-number v-model="scoped.row.max" :min="0" size="small" controls-position="right" />
</template>
</el-table-column>
<el-table-column label="权重" width="150">
<template #default="scoped">
<el-input-number v-model="scoped.row.weight" :min="1" size="small" controls-position="right" />
<el-input-number v-model="scoped.row.value" size="small" controls-position="right" @change="updateDefaultAttributes" />
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="scoped">
<el-button type="danger" size="small" @click="removeAttributePool(scoped.$index)">删除</el-button>
<el-button type="danger" size="small" @click="removeFixedAttr(scoped.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
@ -477,23 +454,11 @@ const fetchAttributeTypes = async () => {
font-weight: 600;
}
.eqType-Boots {
background-color: #7b4ee3;
}
.eqType-Ring {
background-color: #b2c522;
}
.eqType-Necklace {
background-color: #2281c5;
.eqType-accessory {
background-color: #8b5cf6;
}
.eqType-Helmet {
background-color: #22c55e;
}
.eqType-Armor {
.eqType-armor {
background-color: #f59e0b;
}
@ -501,6 +466,10 @@ const fetchAttributeTypes = async () => {
background-color: #ef4444;
}
.eqType-unknown {
background-color: #6b7280;
}
.actions {
display: flex;
gap: 8px;

18
Build_God_Admin_Frontend/Frontend/src/views/admin/MissionView.vue

@ -851,23 +851,11 @@ const fetchEquipments = async (): Promise<EquipmentTemplate[]> => {
font-weight: 600;
}
.eqType-Boots {
background-color: #7b4ee3;
.eqType-accessory {
background-color: #8b5cf6;
}
.eqType-Ring {
background-color: #b2c522;
}
.eqType-Necklace {
background-color: #2281c5;
}
.eqType-Helmet {
background-color: #22c55e;
}
.eqType-Armor {
.eqType-armor {
background-color: #f59e0b;
}

37
Build_God_Api/Build_God_Api/DB/Equipment.cs

@ -4,7 +4,7 @@ using System.ComponentModel;
namespace Build_God_Api.DB
{
/// <summary>
/// 装备模板 - 定义装备的基础信息和随机属性池
/// 装备模板
/// </summary>
public class EquipmentTemplate : BaseEntity
{
@ -16,7 +16,7 @@ namespace Build_God_Api.DB
public string Description { get; set; } = string.Empty;
/// <summary>
/// 装备类型(武器/防具/饰品
/// 装备类型(武器/防具/饰品)
/// </summary>
public EquipmentType Type { get; set; }
@ -37,19 +37,19 @@ namespace Build_God_Api.DB
public int? SetId { get; set; }
/// <summary>
/// 价格
/// 固定属性(JSON),与 EquipmentInstance.Attributes 同格式。库中为 null 时视为 <c>[]</c>。
/// </summary>
public int Money { get; set; }
[SugarColumn(ColumnDataType = "text", IsNullable = true)]
public string? DefaultAttributes { get; set; }
/// <summary>
/// 随机属性池配置(JSON)
/// 格式: [{"type":"AttackFixed","min":100,"max":1000,"weight":1},{"type":"AttackPercent","min":0,"max":50,"weight":1}]
/// 旧版随机属性池(已废弃,仅兼容历史数据;新配置请用 DefaultAttributes)
/// </summary>
[SugarColumn(ColumnDataType = "text")]
public string AttributePool { get; set; }
[SugarColumn(ColumnDataType = "text", IsNullable = true)]
public string? AttributePool { get; set; }
/// <summary>
/// 随机生成几条属性
/// 旧版随机条数(已废弃)
/// </summary>
public int RandomAttrCount { get; set; }
@ -96,7 +96,7 @@ namespace Build_God_Api.DB
public Rarity Rarity { get; set; }
/// <summary>
/// 随机属性(JSON)
/// 属性(JSON)
/// 格式: [{"type":"AttackFixed","value":856},{"type":"AttackPercent","value":40}]
/// </summary>
public string Attributes { get; set; } = "[]";
@ -201,20 +201,19 @@ namespace Build_God_Api.DB
BreakthroughBonusPercent = 12
}
/// <summary>
/// 装备部位:仅武器、防具、饰品三类
/// </summary>
public enum EquipmentType
{
[Description("武器")]
Weapon = 1,
[Description("防具")]
Armor,
[Description("头盔")]
Helmet,
[Description("项链")]
Necklace,
[Description("戒指")]
Ring,
[Description("鞋子")]
Boots
Armor = 2,
[Description("饰品")]
Accessory = 3
}
public enum Rarity

7
Build_God_Api/Build_God_Api/Scripts/equipment_simplify_postgresql.sql

@ -0,0 +1,7 @@
-- 装备类型简并为「武器=1 / 防具=2 / 饰品=3」后,对历史数据的迁移(PostgreSQL)
-- 原枚举:Helmet=3, Necklace=4, Ring=5, Boots=6。新枚举中 3 为「饰品」,与旧头盔同为 3,仅需把 4、5、6 改为 3。
-- UPDATE "EquipmentTemplate" SET "Type" = 3 WHERE "Type" IN (4, 5, 6);
-- UPDATE "EquipmentInstance" SET "Type" = 3 WHERE "Type" IN (4, 5, 6);
-- 新列 "DefaultAttributes" 由 SqlSugar CodeFirst 在启动时添加;旧模板请在前台重新配置固定属性,或手工将旧 AttributePool 转为 DefaultAttributes。

51
Build_God_Api/Build_God_Api/Services/EquipmentService.cs

@ -6,32 +6,29 @@ namespace Build_God_Api.Services
{
public interface IEquipmentService
{
public Task<bool> Add(EquipmentTemplate item);
Task<bool> Add(EquipmentTemplate item);
public Task<bool> Add(List<EquipmentTemplate> items);
Task<bool> Add(List<EquipmentTemplate> items);
public Task<bool> Delete(int id);
Task<bool> Delete(int id);
public Task<bool> Update(EquipmentTemplate item);
Task<bool> Update(EquipmentTemplate item);
public Task<PagedResult<EquipmentTemplate>> GetAll(SearchEquipmentDto dto);
public Task<bool> ExistsByNameAsync(string name);
Task<PagedResult<EquipmentTemplate>> GetAll(SearchEquipmentDto dto);
}
public class EquipmentService(ISqlSugarClient db,ICurrentUserService currentUserService) : IEquipmentService
public class EquipmentService(ISqlSugarClient db, ICurrentUserService currentUserService) : IEquipmentService
{
private readonly ISqlSugarClient db = db;
private readonly ICurrentUserService currentUserService = currentUserService;
private static string NormalizeDefaultAttributesJson(string? json) =>
string.IsNullOrWhiteSpace(json) ? "[]" : json.Trim();
public async Task<bool> Add(EquipmentTemplate item)
{
var bo = await this.ExistsByNameAsync(item.Name);
item.DefaultAttributes = NormalizeDefaultAttributesJson(item.DefaultAttributes);
if (bo)
{
throw new Exception($"已存在名为{item.Name}的装备");
}
item.CreatedOn = DateTime.UtcNow;
item.CreatedBy = currentUserService.UserId;
await db.Insertable(item).ExecuteCommandAsync();
@ -40,13 +37,9 @@ namespace Build_God_Api.Services
public async Task<bool> Add(List<EquipmentTemplate> items)
{
var bo = await db.Queryable<EquipmentTemplate>().Where(x => items.Any(i => x.Name == i.Name)).AnyAsync();
if (bo)
{
throw new Exception("存在重复的装备名称,添加失败");
}
foreach(var item in items)
foreach (var item in items)
{
item.DefaultAttributes = NormalizeDefaultAttributesJson(item.DefaultAttributes);
item.CreatedOn = DateTime.UtcNow;
item.CreatedBy = currentUserService.UserId;
}
@ -61,31 +54,29 @@ namespace Build_God_Api.Services
return true;
}
public async Task<bool> ExistsByNameAsync(string name)
{
return await db.Queryable<EquipmentTemplate>().AnyAsync(x => x.Name == name);
}
public async Task<PagedResult<EquipmentTemplate>> GetAll(SearchEquipmentDto dto)
{
var list = await db.Queryable<EquipmentTemplate>().Skip((dto.PageNumber - 1) * dto.PageSize).Take(dto.PageSize)
var list = await db.Queryable<EquipmentTemplate>()
.Skip((dto.PageNumber - 1) * dto.PageSize)
.Take(dto.PageSize)
.WhereIF(dto.EquipmentType != null, x => (int)x.Type == dto.EquipmentType)
.OrderBy(x=>x.CreatedOn)
.OrderBy(x => x.CreatedOn)
.ToListAsync();
var total = await db.Queryable<EquipmentTemplate>()
.WhereIF(dto.EquipmentType != null, x => (int)x.Type == dto.EquipmentType).CountAsync();
var result = new PagedResult<EquipmentTemplate>
.WhereIF(dto.EquipmentType != null, x => (int)x.Type == dto.EquipmentType)
.CountAsync();
return new PagedResult<EquipmentTemplate>
{
PageNumber = 1,
PageNumber = dto.PageNumber,
TotalCount = total,
Items = list
};
return result;
}
public async Task<bool> Update(EquipmentTemplate item)
{
var equipment = await db.Queryable<EquipmentTemplate>().FirstAsync(x => x.Id == item.Id) ?? throw new Exception("没找到对应装备");
item.DefaultAttributes = NormalizeDefaultAttributesJson(item.DefaultAttributes);
item.UpdatedOn = DateTime.UtcNow;
item.UpdatedBy = currentUserService.UserId;
await db.Updateable(item).ExecuteCommandAsync();

6
Build_God_Api/Build_God_Api/Services/ShopService.cs

@ -332,6 +332,10 @@ namespace Build_God_Api.Services
.FirstAsync(x => x.Id == equipmentTemplateId)
?? throw new Exception("装备模板不存在");
var attrs = string.IsNullOrWhiteSpace(template.DefaultAttributes)
? "[]"
: template.DefaultAttributes.Trim();
var instance = new EquipmentInstance
{
CharacterBagId = characterBagId,
@ -339,7 +343,7 @@ namespace Build_God_Api.Services
Name = template.Name,
Type = template.Type,
Rarity = template.Rarity,
Attributes = "[]",
Attributes = attrs,
EnhanceLevel = 0,
EnhanceBonusPercent = 0,
RequirdLevelId = template.RequirdLevelId,

5
Build_God_Game/src/views/CatalogView.vue

@ -27,10 +27,7 @@ const levels = ref<LevelDto[]>([])
const equipmentTypeLabels: Record<number, string> = {
1: '武器',
2: '防具',
3: '头盔',
4: '项链',
5: '戒指',
6: '鞋子',
3: '饰品',
}
const equipRarityLabels: Record<number, string> = {
1: '普通',

BIN
Build_God_Game/模拟导图.xls

Binary file not shown.
Loading…
Cancel
Save