轮播图页面和对话框搭建
页面简单布局
<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_namethis.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 FileFieldfrom apps.front.forms import BaseFormclass 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 md5from flask import Blueprint, request, current_app, gfrom 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.ididentity = 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() + extimage_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':returnidentity = 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_namethis.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()# BannerModelbanner_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.datatry:banner_model = BannerModel.query.get(banner_id)except Exception as e:return restful.params_error(message="轮播图不存在!")name = form.name.dataimage_url = form.image_url.datalink_url = form.link_url.datapriority = form.priority.databanner_model.name = namebanner_model.image_url = image_urlbanner_model.link_url = link_urlbanner_model.priority = prioritydb.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 = Noneif 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-19start = (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-dialogv-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_namethis.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>