Vue3和flask的管理员功能
管理员的增删改查
前端路由守卫
首先需要在router/index.js里写管理员页面的路由守卫:
{
path: '/admin',
name: 'admin',
component: () => import('@/views/Admin/admin_index.vue'),
// 添加路由守卫
beforeEnter: (to, from, next) => {
const privilege = localStorage.getItem('privilege')
if (privilege === '100') { //检测privilege为100的才是管理员
next()
} else {
next('/') // 如果没有管理员权限,跳转到登录页面
}
}
},
然后我们整个管理员页面采用组件化的思维搭建,如下图:
--Admin
----components
--------admin_filtdata.vue
--------admin_traindata.vue
--------admin_user.vue
----admin_index.vue
将components里的三个组件全部加载到管理员的默认页面admin_index.vue里
<template>
<div>
<el-row class="tac">
<el-col :span="3">
<h5 class="mb-2">管理选项</h5>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
>
<!-- 1、用户管理 -->
<el-sub-menu index="1">
<template #title>
<el-icon><User /></el-icon>
<span>用户管理</span>
</template>
<el-menu-item index="1-1" @click="handleMenuItemClick('adminUser')">
<el-icon><Edit /></el-icon>
增删改查
</el-menu-item>
</el-sub-menu>
<!-- 2、数据处理 -->
<el-sub-menu index="2">
<template #title>
<el-icon><Files /></el-icon>
<span>数据处理</span>
</template>
<el-menu-item index="2-1" @click="handleMenuItemClick('adminFiltData')">
<el-icon><el-icon-download /></el-icon>
清洗数据
</el-menu-item>
</el-sub-menu>
<!-- 3、数据导入 -->
<el-sub-menu index="3">
<template #title>
<el-icon><UploadFilled /></el-icon>
<span>数据导入</span>
</template>
<el-menu-item index="3-1" @click="handleMenuItemClick('adminTrainData')">
<el-icon><el-icon-upload/></el-icon>
模型更新
</el-menu-item>
</el-sub-menu>
<el-col :span="21">
<div class="main-content">
<div class="page-header">
<h1>欢迎管理员</h1>
</div>
<div class="page-content">
<AdminUser v-if="activeTab === 'users'" />
<AdminTrainData v-if="activeTab === 'importData'" />
<AdminFiltData v-if="activeTab==='filtData'" />
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import AdminUser from "@/views/Admin/components/admin_user.vue";
import AdminFiltData from "@/views/Admin/components/admin_filtdata.vue";
import AdminTrainData from "@/views/Admin/components/admin_traindata.vue";
export default {
data() {
return {
activeTab: 'users', // 默认显示用户管理组件
};
},
components: {
AdminUser,
AdminFiltData,
AdminTrainData,
},
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
},
handleMenuItemClick(which) {
// 根据点击的菜单项加载对应的组件
if (which === 'adminUser') {
this.activeTab = 'users';
}
else if(which === 'adminFiltData' ){
this.activeTab = 'filtData';
}
else if (which === 'adminTrainData') {
this.activeTab = 'importData';
}
},
},
}
</script>
请注意,此处使用了element-plus组件代替style的效果,我采用的是vite[按需引入]element-plus所以可以直接使用,请根据自己的实际情况引入
后端蓝图接口
开一个蓝图,下面放所有管理员功能需要的接口
from flask import Blueprint
bp_admin = Blueprint('admin_bp', __name__)
1、获取用户数据、查找用户
分页查询种类:
真分页
每次翻页从数据库里查询数据
优点:不容易造成内存溢出
缺点:实现复杂、性能相对低
接下来是真分页的代码(真分页是我做另一个考试项目的时候做的):
这里附带了[模糊匹配]的题目查找功能,并且我的axios实例附带了response响应拦截器,所以不需要.catch(error => {…})
前端代码
<template>
<!-- 查找题目input框 省略-->
<!-- data属性绑定了要显示的用户数据 -->
<el-table :data="currentPageQuestions" >
<el-table-column prop="id" label="题目ID"></el-table-column>
<el-table-column prop="question" label="题目"></el-table-column>
<el-table-column prop="A" label="选项A"></el-table-column>
<el-table-column prop="B" label="选项B"></el-table-column>
<el-table-column prop="C" label="选项C"></el-table-column>
<el-table-column prop="D" label="选项D"></el-table-column>
<el-table-column prop="answer" label="答案"></el-table-column>
<el-table-column prop="chosen" label="是否被选用"></el-table-column>
<el-table-column label="操作">
<!-- #default 指令来定义作用域插槽(允许在模板中访问父组件传给子组件的数据),再用 scope.row 来访问当前行的数据对象-->
<template #default="scope">
<el-button type="primary" size="small" @click="confirmDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页码列表 -->
<el-pagination background
@current-change="handlePageChange($event)"
:current-page="currentPage"
:page-size="pageSize"
:total="total"
></el-pagination>
</template>
<script>
import axios from '@/utils/http.js';
export default{
data() {
return{
searchform:{ //用于查找题目
question:''
},
currentPage: 1, //当前页码
pageSize: 10, //每页条目数
total: 0, //存储后端返回的题目总数
currentPageQuestions: [] , // 当前用于展示的题目数据!
}
},
methods:{
//1、获取单选题库数据(真分页)
getSingleQuestion(){
this.pageSize = 10; //把每页显示条目数固定为10
axios.get('/admin/get_single_questions',{
params:{
page: this.currentPage, //查询时起点为第当前页码
per_page: this.pageSize //查询时每页的条目数(默认为10)
}
}).then(response =>{
this.currentPageQuestions = response.data.questions;
this.total = response.data.total_questions;
})
},
// 分页码列表API(监听页码变化,更新当前页码并获取对应的用户数据)
handlePageChange(newPage) {
this.currentPage = newPage;
this.getSingleQuestion();
},
//2、查找题目
searchSingleQ(){
this.pageSize = 100; //把每页显示条目数拉大
axios.post('/admin/search_single_question',{
question:this.searchform.question,
})
.then(response => {
console.log(response.data.message);
//将找到的所有用户显示(赋值给currentPageUsers列表)
if(response.data.status === 'success'){
this.currentPageQuestions = response.data.questions;
this.total = response.data.total_questions;
this.$message.success('查找单选题成功');
}
})
},
},
//钩子函数(真分页)
created(){
this.getSingleQuestion();
},
}
</script>
后端代码
from flask import Blueprint,jsonify,session,request
from extendsions import db
from function import verify_token
from function.decorators import check_admin_privilege
from model.single_qbank import Single_qbank
Admin_single_qbank_bp = Blueprint("Admin_single_qbank",__name__)
#检查管理员的前端请求的合法性
def test_admin_token():
# 解码请求头里的token检验session里的uid
if (verify_token.test(request)):
print("token合法正确! verify right!")
return True
else:
print("登录凭证失效,请重新登录!")
return False
#admin的蓝图url前面全部带上了'/admin'
#功能1、分页获取单选题库(真分页)
@Admin_single_qbank_bp.route('/get_single_questions',methods=['GET'])
@check_admin_privilege # 装饰器检查管理员权限
def admin_get_single_questions():
# 调用全局函数,检查管理员的前端请求token凭证
if (test_admin_token() == False):
response = {
'status': 'error',
'message': '登录凭证失效,请重新登录'
}
return jsonify(response), 403
try:
current_page = int(request.args.get('page')) #当前页码(记得转换为int类型,paginate参数不接受str)
per_page = int(request.args.get('per_page')) #一页多少条数
single_questions = Single_qbank.query.all()
total_questions = len(single_questions) #题库总数
pagination = Single_qbank.query.paginate(page=current_page, per_page=per_page, error_out=False) #error_out页数超出范围会抛出异常,设置为 False 则不会抛出异常
questions = pagination.items #分页出来的内容放入questions传回前端
#定义一个response
response = {
'status': 'success',
'message': '成功获取单选题库',
'questions': [],
'total_questions': total_questions
}
#把questions里的值都放入response的列表里
for question in questions:
response['questions'].append({
'id': question.id,
'question': question.question,
'A': question.A,
'B': question.B,
'C': question.C,
'D': question.D,
'answer': question.answer,
'chosen': question.chosen
})
return jsonify(response),200
except Exception as e:
response = {
'status': 'error',
'message': f'获取单选题库失败:{str(e)}'
}
return jsonify(response), 500
#功能2、查找单选题(模糊查找)
@Admin_single_qbank_bp.route('/search_single_question',methods=['POST'])
@check_admin_privilege # 装饰器检查管理员权限
def admin_search_single_question():
# 调用全局函数,检查管理员的前端请求token凭证
if (test_admin_token() == False):
response = {
'status': 'error',
'message': '登录凭证失效,请重新登录'
}
return jsonify(response), 403
try:
data = request.get_json()
question = data.get('question')
#使用like()方法进行模糊查找,% 表示通配符,匹配任意字符,返回所有查询结果
results = Single_qbank.query.filter(Single_qbank.question.like(f'%{question}%')).all()
#查询结果总数量
counts = Single_qbank.query.filter(Single_qbank.question.like(f'%{question}%')).count()
# 定义一个response
response = {
'status': 'success',
'message': '成功获取单选题库',
'questions': [],
'total_questions': counts
}
#把results里的值都放入response的列表里
for result in results:
response['questions'].append({
'id': result.id,
'question': result.question,
'A': result.A,
'B': result.B,
'C': result.C,
'D': result.D,
'answer': result.answer,
'chosen': result.chosen
})
return jsonify(response), 200
except Exception as e:
response = {
'status': 'error',
'message': f'没有找到题目:{str(e)}'
}
return jsonify(response), 404
假分页
一次性查询所有数据存入内存,翻页时从内存里找
优点:实现简单、性能相对高
缺点:容易造成内存溢出
接下来是假分页的代码:
前端代码
显示数据:直接从后端将查询到的所有用户数据存入users列表里,然后翻页的时候查找对应页码的放入currentPageUsers用于在el-table里显示
查找用户:向后端传一个用户名字,然后找到这个用户直接传回前端赋值给currentPageUsers显示
<template>
<h2 class="title" >管理员页面</h2>
<el-form :model="searchform" ref="form" label-width="80px">
<el-form-item >
<el-input v-model="searchform.username" placeholder="输入用户名进行查找"></el-input>
<el-button type="primary" @click="searchUser" >查询用户</el-button>
<el-button type="primary" @click="addUser" >添加用户</el-button>
</el-form-item>
</el-form>
<!-- data属性绑定了要显示的用户数据 -->
<el-table :data="currentPageUsers" >
<el-table-column prop="uid" label="用户ID"></el-table-column>
<el-table-column prop="username" label="用户名"></el-table-column>
<el-table-column prop="email" label="用户邮箱"></el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.row)">修改</el-button>
<el-button type="primary" size="small" @click="confirmDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页码列表 -->
<el-pagination background
@current-change="handlePageChange($event)"
:current-page="currentPage"
:page-size="pageSize"
:total="total"
></el-pagination>
</template>
<script>
import axios from '@/main.js';
export default {
data() {
return {
//输入用户名进行查找
searchform: {
username: ''
},
currentPage: 1, //当前页码
pageSize: 10, //每页页数
users: [], //存储后端返回的全部用户数据
total: 0, //存储后端返回的用户总数
currentPageUsers: [] , // 当前用于展示的用户数据!
}
},
methods: {
//1、获取用户数据
getUser(){
axios.get('/admin/user_get',{
params:{ //(在假分页方式中舍去,不需要用到)
page: 1, //查询时起点为第1页
per_page: 10 //查询时每页的条目数
}
}).then(res =>{
this.users = res.data.users; //后端查询的所有用户数据列表
this.total = res.data.total_users; //后端一共查询到多少条数据
// 更新当前页码对应的用户数据
this.updateCurrentPageUsers();
}).catch(err=>{
console.log(err);
});
},
// 监听页码变化,更新当前页码并获取对应的用户数据
handlePageChange(newPage) {
this.currentPage = newPage;
this.updateCurrentPageUsers();
},
// 根据当前页码获取对应的用户数据
updateCurrentPageUsers() {
const startIndex = (this.currentPage - 1) * this.pageSize;
let endIndex = startIndex + this.pageSize;
// 判断 endIndex 是否超出 users 数组的长度,如果超出则将其设置为 users 数组的长度
if (endIndex > this.users.length) {
endIndex = this.users.length;
}
this.currentPageUsers = this.users.slice(startIndex, endIndex);
},
//2、查找用户
searchUser(){
axios.post('/admin/user_search',{
username:this.searchform.username
})
.then(response => {
console.log(response.data.message);
//将找到的用户显示(赋值给currentPageUsers列表)
if(response.data.status === 'success'){
this.currentPageUsers = [response.data.search_user]
this.$message.success('查找用户成功');
}
})
.catch(error => {
this.$message.error('未找到该用户');
console.log(error);
})
},
//钩子函数,页面加载时触发,查询所有用户数据(假分页)
created(){
this.getUser();
}
}
</script>
后端代码
做一个管理员权限检查装饰器(使用session会话实现)
(其实也可以使用JWT技术,每次去解码前端请求头中的token检查权限)
def check_admin_privilege(func):
@wraps(func)
def wrapper(*args, **kwargs):
if session.get('privilege') == 100:
return func(*args, **kwargs)
else:
return jsonify({
'status': 'fail',
'message': '您不是管理员,没有权限进行此操作'
}),403
return wrapper
#蓝图开接口
@bp_admin.route('/admin/user_get', methods=['GET'])
# 使用装饰器检查是否有管理员权限
@check_admin_privilege
# 管理员功能1:通过查询数据库来获取用户表中的数据,并将其转换成字典形式后返回给前端。
def get_users():
users = User.query.all() #查询所有用户数据,传给前端
total_users = len(users) #一共有多少条数据
user_list = [] #列表,用户存储全部用户数据
for user in users:
user_data = {
'uid': user.uid,
'username': user.username,
'email': user.email
}
user_list.append(user_data)
response = {
'status': 'success',
'message': '获取成功',
'users' : user_list,
'total_users':total_users
}
return jsonify(response),200
# 管理员功能2:查找用户
#省略...
2、增加、修改、删除用户
前端
有了前面的用户显示、查询的铺垫,后面三个功能就好做多了(三个功能大同小异)
我们这里增加、修改都采用弹窗:
<template>
<!-- 添加用户弹窗 -->
<el-dialog v-model="addDialogVisible" title="添加用户">
<el-form :model="addForm" ref="addForm" :rules="addFormRules" label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input v-model="addForm.username"></el-input>
</el-form-item>
<el-form-item label="用户密码" prop="password">
<el-input v-model="addForm.password"></el-input>
</el-form-item>
<el-form-item label="用户邮箱" prop="email">
<el-input v-model="addForm.email"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="addDialogVisible = false">取消</el-button>
<el-button type="primary" @click="addUserSubmit">确定</el-button>
</span>
</template>
</el-dialog>
<!-- 修改用户弹窗 -->
//修改用户弹窗 省略....
</template>
<script>
import axios from '@/main.js';
export default {
data() {
return {
addDialogVisible: false,
editDialogVisible: false,
addForm:{
username: '',
password: '',
email: ''
},
editForm: {
uid: '',
username: '',
password: '',
email: ''
},
addFormRules: {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入用户密码', trigger: 'blur' }],
email: [{ required: true, message: '请输入用户邮箱', trigger: 'blur' }]
},
editFormRules: {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
email: [{ required: true, message: '请输入用户邮箱', trigger: 'blur' }]
}
}
},
methods: {
//3、添加用户数据
addUser(){
this.addDialogVisible = true;
},
addUserSubmit(){
//valid表示满足表单rules以后才能执行
this.$refs.addForm.validate(valid =>{
if(valid){
axios.post('/admin/user_add',{
username:this.addForm.username,
password:this.addForm.password,
email:this.addForm.email
}).then(res => {
this.$message.success('添加用户成功');
this.getUser();
this.addDialogVisible = false;
}).catch(err => {
console.log(err);
});
} else{
return false;
}
});
},
//修改用户省略....
}
}
</script>
而删除用户则直接采用函数弹窗的形式:
//5、删除用户数据
confirmDelete(row) {
this.$confirm(`确定要删除用户${row.username}吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
axios.delete('admin/user_delete',{
//params 对象表示请求的查询参数,将要删除的用户的 uid 作为参数传递给后端。
params: {
uid: row.uid
}
}
).then(res => {
this.$message.success('删除用户成功');
this.getUser();
}).catch(err => {
console.log(err);
});
}).catch(() => {});
},
后端
增加、修改、删除,会一个就会全部
# 管理员功能4:修改用户
@bp_admin.route('/admin/user_update',methods=['PUT'])
@check_admin_privilege
def update_user():
data = request.get_json()
user_id = data.get('uid')
user = User.query.filter_by(uid = user_id).first()
if 'username' in data:
user.username = data.get('username')
if 'password' in data:
user.password = data.get('password')
if 'email' in data:
user.email = data.get('email')
try:
db.session.commit()
except Exception:
print("update error")
return '',400
return jsonify(user.to_dict()),200
请求方法(GET,POST,PUT,DELETE)
请特别注意:
post,put是有请求体的
前端可以写:
axios.put('admin/user_update', {
//内容
uid:this.uid,
}
后端的获取数据逻辑为:
data = request.get_json()
user_id = data.get('uid')
但是get,delete一般是没有请求体的,后端无法读request的body
前端逻辑是这样的:(放在查询参数params里)
axios.delete('admin/user_delete',{
//params 对象表示请求的查询参数,将要删除的用户的 uid 作为参数传递给后端。
params: {
uid: row.uid
}
}
后端接收逻辑为:
#从request的params里取数据
user_id = request.args.get('uid')