【实战】十一、看板页面及任务组页面开发(二) —— 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 |
prettier | 2.8.4 |
json-server | 0.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>);
};
...
查看功能和效果,点击 +创建任务
输入框出现,点击输入框以外的地方输入框隐藏,输入新增任务名后回车,即可看到新任务:
部分引用笔记还在草稿阶段,敬请期待。。。
相关文章:

【实战】十一、看板页面及任务组页面开发(二) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十四)
文章目录 一、项目起航:项目初始化与配置二、React 与 Hook 应用:实现项目列表三、TS 应用:JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook&…...
Vue2.7.14、vuecli@5.0.8 升级 vite@4.4.8
项目背景 Vue2.7.14、vuecli5.0.8、element-ui2.15.13、node14.18.3 vite安装 pnpm add vite4.4.8 -D 入口文件index.html 文件位置修改 将pulic里的index.html移到根目录下 根目录/public/index.html 到 根目录/index.html 文件内容修改 <link rel"icon"…...

LeetCode[面试题04.12]求和路径
难度:Medium 题目: 给定一棵二叉树,其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法,打印节点数值总和等于某个给定值的所有路径的数量。注意,路径不一定非得从二叉树的根节点或叶节点开始或结束&#x…...

骑行运动耳机哪款好?五年骑行爱好者给你分享分享
作为一名骑行达人,我尝试过多种骑行耳机,有入耳式、耳罩式、骨传导等等,但总有一款让我特别满意。直到我遇到了这几款耳机,它不仅音质出色,而且非常适合骑行,让我爱不释手。下面,我将分享一下这…...

SpringBoot3集成ElasticSearch
标签:ElasticSearch8.Kibana8; 一、简介 Elasticsearch是一个分布式、RESTful风格的搜索和数据分析引擎,适用于各种数据类型,数字、文本、地理位置、结构化数据、非结构化数据; 在实际的工作中,历经过Ela…...
详解23种设计模式优缺点以及解决方案
1. 单例模式(Singleton Pattern): 优点:确保一个类只有一个实例,提供全局访问点,节省资源。缺点:可能引入全局状态,难以扩展和测试。解决方法:使用依赖注入来替代直接访…...
Oracle 数据库中删除表空间的详细步骤与示例
系列文章目录 文章目录 系列文章目录前言一、查看表空间二、数据迁移和备份三、下线表空间中的对象四、删除表空间五、删除完成后的操作总结前言 在 Oracle 数据库中,表空间是存储数据的逻辑容器。有时候,我们可能需要删除不再使用的表空间以释放空间或进行数据库重组。本文…...
<kernel>kernel 6.4 笔记
<kernel>kernel 6.4 笔记 1、kernel 与用户层通信过程 (1) kernel 通过uevent事件 通知 用户层; 第一步:准备同事事件的参数键值对存到环境变量中; 第二步 :准备环境变量数据 ACTION、DEVPATH、SUBSYSTEM…...
介绍一些编程语言— Perl 语言
介绍一些编程语言— Perl 语言 Perl 语言 简介 Perl 是一种动态解释型的脚本语言。 最初的设计者为拉里・沃尔,它于 1987 1987 1987 年 12 12 12 月 18 18 18 日发表。Perl 借取了 C、sed、awk、shell scripting 以及很多其他编程语言的特性。其中最重要的特性…...
原型与继承
原型与继承 在 JavaScript 中,对象有一个特殊的隐藏属性 [[Prototype]](如规范中所命名的),它要么为 null,要么就是对另一个对象的引用。该对象被称为“原型。 当我们从 object 中读取一个缺失的属性时,Jav…...
Flink流批一体计算(14):PyFlink Tabel API之SQL查询
举个例子 查询 source 表,同时执行计算 # 通过 Table API 创建一张表: source_table table_env.from_path("datagen") # 或者通过 SQL 查询语句创建一张表: source_table table_env.sql_query("SELECT * FROM datagen&quo…...

JRebel插件扩展-mac版
前言 上一篇分享了mac开发环境的搭建,但是欠了博友几个优化的债,今天先还一个,那就是idea里jRebel插件的扩展。 一、场景回眸 这个如果在win环境那扩展是分分钟,一个exe文件点点就行。现在在mac环境就没有这样的dmg可以执行的&…...

C语言中常见的一些语法概念和功能
常用代码: 程序入口:int main() 函数用于定义程序的入口点。 输出:使用 printf() 函数可以在控制台打印输出。 输入:使用 scanf() 函数可以接收用户的输入。 条件判断:使用 if-else 语句可以根据条件执行不同的代码…...

Python土力学与基础工程计算.PDF-钻探泥浆制备
Python 求解代码如下: 1. rho1 2.5 # 黏土密度,单位:t/m 2. rho2 1.0 # 泥浆密度,单位:t/m 3. rho3 1.0 # 水的密度,单位:t/m 4. V 1.0 # 泥浆容积,单位:…...

【机器学习】— 2 图神经网络GNN
一、说明 在本文中,我们探讨了图神经网络(GNN)在推荐系统中的潜力,强调了它们相对于传统矩阵完成方法的优势。GNN为利用图论来改进推荐系统提供了一个强大的框架。在本文中,我们将在推荐系统的背景下概述图论和图神经网…...

QT的布局与间隔器介绍
布局与间隔器 1、概述 QT中使用绝对定位的布局方式,无法适用窗口的变化,但是,也可以通过尺寸策略来进行 调整,使得 可以适用窗口变化。 布局管理器作用最主要用来在qt设计师中进行控件的排列,另外,布局管理…...
深入浅出Pytorch函数——torch.nn.Linear
分类目录:《深入浅出Pytorch函数》总目录 对输入数据做线性变换 y x A T b yxA^Tb yxATb 语法 torch.nn.Linear(in_features, out_features, biasTrue, deviceNone, dtypeNone)参数 in_features:[int] 每个输入样本的大小out_features :…...
Vue3.2+TS的defineExpose的应用
defineExpose通俗来讲,其实就是讲子组件的方法或者数据,暴露给父组件进行使用,这样对组件的封装使用,有很大的帮助,那么defineExpose应该如何使用,下面我来用一些实际的代码,带大家快速学会defi…...
牛客网Python入门103题练习|【08--元组】
⭐NP62 运动会双人项目 描述 牛客运动会上有一项双人项目,因为报名成功以后双人成员不允许被修改,因此请使用元组(tuple)进行记录。先输入两个人的名字,请输出他们报名成功以后的元组。 输入描述: 第一…...

Jenkins改造—nginx配置鉴权
先kill掉8082的端口进程 netstat -natp | grep 8082 kill 10256 1、下载nginx nginx安装 EPEL 仓库中有 Nginx 的安装包。如果你还没有安装过 EPEL,可以通过运行下面的命令来完成安装 sudo yum install epel-release 输入以下命令来安装 Nginx sudo yum inst…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...