# 後端

# view(篩選+排序高評分產品)

  • gte: greater than or equal to,只抓取評分 >= 4 的產品
  • 排序方式為 rating 由高到低,只取前 5 個產品
# product_views.py
@api_view(['GET'])
def getTopProducts(request):
    products = Product.objects.filter(rating__gte=4).order_by('-rating')[:5]
    serializer = ProductSerializer(products, many=True)
    return Response(serializer.data)

# url

# urls.py
path('top-rated/', views.getTopProducts,name="top_rated_products"),

此時去訪問 /api/products/top-rated/,應能看到以下回傳:

drf_ranked

# 前端

# Constants

// productConstants.js
export const PRODUCT_TOP_RATED_REQUEST = "PRODUCT_TOP_RATED_REQUEST";
export const PRODUCT_TOP_RATED_SUCCESS = "PRODUCT_TOP_RATED_SUCCESS";
export const PRODUCT_TOP_RATED_FAIL = "PRODUCT_TOP_RATED_FAIL";

# Reducers

// productReducers.js
// 取得評分高的產品
export const productTopRatedReducer = (state = { products: [] }, action) => {
 switch (action.type) {
  case PRODUCT_TOP_RATED_REQUEST:
   return { loading: true };
  case PRODUCT_TOP_RATED_SUCCESS:
   return { loading: false, products: action.payload };
  case PRODUCT_TOP_RATED_FAIL:
   return { loading: false, error: action.payload };
  default:
   return state;
 }
};

新增到 store.js,一樣例行公事,先回 Redux DevTools 確認新增有新增成功:

redux_tools_check

# Actions

// 取得評分高的產品
export const listTopRatedProducts = () => async (dispatch) => {
 try {
  dispatch({ type: PRODUCT_TOP_RATED_REQUEST });
  const { data } = await axios.get(`/api/products/top-rated/`);
  dispatch({
   type: PRODUCT_TOP_RATED_SUCCESS,
   payload: data,
  });
 } catch (error) {
  dispatch({
   type: PRODUCT_TOP_RATED_FAIL,
   payload: error.response && error.response.data.message ? error.response.data.message : error.message,
  });
 }
};

<Carousel> 即為 Bootstrap 的輪播主體元件,<Carousel.Item> 為輪播的每個項目,<Image> 為顯示圖片的元件,而 <Carousel.Caption> 為顯示文字的元件

import React, { useEffect } from "react";
import { Carousel, Image } from "react-bootstrap";
import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
// 載入元件
import Loader from "./Loader";
import Message from "./Message";
// 載入 action
import { listTopRatedProducts } from "../actions/productActions";

const ProductCarousel = () => {
 const dispatch = useDispatch();

 const { loading, error, products } = useSelector((state) => state.productTopRated);

 useEffect(() => {
  dispatch(listTopRatedProducts());
 }, [dispatch]);

 return loading ? (
  <Loader />
 ) : error ? (
  <Message variant='danger'>{error}</Message>
 ) : (
  <Carousel pause='hover' className='product-carousel'>
   {products.map((product) => (
    <Carousel.Item key={product.id} className='carousel-item'>
     <Link to={`/product/${product.id}`}>
      <div className='image-container'>
       <Image src={product.image} alt={product.name} className='carousel-image' />
      </div>
      <Carousel.Caption className='carousel-caption'>
       <h2>
        {product.name} <span>${product.price}</span>
       </h2>
      </Carousel.Caption>
     </Link>
    </Carousel.Item>
   ))}
  </Carousel>
 );
};

export default ProductCarousel;

加到 HomePage.js後,為了確保用戶在進行關鍵字搜尋的情況下,不顯示 Carousel 輪播,判斷無 keyword 時才顯示 Carousel:

// HomePage.js
return (
 <div>
  ...
  {!keyword && <ProductCarousel />}
  ...
 </div>
);

最後自己寫 CSS 簡單調整一下 Carousel 的樣式,

  1. 固定圖片大小,並添加陰影效果及圓邊
  2. 讓產品及產品價格於 Carousel 相對位置的中間顯示
  3. Hover時能有放大產品效果

得到以下結果:

carousel_done