赞
踩
项目代码已上传至GITEE
技术栈:
后端 :
1.flask
3.sqlite
前端 :
- VUE
- AXIOS
- ELEMENTUI
使用el-table模拟文件Tree系统,使用懒加载来增强体验,编辑器选用为mavon-editor
,预览使用markdown-it
,在后端使用正则表达式解析markdown toc目录增强浏览体验,编辑期间前端定时器会简单的定时缓存数据到后端
演示地址
因为css和js没有走cdn会很慢
总体分为两部分: flask搭建的后端接口以及VUE的前端界面
前端代码后台基于GITHUB大佬的vue-element-admin,在这里使用了大佬的简单模板并进行修改,模板代码源于vue-admin-template。
基于vue-cli
进行构建,电脑需安装npm
环境
在vue-admin-template(文中以VAT来简称)中clone代码, 根据markdown中文文档步骤构建环境并启动
准备一个最小的flask应用,若未使用过flask请于链接了解,文档中会有完整代码
以npm run dev
模式启动VAT框架,框架启动后可以发现输入任意密码既可以登录进后台中,是因为后台框架使用的是./mock文件中的数据进行后端模拟;
进入后发现框架已经做好了侧边栏,面包屑,以及基于AXIOS的token验证机制,只需要简单的修改既可以与后端进行交互起来
更改文件 ./.env.development
# just a flag
ENV = 'development'
# base api
# VUE_APP_BASE_API = '/dev-api' # 更改为后端接口地址
VUE_APP_BASE_API = 'http://localhost:5000' # FLASK端口5000
删除模板中的mock数据的引用
更改文件./src/main.js 对如下部分进行注释 并自己选择是否使用中文版本的 element-ui
根据注释说明修改
/** * If you don't want to use mock-server * you want to use MockJs for mock api * you can execute: mockXHR() * * Currently MockJs will be used in the production environment, * please remove it before going online ! ! ! */ if (process.env.NODE_ENV === 'production') { const { mockXHR } = require('../mock') mockXHR() } // set ElementUI lang to EN Vue.use(ElementUI, { locale }) // 如果想要中文版 element-ui,按如下方式声明 // Vue.use(ElementUI)
重启前端服务,直到登录时显示network Error
解析 @utils/requests.js
上半部分是对axios请求做了一个基本的配置,请求路径为 env.development中的 VUE_APP_BASE_API,超时时间为5s
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
中间部分为配置axios的请求头,对请求(request)进行预处理,携带token进行验证
service.interceptors.request.use( config => { // do something before request is sent if (store.getters.token) { // let each request carry token // ['X-Token'] is a custom headers key // please modify it according to the actual situation config.headers['X-Token'] = getToken() } return config }, error => { // do something with request error console.log(error) // for debug return Promise.reject(error) } )
后面部分为axios的返回数据(response)拦截器
如下代码可知 VAT框架模板预留了几个 response status code 英文注释比较清晰明了
response => { const res = response.data // if the custom code is not 20000, it is judged as an error. if (res.code !== 20000) { Message({ message: res.message || 'Error', type: 'error', duration: 5 * 1000 }) // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; if (res.code === 50008 || res.code === 50012 || res.code === 50014) { // to re-login MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { confirmButtonText: 'Re-Login', cancelButtonText: 'Cancel', type: 'warning' }).then(() => { store.dispatch('user/resetToken').then(() => { location.reload() }) }) } return Promise.reject(new Error(res.message || 'Error')) } else { return res } }, error => { console.log('err' + error) // for debug Message({ message: error.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } )
import axios from 'axios' import { MessageBox, Message } from 'element-ui' import store from '@/store' import { getToken } from '@/utils/auth' // create an axios instance const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url // withCredentials: true, // send cookies when cross-domain requests timeout: 5000 // request timeout }) // request interceptor service.interceptors.request.use( config => { // do something before request is sent if (store.getters.token) { // vuex 携带token验证 Object.assign(config.headers, { Authorization: 'Bearer ' + getToken() }) } return config }, error => { // do something with request error console.log(error) // for debug return Promise.reject(error) } ) // response interceptor service.interceptors.response.use( response => { const res = response.data // flask all return 200 if (res.code !== 200) { Message({ message: 'requests error', type: 'error', duration: 5 * 1000 }) // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; if (res.code === 50008 || res.code === 50012 || res.code === 50014) { // to re-login MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { confirmButtonText: 'Re-Login', cancelButtonText: 'Cancel', type: 'warning' }).then(() => { store.dispatch('user/resetToken').then(() => { location.reload() }) }) } return Promise.reject(new Error(res.message || 'Error')) } else { return res } }, error => { // flask因为使用了一些模块(flask_httpauth) token验证失败只返回401 if (error.response) { switch (error.response.status) { case 401: MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { confirmButtonText: 'Re-Login', cancelButtonText: 'Cancel', type: 'warning' }).then(() => { store.dispatch('user/resetToken').then(() => { location.reload() }) }) break case 400: Message({ message: error.response.data.data.message, type: 'error', duration: 3 * 1000 }) break case 422: Message({ message: error.response.data.data.message, type: 'error', duration: 3 * 1000 }) break } } return Promise.reject(error) } ) export default service
在python环境中pip install flask
,编写如下脚本代码启动一个后端服务
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello Flask!'
if __name__ == '__main__':
app.run()
运行 在浏览器中输入 127.0.0.1:5000 浏览器中打印出 ‘Hello Flask!’ 服务启动成功!
登录接口需要post数据 获取token存在浏览器本地中,与后端的每个接口都需要携带token进行验证
@\src\utils\auth.js: 浏览器Cookie对token的处理
@\src\store\getters.js: 本地储存用户信息的全局字典
@\src\store\modules\user.js: 与用户登录的请求和处理基本都在这里
根据user.js的代码可在flask设计出登录接口返回的数据格式
import { login, logout, getInfo } from '@/api/user'
login
: 无需携带token
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
返回数据
{
"token":"Bearer x",
}
info
: 携带token
getInfo({ commit, state }) { return new Promise((resolve, reject) => { getInfo(state.token).then(response => { const { data } = response if (!data) { return reject('Verification failed, please Login again.') } const { name, avatar } = data commit('SET_NAME', name) commit('SET_AVATAR', avatar) resolve(data) }).catch(error => { reject(error) }) }) },
返回数据
{
"name":"admin",
"avatar":".png"
}
logout
: 清除token并退出
无数据返回
接口位置 @\src\api\user.js
, 修改为如下
import request from '@/utils/request' export function login(data) { return request({ url: '/auth/login', method: 'post', data }) } export function getInfo(token) { return request({ url: '/auth/info', method: 'get' // params: { token } # 获取到token后会自己携带token进行请求 }) } export function logout() { return request({ url: '/auth/logout', method: 'post' }) }
登录组件位置 @\src\views\login\index.vue
, 可以看到post form如下
loginForm: {
username: 'admin',
password: '111111'
},
修改flask的接口来兼容VAT吧! 复制如下代码到最小的flask应用中重新启动测试
def returnVueDataModel(message, code, **kwargs): kwargs['message'] = message kwargs['code'] = code return {"data": kwargs, 'code': 200} @app.route('/auth/login', methods=['POST']) def login(): params = request.get_json() if 'username' not in params or 'password' not in params: return jsonify(returnVueDataModel('miss arguments', 400)), 400 if params['username'] == 'admin' and params['password'] == '111111': return jsonify(returnVueDataModel('login success', 200, token='x')) return jsonify(returnVueDataModel('no existing user', 400)), 400 @app.route('/auth/info', methods=['GET']) def info(): token = request.headers.get('Authorization') if 'Bearer' not in token: return my_json(returnVueDataModel('please login!', 400)), 400 return jsonify(returnVueDataModel('token verify pass', 200, name='Admin', avatar=""))
Access to XMLHttpRequest at 'http://localhost:5000/auth/login' from origin 'http://localhost:9528' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
:5000/auth/login:1 Failed to load resource: net::ERR_FAILED
:9528/#/login?redirect=%2Fdashboard:1 Access to XMLHttpRequest at 'http://localhost:5000/auth/login' from origin 'http://localhost:9528' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
服务启动并登录后 会出现如上问题,需要配置flask的跨域请求 使用 flask_cors
pip install flask_cors
from flask import Flask, request, jsonify app = Flask(__name__) from flask_cors import * # 导入模块 CORS(app, supports_credentials=True, origins=['http://localhost:9528']) def returnVueDataModel(message, code, **kwargs): kwargs['message'] = message kwargs['code'] = code return {"data": kwargs, 'code': 200} @app.route('/', methods=['GET']) def hello_world(): return 'Hello Flask!' @app.route('/auth/login', methods=['POST']) def login(): params = request.get_json() if 'username' not in params or 'password' not in params: return jsonify(returnVueDataModel('miss arguments', 400)), 400 if params['username'] == 'admin' and params['password'] == '111111': return jsonify(returnVueDataModel('login success', 200, token='x')) return jsonify(returnVueDataModel('no existing user', 400)), 400 @app.route('/auth/info', methods=['GET']) def info(): token = request.headers.get('Authorization') if 'Bearer' not in token: return my_json(returnVueDataModel('please login!', 400)), 400 return jsonify(returnVueDataModel('token verify pass', 200, name='Admin', avatar="")) if __name__ == '__main__': app.run()
短短十多分钟 完成了一个前后端分离的小项目,那么根据这个框架,可以搭建起来任意一个喜欢的后台管理系统,本次我们搭建一个小型的Blog,当然这个Blog因为也是闲着无聊写的,所以可能会有bug哟,比如markdown的Toc目录因为我使用的正则解析的,可能你们写法跟我不一样就无法解析出正常的目录啦
clone到本地,在pycharm或喜欢的编辑器中打开,pycharm会自动创建虚拟环境以及pip 环境包
进入虚拟环境,输入如下命令,搭建sqlalchemy数据库环境
flask db init
flask db migrate
flask db upgrade
搭建完成后,在虚拟环境下输入>>flask shell 进入flask shell环境fake数据(noteapp\faker.py),如下为flask shell中包含的对象
def register_shell_context(app):
"""Register shell context objects."""
def shell_context():
"""Shell context objects."""
return {"db": db, "User": models.User, "Fold": models.Fold, "Blog": models.Blog,
"fake": faker.faker, "wafer": models.Wafer}
app.shell_context_processor(shell_context)
输入命令如下
>>> fake
<class 'noteapp.faker.faker'>
>>> fake().do()
--- 等待完成 若有报错缺少faker模块请pip安装
>>> User.query.all()
[<User('David Richardson')>, <User('Leonard Roberts')>, <User('Samuel Scott')>, <User('Eduardo Jones')>, <User('Shirley Johns')>]
--- 生成的用户列表 密码均为123456
>>> eixt()
回到虚拟环境 输入flask run
启动服务
目录结构
├── README.md 项目介绍 ├── autoapp.py 程序入口 ├── requirements.txt 项目依赖 ├── migrations flask db init 生成的数据库管理 若非必要请在git ignore ├── tests 一些单元测试及一些个人测试文件 ├── noteapp 源码目录 │ ├── models 持久化数据表模型 │ ├── upload 文件上传目录 │ ├── util 公共函数方法 │ ├── views 主要蓝图和业务代码 │ ├── app.py 程序初始化入口 │ ├── commands.py 创建虚拟环境下flask自定义命令 │ └── database.py CURD │ └── dev.db 持久化sqlite │ └── extensions.py 引入的库 │ └── faker.py 虚拟数据 │ └── settings.py 程序配置 └──
clone到本地,在vscode或喜欢的编辑器中打开,根据markdown文档搭建项目,npm run dev
运行
启动成功后浏览器会跳到文章开头的界面中,操作中会需要登录,请使用前面flask shell查到的用户密码登录
Tree中使用elementUi的table懒加载和根据懒加载更新某行数据的方式来模拟文件夹系统
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。