All checks were successful
continuous-integration/drone/push Build is passing
221 lines
7.9 KiB
Vue
221 lines
7.9 KiB
Vue
<template>
|
||
<div>
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
||
<h3 style="margin: 0;">Banner 管理</h3>
|
||
<el-button type="primary" @click="openDialog()">新增 Banner</el-button>
|
||
</div>
|
||
|
||
<el-table :data="list" v-loading="loading" border>
|
||
<el-table-column label="图片" width="120">
|
||
<template #default="{ row }">
|
||
<el-image :src="row.imageUrl" style="width: 80px; height: 45px;" fit="cover" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="linkType" label="链接类型" width="120">
|
||
<template #default="{ row }">
|
||
{{ row.linkType === 'External' ? '外部链接' : '内部页面' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="linkUrl" label="链接地址" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ row.linkType === 'Internal' ? (internalPages.find(p => '/' + p.path === row.linkUrl)?.label || row.linkUrl) : row.linkUrl }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="sortOrder" label="排序" width="80" />
|
||
<el-table-column label="启用状态" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.isEnabled ? 'success' : 'info'">{{ row.isEnabled ? '启用' : '禁用' }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="160" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button size="small" @click="openDialog(row)">编辑</el-button>
|
||
<el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 新增/编辑弹窗 -->
|
||
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑 Banner' : '新增 Banner'" width="500px">
|
||
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
|
||
<el-form-item label="图片" prop="imageUrl">
|
||
<el-upload
|
||
action="/api/upload/image"
|
||
:headers="uploadHeaders"
|
||
:show-file-list="false"
|
||
:on-success="onUploadSuccess"
|
||
:before-upload="beforeUpload"
|
||
accept="image/*"
|
||
>
|
||
<el-image
|
||
v-if="form.imageUrl"
|
||
:src="form.imageUrl"
|
||
style="width: 200px; height: 100px; cursor: pointer;"
|
||
fit="cover"
|
||
/>
|
||
<el-button v-else size="small" type="primary">选择图片</el-button>
|
||
</el-upload>
|
||
<div v-if="form.imageUrl" style="margin-top: 4px;">
|
||
<el-button size="small" text type="danger" @click="form.imageUrl = ''">移除图片</el-button>
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="链接类型" prop="linkType">
|
||
<el-select v-model="form.linkType" style="width: 100%;" @change="form.linkUrl = ''">
|
||
<el-option label="外部链接" value="External" />
|
||
<el-option label="内部页面" value="Internal" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="链接地址" prop="linkUrl">
|
||
<el-select v-if="form.linkType === 'Internal'" v-model="form.linkUrl" placeholder="请选择页面" style="width: 100%;" filterable>
|
||
<el-option v-for="p in internalPages" :key="p.path" :label="p.label" :value="'/' + p.path" />
|
||
</el-select>
|
||
<el-input v-else v-model="form.linkUrl" placeholder="请输入外部链接地址" />
|
||
</el-form-item>
|
||
<el-form-item label="排序权重" prop="sortOrder">
|
||
<el-input-number v-model="form.sortOrder" :min="0" />
|
||
</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>
|
||
</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 internalPages = [
|
||
{ path: 'pages/index/index', label: '首页' },
|
||
{ path: 'pages/order-hall/order-hall', label: '订单大厅' },
|
||
{ path: 'pages/message/message', label: '消息' },
|
||
{ path: 'pages/mine/mine', label: '我的' },
|
||
{ path: 'pages/pickup/pickup', label: '代取' },
|
||
{ path: 'pages/delivery/delivery', label: '代送' },
|
||
{ path: 'pages/help/help', label: '万能帮' },
|
||
{ path: 'pages/purchase/purchase', label: '代购' },
|
||
{ path: 'pages/food/food', label: '美食街' },
|
||
{ path: 'pages/order/my-orders', label: '我的订单' },
|
||
{ path: 'pages/order/my-taken', label: '我的接单' },
|
||
{ path: 'pages/mine/earnings', label: '我的收益' },
|
||
{ path: 'pages/mine/profile', label: '编辑资料' },
|
||
{ path: 'pages/runner/certification', label: '跑腿认证' },
|
||
{ path: 'pages/config/agreement', label: '用户协议' },
|
||
{ path: 'pages/config/privacy', label: '隐私政策' },
|
||
{ path: 'pages/config/qrcode', label: '客服二维码' },
|
||
{ path: 'pages/config/runner-agreement', label: '跑腿协议' },
|
||
{ path: 'pages/message/system-msg', label: '系统消息' },
|
||
{ path: 'pages/message/order-notify', label: '订单通知' },
|
||
{ path: 'pages/mine/earnings-record', label: '收益记录' }
|
||
]
|
||
|
||
/** 上传成功回调 */
|
||
function onUploadSuccess(res) {
|
||
form.imageUrl = res.url
|
||
ElMessage.success('图片上传成功')
|
||
}
|
||
|
||
/** 上传前校验 */
|
||
function beforeUpload(file) {
|
||
const isImage = file.type.startsWith('image/')
|
||
const isLt5M = file.size / 1024 / 1024 < 5
|
||
if (!isImage) {
|
||
ElMessage.error('只能上传图片文件')
|
||
return false
|
||
}
|
||
if (!isLt5M) {
|
||
ElMessage.error('图片大小不能超过 5MB')
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
const defaultForm = () => ({
|
||
imageUrl: '',
|
||
linkType: 'External',
|
||
linkUrl: '',
|
||
sortOrder: 0,
|
||
isEnabled: true
|
||
})
|
||
|
||
const form = reactive(defaultForm())
|
||
|
||
const rules = {
|
||
imageUrl: [{ required: true, message: '图片地址不能为空', trigger: 'blur' }],
|
||
linkType: [{ required: true, message: '请选择链接类型', trigger: 'change' }],
|
||
linkUrl: [{ required: true, message: '链接地址不能为空', trigger: 'blur' }]
|
||
}
|
||
|
||
/** 获取 Banner 列表 */
|
||
async function fetchList() {
|
||
loading.value = true
|
||
try {
|
||
list.value = await request.get('/admin/banners')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
/** 打开新增/编辑弹窗 */
|
||
function openDialog(row) {
|
||
isEdit.value = !!row
|
||
editId.value = row?.id || null
|
||
Object.assign(form, row ? {
|
||
imageUrl: row.imageUrl,
|
||
linkType: row.linkType,
|
||
linkUrl: row.linkUrl,
|
||
sortOrder: row.sortOrder,
|
||
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/banners/${editId.value}`, form)
|
||
ElMessage.success('更新成功')
|
||
} else {
|
||
await request.post('/admin/banners', form)
|
||
ElMessage.success('创建成功')
|
||
}
|
||
dialogVisible.value = false
|
||
fetchList()
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
/** 删除 Banner */
|
||
async function handleDelete(row) {
|
||
await ElMessageBox.confirm('确定删除该 Banner?', '提示', { type: 'warning' })
|
||
await request.delete(`/admin/banners/${row.id}`)
|
||
ElMessage.success('删除成功')
|
||
fetchList()
|
||
}
|
||
|
||
onMounted(fetchList)
|
||
</script>
|