iT邦幫忙

2021 iThome 鐵人賽

DAY 28
0
Modern Web

30天肝出購物網站系列 第 28

Day28:28 - 後端&前端 - 按讚收藏

Moni,我是Charlie!

在Day27當中我們完成了recaptcha驗證,而今天我們將實作按讚收藏的部分。

================================◉‿◉=================================

首先是後端的部分,按讚收藏需要另外一個資料表,這個資料表會包含以下欄位:

  1. id 自增長,主要欄位
  2. user_id 外鍵,連接users表
  3. product_id 外鍵,連接product表
  4. created_time 創建時間
  5. modified_time 修改時間
  6. status 狀態

所以我們先新建一個app叫做favorite:

*$ python manage.py startapp favorite

接著在settings.py當中新增INSTALLED_APPS:

INSTALLED_APPS = [
….
    'users',
    'login',
    'resetPWD',
    'favorite'...
]

並在favorite APP當中建立models:

from django.db import models
from users.models import User
from product.models import Product

# Create your models here.

class Favorite(models.Model):
	id = models.AutoField(primary_key = True)
	user = models.ForeignKey(User,on_delete = models.CASCADE,verbose_name = "用戶")
	product = models.ForeignKey(Product,on_delete = models.CASCADE,verbose_name = "商品")
	created_time = models.DateTimeField(auto_now = True,verbose_name = "創建時間")
	modified_time = models.DateTimeField(auto_now = True,verbose_name = "修改時間")
	status = models.IntegerField(verbose_name = "狀態")

	class Meta:
		db_table = "favorite"

並使用指令遷移:

$ python manage.py makemigrations favorite
$ python manage.py migrate favorite

接著建立urls.py,並且修改keyboardmarket\urls.py,新增favorite app url:

keyboardmarket\urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    url('favorite',include('favorite.urls')),
    url('reset',include('resetPWD.urls')),
    url('usercart',include('usercart.urls')),
    url('userorder',include('userorder.urls')),
    url('user',include('users.urls')),
    url('login',include('login.urls')),
    url('product',include('product.urls')),
]


favorite\urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
	url(r'^$',views.favorite)
]

favorite APP會處理三種請求:

  1. POST 處理按讚請求,當使用者按下讚會打此API,此API會判斷按讚狀態,如果沒有此筆紀錄則新增按讚,如果有此筆紀錄且狀態為1則取消,如果有此筆紀錄且狀態為0則按讚
  2. GET 返回按讚物品,參數為username
  3. GET + product id 返回該商品該用戶的按讚資料

所以在views.py當中建立基本程式碼:

from tools.login_check import logincheck
from tools.R import R
from favorite.models import Favorite
from users.models import User
from product.models import Product
from tools.db import FavoriteStatus
import json

# Create your views here.

@logincheck("GET","POST")
def favorite(request,productID = None):
	if request.method == "GET" and productID:
		pass
	if request.method == "GET":
		pass
	if request.method == "POST":
		pass
	return R.methodNotAllowed("method not allowed")

接著在tools\db.py當中建立FavoriteStatus:

class FavoriteStatus(Enum):
	deactivate = 0
	activate = 1

然後在Favorite model建立toJson方法:

def toJson(self):
	data = {}
	data["id"] = self.id
	data["username"] = self.user.name
	data["product"] = self.product.toJson()
	data["created_time"] = self.created_time
	data["modified_time"] = self.modified_time
	data["status"] = self.status
	return data

先修改GET的部分,GET會讀到username的參數,如果有讀到的話就會查詢按讚紀錄,並返回資料:

if request.method == "GET":
	req = request.GET
	if "username" not in req:
		return R.badRequest("username does not exist")
	username = req["username"]
	user = User.objects.filter(name = username)
	if not user:
		return R.badRequest("User not found")
	user = user[0]
	favorites = Favorite.objects.filter(user = user).filter(status = FavoriteStatus.activate.value)
	favorites = [i.toJson() for i in favorites]
	return R.ok(favorites)

