5 changed files with 373 additions and 0 deletions
@ -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}`); |
||||
|
}; |
||||
@ -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…
Reference in new issue