全栈开发一条龙——前端篇
第一篇:框架确定、ide设置与项目创建
第二篇:介绍项目文件意义、组件结构与导入以及setup的引入。
第三篇:setup语法,设置响应式数据。
第四篇:数据绑定、计算属性和watch监视
第五篇 : 组件间通信及知识补充
第六篇:生命周期和自定义hooks
第七篇:路由
第八篇:传参
第九篇:插槽,常用api和全局api。
全栈开发一条龙——全栈篇
第一篇:初识Flask&MySQL实现前后端通信
第二篇: sql操作、发送http请求和邮件发送
第三篇:全栈实现发送验证码注册账号
第四篇:图片验证码及知识补充
全栈开发一条龙——实战篇
第一篇:项目建立与login页面
本章我们进入后台管理员视图的开发
一、后端
在后端,我们会遇到循环引用的问题:我们在进行后端蓝图的编写的时候,一定会要用到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 text
from 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 text
from flask.views import MethodView
from set_app import app
from 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 = question
question_add.choice_a = a
question_add.choice_b = b
question_add.choice_c = c
question_add.choice_d = d
question_add.point_a = ap
question_add.point_b = bp
question_add.point_c = cp
question_add.point_d = dp
question_add.id = id
try:
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_dic
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)
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 = question
question_add.choice_a = a
question_add.choice_b = b
question_add.choice_c = c
question_add.choice_d = d
question_add.point_a = ap
question_add.point_b = bp
question_add.point_c = cp
question_add.point_d = dp
question_add.id = id
try:
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_ex
sqlex = 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):
pass
sqlex.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>
三、总结与预告
本文我们实现了一些管理员视图的功能,并将它传递到后端存储了,接下来我们要实现获取数据的功能和一些更加有利使用者的操作,比如每次添加完题目或者修改完题目之后,左侧题目列表应该自动刷新等等