【實戰】十一、看板頁面及任務組頁面開發(二) —— React17+React Hook+TS4 最佳實踐,仿 Jira 企業級項目(二十四)

文章目錄

    • 一、項目起航:項目初始化與配置
    • 二、React 與 Hook 應用:實現項目列表
    • 三、TS 應用:JS神助攻 - 強類型
    • 四、JWT、用戶認證與異步請求
    • 五、CSS 其實很簡單 - 用 CSS-in-JS 添加樣式
    • 六、用戶體驗優化 - 加載中和錯誤狀態處理
    • 七、Hook,路由,與 URL 狀態管理
    • 八、用戶選擇器與項目編輯功能
    • 九、深入React 狀態管理與Redux機制
    • 十、用 react-query 獲取數據,管理緩存
    • 十一、看板頁面及任務組頁面開發
      • 1~3
      • 4.添加任務搜索功能
      • 5.優化看板樣式
      • 6.創建看板與任務


學習內容來源:React + React Hook + TS 最佳實踐-慕課網


相對原教程,我在學習開始時(2023.03)采用的是當前最新版本:

版本
react & react-dom^18.2.0
react-router & react-router-dom^6.11.2
antd^4.24.8
@commitlint/cli & @commitlint/config-conventional^17.4.4
eslint-config-prettier^8.6.0
husky^8.0.3
lint-staged^13.1.2
prettier2.8.4
json-server0.17.2
craco-less^2.0.0
@craco/craco^7.1.0
qs^6.11.0
dayjs^1.11.7
react-helmet^6.1.0
@types/react-helmet^6.1.6
react-query^6.1.0
@welldone-software/why-did-you-render^7.0.1
@emotion/react & @emotion/styled^11.10.6

具體配置、操作和內容會有差異,“坑”也會有所不同。。。


一、項目起航:項目初始化與配置

  • 一、項目起航:項目初始化與配置

二、React 與 Hook 應用:實現項目列表

  • 二、React 與 Hook 應用:實現項目列表

三、TS 應用:JS神助攻 - 強類型

  • 三、 TS 應用:JS神助攻 - 強類型

四、JWT、用戶認證與異步請求

  • 四、 JWT、用戶認證與異步請求(上)

  • 四、 JWT、用戶認證與異步請求(下)

五、CSS 其實很簡單 - 用 CSS-in-JS 添加樣式

  • 五、CSS 其實很簡單 - 用 CSS-in-JS 添加樣式(上)

  • 五、CSS 其實很簡單 - 用 CSS-in-JS 添加樣式(下)

六、用戶體驗優化 - 加載中和錯誤狀態處理

  • 六、用戶體驗優化 - 加載中和錯誤狀態處理(上)

  • 六、用戶體驗優化 - 加載中和錯誤狀態處理(中)

  • 六、用戶體驗優化 - 加載中和錯誤狀態處理(下)

七、Hook,路由,與 URL 狀態管理

  • 七、Hook,路由,與 URL 狀態管理(上)

  • 七、Hook,路由,與 URL 狀態管理(中)

  • 七、Hook,路由,與 URL 狀態管理(下)

八、用戶選擇器與項目編輯功能

  • 八、用戶選擇器與項目編輯功能(上)

  • 八、用戶選擇器與項目編輯功能(下)

九、深入React 狀態管理與Redux機制

  • 九、深入React 狀態管理與Redux機制(一)

  • 九、深入React 狀態管理與Redux機制(二)

  • 九、深入React 狀態管理與Redux機制(三)

  • 九、深入React 狀態管理與Redux機制(四)

  • 九、深入React 狀態管理與Redux機制(五)

十、用 react-query 獲取數據,管理緩存

  • 十、用 react-query 獲取數據,管理緩存(上)

  • 十、用 react-query 獲取數據,管理緩存(下)

十一、看板頁面及任務組頁面開發

1~3

  • 十一、看板頁面及任務組頁面開發(一)

4.添加任務搜索功能

接下來為任務看板添加搜索功能

編輯 src\screens\ViewBoard\utils.ts(新增 useTasksSearchParams 為后續 SearchPanel 中數據聯動做準備):

