轮播图页面和对话框搭建
页面简单布局
<template>
<div id="banner">
<el-space direction="vertical" :size="20" style="width: 100%">
<h1>轮播图管理</h1>
<div style="text-align: right">
<el-button type="primary" @click="onAddButtonClick">
<el-icon><plus /></el-icon>
添加轮播图
</el-button>
</div>
</el-space>
<el-dialog v-model="bannerDialogVisible" title="添加修改轮播图" width="30%">
<el-form :model="form">
<el-form-item label="名称" >
<el-input v-model="form.name" autocomplete="off" />
</el-form-item>
<el-form-item label="图片" >
<div style="display: flex">
<el-input v-model="form.image_url" autocomplete="off" style="margin-right:10px"/>
<!-- 上传的地址 -->
<el-upload
:show-file-list="false"
:action="$http.server_host + '/cmsapi/banner/image/upload'"
:headers="{'Authorization': 'Bearer '+$auth.token}"
name="image"
accept="image/jpeg, image/png"
:on-success="onImageUploadSuccess"
:on-error="onImageUploadError"
>
<el-button type="primary" size="small">上传图片</el-button>
</el-upload>
</div>
</el-form-item>
<el-form-item label="跳转" >
<el-input v-model="form.link_url" autocomplete="off" />
</el-form-item>
<el-form-item label="优先级" >
<el-input v-model="form.priority" autocomplete="off" type="number" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="bannerDialogVisible = false">取消</el-button>
<el-button type="primary"> Confirm </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import { Plus } from "@element-plus/icons-vue";
export default {
name: "Banner",
components: {
Plus,
},
data() {
return {
bannerDialogVisible: false,
form: {
name: "",
image_url: "",
link_url: "",
priority: "",
},
};
},
mounted() {},
methods: {
onAddButtonClick() {
this.bannerDialogVisible = true;
},
onImageUploadSuccess(response){
if(response['code'] == 200){
var image_name = response['data']['image_url'];
var image_url = "/media/banner/" + image_name
this.form.image_url = image_url;
}
},
onImageUploadError(err, file, fileList){
console.log(err);
console.log(file);
console.log(fileList);
},
},
};
</script>
<style scoped>
.el-space {
display: block;
}
</style>
上传轮播图
新建目录存储轮播图
配置路径
配置图片前缀
上传文件校验
from flask_wtf.file import FileAllowed, FileSize
from wtforms import FileField
from apps.front.forms import BaseForm
class UploadImageForm(BaseForm):
image = FileField(validators=[FileAllowed(['jpg', 'jpeg', 'png'], message="图片格式不符合要求!"), FileSize(max_size=1024*1024*5, message="图片最大不能超过5M!")])
上传接口
'''
-*- coding: utf-8 -*-
@File : views.py
@author: Lx
@Time : 2023/07/17 15:08
'''
import time
from hashlib import md5
from flask import Blueprint, request, current_app, g
from apps.cmsapi.forms import UploadImageForm
from utils import restful
from flask_jwt_extended import jwt_required,get_jwt_identity
import os
bp = Blueprint("cmsapi", __name__, url_prefix="/cmsapi")
@bp.get('/')
@jwt_required()
def mytest():
# 这个identity是通过create_access_token传入的identity 获取的是User.id
identity = get_jwt_identity()
return restful.ok(message="成功!")
@bp.post("/banner/image/upload")
def upload_banner_image():
form = UploadImageForm(request.files)
if form.validate():
image = form.image.data
# 不要使用用户上传上来的文件名,否则容易被黑客攻击
filename = image.filename
# xxx.png,xx.jpeg
_, ext = os.path.splitext(filename)
filename = md5((g.user.email + str(time.time())).encode("utf-8")).hexdigest() + ext
image_path = os.path.join(current_app.config['BANNER_IMAGE_SAVE_PATH'], filename)
image.save(image_path)
return restful.ok(data={"image_url": filename})
else:
message = form.messages[0]
return restful.params_error(message=message)
此时会报跨域错误
pip install flask-cors
此时上传文件会出现问题因为g.user 没有这个属性
# 调用cmsapi之前先获取jwt 通过之后绑定user属性
@bp.before_request
@jwt_required()
def cmsapi_before_request():
if request.method == 'OPTIONS':
return
identity = get_jwt_identity()
user = UserModel.query.filter_by(id=identity).first()
if user:
setattr(g, "user", user)
#做个判断
if request.method == 'OPTIONS':
return
onImageUploadSuccess(response){
if(response['code'] == 200){
var image_name = response['data']['image_url'];
var image_url = "/media/banner/" + image_name
this.form.image_url = image_url;
}
},
onImageUploadError(err, file, fileList){
console.log(err);
console.log(file);
console.log(fileList);
},
添加轮播图
前端实现
轮播图添加
onDialogSubmitEvent(){
this.$refs["dialogForm"].validate((valid) => {
if(!valid){
ElMessage.error("请确保数据输入正确!");
return;
}
// 走添加操作
this.$http.addBanner(this.form).then((result) => {
let code = result['code'];
if(code === 200){
let banner = result['data'];
// 返回的轮播图添加到数组中
this.banners.push(banner);
ElMessage.success("轮播图添加成功!");
this.bannerDialogVisible = false;
}
}).catch(() => {
ElMessage.error("服务器开小差了,请稍后再试!");
this.bannerDialogVisible = false
})
})
},
轮播图列表展示
后端
@bp.get("/banner/list")
def banner_list():
banners = BannerModel.query.order_by(BannerModel.create_time.desc()).all()
# BannerModel
banner_dicts = [banner.to_dict() for banner in banners]
return restful.ok(data=banner_dicts)
轮播图列表前端实现
前端
http.js
getBannerList(){
const url = "/banner/list"
return this.http.get(url);
}
mounted() {
this.$http.getBannerList().then(res => {
if(res['code'] == 200){
let banners = res['data'];
this.banners = banners;
}else{
ElMessage.error(res['message']);
}
})
},
<el-table :data="banners" style="width: 100%">
<el-table-column prop="name" label="名称" width="250px" />
<el-table-column label="图片">
<template #default="scope">
<img :src="formatImageUrl(scope.row.image_url)" style="width: 200px;height: 60px;" />
</template>
</el-table-column>
<el-table-column label="跳转链接">
<template #default="scope">
<a :href="scope.row.link_url" target="_blank">{{scope.row.link_url}}</a>
</template>
</el-table-column>
<el-table-column prop="priority" label="优先级" width="100px" />
<el-table-column>
<template #default="scope">
<el-button type="primary" circle @click="onEditEvent(scope.$index)">
<el-icon><edit /></el-icon>
</el-button>
<el-button type="danger" circle @click="onDeleteEvent(scope.$index)">
<el-icon><delete /></el-icon>
</el-button>
</template>
</el-table-column>
</el-table>
添加轮播的时候上传本地需要拼接完整Url
如果是在输入框输入完整url则不需要拼接
<el-table-column label="图片">
<template #default="scope">
<img :src="formatImageUrl(scope.row.image_url)" style="width: 200px;height: 60px;" />
</template>
</el-table-column>
formatImageUrl(image_url){
if(image_url.startsWith("http")){
return image_url;
}else{
return this.$http.server_host + image_url;
}
},
删除轮播图前后端实现
deleteBanner(banner_id){
const url = "/banner/delete"
return this._post(url, {"id": banner_id})
}
onDeleteEvent(index){
this.deleteingIndex = index;
this.deleteDialogVisible = true;
},
onConfirmDeleteEvent(){
let banner = this.banners[this.deleteingIndex];
this.$http.deleteBanner(banner.id).then(res => {
let result = res['data'];
let code = result['code'];
if(code === 200){
this.deleteDialogVisible = false;
this.banners.splice(this.deleteingIndex, 1);
ElMessage.success("轮播图删除成功!");
}
})
}
@bp.post("/banner/delete")
def delete_banner():
banner_id = request.form.get("id")
if not banner_id:
return restful.params_error(message="没有传入id!")
try:
banner_model = BannerModel.query.get(banner_id)
except Exception as e:
return restful.params_error(message="此轮播图不存在!")
db.session.delete(banner_model)
db.session.commit()
return restful.ok()
编辑轮播图
forms
class EditBannerForm(AddBannerForm):
id = IntegerField(validators=[InputRequired(message="请输入轮播图的id!")])
@bp.post("/banner/edit")
def edit_banner():
form = EditBannerForm(request.form)
if form.validate():
banner_id = form.id.data
try:
banner_model = BannerModel.query.get(banner_id)
except Exception as e:
return restful.params_error(message="轮播图不存在!")
name = form.name.data
image_url = form.image_url.data
link_url = form.link_url.data
priority = form.priority.data
banner_model.name = name
banner_model.image_url = image_url
banner_model.link_url = link_url
banner_model.priority = priority
db.session.commit()
return restful.ok(data=banner_model.to_dict())
else:
return restful.params_error(message=form.messages[0])
前端页面点击编辑按钮,数据回显
// 如果有值说明是编辑轮播图
initForm(banner){
if(banner){
// 这里加轮播图id 验证是添加轮播图还是编辑
this.form.id = banner.id;
this.form.name = banner.name;
this.form.image_url = banner.image_url;
this.form.link_url = banner.link_url;
this.form.priority = banner.priority;
}else{
// 如果没有则说明是添加轮播图,则把表单清空
this.form = {
name: "",
image_url: "",
link_url: "",
priority: 0
}
}
},
onEditEvent(index){
this.editingIndex = index;
let banner = this.banners[index];
this.initForm(banner);
this.bannerDialogVisible = true;
},
添加轮播图做修改
onDialogSubmitEvent(){
this.$refs["dialogForm"].validate((valid) => {
if(!valid){
ElMessage.error("请确保数据输入正确!");
return;
}
if(this.form.id){
// 走编辑操作
this.$http.editBanner(this.form).then((result) => {
let code = result['code'];
if(code === 200){
let banner = result['data'];
// this.banners.push(banner);
// 需要使用splice才能完成替换操作,不能直接通过下标修改元素
// 直接通过下标修改元素,页面上不会自动改变
this.banners.splice(this.editingIndex, 1, banner);
ElMessage.success("轮播图编辑成功!");
this.bannerDialogVisible = false;
}
})
}else{
// 走添加操作
this.$http.addBanner(this.form).then((result) => {
let code = result['code'];
if(code === 200){
let banner = result['data'];
this.banners.push(banner);
ElMessage.success("轮播图添加成功!");
this.bannerDialogVisible = false;
}
}).catch(() => {
ElMessage.error("服务器开小差了,请稍后再试!");
this.bannerDialogVisible = false
})
}
})
},
http.js
editBanner(data){
const url = "/banner/edit"
return this._post(url, data);
}
前台加载真实轮播图数据
@bp.route('/')
def index():
sort = request.args.get("st", type=int, default=1)
board_id = request.args.get("bd", type=int, default=None)
boards = BoardModel.query.order_by(BoardModel.priority.desc()).all()
post_query = None
if sort == 1:
post_query = PostModel.query.order_by(PostModel.create_time.desc())
else:
# 根据评论数量进行排序
post_query = db.session.query(PostModel).outerjoin(CommentModel).group_by(PostModel.id).order_by(func.count(CommentModel.id).desc(), PostModel.create_time.desc())
page = request.args.get(get_page_parameter(), type=int, default=1)
# 1:0-9
# 2:10-19
start = (page - 1) * current_app.config['PER_PAGE_COUNT']
end = start + current_app.config['PER_PAGE_COUNT']
if board_id:
# "mapped class CommentModel->comment" has no property "board_id"
# CommentModel中寻找board_id,然后进行过滤
# post_query = post_query.filter_by(board_id=board_id)
post_query = post_query.filter(PostModel.board_id==board_id)
total = post_query.count()
posts = post_query.slice(start, end)
pagination = Pagination(bs_version=3, page=page, total=total, prev_label="上一页")
banners = BannerModel.query.order_by(BannerModel.priority.desc()).all()
context = {
"boards": boards,
"posts": posts,
"pagination": pagination,
"st": sort,
"bd": board_id,
"banners": banners
}
return render_template("front/index.html", **context)
前端轮播图展示功能
<template>
<div id="banner">
<el-space direction="vertical" :size="20" style="width: 100%">
<h1>轮播图管理</h1>
<div style="text-align: right">
<el-button type="primary" @click="onAddButtonClick">
<el-icon><plus /></el-icon>
添加轮播图
</el-button>
</div>
<el-table :data="banners" style="width: 100%">
<el-table-column prop="name" label="名称" width="250px" />
<el-table-column label="图片">
<template #default="scope">
<img :src="formatImageUrl(scope.row.image_url)" style="width: 200px;height: 60px;" />
</template>
</el-table-column>
<el-table-column label="跳转链接">
<template #default="scope">
<a :href="scope.row.link_url" target="_blank">{{scope.row.link_url}}</a>
</template>
</el-table-column>
<el-table-column prop="priority" label="优先级" width="100px" />
<el-table-column>
<template #default="scope">
<el-button type="primary" circle @click="onEditEvent(scope.$index)">
<el-icon><edit /></el-icon>
</el-button>
<el-button type="danger" circle @click="onDeleteEvent(scope.$index)">
<el-icon><delete /></el-icon>
</el-button>
</template>
</el-table-column>
</el-table>
</el-space>
<el-dialog v-model="bannerDialogVisible" title="添加/修改轮播图" width="30%">
<el-form :model="form" :rules="rules" ref="dialogForm">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="图片" prop="image_url">
<div style="display: flex;">
<el-input v-model="form.image_url" autocomplete="off" style="margin-right:10px;"></el-input>
<el-upload
:action="$http.server_host+'/cmsapi/banner/image/upload'"
name="image"
:headers="{'Authorization': 'Bearer '+$auth.token}"
:show-file-list="false"
accept="image/jpeg, image/png"
:on-success="onImageUploadSuccess"
:on-error="onImageUploadError"
>
<el-button size="small" type="primary">上传图片</el-button>
</el-upload>
</div>
</el-form-item>
<el-form-item label="跳转" prop="link_url">
<el-input v-model="form.link_url" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="优先级" prop="priority">
<el-input v-model="form.priority" autocomplete="off" type="number"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="bannerDialogVisible = false">取消</el-button>
<el-button type="primary" @click="onDialogSubmitEvent">确认</el-button>
</span>
</template>
</el-dialog>
<!-- 删除轮播图确认对话框 -->
<el-dialog
v-model="deleteDialogVisible"
title="提示"
width="30%"
>
<span>您确定要删除这个轮播图吗?</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="deleteDialogVisible = false">取消</el-button>
<el-button type="primary" @click="onConfirmDeleteEvent">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import { Plus, Edit, Delete } from "@element-plus/icons";
import {ElMessage} from "element-plus";
export default {
name: "Banner",
components: {
Plus,
Edit,
Delete
},
data(){
return {
bannerDialogVisible: false,
deleteDialogVisible: false,
deleteingIndex: 0,
editingIndex: 0,
banners: [],
form: {
name: "",
image_url: "",
link_url: "",
priority: 0
},
rules: {
name: [{required: true,message: '请输入轮播图名称!',trigger: 'blur'}],
image_url: [{required: true,message: '请上传轮播图!',trigger: 'blur'}],
link_url: [{required: true,message: '请输入轮播图跳转链接!',trigger: 'blur'}],
priority: [
{required: true,message: '请输入轮播图优先级!',trigger: 'blur'}
],
}
}
},
mounted() {
this.$http.getBannerList().then(res => {
if(res['code'] == 200){
let banners = res['data'];
this.banners = banners;
}else{
ElMessage.error(res['message']);
}
})
},
methods: {
formatImageUrl(image_url){
if(image_url.startsWith("http")){
return image_url;
}else{
return this.$http.server_host + image_url;
}
},
initForm(banner){
if(banner){
this.form.id = banner.id;
this.form.name = banner.name;
this.form.image_url = banner.image_url;
this.form.link_url = banner.link_url;
this.form.priority = banner.priority;
}else{
this.form = {
name: "",
image_url: "",
link_url: "",
priority: 0
}
}
},
onAddButtonClick(){
this.initForm();
this.bannerDialogVisible = true;
},
onImageUploadSuccess(response){
if(response['code'] == 200){
var image_name = response['data']['image_url'];
var image_url = "/media/banner/" + image_name
this.form.image_url = image_url;
}
},
onImageUploadError(err, file, fileList){
console.log(err);
console.log(file);
console.log(fileList);
},
onDialogSubmitEvent(){
this.$refs["dialogForm"].validate((valid) => {
if(!valid){
ElMessage.error("请确保数据输入正确!");
return;
}
if(this.form.id){
// 走编辑操作
this.$http.editBanner(this.form).then((result) => {
let code = result['code'];
if(code === 200){
let banner = result['data'];
// this.banners.push(banner);
// 需要使用splice才能完成替换操作,不能直接通过下标修改元素
// 直接通过下标修改元素,页面上不会自动改变
this.banners.splice(this.editingIndex, 1, banner);
ElMessage.success("轮播图编辑成功!");
this.bannerDialogVisible = false;
}
})
}else{
// 走添加操作
this.$http.addBanner(this.form).then((result) => {
let code = result['code'];
if(code === 200){
let banner = result['data'];
this.banners.push(banner);
ElMessage.success("轮播图添加成功!");
this.bannerDialogVisible = false;
}
}).catch(() => {
ElMessage.error("服务器开小差了,请稍后再试!");
this.bannerDialogVisible = false
})
}
})
},
// 编辑轮播图
onEditEvent(index){
this.editingIndex = index;
let banner = this.banners[index];
this.initForm(banner);
this.bannerDialogVisible = true;
},
// 每次点击添加轮播图就初始化表单
onAddButtonClick(){
this.initForm();
this.bannerDialogVisible = true;
},
onDeleteEvent(index){
this.deleteingIndex = index;
this.deleteDialogVisible = true;
},
onConfirmDeleteEvent(){
let banner = this.banners[this.deleteingIndex];
this.$http.deleteBanner(banner.id).then(res => {
let result = res['data'];
let code = result['code'];
if(code === 200){
this.deleteDialogVisible = false;
this.banners.splice(this.deleteingIndex, 1);
ElMessage.success("轮播图删除成功!");
}
})
}
}
};
</script>
<style scoped>
.el-space {
display: block;
}
</style>