|
|
@@ -0,0 +1,407 @@
|
|
|
+<template>
|
|
|
+ <div class="home-banner-manage admin-page">
|
|
|
+ <el-card>
|
|
|
+ <div slot="header" class="admin-page-header">
|
|
|
+ <span class="admin-page-title">首页广告栏</span>
|
|
|
+ <el-button type="primary" size="mini" icon="el-icon-plus" @click="openDialog()">新增广告位</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="filter-bar" style="margin-bottom:12px;">
|
|
|
+ <el-select v-model="filterStatus" placeholder="启用状态" @change="loadList" clearable style="width:120px;" clearable>
|
|
|
+ <el-option label="全部状态" value="" />
|
|
|
+ <el-option label="已启用" value="1" />
|
|
|
+ <el-option label="已禁用" value="0" />
|
|
|
+ </el-select>
|
|
|
+ <el-select v-model="filterType" placeholder="类型" @change="loadList" clearable style="width:120px;">
|
|
|
+ <el-option label="全部类型" value="" />
|
|
|
+ <el-option label="活动" value="activity" />
|
|
|
+ <el-option label="商品" value="product" />
|
|
|
+ <el-option label="链接" value="link" />
|
|
|
+ </el-select>
|
|
|
+ <el-button type="primary" size="mini" @click="loadList">查询</el-button>
|
|
|
+ <el-button size="mini" @click="resetFilter">重置</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="table-scroll-wrap-sm">
|
|
|
+ <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe>
|
|
|
+ <el-table-column prop="id" label="ID" width="60" />
|
|
|
+ <el-table-column label="封面" width="80">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <img v-if="row.coverImage" :src="row.coverImage" style="width:50px;height:35px;object-fit:cover;border-radius:4px;" />
|
|
|
+ <span v-else style="color:#999">无</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="title" label="标题" min-width="160" show-overflow-tooltip>
|
|
|
+ <template slot="header"><span class="sort-header" @click.stop="onSortClick('title')">标题<span v-if="sortIndex('title') !== null" class="sort-index" :class="sortClass('title') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('title') }}</span>{{ sortIcon('title') }}</span></template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="类型" width="80">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag size="mini" :type="typeTag(row.bannerType)">{{ typeLabel(row.bannerType) }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="关联" width="120">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span v-if="row.bannerType === 'activity' && row.refId">活动 #{{ row.refId }}</span>
|
|
|
+ <span v-else-if="row.bannerType === 'product' && row.refId">商品 #{{ row.refId }}</span>
|
|
|
+ <span v-else-if="row.bannerType === 'link' && row.linkUrl" style="font-size:12px;color:#999;overflow:hidden;text-overflow:ellipsis;display:block;max-width:110px;">{{ row.linkUrl }}</span>
|
|
|
+ <span v-else style="color:#bbb">-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="排序" width="80">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-input-number v-model="row.sortOrder" :min="0" :max="999" size="mini" @change="onSortChange(row)" controls-position="right" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="投放时间" width="200">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span v-if="row.startTime || row.endTime">{{ formatDate(row.startTime) }} ~ {{ formatDate(row.endTime) }}</span>
|
|
|
+ <span v-else style="color:#bbb">不限</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" width="80">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-switch v-model="row.enabled" :active-value="1" :inactive-value="0" @change="onToggle(row)" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="createdAt" label="创建时间" width="160">
|
|
|
+ <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="140" fixed="right">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-button size="mini" type="primary" @click="openDialog(row)">编辑</el-button>
|
|
|
+ <el-button size="mini" type="danger" @click="handleDelete(row)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="pagination-wrap" style="margin-top:16px;text-align:right;">
|
|
|
+ <el-pagination
|
|
|
+ @current-change="handlePageChange"
|
|
|
+ :current-page="page"
|
|
|
+ :page-size="pageSize"
|
|
|
+ :total="total"
|
|
|
+ layout="total, prev, pager, next"
|
|
|
+ background
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 编辑弹窗 -->
|
|
|
+ <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" :close-on-click-modal="false">
|
|
|
+ <el-form :model="form" :rules="rules" ref="bannerForm" label-width="100px">
|
|
|
+ <el-form-item label="标题" prop="title">
|
|
|
+ <el-input v-model="form.title" placeholder="展示标题,最多50字" maxlength="50" show-word-limit />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="类型" prop="bannerType">
|
|
|
+ <el-select v-model="form.bannerType" placeholder="选择类型" style="width:100%;">
|
|
|
+ <el-option label="活动" value="activity" />
|
|
|
+ <el-option label="商品" value="product" />
|
|
|
+ <el-option label="链接" value="link" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="form.bannerType !== 'link'" label="关联内容" prop="refId">
|
|
|
+ <el-input-number v-model="form.refId" :min="1" placeholder="活动/商品 ID" style="width:100%;" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="form.bannerType === 'link'" label="跳转链接" prop="linkUrl">
|
|
|
+ <el-input v-model="form.linkUrl" placeholder="完整 URL 或小程序页面路径" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="封面图">
|
|
|
+ <el-input v-model="form.coverImage" placeholder="图片 URL" />
|
|
|
+ <div v-if="form.coverImage" style="margin-top:8px;">
|
|
|
+ <el-image :src="form.coverImage" style="width:100px;height:60px;" fit="cover" />
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="排序">
|
|
|
+ <el-input-number v-model="form.sortOrder" :min="0" :max="999" style="width:100px;" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="投放时间">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.startTime"
|
|
|
+ type="datetime"
|
|
|
+ placeholder="开始时间"
|
|
|
+ format="yyyy-MM-dd HH:mm:ss"
|
|
|
+ value-format="timestamp"
|
|
|
+ style="margin-right:8px;"
|
|
|
+ />
|
|
|
+ <span>至</span>
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.endTime"
|
|
|
+ type="datetime"
|
|
|
+ placeholder="结束时间"
|
|
|
+ format="yyyy-MM-dd HH:mm:ss"
|
|
|
+ value-format="timestamp"
|
|
|
+ style="margin-left:8px;"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="启用">
|
|
|
+ <el-switch v-model="form.enabled" :active-value="1" :inactive-value="0" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getHomeBannerList, saveHomeBanner, deleteHomeBanner, batchDeleteHomeBanner, toggleHomeBanner, updateBannerSort } from '@/api/homeBanner'
|
|
|
+
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ list: [],
|
|
|
+ loading: false,
|
|
|
+ page: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 0,
|
|
|
+ filterStatus: '',
|
|
|
+ filterType: '',
|
|
|
+ sortCol: 'id',
|
|
|
+ sortDir: 'asc',
|
|
|
+ dialogVisible: false,
|
|
|
+ submitLoading: false,
|
|
|
+ editId: null,
|
|
|
+ form: {
|
|
|
+ id: null,
|
|
|
+ title: '',
|
|
|
+ bannerType: 'activity',
|
|
|
+ refId: null,
|
|
|
+ coverImage: '',
|
|
|
+ linkUrl: '',
|
|
|
+ sortOrder: 0,
|
|
|
+ enabled: 1,
|
|
|
+ startTime: null,
|
|
|
+ endTime: null
|
|
|
+ },
|
|
|
+ rules: {
|
|
|
+ title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
|
|
|
+ bannerType: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
|
|
+ refId: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请输入关联 ID',
|
|
|
+ trigger: 'blur',
|
|
|
+ validator: (rule, value, callback) => {
|
|
|
+ if (this.form.bannerType === 'link') {
|
|
|
+ callback()
|
|
|
+ } else if (!value || value <= 0) {
|
|
|
+ callback(new Error('请输入有效ID'))
|
|
|
+ } else {
|
|
|
+ callback()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ tableHeight() {
|
|
|
+ return window.innerHeight - 280
|
|
|
+ },
|
|
|
+ dialogTitle() {
|
|
|
+ return this.editId ? '编辑广告位' : '新增广告位'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.loadList()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ loadList() {
|
|
|
+ var self = this
|
|
|
+ self.loading = true
|
|
|
+ var params = {
|
|
|
+ current: self.page,
|
|
|
+ size: self.pageSize
|
|
|
+ }
|
|
|
+ if (self.filterStatus !== '') params.enabled = self.filterStatus
|
|
|
+ if (self.filterType !== '') params.bannerType = self.filterType
|
|
|
+ getHomeBannerList(params).then(function(res) {
|
|
|
+ self.loading = false
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ var d = res.data.data || {}
|
|
|
+ self.list = d.list || []
|
|
|
+ self.total = d.total || 0
|
|
|
+ } else if (res.data && res.data.list !== undefined) {
|
|
|
+ self.list = res.data.list || []
|
|
|
+ self.total = res.data.total || 0
|
|
|
+ }
|
|
|
+ }).catch(function() {
|
|
|
+ self.loading = false
|
|
|
+ self.$message.error('加载失败')
|
|
|
+ })
|
|
|
+ },
|
|
|
+ handlePageChange(newPage) {
|
|
|
+ this.page = newPage
|
|
|
+ this.loadList()
|
|
|
+ },
|
|
|
+ resetFilter() {
|
|
|
+ this.filterStatus = ''
|
|
|
+ this.filterType = ''
|
|
|
+ this.page = 1
|
|
|
+ this.loadList()
|
|
|
+ },
|
|
|
+ openDialog(row) {
|
|
|
+ if (row) {
|
|
|
+ this.editId = row.id
|
|
|
+ this.form = {
|
|
|
+ id: row.id,
|
|
|
+ title: row.title,
|
|
|
+ bannerType: row.bannerType,
|
|
|
+ refId: row.refId,
|
|
|
+ coverImage: row.coverImage || '',
|
|
|
+ linkUrl: row.linkUrl || '',
|
|
|
+ sortOrder: row.sortOrder || 0,
|
|
|
+ enabled: row.enabled != null ? row.enabled : 1,
|
|
|
+ startTime: row.startTime || null,
|
|
|
+ endTime: row.endTime || null
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.editId = null
|
|
|
+ this.form = {
|
|
|
+ id: null,
|
|
|
+ title: '',
|
|
|
+ bannerType: 'activity',
|
|
|
+ refId: null,
|
|
|
+ coverImage: '',
|
|
|
+ linkUrl: '',
|
|
|
+ sortOrder: 0,
|
|
|
+ enabled: 1,
|
|
|
+ startTime: null,
|
|
|
+ endTime: null
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.dialogVisible = true
|
|
|
+ this.$nextTick(function() {
|
|
|
+ if (this.$refs.bannerForm) this.$refs.bannerForm.clearValidate()
|
|
|
+ }.bind(this))
|
|
|
+ },
|
|
|
+ handleSubmit() {
|
|
|
+ var self = this
|
|
|
+ self.$refs.bannerForm.validate(function(valid) {
|
|
|
+ if (!valid) return
|
|
|
+ self.submitLoading = true
|
|
|
+ var saveData = Object.assign({}, self.form)
|
|
|
+ // 如果类型是link,清空refId;反之清空linkUrl
|
|
|
+ if (saveData.bannerType === 'link') {
|
|
|
+ saveData.refId = null
|
|
|
+ } else {
|
|
|
+ saveData.linkUrl = ''
|
|
|
+ }
|
|
|
+ saveHomeBanner(saveData).then(function(res) {
|
|
|
+ self.submitLoading = false
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ self.$message.success('保存成功')
|
|
|
+ self.dialogVisible = false
|
|
|
+ self.loadList()
|
|
|
+ } else {
|
|
|
+ self.$message.error(res.data && res.data.message ? res.data.message : '保存失败')
|
|
|
+ }
|
|
|
+ }).catch(function(e) {
|
|
|
+ self.submitLoading = false
|
|
|
+ self.$message.error('保存失败')
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ handleDelete(row) {
|
|
|
+ var self = this
|
|
|
+ this.$confirm('确定删除该广告位?', '提示', { type: 'warning' }).then(function() {
|
|
|
+ deleteHomeBanner({ id: row.id }).then(function(res) {
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ self.$message.success('删除成功')
|
|
|
+ self.loadList()
|
|
|
+ } else {
|
|
|
+ self.$message.error('删除失败')
|
|
|
+ }
|
|
|
+ }).catch(function() {
|
|
|
+ self.$message.error('删除失败')
|
|
|
+ })
|
|
|
+ }).catch(function() {})
|
|
|
+ },
|
|
|
+ onToggle(row) {
|
|
|
+ toggleHomeBanner({ id: row.id, enabled: row.enabled }).then(function(res) {
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ self.$message.success(row.enabled === 1 ? '已启用' : '已禁用')
|
|
|
+ }
|
|
|
+ }).catch(function() {})
|
|
|
+ },
|
|
|
+ onSortChange(row) {
|
|
|
+ updateBannerSort({ id: row.id, sortOrder: row.sortOrder }).catch(function() {})
|
|
|
+ },
|
|
|
+ typeLabel(type) {
|
|
|
+ var map = { activity: '活动', product: '商品', link: '链接' }
|
|
|
+ return map[type] || type || '-'
|
|
|
+ },
|
|
|
+ typeTag(type) {
|
|
|
+ var map = { activity: 'success', product: 'warning', link: 'info' }
|
|
|
+ return map[type] || ''
|
|
|
+ },
|
|
|
+ sortIndex(col) {
|
|
|
+ return this.sortCol === col ? (this.sortDir === 'asc' ? 1 : 2) : null
|
|
|
+ },
|
|
|
+ sortClass(col) {
|
|
|
+ return this.sortCol === col ? this.sortDir : ''
|
|
|
+ },
|
|
|
+ sortIcon(col) {
|
|
|
+ if (this.sortCol !== col) return '↕'
|
|
|
+ return this.sortDir === 'asc' ? '↑' : '↓'
|
|
|
+ },
|
|
|
+ onSortClick(col) {
|
|
|
+ if (this.sortCol === col) {
|
|
|
+ this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc'
|
|
|
+ } else {
|
|
|
+ this.sortCol = col
|
|
|
+ this.sortDir = 'asc'
|
|
|
+ }
|
|
|
+ this.loadList()
|
|
|
+ },
|
|
|
+ formatDate(ts) {
|
|
|
+ if (!ts) return '-'
|
|
|
+ var d = new Date(ts)
|
|
|
+ if (isNaN(d.getTime())) return '-'
|
|
|
+ var y = d.getFullYear()
|
|
|
+ var m = String(d.getMonth() + 1).padStart(2, '0')
|
|
|
+ var day = String(d.getDate()).padStart(2, '0')
|
|
|
+ var h = String(d.getHours()).padStart(2, '0')
|
|
|
+ var min = String(d.getMinutes()).padStart(2, '0')
|
|
|
+ var s = String(d.getSeconds()).padStart(2, '0')
|
|
|
+ return y + '-' + m + '-' + day + ' ' + h + ':' + min + ':' + s
|
|
|
+ },
|
|
|
+ formatTime(t) {
|
|
|
+ if (!t) return '-'
|
|
|
+ // 兼容字符串格式和 timestamp
|
|
|
+ var ts = t
|
|
|
+ if (typeof t === 'string' && t.indexOf('T') > -1) {
|
|
|
+ ts = new Date(t).getTime()
|
|
|
+ }
|
|
|
+ return this.formatDate(ts)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.admin-page-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.admin-page-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+.filter-bar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+.pagination-wrap {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+</style>
|