在這篇博客中,我們將逐步構建一個 求職網站應用程式,使用 React.js(搭配 Vite 進行設置)、Node.js(使用 Express)、SerpApi 從 Google 職位中獲取求職清單,以及使用 Material-UI (MUI) 進行樣式設計。

到本教程結束時,您將擁有一個功能性的求職網站,使用者可以搜尋工作並查看從 Google 求職清單中獲取的結果。

以下是此專案的演示:

demo1

demo2

demo3

demo4

先決條件
要跟隨進行,您需要:

  • 基本的 React.js 和 Node.js 知識
  • 安裝 Node.js
  • SerpApi 帳戶及 API 金鑰
  • Vite 用於專案設置
  • MUI 用於樣式設計

1. 在 SerpAPI 網站上創建帳戶:

網站連結: https://serpapi.com/

SerpAPI 網站

您可以創建一個帳戶或登錄(如果帳戶已存在)。

接下來,選擇左側邊欄中的 API 金鑰區域,選擇生成新金鑰、使用現有金鑰或創建新金鑰。

獲取 API 金鑰

2. 專案結構

最終的專案結構如下,包含伺服器和客戶端:

job-board/
│
├── job-board-client/            # 前端 (React + Vite)
│   ├── node_modules/
│   ├── public/
│   ├── src/
│   │   ├── components/
│   │   │   ├── SearchBar.jsx     # 搜尋欄元件
│   │   │   ├── JobList.jsx       # 工作清單顯示元件
│   │   ├── App.jsx               # 主應用程式元件
│   │   ├── main.jsx              # React 應用程式的切入點
│   │   └── index.css             # 全域 CSS
│   ├── .gitignore
│   ├── index.html                # 主 HTML 文件
│   ├── package.json              # 相依項目和腳本
│   ├── vite.config.js            # Vite 設定
│   └── README.md
│
├── job-board-server/             # 後端 (Node.js + Express)
│   ├── node_modules/
│   ├── index.js                  # Express 伺服器的切入點
│   ├── .env                      # 環境變數(例如:SERP_API_KEY)
│   ├── package.json              # 相依項目和腳本
│   ├── .gitignore
│   └── README.md

3. 創建根文件夾 job-board

mkdir job-board
cd job-board/

4. 初始化 React 前端(Vite + React.js)

首先使用 Vite 設置 React 專案。

# 使用 Vite 創建 React 專案
npm create vite@latest job-board-client --template react

# 進入專案目錄
cd job-board-client

# 安裝相依項目
npm install

步驟1

步驟2

安裝 Material-UI (MUI) 用於樣式設計,以及 axios 用於 API 調用。

# MUI Core 和圖示用於樣式設計
npm install axios @mui/material @emotion/react @emotion/styled @mui/icons-material

5. 初始化 Node.js 後端(Express)

接下來,創建一個後端資料夾並初始化 Express 伺服器。要創建後端,您必須位於 job-board 中。可以透過運行 cd .. 命令達成。
之後運行:

# 創建後端目錄
mkdir job-board-server
cd job-board-server

# 初始化 Node.js 專案
npm init -y

# 安裝 Express
npm install express cors axios dotenv

步驟3

步驟4

6. 在 job-board-server 目錄中設置基本的 Express 伺服器(index.js)。

job-board-server 資料夾中創建 index.js 文件,並創建返回工作的 API。

const express = require('express');
const cors = require('cors');
const axios = require('axios');
require('dotenv').config();

const app = express();
app.use(cors());

const PORT = process.env.PORT || 5000;

// 獲取工作清單的端點
app.get('/api/jobs', async (req, res) => {
  const { query } = req.query;

  try {
    const serpApiUrl = `https://serpapi.com/search.json?engine=google_jobs&q=${query}&api_key=${process.env.SERP_API_KEY}`;
    const response = await axios.get(serpApiUrl);
    res.json(response.data.jobs_results || []);
  } catch (error) {
    res.status(500).json({ error: '獲取工作的過程中發生錯誤' });
  }
});

app.listen(PORT, () => {
  console.log(`伺服器正在端口 ${PORT} 上運行`);
});

7. 添加環境變數

在您的 job-board-server 目錄中創建一個 .env 文件,並添加您的 SerpApi API 金鑰。

SERP_API_KEY=your_serp_api_key_here

8. 前端:工作搜尋 UI(React + MUI)

在 React 專案中,創建一個搜尋元件,允許用戶輸入搜尋詞並查看工作結果。

  1. src/components/SearchBar.jsx 中創建一個 SearchBar 元件。
import React, { useState } from 'react';
import { TextField, Button, CircularProgress } from '@mui/material';

