全栈开发一条龙——前端篇
第一篇:框架确定、ide设置与项目创建
第二篇:介绍项目文件意义、组件结构与导入以及setup的引入。
第三篇:setup语法,设置响应式数据。
第四篇:数据绑定、计算属性和watch监视
第五篇 : 组件间通信及知识补充
第六篇:生命周期和自定义hooks
第七篇:路由
第八篇:传参
第九篇:插槽,常用api和全局api。
全栈开发一条龙——全栈篇
第一篇:初识Flask&MySQL实现前后端通信
第二篇: sql操作、发送http请求和邮件发送
第三篇:全栈实现发送验证码注册账号
第四篇:图片验证码及知识补充
全栈开发一条龙——实战篇
第一篇:项目建立与login页面
本章我们进入后台管理员视图的开发
文章目录
- 一、后端
- set_app
- main
- sql_ex
- sql_ex_blueprint
- 二、前端
- addquestion
- editquestion
- questionlist
- 三、总结与预告
一、后端
在后端,我们会遇到循环引用的问题:我们在进行后端蓝图的编写的时候,一定会要用到app(flask对象)来操作数据库,但是app我们之前放在了main.py中,这就很吊诡了,我们启动main服务,就要调用蓝图程序,而蓝图程序又要调用main中初始化的app来进行上下文操作,这就造成了循环引用(死锁),所以我们要改进我们的后端代码结构。
set_app
我们使用set_app把建立app对象的过程独立出来
from flask import Flask,jsonify,request
#jsonify将py数据转换为json数据,传给前端接口
from flask_cors import CORS
#跨域,因为有浏览器的同源策略,不同协议、域名、端口不能通信,我们要用cors来通信
from sqlalchemy import textfrom flask.views import MethodView#建立对象
app = Flask(__name__)#转码,老外跟我们用的不一样,不改会乱码,如果有中文你要加上
app.config["JSON_AS_ASCII"] = False
main
接下来修改一下main,使得在main里我们正确的初始化app
from flask import Flask,jsonify,request
#jsonify将py数据转换为json数据,传给前端接口
from flask_cors import CORS
#跨域,因为有浏览器的同源策略,不同协议、域名、端口不能通信,我们要用cors来通信
from sqlalchemy import textfrom flask.views import MethodViewfrom set_app import appfrom login.login_blueprint import login
app.register_blueprint(login)from dataset_info import *
#导入数据库
from data_set import db
# 配置数据库
URI = "mysql://" + mysql_account + ":" + mysql_password + "@" + mysql_host + ":" + mysql_port + "/" + mysql_data_col+"?charset=utf8"
app.config["SQLALCHEMY_DATABASE_URI"] = URI
app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"] = True
#初始化操作
db.init_app(app)#配置跨域,*表示所有人
CORS(app,cors_allowed_orgins = "*")import sql_ex_blueprint
app.register_blueprint(sql_ex_blueprint.sqlex)if __name__ == "__main__":#调试模式 这样每次修改代码不用每次重启服务app.run(debug=True, host = "0.0.0.0",port = 5000)
sql_ex
我们专门建立一个操作数据库的文件,防止我们的服务蓝图文件内容过多无法维护。
首先,我们要先建立一个数据库对象
class sql_ex_object(db.Model):__tablename__ = 'questions'questions = db.Column(db.String(300))choice_a = db.Column(db.String(80))choice_b = db.Column(db.String(80))choice_c = db.Column(db.String(80))choice_d = db.Column(db.String(80))point_a = db.Column(db.Integer)point_b = db.Column(db.Integer)point_c = db.Column(db.Integer)point_d = db.Column(db.Integer)id = db.Column(db.String(45),primary_key = True)
tablename是跟数据库中的column名一样,这里确保要和数据库中的一一对应。
class sql_ex():def add(self,question,a,b,c,d,ap,bp,cp,dp,id):with app.app_context():question_add = sql_ex_object()question_add.questions = questionquestion_add.choice_a = aquestion_add.choice_b = bquestion_add.choice_c = cquestion_add.choice_d = dquestion_add.point_a = apquestion_add.point_b = bpquestion_add.point_c = cpquestion_add.point_d = dpquestion_add.id = idtry: db.session.add(question_add)db.session.commit()db.session.close()print("\n\n\ncg\n\n\n")return "添加成功"except:return "题目已存在!"def delete(self,id):with app.app_context():question_delete = sql_ex_object.query.filter(sql_ex_object.id==id).first()try:db.session.delete(question_delete)db.session.commit()db.session.close()return "删除成功"except:return "删除失败"def search(self):with app.app_context():raw_list = db.session.execute( text("select * from questions") ).fetchall()list = list_row2list_dic(raw_list)print(list)
这里的内容我们基本之前的内容都讲过,就是我们题目的增删改查的工具箱,唯一的问题是,请不要忘记在每个的开头加上with app.app_content()这是在声明我以下的代码是在flask的app环境下运行的,这样才能正确的使用flask的数据库。
完整代码如下
from set_app import app
from data_set import db
from flask import current_app
from sqlalchemy import text
with app.app_context():
# ctx = app.app_context()
# ctx.push()def list_row2list_dic(list_row): dic_temp = {}list_dic = []for x in list_row:listda = []listidx= []for dx in x: listda.append(dx)xx = x._key_to_index for idx in xx:listidx.append(idx)dic_temp=dict(zip(listidx,listda))list_dic.append(dic_temp)return list_dicclass sql_ex_object(db.Model):__tablename__ = 'questions'questions = db.Column(db.String(300))choice_a = db.Column(db.String(80))choice_b = db.Column(db.String(80))choice_c = db.Column(db.String(80))choice_d = db.Column(db.String(80))point_a = db.Column(db.Integer)point_b = db.Column(db.Integer)point_c = db.Column(db.Integer)point_d = db.Column(db.Integer)id = db.Column(db.String(45),primary_key = True)class sql_ex():def add(self,question,a,b,c,d,ap,bp,cp,dp,id):with app.app_context():question_add = sql_ex_object()question_add.questions = questionquestion_add.choice_a = aquestion_add.choice_b = bquestion_add.choice_c = cquestion_add.choice_d = dquestion_add.point_a = apquestion_add.point_b = bpquestion_add.point_c = cpquestion_add.point_d = dpquestion_add.id = idtry: db.session.add(question_add)db.session.commit()db.session.close()print("\n\n\ncg\n\n\n")return "添加成功"except:return "题目已存在!"def delete(self,id):with app.app_context():question_delete = sql_ex_object.query.filter(sql_ex_object.id==id).first()try:db.session.delete(question_delete)db.session.commit()db.session.close()return "删除成功"except:return "删除失败"def search(self):with app.app_context():raw_list = db.session.execute( text("select * from questions") ).fetchall()list = list_row2list_dic(raw_list)print(list)# temp = sql_ex()# print(1+temp.add(question="接口",a='a',b='b',c='c',d='d',pa=1,pb=2,pc=3,pd=4))# ctx.pop()
sql_ex_blueprint
接下来我们来写操作数据库的蓝图文件,主要逻辑就是接收数据,然后调用刚刚做好的数据库操作,将数据存入。此处,我们先实现添加题目的功能。
from flask import Blueprint, jsonify, request
from flask.views import MethodView
import sql_exsqlex = Blueprint("sqlex", __name__)class sqlex_(MethodView):def get(self):question = request.args.get("question",None)a = request.args.get("a",None)b = request.args.get("b",None)c = request.args.get("c",None)d = request.args.get("d",None)ap = request.args.get("ap",None)bp = request.args.get("bp",None)cp = request.args.get("cp",None)dp = request.args.get("dp",None)id = request.args.get("id",None)try:ob = sql_ex.sql_ex()res = ob.add(question=question,a=a,b=b,c=c,d=d,ap=ap,bp=bp,cp=cp,dp=dp,id=id)return jsonify( {"errcode":0,"msg":res} )except:return jsonify( {"errcode":1,"msg":res} )def post(self):passsqlex.add_url_rule("/sqlex/", view_func=sqlex_.as_view("sqlex"))
至此,我们后端已经提供了添加题目的接口,接下来我们要写前端,以至于可以正确的发出请求。
二、前端
先说一下我们这一部分前端的结构。上一章,我们做了login页面,登录成功会自动push到我们的home页面。于是我们把home作为管理员的根节点。我们将home分为左右两个部分,左边选择、查看题目,右边添加题目、编辑题目信息、删除题目
效果如下
由于目前我们只实现了添加题目的后端接口,我们左边的题目暂时使用静态数据。
下面我们来将如何来实现。
<template><div class="container"><div class="sidebar"><QuestionList @selectQuestion="selectQuestion" :selectedQuestionId="selectedQuestion?.id" @refreshQuestions="refreshQuestions" ref="questionList" /></div><div class="content"><AddQuestion v-if="!selectedQuestion" @refreshQuestions="refreshQuestions" /><EditQuestion v-if="selectedQuestion" :question="selectedQuestion" @refreshQuestions="refreshQuestions" @clearSelection="clearSelection" /></div></div>
</template><script>
import QuestionList from '@/components/QuestionList.vue';
import AddQuestion from '@/components/AddQuestion.vue';
import EditQuestion from '@/components/EditQuestion.vue';export default {components: {QuestionList,AddQuestion,EditQuestion},data() {return {selectedQuestion: null};},methods: {selectQuestion(question) {this.selectedQuestion = { ...question };},clearSelection() {this.selectedQuestion = null;},refreshQuestions() {this.clearSelection();this.$refs.questionList.fetchQuestions();}}
};
</script><style>
.container {display: flex;height: 100vh;
}.sidebar {width: 500px;background-color: #f4f4f4;padding: 20px;box-shadow: 2px 0 5px rgba(0,0,0,0.1);overflow-y: auto;
}.content {width: 750px;padding: 20px;overflow-y: auto;
}
</style>
先说我们的home组件,我们的home组件用于实现区分左右,制作两个容器,分别将左边的questionlist和右边的add和edit放入,其中,根据我左边是否选择了已有题目来区分是要add还是edit
addquestion
<template><div><h2>添加新题目</h2><form @submit.prevent="addQuestion"><div><label for="question_id">题目 ID</label><input type="text" v-model="question.id" required /></div><div><label for="question_text">题干</label><input type="text" v-model="question.question_text" required /></div><div class="form-row"><label for="choice_a">选项 A</label><input type="text" v-model="question.choice_a" required /><label for="score_a">分数</label><input type="number" v-model="question.score_a" required /></div><div class="form-row"><label for="choice_b">选项 B</label><input type="text" v-model="question.choice_b" required /><label for="score_b">分数</label><input type="number" v-model="question.score_b" required /></div><div class="form-row"><label for="choice_c">选项 C</label><input type="text" v-model="question.choice_c" required /><label for="score_c">分数</label><input type="number" v-model="question.score_c" required /></div><div class="form-row"><label for="choice_d">选项 D</label><input type="text" v-model="question.choice_d" required /><label for="score_d">分数</label><input type="number" v-model="question.score_d" required /></div><button type="submit">添加题目</button></form></div>
</template><script setup>// const question= {// id: null,// question_text: '',// choice_a: '',// score_a: 0,// choice_b: '',// score_b: 0,// choice_c: '',// score_c: 0,// choice_d: '',// score_d: 0// }const question= {id: "22",question_text: '12fr',choice_a: 'a',score_a: 0,choice_b: 'b',score_b: 0,choice_c: 'c',score_c: 0,choice_d: 'd',score_d: 0}import axios from 'axios';async function addQuestion() {try{let result=await axios.get('http://127.0.0.1:5000/sqlex/',{params:{id:question.id,question:question.question_text,a:question.choice_a,b:question.choice_b, c:question.choice_c, d:question.choice_d, ap:question.score_a,bp:question.score_b,cp:question.score_c,dp:question.score_d,}})window.alert(result.data.msg)}catch(error){alert(error)}}</script><style scoped>
h2 {color: #2c3e50;
}form div {margin-bottom: 15px;
}.form-row {display: flex;align-items: center;justify-content: space-between;
}label {margin-right: 10px;
}input[type="text"],
input[type="number"] {padding: 10px;margin-right: 10px;
}button {padding: 10px 20px;background-color: #2c3e50;color: #fff;border: none;cursor: pointer;
}
</style>
add部分也不难理解,我们先制作出样式的页面,然后将各个输入做成ref响应式数据,在用户点击添加的时候,我们将这些数据发给后端,申请加入题目。
editquestion
编辑问题的原理与add类似,实际上没有什么差别,只不过要多实现一个展示选中题目的功能,这需要用到emit组件间通信,来与questionlist联动。
<template><div><h2>编辑题目</h2><form @submit.prevent="editQuestion"><div><label for="question_id">题目 ID</label><input type="number" v-model="localQuestion.id" required /></div><div><label for="question_text">题干</label><input type="text" v-model="localQuestion.question_text" required /></div><div class="form-row"><label for="choice_a">选项 A</label><input type="text" v-model="localQuestion.choice_a" required /><label for="score_a">分数</label><input type="number" v-model="localQuestion.score_a" required /></div><div class="form-row"><label for="choice_b">选项 B</label><input type="text" v-model="localQuestion.choice_b" required /><label for="score_b">分数</label><input type="number" v-model="localQuestion.score_b" required /></div><div class="form-row"><label for="choice_c">选项 C</label><input type="text" v-model="localQuestion.choice_c" required /><label for="score_c">分数</label><input type="number" v-model="localQuestion.score_c" required /></div><div class="form-row"><label for="choice_d">选项 D</label><input type="text" v-model="localQuestion.choice_d" required /><label for="score_d">分数</label><input type="number" v-model="localQuestion.score_d" required /></div><button type="button">保存修改</button><button type="button" @click="cancelEdit">取消</button><button type="button" @click="deleteQuestion">删除</button></form></div>
</template><script>
export default {props: {question: {type: Object,required: true}},data() {return {localQuestion: { ...this.question }};},watch: {question: {handler(newQuestion) {this.localQuestion = { ...newQuestion };},deep: true}},methods: {editQuestion() {console.log('Editing question:', this.localQuestion);// 这里可以添加发送请求到后端的代码// axios.put(`/api/questions/${this.localQuestion.id}`, this.localQuestion)// .then(response => {// console.log(response.data);// this.$emit('refreshQuestions');// this.$emit('clearSelection');// });},cancelEdit() {this.$emit('clearSelection');},deleteQuestion() {console.log('Deleting question:', this.localQuestion.id);// 这里可以添加发送请求到后端的代码// axios.delete(`/api/questions/${this.localQuestion.id}`)// .then(response => {// console.log(response.data);// this.$emit('refreshQuestions');// this.$emit('clearSelection');// });// 暂时使用静态数据this.$emit('refreshQuestions');this.$emit('clearSelection');}}
};
</script><style scoped>
h2 {color: #2c3e50;
}form div {margin-bottom: 15px;
}.form-row {display: flex;align-items: center;justify-content: space-between;
}label {margin-right: 10px;
}input[type="text"],
input[type="number"] {padding: 10px;margin-right: 10px;
}button {padding: 10px 20px;background-color: #2c3e50;color: #fff;border: none;cursor: pointer;margin-right: 10px;
}
button[type="button"] {background-color: #2c3e50;
}
button[type="button"]:hover {background-color: rgb(4, 23, 44);
}
</style>
questionlist
我们接下来来实现questionlist,这一部分不仅需要将题目输出到左边的题目栏目中,还要将问题数据传递给我们的edit,问题数据我们暂时使用静态数据。
<template><div><h2>所有题目</h2><ul><li v-for="question in questions" :key="question.id" @click="selectQuestion(question)":class="{ selected: question.id === selectedQuestionId }"><h3>ID: {{ question.id }} - {{ question.question_text }}</h3><p>A. {{ question.choice_a }} ({{ question.score_a }} 分)</p><p>B. {{ question.choice_b }} ({{ question.score_b }} 分)</p><p>C. {{ question.choice_c }} ({{ question.score_c }} 分)</p><p>D. {{ question.choice_d }} ({{ question.score_d }} 分)</p><!-- <button @click.stop="deleteQuestion(question.id)">删除</button> --></li></ul></div>
</template><script>
export default {props: {selectedQuestionId: {type: Number,default: null}},data() {return {questions: []};},mounted() {this.fetchQuestions();},methods: {fetchQuestions() {// 这里可以添加从后端获取数据的代码// axios.get('/api/questions')// .then(response => {// this.questions = response.data;// });// 暂时使用静态数据this.questions = [{id: 1,question_text: '题目 1',choice_a: '选项 A1',score_a: 1,choice_b: '选项 B1',score_b: 2,choice_c: '选项 C1',score_c: 3,choice_d: '选项 D1',score_d: 4},{id: 2,question_text: '题目 2',choice_a: '选项 A2',score_a: 1,choice_b: '选项 B2',score_b: 2,choice_c: '选项 C2',score_c: 3,choice_d: '选项 D2',score_d: 4},{id: 3,question_text: '题目 3',choice_a: '选项 A3',score_a: 1,choice_b: '选项 B3',score_b: 2,choice_c: '选项 C3',score_c: 3,choice_d: '选项 D3',score_d: 4}];},selectQuestion(question) {this.$emit('selectQuestion', question);},deleteQuestion(questionId) {// 这里可以添加发送请求到后端的代码// axios.delete(`/api/questions/${questionId}`)// .then(response => {// console.log(response.data);// this.fetchQuestions();// this.$emit('refreshQuestions');// });// 暂时使用静态数据this.questions = this.questions.filter(question => question.id !== questionId);this.$emit('refreshQuestions');}}
};
</script><style scoped>
h2 {color: #2c3e50;
}
ul {list-style-type: none;padding: 0;
}
li {padding: 10px;cursor: pointer;border-bottom: 1px solid #ddd;
}
li:hover {background-color: #eaeaea;
}
li.selected {background-color: #d0e6f7;
}
h3 {margin: 0;
}
p {margin: 0;
}
button {margin-top: 10px;padding: 5px 10px;background-color: red;color: white;border: none;cursor: pointer;
}
button:hover {background-color: darkred;
}
</style>
三、总结与预告
本文我们实现了一些管理员视图的功能,并将它传递到后端存储了,接下来我们要实现获取数据的功能和一些更加有利使用者的操作,比如每次添加完题目或者修改完题目之后,左侧题目列表应该自动刷新等等