import { useMemo } from "react";
import { useLocation } from "react-router";
import { useProject } from "utils/project";
import { useUrlQueryParam } from "utils/url";...
export const useTasksSearchParams = () => {const [param, setParam] = useUrlQueryParam(["name","typeId","processorId","tagId",]);const projectId = useProjectIdInUrl();return useMemo(() => ({projectId,typeId: Number(param.typeId) || undefined,processorId: Number(param.processorId) || undefined,tagId: Number(param.tagId) || undefined,name: param.name,}),[projectId, param]);
};
...

新建 src\components\task-type-select.tsx(仿照 UserSelect 改造出一個 TaskTypeSelect):

import { useTaskTypes } from "utils/task-type";
import { IdSelect } from "./id-select";export const TaskTypeSelect = (props: React.ComponentProps<typeof IdSelect>) => {const { data: taskTypes } = useTaskTypes();return <IdSelect options={taskTypes || []} {...props} />;
};

新建 src\screens\ViewBoard\components\SearchPanel.tsx

import { useSetUrlSearchParam } from "utils/url"
import { useTasksSearchParams } from "../utils"
import { Row } from "components/lib"
import { Button, Input } from "antd"
import { UserSelect } from "components/user-select"
import { TaskTypeSelect } from "components/task-type-select"export const SearchPanel = () => {const searchParams = useTasksSearchParams()const setSearchParams = useSetUrlSearchParam()const reset = () => {setSearchParams({typeId: undefined,processorId: undefined,tagId: undefined,name: undefined})}return <Row marginBottom={4} gap={true}><Input style={{width: '20rem'}} placeholder='任務名' value={searchParams.name}onChange={e => setSearchParams({name: e.target.value})}/><UserSelect defaultOptionName="經辦人" value={searchParams.processorId}onChange={val => setSearchParams({processorId: val})}/><TaskTypeSelect defaultOptionName="類型" value={searchParams.typeId}onChange={val => setSearchParams({typeId: val})}/><Button onClick={reset}>清除篩選器</Button></Row>
}

編輯 src\screens\ViewBoard\index.tsx(引入 SearchPanel):

...
import { SearchPanel } from "./components/SearchPanel";export const ViewBoard = () => {...return (<div><h1>{currentProject?.name}看板</h1><SearchPanel/><ColumnsContainer>...</ColumnsContainer></div>);
};
...

查看功能和效果:
效果圖

5.優化看板樣式

功能實現一部分了,接下來優化樣式

編輯 src\components\lib.tsx(新增 ViewContainer 處理內邊距):

export const ViewContainer = styled.div`padding: 3.2rem;width: 100%;display: flex;flex-direction: column;
`

編輯 src\authenticated-app.tsx(調整 Main 樣式,垂直占滿):

...
const Main = styled.main`display: flex;/* overflow: hidden; */
`;

編輯 src\screens\ViewBoard\index.tsx(應用 ViewContainer ,增加 Loading 調整 ColumnsContainer 樣式并暴露出來,使其觸底):

...
import { useProjectInUrl, useTasksSearchParams, useViewBoardSearchParams } from "./utils";
...
import { ViewContainer } from "components/lib";
import { useTasks } from "utils/task";
import { Spin } from "antd";export const ViewBoard = () => {...const { data: viewboards, isLoading: viewBoardIsLoading } = useViewboards(useViewBoardSearchParams());const { isLoading: taskIsLoading } = useTasks(useTasksSearchParams())const isLoading = taskIsLoading || viewBoardIsLoadingreturn (<ViewContainer><h1>{currentProject?.name}看板</h1><SearchPanel />{isLoading ? <Spin/> : <ColumnsContainer>...</ColumnsContainer>}</ViewContainer>);
};const ColumnsContainer = styled.div`display: flex;overflow-x: scroll;flex: 1;
`;

編輯 src\screens\ProjectDetail\index.tsx(引入 Menu 并調整整個組件樣式,Menu 高亮狀態從路由中獲取):