const SearchBar = ({ onSearch, loading }) => {
  const [query, setQuery] = useState('');

  const handleSearch = () => {
    if (query.trim()) {
      onSearch(query);
    }
  };

  return (
    <div style={{ display: 'flex', justifyContent: 'center', marginTop: '20px' }}>
      <TextField
        label="搜尋工作"
        variant="outlined"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        style={{ marginRight: '10px', width: '300px' }}
      />
      <Button 
        variant="contained" 
        color="primary" 
        onClick={handleSearch} 
        disabled={loading}
      >
        {loading ? <CircularProgress size={24} /> : '搜尋'}
      </Button>
    </div>
  );
};

export default SearchBar;
  1. src/components/JobList.jsx 中創建一個 JobList 元件,顯示工作結果。
import React from 'react';
import { Card, CardContent, CardActions, Typography, Button, Grid, CircularProgress, Box } from '@mui/material';
import { WorkOutline } from '@mui/icons-material';

const JobCard = ({ job }) => {
    return (
        <Card
            sx={{
                display: 'flex',
                flexDirection: 'column',
                justifyContent: 'space-between',
                boxShadow: '0 4px 8px #1976d2',
                p: 2,
                mt:3
            }}>
            <CardContent>
                <Typography variant="h5" component="div">
                    {job.title}
                </Typography>
                <Typography sx={{ mb: 1.5 }} color="text.secondary">
                    {job.company_name} - {job.location}
                </Typography>
                <Typography variant="body2">
                    {job.description.slice(0, 150)}... {/* 預覽描述的一部分 */}
                </Typography>
            </CardContent>
            <CardActions>
                <Button
                    sx={{
                        backgroundColor: '#1976d2',
                        color: '#fff',
                        '&:hover': {
                            backgroundColor: '#1565c0',
                        },
                        width: '100%',
                    }}
                    size="small" href={job.share_link} target="_blank" rel="noopener">
                    申請
                </Button>
            </CardActions>
        </Card>
    );
};

const JobList = ({ jobs, loading }) => {
    if (loading) {
        return (
          <Box display="flex" justifyContent="center" marginTop="20px">
            <CircularProgress />
          </Box>
        );
    }

    if (jobs.length === 0) {
        return (
          <Box display="flex" justifyContent="center" alignItems="center" flexDirection="column" marginTop="20px">
            <WorkOutline style={{ fontSize: 60, color: 'gray' }} />
            <Typography variant="h6" color="textSecondary">
              沒有可用的工作
            </Typography>
          </Box>
        );
    }

    return (
        <Grid container spacing={2}>
            {jobs.map((job, index) => (
                <Grid item xs={12} sm={6} md={4} key={index}>
                    <JobCard job={job} />
                </Grid>
            ))}
        </Grid>
    );
};

export default JobList;
  1. 在 App.jsx 中整合所有部分。
import React, { useState } from 'react';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import SearchBar from './components/SearchBar';
import JobList from './components/JobList';
import axios from 'axios';
import { Container } from '@mui/material';

const theme = createTheme({
  palette: {
    primary: {
      main: '#1976d2',
    },
    secondary: {
      main: '#ff4081',
    },
  },
});

const App = () => {
  const [jobs, setJobs] = useState([]);
  const [loading, setLoading] = useState(false);

  const handleSearch = async (query) => {
    try {
      setLoading(true);
      const response = await axios.get(`http://localhost:5000/api/jobs`, {
        params: { query }
      });
      setJobs(response.data);
      setLoading(false);
    } catch (error) {
      console.error('獲取工作清單時發生錯誤:', error);
      setLoading(false);
    }
  };

  return (
    <ThemeProvider theme={theme}>
      <Container>
      <SearchBar onSearch={handleSearch} loading={loading} />
      <JobList jobs={jobs} loading={loading} />
      </Container>
    </ThemeProvider>
  );
};

export default App;

9. 啟動伺服器

確保後端和前端都在運行。

# 在 job-board-server 資料夾中
node index.js
# 在 job-board-client 資料夾中
npm run dev

🎉 您現在已經構建了一個功能完整的求職網站應用程式!前端使用 React.js ⚛️ 构建,並使用 Material-UI 🎨 進行樣式設計,而後端則使用 Node.js 🚀 和 Express 來提供來自 SerpApi 🌐 的求職清單。

這是一個很好的起點,可以添加更先進的功能,例如按位置篩選 📍、添加工作細節頁面 📄,甚至允許用戶保存工作清單 💾。

這篇博客就到此為止!請持續關注更多更新,並繼續構建精彩的應用程式!💻✨
祝您編碼愉快!😊


原文出處:https://dev.to/jagroop2001/how-to-create-your-own-job-board-web-app-using-reactjs-nodejs-serpapi-and-mui-4be9


共有 0 則留言