Flask+Vue3 Full Stack
0. 环境
- OS:
Ubuntu20.04 - pipenv:
2023.7.4 - Flask:
2.0.1 - nodejs:
v18.16.1: 基于 Chrome V8 引擎的JS运行环境 - npm:
6.14.4: node.js 的包管理工具 - Vue:
@vue/cli 5.0.8 - element-plus:
2.3.7 - bootstrap:
4.6.1 - pinia:
2.1.3
- npm国内换源:
sudo npm config set registry https://registry.npm.taobao.org- 为了升级node全局安装n版本管理工具n:
sudo npm install n -g(1)su root(2)export N_NODE_MIRROR=https://npm.taobao.org/mirrors/node(3)n stable- 安装Vue:
sudo npm install -g vue安装Vue脚手架sudo npm install -g @vue/cli
1. 项目结构初始化
🐵backend
后端工作:python环境,使用虚拟环境
~/Desktop/flask-vue-tutorial/backend
$ pipenv shell
$ nano requirements.txt
$ pipenv install -r requirements.txt
$ nano main.py
$ python main.py
按照惯例,Hello一下🖐️
💰frontend
~/Desktop/flask-vue-tutorial/frontend
$ npm init vue@latest
$ cd gameList/
$ npm install
$ npm run format
$ npm run dev
- 修改vite.config.js
还可设置
port:8086,设置open: true让服务启动时自动打开浏览器 - Hello一下🖐️
2. axios配置
npm i axios
配置路由
在前端通过axios请求后台服务
后台响应
前台回显结果,合作愉快🤝
3. 🍃页面设计
引入Element Plus
- 安装:
npm install element-plus --save - 配置按需导入:
npm install -D unplugin-vue-components unplugin-auto-import - vite.config.js中配置:
- 注释原来的样式文件main.css
✌单页面设计
frontend/gameList/src/components/Games.vue
<script setup>
import {ref} from 'vue';
import axios from 'axios';
const tableData = ref()
const path = 'http://192.168.133.130:5000/games'
axios.get(path).then((res)=>{
tableData.value = res.data.games;
})
</script>
<template>
<div>
<el-container>
<el-header>
<el-row justify="center">
<el-text tag="b" type="primary" size="large">Games Library🕹️</el-text>
</el-row>
<el-divider/>
</el-header>
<el-main>
<!-- Alert Message -->
<el-row justify="center">
<el-button plain type="success" @click="addGame">Add Game</el-button>
</el-row>
<el-table :data="tableData" stripe>
<el-table-column prop="title" label="Title" min-width="80" align="center"/>
<el-table-column prop="genre" label="Genre" min-width="80" align="center"/>
<el-table-column prop="played" label="Played?" min-width="80" align="center"/>
<el-table-column fixed="right" label="Actions" min-width="150" align="center">
<template #default>
<el-button round type="primary" size="small" @click="updateGame"
>update</el-button
>
<el-button round type="danger" size="small" @click="deleteGame">delete</el-button>
</template>
</el-table-column>
</el-table>
</el-main>
<el-footer>
<el-row justify="center">
<el-text type="info">Over</el-text>
</el-row>
</el-footer>
</el-container>
</div>
</template>
后台响应💡
backend/main.py
UI效果:
尝试用用bootstrap
npm install bootstrap@4.6.1 --save