import { Link, Navigate } from "react-router-dom";
import { Route, Routes, useLocation } from "react-router";
import { TaskGroup } from "screens/TaskGroup";
import { ViewBoard } from "screens/ViewBoard";
import styled from "@emotion/styled";
import { Menu } from "antd";const useRouteType = () => {const pathEnd = useLocation().pathname.split('/')return pathEnd[pathEnd.length - 1]
}export const ProjectDetail = () => {const routeType = useRouteType()return (<Container><Aside><Menu mode="inline" selectedKeys={[routeType]}><Menu.Item key='viewboard'><Link to="viewboard">看板</Link></Menu.Item><Menu.Item key='taskgroup'><Link to="taskgroup">任務組</Link></Menu.Item></Menu></Aside><Main><Routes><Route path="/viewboard" element={<ViewBoard />} /><Route path="/taskgroup" element={<TaskGroup />} /><Route index element={<Navigate to="viewboard" replace />} /></Routes></Main></Container>);
};const Aside = styled.aside`background-color: rgb(244, 245, 247);display: flex;
`const Main = styled.div`display: flex;box-shadow: -5px 0 5px -5px rgbs(0, 0, 0, 0.1);overflow: hidden;
`const Container = styled.div`display: grid;grid-template-columns: 16rem 1fr;width: 100%;
`

查看功能和效果:
效果圖

6.創建看板與任務

接下來新建創建看板的組件:

先準備好調用新增看板接口的 Hook,編輯 src\utils\viewboard.ts

...
export const useAddViewboard = (queryKey: QueryKey) => {const client = useHttp();return useMutation((params: Partial<Viewboard>) =>client(`kanbans`, {method: "POST",data: params,}),useAddConfig(queryKey));
};

新建組件:src\screens\ViewBoard\components\CreateViewboard.tsx

import { useState } from "react"
import { useProjectIdInUrl, useViewBoardQueryKey } from "../utils"
import { useAddViewboard } from "utils/viewboard"
import { Input } from "antd"
import { Container } from "./ViewboardCloumn"export const CreateViewBoard = () => {const [name, setName] = useState('')const projectId = useProjectIdInUrl()const { mutateAsync: addViewBoard } = useAddViewboard(useViewBoardQueryKey())const submit = async () => {await addViewBoard({name, projectId})setName('')}return <Container><Inputsize="large"placeholder="新建看板名稱"onPressEnter={submit}value={name}onChange={evt => setName(evt.target.value)}/></Container>
}

編輯:src\screens\ViewBoard\index.tsx(引入 CreateViewBoard):

...
import { CreateViewBoard } from "./components/CreateViewboard";export const ViewBoard = () => {...return (<ViewContainer>...{isLoading ? <Spin/> : <ColumnsContainer>{viewboards?.map((vbd) => (<ViewboardColumn viewboard={vbd} key={vbd.id} />))}<CreateViewBoard/></ColumnsContainer>}</ViewContainer>);
};
...

查看功能和效果,輸入新增看板名后回車,即可看到新看板:
效果圖

接下來新建創建任務的組件:

先準備好調用新增任務接口的 Hook,編輯 src\utils\task.ts

...
import { QueryKey, useMutation, useQuery } from "react-query";
import { useAddConfig } from "./use-optimistic-options";...
export const useAddTask = (queryKey: QueryKey) => {const client = useHttp();return useMutation((params: Partial<Task>) =>client(`tasks`, {method: "POST",data: params,}),useAddConfig(queryKey));
};

新建組件:src\screens\ViewBoard\components\CreateTask.tsx

import { useEffect, useState } from "react";
import { useProjectIdInUrl, useTasksQueryKey } from "../utils";
import { Card, Input } from "antd";
import { useAddTask } from "utils/task";export const CreateTask = ({kanbanId}: {kanbanId: number}) => {const [name, setName] = useState("");const { mutateAsync: addTask } = useAddTask(useTasksQueryKey());const projectId = useProjectIdInUrl();const [inputMode, setInputMode] = useState(false)const submit = async () => {await addTask({ name, projectId, kanbanId });setName("");setInputMode(false)};const toggle = () => setInputMode(mode => !mode)useEffect(() => {if (!inputMode) {setName('')}}, [inputMode])if (!inputMode) {return <div onClick={toggle}>+創建任務</div>}return (<Card><InputonBlur={toggle}placeholder="需要做些什么"autoFocus={true}onPressEnter={submit}value={name}onChange={(evt) => setName(evt.target.value)}/></Card>);
};

編輯:src\screens\ViewBoard\components\ViewboardCloumn.tsx(引入 CreateTask):

...
import { CreateTask } from "./CreateTask";...
export const ViewboardColumn = ({ viewboard }: { viewboard: Viewboard }) => {...return (<Container><h3>{viewboard.name}</h3><TasksContainer>...<CreateTask kanbanId={viewboard.id}/></TasksContainer></Container>);
};
...

