# 後端
# 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/,應能看到以下回傳:

# 前端
# 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 確認新增有新增成功:

# 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 元件製作
<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 的樣式,
- 固定圖片大小,並添加陰影效果及圓邊
- 讓產品及產品價格於
Carousel相對位置的中間顯示 - Hover時能有放大產品效果
得到以下結果:

