Ver código fonte

React项目: 商品页面逻辑实现

大侠 2 anos atrás
pai
commit
44998c49f4

+ 38 - 0
15_React/day-7/code/my-app/public/products.json

@@ -0,0 +1,38 @@
+[
+  {
+    "category": "Sporting Goods",
+    "price": "$49.99",
+    "stocked": true,
+    "name": "Football"
+  },
+  {
+    "category": "Sporting Goods",
+    "price": "$9.99",
+    "stocked": true,
+    "name": "Baseball"
+  },
+  {
+    "category": "Sporting Goods",
+    "price": "$29.99",
+    "stocked": false,
+    "name": "Basketball"
+  },
+  {
+    "category": "Electronics",
+    "price": "$99.99",
+    "stocked": true,
+    "name": "iPod Touch"
+  },
+  {
+    "category": "Electronics",
+    "price": "$399.99",
+    "stocked": false,
+    "name": "iPhone 5"
+  },
+  {
+    "category": "Electronics",
+    "price": "$199.99",
+    "stocked": true,
+    "name": "Nexus 7"
+  }
+]

+ 3 - 0
15_React/day-7/code/my-app/src/api/shop.js

@@ -0,0 +1,3 @@
+import axios from 'axios';
+
+export const getAllProducts = () => axios.get('/products.json');

+ 5 - 1
15_React/day-7/code/my-app/src/layout/index.css