查看功能和效果,點擊 +創建任務 輸入框出現,點擊輸入框以外的地方輸入框隱藏,輸入新增任務名后回車,即可看到新任務:
效果圖


部分引用筆記還在草稿階段,敬請期待。。。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/43178.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/43178.shtml
英文地址,請注明出處:http://en.pswp.cn/news/43178.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

“深入探索JVM:解析Java虛擬機的工作原理與優化“

標題&#xff1a;深入探索JVM&#xff1a;解析Java虛擬機的工作原理與優化 摘要&#xff1a;本篇博客將深入探討Java虛擬機&#xff08;JVM&#xff09;的工作原理以及如何優化JVM的性能。我們將介紹JVM的組成部分、類加載過程、內存管理、垃圾回收機制以及常見的性能優化技術…

記一次線上OOM事故

OOM 問題 linux內核有個機制叫OOM killer(Out-Of-Memory killer)&#xff0c;當系統需要申請內存卻申請不到時&#xff0c;OOM killer會檢查當前進程中占用內存最大者&#xff0c;將其殺掉&#xff0c;騰出內存保障系統正常運行。 一般而言&#xff0c;一個應用的內存逐漸增加&…

__setitem__和__getitem和__delitem__

目錄 一、__setitem__ 二、__getitem__ 三、__delitem__與__delattr__ python從小白到總裁完整教程目錄:https://blog.csdn.net/weixin_67859959/article/details/129328397?spm1001.2014.3001.5502 class Foo:def __init__(self, name):self.name namedef __getitem__(s…

mekefile 編寫

mekefile 編寫 參考 Linux下使用 autoconf和automake 自動構建 項目 make file文件 makefile 中加入shell語句 if shell 參考 foo.bak: foo.barecho "foo"if [ -d "~/Dropbox" ]; then echo "Dir exists"; fi Or foo.bak: foo.barecho &quo…

文心一言 VS 訊飛星火 VS chatgpt (76)-- 算法導論7.3 1題

一、如果用go語言&#xff0c;為什么我們分析隨機化算法的期望運行時間&#xff0c;而不是其最壞運行時間呢? 文心一言&#xff1a; 在分析算法的性能時&#xff0c;期望運行時間是一個重要的指標&#xff0c;它描述了算法在平均情況下的表現。期望運行時間考慮了所有可能的…

管理類聯考——邏輯——真題篇——按知識分類——匯總篇——二、論證邏輯——支持加強——第二節——分類2——正面支持

