今天這兩個頁面比較單純的只是呼叫API去取得當前的資料,並顯示於頁面上。
先建立共用的元件
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;
在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;
回到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;
今日的程式碼在此