手把手制作Vue3+Flask全栈项目 全栈开发之路实战篇 问卷网站(二)管理员后台

全栈开发一条龙——前端篇
第一篇:框架确定、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>

三、总结与预告

本文我们实现了一些管理员视图的功能,并将它传递到后端存储了,接下来我们要实现获取数据的功能和一些更加有利使用者的操作,比如每次添加完题目或者修改完题目之后,左侧题目列表应该自动刷新等等

  • 15
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: 一个vue+node+mysql全栈项目是指使用vue作为前端框架,node作为后端框架,mysql作为数据库的项目。在这个项目中,前端开发者可以使用vue进行前端页面的开发,同时使用node作为后端框架来开发后端接口,通过mysql来存储和管理数据。这样的项目可以实现前后端的完整交互和数据传输,实现全栈开发。在项目中,可以使用express作为node的服务器框架,使用axios进行前后端的跨域访问,使用vuex进行数据管理,使用elementUI来搭建页面。这样的技术栈可以提供丰富的学习资源和开发工具,帮助开发者实现全栈开发的目标。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* [vue+node.js+mysql项目搭建初体验(接口)](https://blog.csdn.net/weixin_48931875/article/details/121156074)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [vue+node+mysql全栈项目完整记录](https://blog.csdn.net/u012848304/article/details/128713937)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值