文章目錄 第二節 支持加強-分類2-正面支持題-支持加強-分類2-正面支持-表達“確實如此”題-支持加強-分類2-正面支持-表達“確實如此”-正面支持不直觀:轉為削弱反面更直觀真題(2010-38)-支持加強-分類2-正面支持真題(2018-29)-支持加強-分類2-正面支持-支持關鍵詞真題(…

musl libc ldso 動態加載研究筆記:02

前言 本篇繼續研究 musl libc ldso 的動態加載過程中遇到的關鍵性的概念&#xff1a;到底要加載ELF 文件的哪些內容到 內存 當前如果遇到 ELF 動態加載&#xff0c;當前系統需要有【文件系統】&#xff0c;并且有較大的內存&#xff0c;因為 ELF 文件是無法直接運行的&#xf…

IDEA兩種方法修改生成的jar包名字

方法一&#xff1a; 直接修改pom文件中的如下部分 <artifactId>excelreport</artifactId> <version>0.0.1-SNAPSHOT</version> <name>excelreport</name> <description>excelreport</description> 修改完成后&#xff0c;點…

SpringBoot3集成Kafka

標簽&#xff1a;Kafka3.Kafka-eagle3&#xff1b; 一、簡介 Kafka是一個開源的分布式事件流平臺&#xff0c;常被用于高性能數據管道、流分析、數據集成和關鍵任務應用&#xff0c;基于Zookeeper協調的處理平臺&#xff0c;也是一種消息系統&#xff0c;具有更好的吞吐量、內…

跟著美團學設計模式(感處)

讀了著篇文章之后發現真的是&#xff0c;你的思想&#xff0c;你的思維是真的比比你擁有什么技術要強的。 注 開閉原則 開閉原則&#xff08;Open-Closed Principle&#xff09;是面向對象設計中的基本原則之一&#xff0c;它的定義是&#xff1a;一個軟件實體應該對擴展開放…

python生成旗幟--比如美國國旗生成

目錄 1、解釋說明&#xff1a; 2、使用示例&#xff1a; 3、注意事項&#xff1a; 1、解釋說明&#xff1a; 在Python中&#xff0c;生成國旗可以通過使用第三方庫或者自定義函數來實現。通常&#xff0c;我們可以使用Pillow庫來處理圖像&#xff0c;以及使用matplotlib庫來…

python爬蟲7:實戰1

python爬蟲7&#xff1a;實戰1 前言 ? python實現網絡爬蟲非常簡單&#xff0c;只需要掌握一定的基礎知識和一定的庫使用技巧即可。本系列目標旨在梳理相關知識點&#xff0c;方便以后復習。 申明 ? 本系列所涉及的代碼僅用于個人研究與討論&#xff0c;并不會對網站產生不好…

carla中lka實現(二)

前言&#xff1a; 首先計算之前檢測出來的車道線的中線與輸入圖像的中線進行計算距離&#xff0c;&#xff0c;并設置不同的閾值對于不同的方向進行相關的調整。 一、車輛中心線 一般而言將攝像頭架設在車輛的正中心軸上&#xff0c;所獲得的圖像的中間線極為車輛的中心。 …

QGraphicsView 實例3地圖瀏覽器

主要介紹Graphics View框架&#xff0c;實現地圖的瀏覽、放大、縮小&#xff0c;以及顯示各個位置的視圖、場景和地圖坐標 效果圖: mapwidget.h #ifndef MAPWIDGET_H #define MAPWIDGET_H #include <QLabel> #include <QMouseEvent> #include <QGraphicsView&…

WSL2 ubuntu子系統OpenCV調用本機攝像頭的RTSP視頻流做開發測試

文章目錄 前言一、Ubuntu安裝opencv庫二、啟動 Windows 本機的 RTSP 視頻流下載解壓 EasyDarwin查看本機攝像頭設備開始推流 三、在ubuntu 終端編寫代碼創建目錄及文件創建CMakeLists.txt文件啟動 cmake 配置并構建 四、結果展示啟動圖形界面在圖形界面打開終端找到 rtsp_demo運…

linux系統服務學習(二)linux下yum源配置實戰

文章目錄 Linux下yum源配置實戰一、Linux下軟件包的管理1、軟件安裝方式2、源碼安裝的配置過程3、詳解源碼安裝的配置過程&#xff08;定制&#xff09;4、詳解編譯過程5、安裝過程6、axel多線程下載軟件源碼安裝7、使用軟鏈接解決command not found8、使用環境變量解決command…

軟考A計劃-系統集成項目管理工程師-收尾管理

點擊跳轉專欄>Unity3D特效百例點擊跳轉專欄>案例項目實戰源碼點擊跳轉專欄>游戲腳本-輔助自動化點擊跳轉專欄>Android控件全解手冊點擊跳轉專欄>Scratch編程案例點擊跳轉>軟考全系列點擊跳轉>藍橋系列 &#x1f449;關于作者 專注于Android/Unity和各種游…

字符串的無重復排列組合

題目描述&#xff1a; 無重復字符串的排列組合。編寫一種方法&#xff0c;計算某字符串的所有排列組合&#xff0c;字符串每個字符均不相同。 示例1: 輸入&#xff1a;S "qwe" 輸出&#xff1a;["qwe", "qew", "wqe", "weq&q…

中間件(二)dubbo負載均衡介紹

一、負載均衡概述 支持輪詢、隨機、一致性hash和最小活躍數等。 1、輪詢 ① sequences&#xff1a;內部的序列計數器 ② 服務器接口方法權重一樣&#xff1a;&#xff08;sequences1&#xff09;%服務器的數量&#xff08;決定調用&#xff09;哪個服務器的服務。 ③ 服務器…

opencv直方圖與模板匹配

import cv2 #opencv讀取的格式是BGR import numpy as np import matplotlib.pyplot as plt#Matplotlib是RGB %matplotlib inline def cv_show(img,name):cv2.imshow(name,img)cv2.waitKey()cv2.destroyAllWindows() 直方圖 cv2.calcHist(images,channels,mask,histSize,ran…