再來是POST的部分,這部分需要判斷的比較多,如果沒有此筆資料的話要新增按讚資料,如果有此筆資料的話如果狀態為1就更改為狀態為0,如果狀態為0則更改狀態為1:

if request.method == "POST":
	req = request.body
	data = json.loads(req)
	if "username" not in data or "pid" not in data:
		return R.badRequest("not enough parameters!")
	username = data["username"]
	user = User.objects.filter(name = username)
	if not user:
		return R.badRequest("User not found")
	user = user[0]
	pid = data["pid"]
	product = Product.objects.filter(id = pid)
	if not product:
		return R.badRequest("product does not exist!")
	product = product[0]
	favorite = Favorite.objects.filter(user = user).filter(product = product)
	if not favorite:
		favorite = Favorite.objects.create(
			user = user,
			product = product,
			status = FavoriteStatus.activate.value
		)
	else:
		favorite = favorite[0]
		if favorite.status == FavoriteStatus.activate.value:
			favorite.status = FavoriteStatus.deactivate.value
		else:
			favorite.status = FavoriteStatus.activate.value
	favorite.save()
	return R.ok({"status":favorite.status})

再來是個別商品的部分:

if request.method == "GET" and productID:
	req = request.GET
	if "username" not in req:
		return R.badRequest("username does not exist")
	username = req["username"]
	user = User.objects.filter(name = username)
	if not user:
		return R.badRequest("User not found")
	user = user[0]
	product = Product.objects.filter(id = productID)
	if not product:
		return R.badRequest("Product does not exist")
	product = product[0]
	favorite = Favorite.objects.filter(user = user).filter(product = product)
	data = {
		"status":0
	}
	if favorite:
		data["status"] = favorite[0].status
	return R.ok(data)

再來是前端的部分,到apis資料夾當中新增favorite.js,並且新增加載商品按讚狀態跟按讚的API:

import { host,port } from '@/apis/constant.js'
import axios from 'axios'

export function getProductFavorite(pid,token,username){
  return axios.get(`http://${host()}:${port()}/favorite/${pid}`,{
    params:{
      "username":username
    },
    headers:{
      "AUTHORIZATION":token
    }
  })
}

export function addFavorite(pid,token,username){
  return axios.post(`http://${host()}:${port()}/favorite`,{
    "username":username,
    "pid":pid
  },{
    headers:{
    	"AUTHORIZATION":token
    }
  })
}

到productDetail中新增按讚按紐:

<b-button variant="info">
                  {{ favoriteText }}
</b-button>

並在created方法當中新增獲取商品按讚狀態的程式碼:

var token = window.localStorage.getItem("token")
var username = window.localStorage.getItem("username")
if(token == null || username == null){
  this.favoriteText = "登入以按讚"
}else{
  getProductFavorite(pid,token,username).then((response) => {
    if(response.data.code == STATUS_OK){
      var status = response.data.data.status
      this.favoriteText = status == 0 ? "按讚" : "已按讚" 
    }
  })
}

接著在按讚按鈕上新增onclick function:

<b-button variant="info" @click="addToFavorite">
  {{ favoriteText }}
</b-button>

並且新增addToFavorite方法,打後端的按讚API,並且根據狀態顯示不同按鈕文字:

addToFavorite(){
  var pid = this.$route.params.pid
  var username = window.localStorage.getItem("username")
  var token = window.localStorage.getItem("token")
  if(username == null || token == null){
    this.$fire({type:"error",text:"請登入!"}).then(() => {
      location.href = "/#/login"
    })
  }else{
    addFavorite(pid,token,username).then((response) => {
      if(response.data.code == STATUS_OK){
        this.favoriteText = response.data.data.status == 0 ? "按讚" : "已按讚"
      }
    })
  }
},

就可以測試看看是否可以按讚了:
https://ithelp.ithome.com.tw/upload/images/20211012/20141666mDMAvmFuOX.png

接下來是按讚好物的部分,在headers當中新增:

<b-nav-item-dropdown text="會員" right>
	<b-dropdown-item href="/#/login" v-show="!isLogin">登入</b-dropdown-item>
	<b-dropdown-item href="/#/register" v-show="!isLogin">註冊</b-dropdown-item>
	<b-dropdown-item href="/#/self" v-show="isLogin">個人資料</b-dropdown-item>
  <b-dropdown-item href="/#/favorite" v-show="isLogin">按讚好物</b-dropdown-item>
	<b-dropdown-item href="/#/order" v-show="isLogin">訂單</b-dropdown-item>
	<b-dropdown-item v-show="isLogin" @click="logout">登出</b-dropdown-item>
</b-nav-item-dropdown>

另外新增components\favorite.vue,做出模板:

<template>
	<html lang="zh-Hant-TW">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<link rel="icon" type="image/x-con" href="@/assets/favicon.ico">
		<div id="app">
			<headerComponent></headerComponent>
			<div id="favoriteHeader" style="width:100%;height:100px;background-image: linear-gradient(to right,#C9C9C9,#F2FFFF,#00E6E6);padding-top: 40px;">
				<h3>按讚好物</h3>
			</div>
			<b-row>
				<b-col cols="2" v-for="item in items" :key="item.id" style="margin: 20px;border:1px solid #5B5B5B;padding: 10px;text-align: center;box-shadow:3px 3px 12px #4F4F4F;border-radius: 30px;">
					<a :href="'/#/productDetail/' + item.product.id">
						<img :src="'http://localhost:8000' + item.product.img" alt="" style="width: 200px;height: 200px;">
					</a>
					<h4 class="productTitle">
						{{ item.product.name }}
					</h4>
					<h5 class="productPrice">
						{{ item.product.price }}
					</h5>
					<div>
						<b-button variant="danger" @click="cancel(item.product.id)">
							取消按讚
						</b-button>
					</div>
				</b-col>
			</b-row>
		</div>

	</html>
</template>

<script>
  export default{
    name: "favoritePage",
    components:{
      'headerComponent':() => import('@/components/header.vue')
    },
    data(){
      return {
        items:[
          {
            "id":1,
            "username":"admin123",
            "product":{
              "id":3,
              "name":"耳機",
              "price":123,
              "stored_amount":12,
              "img":"/media/productImage/airpod.png"
            }
          }
        ]
      }
    },
    methods:{
      cancel(data){
        console.log(data)
      }
    }
  }
</script>

呈現出來的頁面:
https://ithelp.ithome.com.tw/upload/images/20211012/20141666YIS3b34GaP.png

在favorite.js當中新增getAllFavorite方法:

export function getAllFavorite(username,token){
  return axios.get(`http://${host()}:${port()}/favorite`,{
    "username":username
  },{
    headers:{
      "AUTHORIZATION":token
    }
  })
}

並新增created方法,使用getAllFavorite方法取得資料:

created(){
  var token = window.localStorage.getItem("token")
  var username = window.localStorage.getItem("username")
  if(token == null || username == null){
    this.$fire({type:"error",text:"請登入"}).then(() => {
      location.href = "/#/login"
    })
  }
  getAllFavorite(username,token).then((response) => {
    if(response.data.code == STATUS_OK){
      this.items = response.data.data
    }else{
      this.$fire({type:"error",text:response.data.data})
    }
  })
},

接著新增取消方法,這裡使用的是在商品詳情頁面用過的addFavorite API,讓後端自動判定狀態:

cancel(data){
  var token = window.localStorage.getItem("token")
  var username = window.localStorage.getItem("username")
  var pid = data
  addFavorite(pid,token,username).then((response) => {
    if(response.data.code == STATUS_OK){
      this.items.forEach((item,index) => {
        if(item.product.id == pid){
          this.items.splice(index,1)
        }
      })
    }
  })
}

即可測試是否可以按讚跟收回,還有取消後會不會從按讚好物清單中消失。

================================◉‿◉=================================

Day28結束了!在今天我們完成了按讚跟取消按讚還有收藏的機制,而明天我們將加上像是Facebook分享、Line分享的按紐,See ya next day!


上一篇
Day27:27 - 優化 - 後端 - recaptcha 驗證機制
下一篇
Day29:29 - Facebook、Line分享
系列文
30天肝出購物網站30

尚未有邦友留言

立即登入留言