KuoHsiangYu

DataGridPage03

Nov 20th, 2025
405
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import React, { useState, useMemo, useEffect } from "react";
  2. import "./DataGridPage.css";
  3. import apiService from "./service/dataGridService"; /* apiService 是自行客製化命名 */
  4.  
  5. function DataGridPage() {
  6.  
  7.   const [tableHeader, setTableHeader] = useState([]); // table header.
  8.   const [tableBody, setTableBody] = useState([]); // table body.
  9.  
  10.   const [searchText, setSearchText] = useState(""); // 搜尋.
  11.   const [sortConfig, setSortConfig] = useState({ key: null, direction: "asc" }); // 排序.
  12.   const [currentPage, setCurrentPage] = useState(1); // 第幾頁.
  13.   const [rowsPerPage, setRowsPerPage] = useState(5); // 多少筆資料做分頁.
  14.  
  15.   useEffect(() => {
  16.     /* 當 元件 render 完畢時,fetch data 打後端 Web API 抓取資料 */
  17.     console.log("#1 mounted data");
  18.  
  19.     // fetch data.
  20.     const tempTableHeader = apiService.getTableHeader();
  21.     const tempTableBody = apiService.getTableBody();
  22.  
  23.     console.log("tempTableHeader ", tempTableHeader);
  24.     console.log("tempTableBody ", tempTableBody);
  25.  
  26.     setTableHeader(tempTableHeader);
  27.     setTableBody(tempTableBody);
  28.  
  29.     return () => {
  30.       /* 當元件卸載時清除 */
  31.       console.log("#2 unmounted _ ");
  32.     };
  33.   }, []);
  34.  
  35.   // 搜尋功能.
  36.   const filteredData = useMemo(() => {
  37.     return tableBody.filter((row) =>
  38.       row.title.toLowerCase().includes(searchText.toLowerCase())
  39.     );
  40.   }, [searchText, tableBody]);
  41.  
  42.   // 排序功能.
  43.   const sortedData = useMemo(() => {
  44.     if (!sortConfig.key) {
  45.       // key 為 null ,則無排序。.
  46.       return filteredData;
  47.     }
  48.  
  49.     const sorted = [...filteredData].sort((a, b) => {
  50.       if (a[sortConfig.key] < b[sortConfig.key]) {
  51.         return (sortConfig.direction === "asc") ? -1 : 1;
  52.       }
  53.       if (a[sortConfig.key] > b[sortConfig.key]) {
  54.         return (sortConfig.direction === "asc") ? 1 : -1;
  55.       }
  56.       return 0;
  57.     });
  58.     return sorted;
  59.   }, [filteredData, sortConfig]);
  60.  
  61.   // 使用者點擊,啟動排序功能。.
  62.   function requestSort(hKey) {
  63.     // asc 從小排到大.
  64.     // desc 從大排到小.
  65.     let direction = "asc";
  66.     if (sortConfig.key === hKey && sortConfig.direction === "asc") {
  67.       // asc 再按一次,進入 desc排序狀態.
  68.       direction = "desc";
  69.     }
  70.     if (sortConfig.key === hKey && sortConfig.direction === "desc") {
  71.       // desc 再按一次解除排序.
  72.       hKey = null;
  73.       direction = "asc";
  74.     }
  75.     setSortConfig({
  76.       key: hKey,
  77.       direction: direction
  78.     });
  79.   };
  80.  
  81.   // 分頁功能.
  82.   // 計算總共幾頁.
  83.   const totalPages = Math.ceil(sortedData.length / rowsPerPage);
  84.  
  85.   /* 取出某一頁的指定筆數 sortedData.slice(起始值, 終止值) */
  86.   const pagedData = sortedData.slice(((currentPage - 1) * rowsPerPage), (currentPage * rowsPerPage));
  87.  
  88.   function handlePageChange(page) {
  89.     if ((page >= 1) && (page <= totalPages)) {
  90.       setCurrentPage(page);
  91.     }
  92.   };
  93.  
  94.   // 本地化價格標註,標註新台幣。.
  95.   function getPrice(price) {
  96.     return `${price} NT`;
  97.   };
  98.  
  99.   // sort 排序標籤的 CSS Class.
  100.   function getSortClass(key, sortConfig) {
  101.     if (sortConfig.key !== key) {
  102.       return "";
  103.     }
  104.     return (sortConfig.direction === "asc") ? "asc" : "desc";
  105.   }
  106.  
  107.   // true 代表這個欄位 【不排序】.
  108.   // false 這個欄位 【排序】.
  109.   function isNonSortableFun(key) {
  110.     if (key === "url") {
  111.       return true;
  112.     }
  113.     return false;
  114.   }
  115.  
  116.   // 點擊 URL 彈出子視窗.
  117.   function openPopup(url) {
  118.     window.open(url, "popup", "width=800,height=600,scrollbars=yes");
  119.   }
  120.  
  121.   return (
  122.     <div className="page-container">
  123.       {/* Header */}
  124.       <header className="header">
  125.         <h2>React Data Grid Table</h2>
  126.       </header>
  127.  
  128.       {/* Main */}
  129.       <main className="main-content">
  130.         {/* 搜尋與分頁選擇 */}
  131.         <div className="top-controls">
  132.           <input
  133.             type="text"
  134.             placeholder="搜尋 Title..."
  135.             value={searchText}
  136.             onChange={(e) => {
  137.               setSearchText(e.target.value);
  138.               setCurrentPage(1);
  139.             }}
  140.           />
  141.           <br />
  142.  
  143.           {/* 資料表 */}
  144.           <table className="data-table">
  145.             <thead>
  146.               <tr>
  147.                 {tableHeader.map((header) => {
  148.                   /* 『true』 表示此欄位不排序. */
  149.                   const isNonSortable = isNonSortableFun(header.key);
  150.  
  151.                   const tSortClass = getSortClass(header.key, sortConfig);
  152.  
  153.                   /* check 檢查 當前的 欄位 是 排序欄位. */
  154.                   const isActive = (sortConfig.key === header.key);
  155.  
  156.                   /* 切換顯示上下符號icon排序 */
  157.                   const tempArrow = isActive && (sortConfig?.direction === "asc" ? "↑" : "↓");
  158.  
  159.                   return (
  160.                     /* <th key={header.key} scope="col"> */
  161.                     <th key={header.key}>
  162.                       {isNonSortable ? (
  163.                         /* 不可排序. */
  164.                         <span>{header.label}</span>
  165.                       ) : (
  166.                         /* 其它欄位顯示為可操作按鈕,並會顯示排序 icon. */
  167.                         <div onClick={() => requestSort(header.key)}>
  168.                           <span>{header.label}</span>
  169.                           <span className={`sort-icon ${tSortClass}`}>
  170.                             <span>| {tempArrow}</span>
  171.                           </span>
  172.                         </div>
  173.                       )}
  174.                     </th>
  175.                   );
  176.                 })}
  177.               </tr>
  178.             </thead>
  179.             <tbody>
  180.               {(pagedData.length > 0) ? (
  181.                 pagedData.map((row) => {
  182.                   return (
  183.                     <tr key={`${row.No}_computer`}>
  184.                       <td>{row.No}</td>
  185.                       <td>{row.title}</td>
  186.                       <td>{row.content}</td>
  187.                       <td className="price">
  188.                         {getPrice(row.price.toLocaleString())}
  189.                       </td>
  190.                       <td>
  191.                         <a
  192.                           onClick={() => openPopup(row.url)}
  193.                           style={{ color: "blue", textDecoration: "underline" }}
  194.                         >
  195.                           詳情頁
  196.                         </a>
  197.                       </td>
  198.                     </tr>
  199.                   );
  200.                 })
  201.               ) : (
  202.                 <tr>
  203.                   <td colSpan={4} className="no-data">
  204.                     找不到符合的資料
  205.                   </td>
  206.                 </tr>
  207.               )}
  208.             </tbody>
  209.           </table>
  210.  
  211.           <br />
  212.           <br />
  213.  
  214.         </div>
  215.  
  216.         {/* 分頁控制 */}
  217.         <div>
  218.           <div className="pagination">
  219.             <div className="select-container">
  220.               <label>每頁筆數:</label>
  221.               <select
  222.                 value={rowsPerPage}
  223.                 onChange={(e) => {
  224.                   setRowsPerPage(Number(e.target.value));
  225.                   setCurrentPage(1);
  226.                 }}
  227.               >
  228.                 <option value={5}>5</option>
  229.                 <option value={10}>10</option>
  230.               </select>
  231.             </div>
  232.  
  233.             <p>
  234.               &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
  235.             </p>
  236.  
  237.             <button
  238.               onClick={() => handlePageChange(currentPage - 1)}
  239.               disabled={currentPage === 1}
  240.             >
  241.               上一頁
  242.             </button>
  243.             <span>
  244.               頁面 {currentPage} / {totalPages}
  245.             </span>
  246.             <button
  247.               onClick={() => handlePageChange(currentPage + 1)}
  248.               disabled={currentPage === totalPages}
  249.             >
  250.               下一頁
  251.             </button>
  252.           </div>
  253.  
  254.         </div>
  255.       </main >
  256.  
  257.       {/* Footer */}
  258.       < footer className="footer" >React DataGrid 小練習</footer >
  259.     </div >
  260.   );
  261. }
  262.  
  263. export default DataGridPage;
  264.  
Advertisement
Add Comment
Please, Sign In to add comment