iT邦幫忙

2023 iThome 鐵人賽

DAY 25
0

今天這兩個頁面比較單純的只是呼叫API去取得當前的資料,並顯示於頁面上。

大綱

  1. 文章列表頁開發
  2. 標籤頁開發
  3. 路由設定

1.文章列表頁開發

Wireframe

https://ithelp.ithome.com.tw/upload/images/20231010/20136558N0FAZ3SQBc.jpg

頁面共用元件開發

先建立共用的元件

  • PostItem(文章樣式)
  • Tag(文章內的標籤)

PostItem.js
components資料夾底下建立PostItem.js

//PostItem.js

import avatar from '../../assets/avatar.jpg';
import Tag from '../UI/Tag';
import { useEffect, useState } from 'react';

const PostItem = ({post}) =>{
  const { content, title, tags, coverImage } = post;

  const [summary, setSummary] =  useState('');

  //取得文章部分內容,顯示在文章預覽上
  const getSummary = () =>{
    const domParser = new DOMParser();
    const doc = domParser.parseFromString(content, 'text/html');
    const textContent = doc.body.textContent;
    setSummary(textContent);
  } 

  useEffect(()=>{
    getSummary();
  },[]);

   return (
     <div className="flex items-center justify-between w-full mb-8">
       <div>
         <div className="flex items-center mt-2">
           <div className="text-left pr-4">
             <h3 className="text-2xl font-semibold">{title}</h3>
             <p className="text-m text-gray-600">{summary}</p>
             <div className="flex items-center my-4">
               <div className="w-[32px] h-[32px] rounded-full border border-gray-200 overflow-hidden">
                 <img src={avatar} alt="avatar" />
               </div>
               <p className="text-violet-600 ml-2 text-sm">Jonas Kakaroto</p>
               <p className="text-sm text-gray-400 ml-3 text-sm">Jan.10.2023</p>
             </div>
           </div>
         </div>
         <div className="flex items-center text-sm">
           {tags.map((tag,index) => (
            <Tag name={tag} key={index} classes={"mr-2"} />
           ))}
         </div>
       </div>
       <div className="w-[100px] h-[100px] min-w-[100px] overflow-hidden">
        {
          coverImg && <img className="h-[100px] max-w-none" src={coverImg} alt="cover" />
        }
       </div>
     </div>
   );
}

export default PostItem;

Tag.js
components資料夾底下建立Tag.js

const Tag = ({ classes, name }) => {
  return (
    <button
      className={`bg-violet-100 text-gray-500 text-sm rounded-full py-1 px-2  ${classes}`}
    >
      {name}
    </button>
  );
};
export default Tag;

文章列表頁面開發

pages資料夾底下新增PostList.js檔案

//PostList

import { useEffect,useState } from 'react';
import api from '../api/api';
import PostItem from '../components/Posts/PostItem';

const PostList = props =>{
    //顯示的文章內容
    const [postList, setPostList] = useState([]);

    //查詢的關鍵字
    const [keyword, setKeyword] =  useState('');

    useEffect(() => {
      getPageData();
    },[])

    const getPageData = () =>{
         //進到頁面時先取得近期20筆的文章資料,用於顯示在頁面上
        api.get('/posts?top=20')
        .then((result) => {
            setPostList(result.data);
        })
        .catch((error) => {
            console.error(error);
        });
    }

    //處理關鍵字輸入
    const handleInputChange = (event) =>{
        const value = event.target.value;
        setKeyword(value);
    }


    //查詢功能 GET /posts,將輸入的標題關鍵字當作查詢條件
    const handleSearch = () =>{
        const url = `/posts?title=${keyword}`;
        api.get(url).then(response => {
            setPostList(response.data);
        }).catch((error) => {
            console.error(error);
        });      
    }

    return <div className="w-[600px] mx-auto">
        <h1 className=" mt-24 mb-4 text-4xl text-black custom-font text-center">Posts</h1>
    
        <div className="flex items-center mb-6">
            <input
                id="email"
                type="text"
                placeholder="Enter Title"
                value={keyword}
                onChange={handleInputChange}
                className={`block w-full px-3 py-2 bg-white border 
                rounded-md text-sm shadow-sm placeholder-slate-400
                                        focus:outline-none focus:ring-1`}
            />
            <button className="w-[120px] py-2 px-3 ml-4 text-white bg-violet-600  rounded-md disabled:opacity-30"
                    disabled={!keyword}
                    onClick={handleSearch}>
                Search
            </button>
        </div>
        <div className="w-full">
            <p className="text-gray-400 text-sm">
            result:
            <span className="ml-2 text-gray-500">{postList.length || 0}
            </span> entries
            </p>
        </div>
        <div className="py-8"> 
            { postList.map(post => <PostItem key={post._id} post={post}/>)}
        </div>

    </div>
}