@@ -16,6 +16,7 @@
 .layout main {
   flex: 1;
   position: relative;
+  border-left: 1px solid rgba(5, 5, 5, 0.06);
 }
 
 .layout main footer {
@@ -32,7 +33,9 @@
 
 .layout main header {
   height: 50px;
-  background-color: burlywood;
+  background-color: #e6f4fe;
+  /* background-color: #5792ff; */
+  border-bottom: 1px solid rgba(5, 5, 5, 0.06);
 }
 
 .layout main .main-page {
@@ -44,6 +47,7 @@
   box-sizing: border-box;
   height: 100%;
   font-size: 12px;
+  border: none !important;
 }
 
 aside .ant-btn {

+ 1 - 1
15_React/day-7/code/my-app/src/layout/index.jsx

@@ -21,7 +21,7 @@ export default function Layout() {
           导航菜单
         </Button>
         <Menu
-          defaultSelectedKeys={['1']}
+          defaultSelectedKeys={['/']}
           mode="inline"
           inlineCollapsed={false}
           items={items}

+ 4 - 0
15_React/day-7/code/my-app/src/pages/shop-list.css

@@ -20,6 +20,10 @@
   align-items: center;
 }
 
+.shop-list .list-header > label > input {
+  margin-left: 0;
+}
+
 .shop-list .list-content {
   padding-top: 10px;
 }

+ 80 - 54
15_React/day-7/code/my-app/src/pages/shop-list.jsx

@@ -1,50 +1,91 @@
-import { useState } from 'react';
 import './shop-list.css';
-
-const initialProducts = [
-  {
-    category: 'Sporting Goods',
-    price: '$49.99',
-    stocked: true,
-    name: 'Football',
-  },
-  {
-    category: 'Sporting Goods',
-    price: '$9.99',
-    stocked: true,
-    name: 'Baseball',
-  },
-  {
-    category: 'Sporting Goods',
-    price: '$29.99',
-    stocked: false,
-    name: 'Basketball',
-  },
-  {
-    category: 'Electronics',
-    price: '$99.99',
-    stocked: true,
-    name: 'iPod Touch',
-  },
-  {
-    category: 'Electronics',
-    price: '$399.99',
-    stocked: false,
-    name: 'iPhone 5',
-  },
-  { category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7' },
-];
+import { Fragment } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+import { useEffect } from 'react';
+import {
+  setFilterName,
+  setShowStocked,
+  fetchProducts,
+} from '../store/slices/shop';
 
 export default function ShopList() {
-  let [products] = useState(() => initialProducts);
+  const dispatch = useDispatch();
+  let products = useSelector(({ shop }) => shop.products);
+  let filterName = useSelector(({ shop }) => shop.filterName);
+  let showStocked = useSelector(({ shop }) => shop.showStocked);
+
+  // 组件首次渲染后,获取数据
+  useEffect(() => {
+    dispatch(fetchProducts());
+  }, []);
+  let prevCategory = ''; // 上一次的分类标题
+
+  const renderProductsRow = () => {
+    // 根据 products, filterName, showStocked 得到要渲染的数据列表
+    // 1 showStocked = true ; product.name.includes(fitlerName) && product.stoked
+    // 2  showStoked = false; product.name.includes(fitlerName) && true
+    let renderProducts = products.filter(
+      (product) =>
+        product.name.includes(filterName) &&
+        (showStocked ? product.stocked : true)
+    );
+
+    return renderProducts.map((product) => {
+      let categoryElement = null;
+      if (prevCategory !== product.category) {
+        categoryElement = (
+          <tr>
+            <th colSpan={2}>{product.category}</th>
+          </tr>
+        );
+        // 只要重新绘制分类行,就需要将当前分类标题更新一下
+        prevCategory = product.category;
+      }
+
+      let productNameElement = <span>{product.name}</span>;
+      if (!product.stocked) {
+        productNameElement = (
+          <span
+            style={{
+              color: 'red',
+            }}
+          >
+            {product.name}
+          </span>
+        );
+      }
+      return (
+        <Fragment key={product.name}>
+          {categoryElement}
+          <tr>
+            <td>{productNameElement}</td>
+            <td>{product.price}</td>
+          </tr>
+        </Fragment>
+      );
+    });
+  };
 
   return (
     <div className="shop-list">
       <h3 style={{ marginTop: '0' }}>商品列表</h3>
       <div className="list-header">
-        <input type="text" placeholder="Search..." />
+        <input
+          type="text"
+          placeholder="Search..."
+          value={filterName}
+          onChange={(e) => {
+            dispatch(setFilterName(e.target.value));
+          }}
+        />
         <label>
-          <input type="checkbox" />
+          <input
+            type="checkbox"
+            value={showStocked}
+            onChange={(e) => {
+              dispatch(setShowStocked(e.target.checked));
+            }}
+          />
           仅显示在库商品
         </label>
       </div>
@@ -56,22 +97,7 @@ export default function ShopList() {
               <th>商品价格</th>
             </tr>
           </thead>
-          <tbody>
-            <tr>
-              <th colSpan={2}>运动商品</th>
-            </tr>
-            <tr>
-              <td>篮球</td>
-              <td>$19.9</td>
-            </tr>
-            <tr>
-              <th colSpan={2}>电子商品</th>
-            </tr>
-            <tr>
-              <td>Iphone 14 pm</td>
-              <td>$1399.9</td>
-            </tr>
-          </tbody>
+          <tbody>{renderProductsRow()}</tbody>
         </table>
       </div>
     </div>

+ 2 - 0
15_React/day-7/code/my-app/src/store/index.js

@@ -1,11 +1,13 @@
 import { configureStore } from '@reduxjs/toolkit';
 import loaderReducer from './slices/loader';
 import systemReducer from './slices/system';
+import shopReducer from './slices/shop';
 
 const store = configureStore({
   reducer: {
     loader: loaderReducer,
     system: systemReducer,
+    shop: shopReducer,
   },
 });
 

+ 31 - 0
15_React/day-7/code/my-app/src/store/slices/shop.js

@@ -0,0 +1,31 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { getAllProducts } from '../../api/shop';
+
+export const shopSlice = createSlice({
+  name: 'shop',
+  initialState: {
+    products: [], // 存储所有商品
+    filterName: '', // 根据 商品名 过滤商品列表
+    showStocked: false, // 默认展示所有商品,如果true 就展示有库存的商品
+  },
+  reducers: {
+    setProducts(state, { payload }) {
+      state.products = payload;
+    },
+    setFilterName(state, { payload }) {
+      state.filterName = payload;
+    },
+    setShowStocked(state, { payload }) {
+      state.showStocked = payload;
+    },
+  },
+});
+
+export default shopSlice.reducer;
+export const { setProducts, setFilterName, setShowStocked } = shopSlice.actions;
+
+// 异步处理
+export const fetchProducts = () => async (dispatch) => {
+  let result = await getAllProducts();
+  dispatch(setProducts(result.data));
+};

+ 1 - 1
15_React/day-7/code/my-app/src/store/slices/system.js

@@ -17,7 +17,7 @@ export const systemSlice = createSlice({
         icon: <HomeOutlined />,
       },
       {
-        id: 1,
+        id: 2,
         key: '/shop-list',
         label: '商品列表',
         title: '商品列表',