campus-errand/admin/src/views/Shops.vue
2026-03-17 15:42:18 +08:00

214 lines
8.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h3 style="margin: 0;">门店管理</h3>
<el-button type="primary" @click="openDialog()">新增门店</el-button>
</div>
<el-table :data="list" v-loading="loading" border stripe>
<el-table-column label="照片" width="120" align="center">
<template #default="{ row }">
<el-image :src="row.photo" style="width: 80px; height: 80px; border-radius: 8px;" fit="cover" :preview-src-list="[row.photo]" preview-teleported />
</template>
</el-table-column>
<el-table-column prop="name" label="门店名称" min-width="120" />
<el-table-column prop="location" label="位置" min-width="140" show-overflow-tooltip />
<el-table-column label="打包费" width="140" align="center">
<template #default="{ row }">
<span>{{ row.packingFeeType === 'Fixed' ? '总打包费' : '单份' }} ¥{{ row.packingFeeAmount }}</span>
</template>
</el-table-column>
<el-table-column prop="dishCount" label="菜品数" width="80" align="center" />
<el-table-column label="状态" width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.isEnabled ? 'success' : 'info'" size="small">{{ row.isEnabled ? '启用' : '禁用' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="300" fixed="right" align="center">
<template #default="{ row }">
<div style="display: flex; gap: 4px; justify-content: center; flex-wrap: nowrap;">
<el-button size="small" @click="openDialog(row)">编辑</el-button>
<el-button size="small" @click="manageBanners(row)">Banner</el-button>
<el-button size="small" type="primary" @click="$router.push(`/shops/${row.id}/dishes`)">菜品</el-button>
<el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 新增/编辑门店弹窗 -->
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑门店' : '新增门店'" width="550px">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="门店名称" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="门店照片" prop="photo">
<el-upload action="/api/upload/image" :headers="uploadHeaders" :show-file-list="false"
:on-success="(res) => form.photo = res.url" accept="image/*">
<el-image v-if="form.photo" :src="form.photo" style="width: 120px; height: 120px; cursor: pointer; border-radius: 8px;" fit="cover" />
<el-button v-else size="small">上传照片</el-button>
</el-upload>
<el-button v-if="form.photo" size="small" text type="danger" style="margin-top: 4px;" @click="form.photo = ''">移除</el-button>
</el-form-item>
<el-form-item label="门店位置" prop="location">
<el-input v-model="form.location" />
</el-form-item>
<el-form-item label="注意事项">
<el-input v-model="form.notice" type="textarea" :rows="3" />
</el-form-item>
<el-form-item label="打包费类型" prop="packingFeeType">
<el-select v-model="form.packingFeeType" style="width: 100%;">
<el-option label="总打包费(固定金额)" value="Fixed" />
<el-option label="单份打包费(按份数)" value="PerItem" />
</el-select>
</el-form-item>
<el-form-item label="打包费金额">
<el-input-number v-model="form.packingFeeAmount" :min="0" :precision="2" :step="0.5" />
</el-form-item>
<el-form-item label="启用状态">
<el-switch v-model="form.isEnabled" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
<!-- 门店 Banner 管理弹窗 -->
<el-dialog v-model="bannerDialogVisible" :title="`${bannerShopName} - Banner 管理`" width="600px">
<el-upload action="/api/upload/image" :headers="uploadHeaders" :show-file-list="false"
:on-success="handleBannerUpload" accept="image/*" style="margin-bottom: 16px;">
<el-button type="primary" size="small">添加 Banner</el-button>
</el-upload>
<el-table :data="shopBanners" v-loading="bannerLoading" border size="small">
<el-table-column label="图片" width="160">
<template #default="{ row }">
<el-image :src="row.imageUrl" style="width: 120px; height: 60px;" fit="cover" />
</template>
</el-table-column>
<el-table-column prop="sortOrder" label="排序" width="80" />
<el-table-column label="操作" width="80">
<template #default="{ row }">
<el-button size="small" type="danger" @click="deleteBanner(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '../utils/request'
const loading = ref(false)
const submitting = ref(false)
const list = ref([])
const dialogVisible = ref(false)
const isEdit = ref(false)
const editId = ref(null)
const formRef = ref(null)
const uploadHeaders = { Authorization: `Bearer ${localStorage.getItem('admin_token')}` }
const defaultForm = () => ({
name: '', photo: '', location: '', notice: '',
packingFeeType: 'Fixed', packingFeeAmount: 0, isEnabled: true
})
const form = reactive(defaultForm())
const rules = {
name: [{ required: true, message: '门店名称不能为空', trigger: 'blur' }],
photo: [{ required: true, message: '门店照片不能为空', trigger: 'blur' }],
location: [{ required: true, message: '门店位置不能为空', trigger: 'blur' }],
packingFeeType: [{ required: true, message: '请选择打包费类型', trigger: 'change' }]
}
// 门店 Banner 相关
const bannerDialogVisible = ref(false)
const bannerLoading = ref(false)
const bannerShopId = ref(null)
const bannerShopName = ref('')
const shopBanners = ref([])
async function fetchList() {
loading.value = true
try {
list.value = await request.get('/admin/shops')
} finally {
loading.value = false
}
}
function openDialog(row) {
isEdit.value = !!row
editId.value = row?.id || null
Object.assign(form, row ? {
name: row.name, photo: row.photo, location: row.location,
notice: row.notice || '', packingFeeType: row.packingFeeType,
packingFeeAmount: row.packingFeeAmount, isEnabled: row.isEnabled
} : defaultForm())
dialogVisible.value = true
}
async function handleSubmit() {
const valid = await formRef.value.validate().catch(() => false)
if (!valid) return
submitting.value = true
try {
if (isEdit.value) {
await request.put(`/admin/shops/${editId.value}`, form)
ElMessage.success('更新成功')
} else {
await request.post('/admin/shops', form)
ElMessage.success('创建成功')
}
dialogVisible.value = false
fetchList()
} finally {
submitting.value = false
}
}
async function handleDelete(row) {
await ElMessageBox.confirm(`确定删除门店「${row.name}」?将同时删除其菜品和 Banner`, '提示', { type: 'warning' })
await request.delete(`/admin/shops/${row.id}`)
ElMessage.success('删除成功')
fetchList()
}
/** 门店 Banner 管理 */
async function manageBanners(row) {
bannerShopId.value = row.id
bannerShopName.value = row.name
bannerDialogVisible.value = true
await fetchBanners()
}
async function fetchBanners() {
bannerLoading.value = true
try {
shopBanners.value = await request.get(`/admin/shops/${bannerShopId.value}/banners`)
} finally {
bannerLoading.value = false
}
}
async function handleBannerUpload(res) {
await request.post(`/admin/shops/${bannerShopId.value}/banners`, { imageUrl: res.url, sortOrder: 0 })
ElMessage.success('添加成功')
fetchBanners()
}
async function deleteBanner(row) {
await ElMessageBox.confirm('确定删除该 Banner', '提示', { type: 'warning' })
await request.delete(`/admin/shops/${bannerShopId.value}/banners/${row.id}`)
ElMessage.success('删除成功')
fetchBanners()
}
onMounted(fetchList)
</script>