引入bootswatch CDN
思考🤔:
- bootstrap和elementplus搭配是否可行?
- element-plus的表格如何自定义样式?
- 怎么加背景图,怎么让表格透明显示?
背景图:
对样式进行魔改⌨️
<script setup>
import {ref} from 'vue';
import axios from 'axios';
const tableData = ref()
const path = 'http://192.168.133.130:5000/games'
axios.get(path).then((res)=>{
tableData.value = res.data.games;
})
</script>
<template>
<div id="building">
<br/>
<div class="jumbotron vertical-center" id="content">
<el-container>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@4.5.2/dist/sketchy/bootstrap.min.css" integrity="sha384-RxqHG2ilm4r6aFRpGmBbGTjsqwfqHOKy1ArsMhHusnRO47jcGqpIQqlQK/kmGy9R" crossorigin="anonymous">
<el-header>
<!-- bootswatch CDN -->
<el-row class="nav">
<h2 class="logo">To Play🕹️</h2>
<button class="btnAdd-popup" @click="addGame">Add</button>
<el-divider/>
</el-row>
</el-header>
<el-main>
<!-- Alert Message -->
<hr><br>
<el-table
:data="tableData"
style="max-width:800px;margin:0 auto"
>
<el-table-column prop="title" label="Title" min-width="80" align="center"/>
<el-table-column prop="genre" label="Genre" min-width="80" align="center"/>
<el-table-column prop="played" label="Played?" min-width="80" align="center"/>
<el-table-column label="Actions" min-width="150" align="center">
<template #default>
<el-button round type="primary" size="small" @click="updateGame"
>update</el-button
>
<el-button round type="danger" size="small" @click="deleteGame">delete</el-button>
</template>
</el-table-column>
</el-table>
</el-main>
<el-footer>
<hr><br>
<el-row justify="center">
<el-text tag="ins" class="footer">Copyright © . All Rights Reserved 2023.</el-text>
</el-row>
</el-footer>
</el-container>
</div>
</div>
</template>
<style scoped>
#building{
background:linear-gradient(rgba(233, 226, 226, 0.251), rgba(228, 223, 223, 0.551)), url('../assets/wallhaven1.jpg') no-repeat ;
width:100%;
height:100%;
position:fixed;
background-size:100% 100%;
}
#content{
margin-left:20px;
margin-right:20px;
}
.nav{
justify-content: space-between;
align-items: center;
z-index: 99;
}
.logo{
font-size: 2em;
font-weight: bolder;
color : #fff;
user-select: none;
margin-left:45%;
}
.nav .btnAdd-popup{
width: 130px;
height: 50px;
background: transparent;
border: 2px solid #fff;
outline: none;
border-radius: 6px;
cursor: pointer;
font-size: 1.1em;
color: #fff;
font-weight: 500;
margin-right:40px;
transition: .5s;
}
.nav .btnAdd-popup:hover{
background: #fff;
color: #162938;
}
.footer{
font-size:1.1em;
font-weight: bolder;
color:antiquewhite;
}
:deep(.el-table){
border: 1px solid black;
background-color: transparent;
}
:deep(.el-table th){
background-color: rgba(223, 168, 168, 0.5);
}
:deep(.el-table tr){
background-color: transparent;
}
:deep(.el-table td){
background-color: rgba(255,255,255,0.5);
}
:deep(.el-table th .cell){
color:#fff;
font-weight: bold;
font-size:large;
}
:deep(.el-table td .cell){
color:green;
font-weight: bold;
font-size:large;
}
</style>
🤟最终UI效果:
4. 🕹️按钮点击事件
后台API
@app.route('/games', methods=['GET', 'POST'])
def allGames():
response_object = {'status': 'success'}
if request.method == 'POST':
post_data = request.get_json()
GAMES.append({
'id': uuid.uuid4().hex,
'title': post_data.get('title'),
'genre': post_data.get('genre'),
'played': post_data.get('played')
})
response_object['message'] = 'Game Added!'
else:
response_object['games'] = GAMES
return jsonify(response_object)
@app.route('/games/<game_id>', methods=['PUT','DELETE'])
def single_game(game_id):
response_object = {'status': 'success'}
if request.method == 'PUT':
post_data = request.get_json()
remove_game(game_id)
GAMES.append({
'id': uuid.uuid4().hex,
'title': post_data.get('title'),
'genre': post_data.get('genre'),
'played': post_data.get('played')
})
response_object['message'] = 'Game updated!'
if request.method == "DELETE":
remove_game(game_id)
response_object['message'] = 'Game removed!'
return jsonify(response_object)
def remove_game(game_id):
for game in GAMES:
if game['id'] == game_id:
GAMES.remove(game)
return True
return False
增➕
-
dialog变量控制弹窗显示,form变量绑定表单数据 -
click触发弹窗 -
dialog的具体样式 -
点击submit后,打包数据,传给API
-
前台API的实现, 发送请求,刷新数据,消息提示
删❌
- 思考:点击删除按钮,需要传递当前行的id,怎么传递?
答: 通过作用域插槽
- 与后台通信,然后更新列表,回显消息
改✂️
dialog变量控制弹窗显示,form变量绑定表单数据click触发弹窗, 使用作用域插槽传递当前行信息dialog的具体样式- 点击submit后,打包数据,传给API
- API的实现, 发送请求,刷新数据,消息提示
5. 预览
预览网址📖 部署在Netlify上 为了简单,去掉了flask后端,使用pinia来进行状态管理
什么是Netlify? 一个现代网站自动化系统,只要在本机Git中编写前端代码,然后推送它,网站就能完美地对外呈现,类似网站托管工具
6. 源码
源码🐴在github可找到