campus-errand/admin/src/views/Banners.vue
18631081161 236ee5570e
All checks were successful
continuous-integration/drone/push Build is passing
内部地址
2026-04-06 14:09:54 +08:00

221 lines
7.9 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;">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>