export default PostList;

2.標籤頁開發

Wireframe

https://ithelp.ithome.com.tw/upload/images/20231010/20136558dqiVTguG53.jpg

頁面共用元件開發

components資料夾底下建立共用UI元件TagItem

//TagItem

import avatar from '../../assets/avatar.jpg';

const TagItem = ({posts,tag}) =>{
   return <div className="w-full mb-8">
            <h1 className="text-violet-600 text-2xl">{tag}</h1>
            <div className="ml-8">
                {
                    posts.map((post,index) => (
                    <div className="mt-5" key={index}>
                        <h3 className="text-2xl mb-2">{post.title}</h3>
                        <div className="flex items-center">
                            <div className="w-[32px] h-[32px] rounded-full border border-gray-200 overflow-hidden">
                                <img src={avatar} alt="avatar"/>
                            </div>
                            <p className="text-violet-600 ml-2 text-sm">Jonas Kakaroto</p>
                            <p className="text-sm text-gray-400 ml-3 text-sm">{post.createdDate}</p>
                        </div>
                    </div>
                    ))
                }
            </div>
           
        </div>
}

export default TagItem;

標籤頁面開發

pages資料夾底下新增TagList.js檔案

//TagList.js

import TagItem from "../components/TagItem";
import api from "../api/api";
import { useState, useEffect } from "react";

const TagList = (props) => {
  const [tagList, setTagList] = useState([]);
  const [tag, setTag] = useState("");
    
  //查詢,GET /posts/byTag 將輸入的tag當作查詢條件
  const handleSearch = () => {
    const url = `/posts/byTag?tag=${tag}`;
    api
      .get(url)
      .then((response) => {
        setTagList(response.data);
      })
      .catch((error) => {
        console.error(error);
      });
  };

  //取得當前頁面資料  GET /posts/byTag
  const getDataByTag = () => {
    const url = `/posts/byTag`;
    api
      .get(url)
      .then((response) => {
        setTagList(response.data);
      })
      .catch((error) => {
        console.error(error);
      });
  };

  const handleInputChange = (event) => {
    const value = event.target.value;
    setTag(value);
  };

  useEffect(() => {
    getDataByTag();
  }, []);

  return (
    <div className="w-[600px] mx-auto">
      <h1 className=" mt-24 mb-4 text-4xl text-black custom-font text-center">
        Tags
      </h1>

      <div className="flex items-center mb-6">
        <input
          id="email"
          type="text"
          placeholder="Enter Tags Name"
          value={tag}
          onChange={handleInputChange}
          className={`block w-full px-3 py-2 bg-white border 
                rounded-md text-sm shadow-sm placeholder-slate-400
                                        focus:outline-none focus:ring-1`}
        />
        <button
          className="w-[120px] py-2 px-3 ml-4 text-white bg-violet-600  rounded-md"
          onClick={handleSearch}
        >
          Search
        </button>
      </div>

      <div className="py-8">
        { 
            tagList && tagList.map((data, index) => 
                <TagItem key={index} posts={data.posts} tag={data.tag} />
            )
        }
      </div>
    </div>
  );
};

export default TagList;

3.路由設定

回到App.js設定今天新增的兩個頁面路由,這樣點擊導覽列(Header.js)的時候才會切換到對應的頁面

//App.js

import './App.css';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { AuthProvider } from './contexts/AuthContext';

import Login  from './pages/Login';
import Register from './pages/Register';
import RootLayout from './pages/Root';
import HomePage from './pages/Home';
import TagList  from './pages/TagList'; //新增的Tag頁面
import PostList from './pages/PostList'; //新增的Post頁面

const router = createBrowserRouter([
  {
    path: '/', 
    element: <RootLayout/>,
    children:[
      { path: '/', element: <HomePage />},
      { path: '/posts', element: <PostList />},
      { path: '/tags', element: <TagList />}
    ]
  },
  {
    path: '/login', 
    element: <Login/>,
  },
  {
    path: '/register', 
    element: <Register/>,
  }
])

const App = () => {
  return (
    <AuthProvider>
      <RouterProvider router={router}/>
    </AuthProvider>
  );
}

export default App;

今日的程式碼在此


上一篇
[Day24] 框架頁面和首頁切版
下一篇
[Day26]發布文章頁面開發和React Quill使用
系列文
初探全端之旅: 以MERN技術建立個人部落格31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言