Browse Source

增加商店的管理端UI

master
秦汉 3 weeks ago
parent
commit
7453110794
  1. 27
      Build_God_Admin_Frontend/Frontend/src/api/shop.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. 334
      Build_God_Admin_Frontend/Frontend/src/views/admin/ShopView.vue

27
Build_God_Admin_Frontend/Frontend/src/api/shop.ts

@ -0,0 +1,27 @@
import http from "../api/index";
export interface ShopItem {
id: number;
itemType: number;
itemId: number;
price: number;
dailyLimit: number;
isActive: boolean;
sortOrder: number;
}
export const GetShopItemList = (): Promise<ShopItem[]> => {
return http.get('/shop/admin/all');
};
export const CreateShopItem = (data: ShopItem): Promise<boolean> => {
return http.post('/shop/admin', data);
};
export const UpdateShopItem = (data: ShopItem): Promise<boolean> => {
return http.put(`/shop/admin/${data.id}`, data);
};
export const DeleteShopItem = (id: number): Promise<boolean> => {
return http.delete(`/shop/admin/${id}`);
};

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

@ -55,6 +55,11 @@ const menuItems = [
icon: ICONS.monster,
label: '怪兽管理',
path: '/admin/monsters'
},
{
icon: ICONS.shop,
label: '商店管理',
path: '/admin/shops'
}
]

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

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

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

@ -80,6 +80,12 @@ const routes: RouteRecordRaw[] = [
name: 'settings',
component: () => import('../views/admin/SettingsView.vue'),
meta: { title: '系统设置' }
},
{
path: 'shops',
name: 'shops',
component: () => import('../views/admin/ShopView.vue'),
meta: { title: '商店管理' }
}
]
},

334
Build_God_Admin_Frontend/Frontend/src/views/admin/ShopView.vue

