一個Flask + vue 前后端分離的 二手書App
效果展示:
https://blog.csdn.net/qq_42239520/article/details/88534955
所用技術清單

項目地址:項目地址
vue代碼地址:vue代碼地址
項目部分過程筆記:
后台:
項目結構
Secondhanbook / 項目目錄
apps
v1
__init__.py 導入 urls
urls.py 路由配置
forms.py
models.py
views
user_views.py 用戶相關視圖
book_vews.py 書籍相關視圖
config.py 配置文件
run.py 項目啟動文件
manage.py 數據庫遷移文件
Secondhanbook.py 初始化文件
utils 工具類目錄
......
......
1. Flask_RESTful 的返回 和 緩存時候的自定義返回
flask_restful 在視圖方法上加上marshal_with 裝飾器,並傳遞你返回的模式。但當自定義時,需要返回自己的模式
return marshal(books,self.resource_fields,envelope='data')
resource_fields:自定義返回模式
data: 返回時,數據包裝
2. 使用Flask_RESTful 下表單驗證以及csrf防御
每當用戶進入提交Post請求的頁面,實例化一個表單form,返回csrf_token
csrf_token = form.csrf_token.current_token
return restful.success(data=csrf_token)
3.路由配置
api = Api(prefix='/api')
#用戶相關
api.add_resource(Login,'/login/',endpoint='login')
......
#書籍相關
api.add_resource(BookAddView,'/bookadd/',endpoint='bookadd')
......
4.使用itsdangerous生成臨時身份令牌
生成令牌
def generate_auth_token(self,expiration=172800):#兩天過期
s = Serializer(config.BaseConfig.SECRET_KEY,expires_in=expiration)
return s.dumps({'id':self.id})
身份檢驗
def verify_auth_token(token):
s = Serializer(config.BaseConfig.SECRET_KEY)
try:
data = s.loads(token)
except Exception as e:
return None
user = FrontUserModel.query.get(data['id'])
return user
5.配置文件的書寫方式,類繼承的方式
class BaseConfig(object):
pass
class DevelopmentConfig(BaseConfig):
pass
class OnlineConfig(BaseConfig):
pass
6. celery 處理費時任務
@celery.task
def send_mail(subject,recipients,user_id):
......
@celery.task
def BookCacheAdd(books):
......
7.redis 存儲數據bytes 問題解決
cache = redis.StrictRedis(host='127.0.0.1',port=6379,db=0,decode_responses=True)
8. 封裝jsonfy的返回
class HttpCode(object):
Ok = 200
ParamerError = 400
Unauth = 401
ServerError = 500
def RestfulResult(code,message,data):
return jsonify({'code':code,'message':message,'data':data})
def success(message="",data=None):
return RestfulResult(HttpCode.Ok,message=message,data=data)
......
9.對axios 提交bytes類型數據進行form驗證
from werkzeug.datastructures import MultiDict
......
myform = json.loads((request.data.decode('utf-8')))
form = LoginForm(MultiDict(myform))
......
10. flask 接收 接收多個文件axios 上傳的多個文件
** AddBookView **
files = request.files
for file in files.values():
filename = upload.change_filename(secure_filename(file.filename))
**upload.vue**
for (var i = 0; i < this.files.length; i++) {
this.formdata.append('file' + i, this.files[i])
}
axios.post('api/bookadd/', this.formdata, {
headers: {'Content-Type': 'multipart/form-data'}
}).then(this.handleAxiosDone)
11.表單對files, 輸入字段驗證
from werkzeug.datastructures import CombinedMultiDict
form = Yourform(CombinedMultiDict([request.form, request.files])) // 合並 數據和文件
文件驗證:
定義表單時,采用FileField這個類型
驗證器導入:flask_wtf.file
flask_wtf.file.FileRequired 驗證是否為空
flask_wtf.file.FileAllowed 驗證上傳文件后綴名
前台部分知識點:
項目結構
......
src
common
footer 頁腳
alert 提示
fade 動畫
gallary 畫廊
......
store
... vuex相關
pages
home
components
header.vue
......
Home.vue
detail
me
sign
......
......
1. flask_result 返回時,提供默認值
返回數據時,當axios 未返回渲染到頁面時,使用變量出錯。
**** 解決方法: 定義默認值 例如有使用book.owner.username , book.owner.avatar時,否則報錯無定義,並無法顯示。
book: {
owner: {......Object}
},
2. vuex store一個Message,供於消息提醒
項目基本上每個頁面都有操作結果的提醒,封裝一個消息提醒的組件 alert.vue
<div v-show="isshow" class="alertBox" :style="{background:this.$store.state.color}">
{{mymessage}}
</div>
......
computed: {
mymessage () {
return this.$store.state.message
}
},
watch: {
mymessage (e) {
this.isshow = true
}
}
state: {
message: '默認值',
color: ''
},
mutations: {
msgchange (state, res) {
state.message = res.message // color 自定義消息
state.color = res.color // color 自定義顏色
setTimeout(() => {
state.message = ''
state.color = ''
}, 3000)
}
}
**** 重點:必須重置,否則下一次一樣的消息將不顯示
組件寫一個發送提醒消息的方法,方便多次調用
handleemit (message, color) {
this.$store.commit('msgchange', {message: message, color: color})
}
3. 登陸注冊:
利用 vue mounted 生命周期函數請求后端返回的csrf_token , 以及檢驗本地 localStorage,token是否過期。首次登陸成功返回存儲token
localStorage.setExpire('token', res.headers.token, 1000 * 60 * 60 * 24 * 2) // 設置兩天過期
注冊發送email 激活賬號
4 . 使用better-scroll 加載更多
swiper 盒子必須小於content高度才能滾動
可以滾動后,頁面將不能點擊,解決:****
this.scroll = new BScroll(this.$refs.wrapper, {
click: true,
......
監聽下拉方法,加載更多
pullUpLoad: {
// 當上拉距離超過盒子高度的的時候,就派發一個上拉加載的事件(觸發條件)
threshold: 0
}
監聽事件
this.scroll.on('pullingUp', () => {
axios.get('/api/booklist/?start=' + this.start).then(this.handleAxiosSuccess)
})
*** 對於下拉加載更多,雙重遍歷,遍歷頁碼對應的數據
v-for="(p,index) in page"
v-for="item in booklist[index]" :key="item.id" // booklist[index] 為第幾次下拉的返回的數據
5. vue-awesome-swiper
圖片點擊事件
監聽事件:
on: {
click: function (e) {
window.open(e.target.src) // 跳轉到網頁
}
}
使用:
<swiper :options="swiperOption"> swiperOption為參數{ loop: true,effect: 'fade'......}
<!-- slides -->
<swiper-slide v-for ='item of swiperList' :key="item.id">
<img class="swiper-img" :src="item.url" alt=""> // 傳遞圖片url
</swiper-slide>
</swiper>
畫廊 組件關鍵參數:
// observer啟動動態檢查器(OB/觀眾/觀看者),當改變swiper的樣式(例如隱藏/顯示)或者修改swiper的子元素時,自動初始化swiper。
// 默認false
observer: true,
observeParents: true
6. 過濾器,傳遞data中的值,並且使用v-html 顯示
v-html="$options.filters.filemotion(comment.content,emotions)" // emotions: [] 是data中自定義的值
本過濾器是對,表情的插入表情標簽的過濾 [贊] [哈哈] 替換成 圖片
方法:
emotions格式 :
filemotion (value, emotions) {
value = value.replace(/(\[.+?\])/g, (e, e1) => {
for (var i in emotions) {
if ((emotions[i].value.indexOf(e1)) > -1) {
return '<img src="' + emotions[i].icon + '">'
}
}
})
return value
}
7. axios 更改請求頭:
axios.post('/api/comment/',參數, {
headers: {
'Content-Type': 'application/json'
......
}
})
8. Proxytable設置跨域,進行數據交互
proxyTable: {
'/api': {
target: 'http://127.0.0.1:5000', //目標接口域名
changeOrigin: true, //是否跨域
pathRewrite: {
'^/api': '/v1/api/' //重寫接口
}
}
}
9.router 模式
路由 mode="history"模式
當前端項目結合到flask 項目中,當vue 路由為history模式時,出現“刷新頁面報錯404”的問題
這是因為這是單頁應用…其實是因為調用了history.pushState API 所以所有的跳轉之類的操作
都是通過router來實現的,解決這個問題很簡單,只需要在后台配置如果URL匹配不到任何靜態資源
進入新頁面,不回到頁面頂部解決:
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
},
