Files
sale/frontend/src/views/admin/Banners.vue
T

332 lines
11 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 class="banners-page">
<div class="page-header">
<el-button type="primary" @click="openAdd">添加轮播图</el-button>
</div>
<div class="table-card">
<el-table :data="banners">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column label="图片" width="150">
<template #default="{ row }">
<img :src="getImageUrl(row.image)" style="width: 120px; height: 60px; object-fit: cover; border-radius: 4px;" />
</template>
</el-table-column>
<el-table-column prop="title" label="标题" />
<el-table-column prop="desc" label="描述" />
<el-table-column prop="button" label="按钮文字" width="120" />
<el-table-column prop="sort_order" label="排序" width="80" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.is_active ? 'success' : 'info'">
{{ row.is_active ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="editBanner(row)">编辑</el-button>
<el-button link :type="row.is_active ? 'warning' : 'success'" size="small" @click="toggleStatus(row)">
{{ row.is_active ? '禁用' : '启用' }}
</el-button>
<el-button link type="danger" size="small" @click="deleteBanner(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog v-model="showAdd" :title="editing ? '编辑轮播图' : '添加轮播图'" width="600px">
<el-form :model="form" label-width="100px">
<el-form-item label="标题" required>
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.desc" type="textarea" :rows="2" placeholder="请输入描述" />
</el-form-item>
<el-form-item label="图片" required>
<el-upload
class="banner-uploader"
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleImageSuccess"
:before-upload="beforeImageUpload"
accept="image/*"
>
<img v-if="form.image" :src="form.image" class="banner-image" />
<el-icon v-else class="banner-uploader-icon"><Plus /></el-icon>
</el-upload>
<div class="image-tip">建议尺寸: 1920x400px支持 jpgpngwebp 格式</div>
</el-form-item>
<el-form-item label="链接类型">
<el-radio-group v-model="linkType" @change="handleLinkTypeChange">
<el-radio-button value="custom">自定义</el-radio-button>
<el-radio-button value="product">商品</el-radio-button>
<el-radio-button value="article">资讯</el-radio-button>
<el-radio-button value="lottery">活动</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="链接" v-if="linkType === 'custom'">
<el-input v-model="form.link" placeholder="点击跳转链接,如: /products" />
</el-form-item>
<el-form-item label="选择商品" v-if="linkType === 'product'">
<el-select v-model="selectedProduct" placeholder="请选择商品" filterable @change="handleProductSelect" style="width: 100%">
<el-option v-for="p in products" :key="p.id" :label="p.name" :value="p.id" />
</el-select>
</el-form-item>
<el-form-item label="选择资讯" v-if="linkType === 'article'">
<el-select v-model="selectedArticle" placeholder="请选择资讯" filterable @change="handleArticleSelect" style="width: 100%">
<el-option v-for="a in articles" :key="a.id" :label="a.title" :value="a.id" />
</el-select>
</el-form-item>
<el-form-item label="选择活动" v-if="linkType === 'lottery'">
<el-select v-model="selectedLottery" placeholder="请选择活动" filterable @change="handleLotterySelect" style="width: 100%">
<el-option v-for="l in lotteries" :key="l.id" :label="l.name" :value="l.id" />
</el-select>
</el-form-item>
<el-form-item label="按钮文字">
<el-input v-model="form.button" placeholder="如: 立即选购" />
</el-form-item>
<el-form-item label="背景颜色">
<el-input v-model="form.bg_color" placeholder="如: linear-gradient(135deg, #4e6ef2 0%, #7c5cfc 100%)" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="form.sort_order" :min="0" />
</el-form-item>
<el-form-item label="启用">
<el-switch v-model="form.is_active" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAdd = false">取消</el-button>
<el-button type="primary" @click="saveBanner">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { adminApi, productApi, articleApi, lotteryApi } from '../../api'
import { getImageUrl } from '../../utils/image'
const uploadUrl = import.meta.env.VITE_API_BASE_URL + '/upload'
const uploadHeaders = { Authorization: `Bearer ${localStorage.getItem('token')}` }
const banners = ref<any[]>([])
const products = ref<any[]>([])
const articles = ref<any[]>([])
const lotteries = ref<any[]>([])
const showAdd = ref(false)
const editing = ref<any>(null)
const linkType = ref('custom')
const selectedProduct = ref<number | null>(null)
const selectedArticle = ref<number | null>(null)
const selectedLottery = ref<number | null>(null)
const form = reactive({
title: '',
desc: '',
image: '',
link: '',
button: '',
bg_color: '',
sort_order: 0,
is_active: true
})
async function fetchBanners() {
const res: any = await adminApi.getBanners()
banners.value = res.data || []
}
async function fetchProducts() {
const res: any = await productApi.list({ page: 1, page_size: 100 })
products.value = res.data || []
}
async function fetchArticles() {
const res: any = await articleApi.list({ page: 1, page_size: 100 })
articles.value = res.data || []
}
async function fetchLotteries() {
const res: any = await lotteryApi.list()
lotteries.value = res.data || []
}
function handleLinkTypeChange() {
form.link = ''
selectedProduct.value = null
selectedArticle.value = null
selectedLottery.value = null
}
function handleProductSelect(productId: number) {
form.link = `/products/${productId}`
}
function handleArticleSelect(articleId: number) {
form.link = `/articles/${articleId}`
}
function handleLotterySelect(lotteryId: number) {
form.link = `/lotteries/${lotteryId}`
}
function openAdd() {
editing.value = null
linkType.value = 'custom'
selectedProduct.value = null
selectedArticle.value = null
selectedLottery.value = null
Object.assign(form, { title: '', desc: '', image: '', link: '', button: '', bg_color: '', sort_order: 0, is_active: true })
showAdd.value = true
}
function editBanner(row: any) {
editing.value = row
Object.assign(form, {
title: row.title,
desc: row.desc || '',
image: row.image || '',
link: row.link || '',
button: row.button || '',
bg_color: row.bg_color || '',
sort_order: row.sort_order,
is_active: row.is_active
})
// 根据链接判断类型
if (row.link) {
if (row.link.startsWith('/products/')) {
linkType.value = 'product'
const id = parseInt(row.link.split('/')[2])
selectedProduct.value = isNaN(id) ? null : id
} else if (row.link.startsWith('/articles/')) {
linkType.value = 'article'
const id = parseInt(row.link.split('/')[2])
selectedArticle.value = isNaN(id) ? null : id
} else if (row.link.startsWith('/lotteries/')) {
linkType.value = 'lottery'
const id = parseInt(row.link.split('/')[2])
selectedLottery.value = isNaN(id) ? null : id
} else {
linkType.value = 'custom'
}
} else {
linkType.value = 'custom'
}
showAdd.value = true
}
function beforeImageUpload(file: 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
}
function handleImageSuccess(response: any) {
if (response.url) {
form.image = response.url
ElMessage.success('图片上传成功')
} else {
ElMessage.error('上传失败')
}
}
async function saveBanner() {
if (!form.title || !form.image) {
ElMessage.warning('请填写必填项')
return
}
if (editing.value) {
await adminApi.updateBanner(editing.value.id, form)
} else {
await adminApi.createBanner(form)
}
ElMessage.success('保存成功')
showAdd.value = false
editing.value = null
await fetchBanners()
}
async function toggleStatus(row: any) {
await adminApi.toggleBanner(row.id)
ElMessage.success('操作成功')
await fetchBanners()
}
async function deleteBanner(id: number) {
await ElMessageBox.confirm('确定删除此轮播图?', '确认删除')
await adminApi.deleteBanner(id)
ElMessage.success('删除成功')
await fetchBanners()
}
onMounted(async () => {
await Promise.all([
fetchBanners(),
fetchProducts(),
fetchArticles(),
fetchLotteries()
])
})
</script>
<style scoped lang="scss">
.banners-page { padding: 0; }
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; }
.table-card { background: #2d2d44; border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 12px; padding: 20px; }
.banner-uploader {
:deep(.el-upload) {
border: 1px dashed rgba(255, 255, 255, 0.2);
border-radius: 8px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: all 0.3s;
width: 300px;
height: 120px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
border-color: #4e6ef2;
}
}
}
.banner-image {
width: 300px;
height: 120px;
object-fit: cover;
border-radius: 8px;
}
.banner-uploader-icon {
font-size: 28px;
color: rgba(255, 255, 255, 0.4);
}
.image-tip {
margin-top: 8px;
font-size: 12px;
color: rgba(255, 255, 255, 0.4);
}
</style>