@ -0,0 +1,334 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
GetShopItemList,
CreateShopItem,
UpdateShopItem,
DeleteShopItem,
type ShopItem
} from '@/api/shop'
import { GetEquipmentTemplateList, type EquipmentTemplate } from '@/api/equipment'
import { GetPillList, type Pill } from '@/api/pill'
import { GetAllScraps, type Scrap } from '@/api/scrap'
import { GetBagItemTypes } from '@/api/bag'
import type { EnumInfoDto } from '@/api/index'
import { Plus, Edit, Delete } from '@element-plus/icons-vue'
const shopItems = ref<ShopItem[]>([])
const itemTypes = ref<EnumInfoDto[]>([])
const equipmentList = ref<EquipmentTemplate[]>([])
const pillList = ref<Pill[]>([])
const scrapList = ref<Scrap[]>([])
const showDialog = ref(false)
const isEditing = ref(false)
const searchQuery = ref('')
const formData = ref<Partial<ShopItem>>({
itemType: 1,
itemId: 0,
price: 0,
dailyLimit: 0,
isActive: true,
sortOrder: 0
})
const filteredItems = computed(() => {
return shopItems.value.filter(item => {
const name = getItemName(item.itemType, item.itemId)
return name.toLowerCase().includes(searchQuery.value.toLowerCase())
})
})
const getItemName = (itemType: number, itemId: number): string => {
if (itemType === 1) {
const eq = equipmentList.value.find(e => e.id === itemId)
return eq?.name || `装备-${itemId}`
} else if (itemType === 2) {
const pill = pillList.value.find(p => p.id === itemId)
return pill?.name || `丹药-${itemId}`
} else if (itemType === 3) {
const scrap = scrapList.value.find(s => s.id === itemId)
return scrap?.name || `垃圾-${itemId}`
}
return `未知-${itemId}`
}
const translateItemType = (typeId: number) => {
const type = itemTypes.value.find((t: EnumInfoDto) => t.id === typeId)
return type ? type.description : '未知类型'
}
const getCurrentTypeItems = computed(() => {
const type = formData.value.itemType
if (type === 1) {
return equipmentList.value.map(e => ({ value: e.id, label: e.name }))
} else if (type === 2) {
return pillList.value.map(p => ({ value: p.id, label: p.name }))
} else if (type === 3) {
return scrapList.value.map(s => ({ value: s.id, label: s.name }))
}
return []
})
const refreshShopItems = async () => {
shopItems.value = await GetShopItemList()
}
const loadItemTypes = async () => {
itemTypes.value = await GetBagItemTypes()
}
const loadEquipmentList = async () => {
const res = await GetEquipmentTemplateList()
if (Array.isArray(res)) {
equipmentList.value = res
} else {
equipmentList.value = res.items || []
}
}
const loadPillList = async () => {
pillList.value = await GetPillList()
}
const loadScrapList = async () => {
scrapList.value = await GetAllScraps()
}
const loadAllData = async () => {
await Promise.all([
refreshShopItems(),
loadItemTypes(),
loadEquipmentList(),
loadPillList(),
loadScrapList()
])
}
const openDialog = (item?: ShopItem) => {
if (item) {
isEditing.value = true
formData.value = { ...item }
} else {
isEditing.value = false
formData.value = {
itemType: 1,
itemId: 0,
price: 0,
dailyLimit: 0,
isActive: true,
sortOrder: 0
}
}
showDialog.value = true
}
const closeDialog = () => {
showDialog.value = false
}
const saveShopItem = async () => {
if (formData.value.itemType === undefined || formData.value.itemId === undefined || formData.value.itemId === 0) {
ElMessage.error('请选择物品')
return
}
if (formData.value.price === undefined || formData.value.price <= 0) {
ElMessage.error('请填写价格')
return
}
if (isEditing.value) {
const result = await UpdateShopItem(formData.value as ShopItem)
if (result) {
ElMessage.success('商店物品更新成功')
closeDialog()
await refreshShopItems()
} else {
ElMessage.error('商店物品更新失败')
}
} else {
const newItem: ShopItem = {
id: 0,
itemType: formData.value.itemType || 1,
itemId: formData.value.itemId || 0,
price: formData.value.price || 0,
dailyLimit: formData.value.dailyLimit || 0,
isActive: formData.value.isActive ?? true,
sortOrder: formData.value.sortOrder || 0
}
const result = await CreateShopItem(newItem)
if (result) {
ElMessage.success('商店物品添加成功')
closeDialog()
await refreshShopItems()
} else {
ElMessage.error('添加失败')
}
}
}
const deleteShopItem = (item: ShopItem) => {
ElMessageBox.confirm(`确定删除商店物品 "${getItemName(item.itemType, item.itemId)}" 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
const result = await DeleteShopItem(item.id)
if (result) {
ElMessage.success('商店物品删除成功')
await refreshShopItems()
} else {
ElMessage.error('删除失败')
}
}).catch(() => {})
}
const onItemTypeChange = () => {
formData.value.itemId = 0
}
onMounted(() => {
loadAllData()
})
</script>
<template>
<div class="shop-container">
<div class="header">
<h2>商店管理</h2>
<div class="header-actions">
<el-input
v-model="searchQuery"
placeholder="搜索物品名称"
class="search-input"
clearable
/>
<el-button type="primary" :icon="Plus" @click="openDialog()">
添加商店物品
</el-button>
</div>
</div>
<el-table :data="filteredItems" border stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column label="物品类型" width="120">
<template #default="{ row }">
{{ translateItemType(row.itemType) }}
</template>
</el-table-column>
<el-table-column label="物品名称" min-width="150">
<template #default="{ row }">
{{ getItemName(row.itemType, row.itemId) }}
</template>
</el-table-column>
<el-table-column prop="price" label="价格(灵石)" width="120" />
<el-table-column prop="dailyLimit" label="每日限购" width="100">
<template #default="{ row }">
{{ row.dailyLimit === 0 ? '不限' : row.dailyLimit }}
</template>
</el-table-column>
<el-table-column prop="sortOrder" label="排序" width="80" />
<el-table-column label="状态" width="80">
<template #default="{ row }">
<el-tag :type="row.isActive ? 'success' : 'info'">
{{ row.isActive ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" :icon="Edit" circle @click="openDialog(row)" />
<el-button type="danger" :icon="Delete" circle @click="deleteShopItem(row)" />
</template>
</el-table-column>
</el-table>
<el-dialog
v-model="showDialog"
:title="isEditing ? '编辑商店物品' : '添加商店物品'"
width="500px"
:close-on-click-modal="false"
>
<el-form :model="formData" label-width="100px">
<el-form-item label="物品类型">
<el-select v-model="formData.itemType" placeholder="选择物品类型" @change="onItemTypeChange">
<el-option
v-for="type in itemTypes"
:key="type.id"
:label="type.description"
:value="type.id"
/>
</el-select>
</el-form-item>
<el-form-item label="选择物品">
<el-select
v-model="formData.itemId"
placeholder="选择物品"
filterable
:disabled="!formData.itemType"
>
<el-option
v-for="item in getCurrentTypeItems"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="价格(灵石)">
<el-input-number v-model="formData.price" :min="0" :step="100" />
</el-form-item>
<el-form-item label="每日限购">
<el-input-number v-model="formData.dailyLimit" :min="0" :step="1" />
<span class="form-tip">0表示不限购</span>
</el-form-item>
<el-form-item label="排序权重">
<el-input-number v-model="formData.sortOrder" :min="0" :step="1" />
<span class="form-tip">越大越优先出现</span>
</el-form-item>
<el-form-item label="是否启用">
<el-switch v-model="formData.isActive" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="saveShopItem">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="css">
.shop-container {
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.header h2 {
margin: 0;
}
.header-actions {
display: flex;
gap: 10px;
align-items: center;
}
.search-input {
width: 200px;
}
.form-tip {
margin-left: 10px;
color: #909399;
font-size: 12px;
}
</style>
Loading…
Cancel
Save