【实战】十一、看板页面及任务组页面开发(五) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十七)
文章目录
- 一、项目起航:项目初始化与配置
- 二、React 与 Hook 应用:实现项目列表
- 三、TS 应用:JS神助攻 - 强类型
- 四、JWT、用户认证与异步请求
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式
- 六、用户体验优化 - 加载中和错误状态处理
- 七、Hook,路由,与 URL 状态管理
- 八、用户选择器与项目编辑功能
- 九、深入React 状态管理与Redux机制
- 十、用 react-query 获取数据,管理缓存
- 十一、看板页面及任务组页面开发
- 1~3
- 4~6
- 7&8
- 9&10
- 11.排序乐观更新
- 12.任务组页面 (上)
学习内容来源: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~6
- 十一、看板页面及任务组页面开发(二)
7&8
- 十一、看板页面及任务组页面开发(三)
9&10
- 十一、看板页面及任务组页面开发(四)
11.排序乐观更新
新建 src\utils\reorder.ts (这部分视频没有详细讲。。。之后项目中如果用到可以深挖一下):
/*** 在本地对排序进行乐观更新* @param fromId 要排序的项目的id* @param type 'before' | 'after'* @param referenceId 参照id* @param list 要排序的列表, 比如tasks, kanbans*/
export const reorder = ({fromId,type,referenceId,list,
}: {list: { id: number }[];fromId: number;type: "after" | "before";referenceId: number;
}) => {const copiedList = [...list];// 找到fromId对应项目的下标const movingItemIndex = copiedList.findIndex((item) => item.id === fromId);if (!referenceId) {return insertAfter([...copiedList], movingItemIndex, copiedList.length - 1);}const targetIndex = copiedList.findIndex((item) => item.id === referenceId);const insert = type === "after" ? insertAfter : insertBefore;return insert([...copiedList], movingItemIndex, targetIndex);
};/*** 在list中,把from放在to的前边* @param list* @param from* @param to*/
const insertBefore = (list: unknown[], from: number, to: number) => {const toItem = list[to];const removedItem = list.splice(from, 1)[0];const toIndex = list.indexOf(toItem);list.splice(toIndex, 0, removedItem);return list;
};/*** 在list中,把from放在to的后面* @param list* @param from* @param to*/
const insertAfter = (list: unknown[], from: number, to: number) => {const toItem = list[to];const removedItem = list.splice(from, 1)[0];const toIndex = list.indexOf(toItem);list.splice(toIndex + 1, 0, removedItem);return list;
};
编辑 src\utils\use-optimistic-options.ts(在之前写的配置中调用,完成乐观更新):
...
export const useReorderViewboardConfig = (queryKey: QueryKey) =>useConfig(queryKey, (target, old) => reorder({ list: old, ...target }));export const useReorderTaskConfig = (queryKey: QueryKey) =>useConfig(queryKey, (target, old) => {const orderedList = reorder({ list: old, ...target }) as Task[];return orderedList.map((item) =>item.id === target.fromId? { ...item, kanbanId: target.toKanbanId }: item);});
由于 task 的排序有可能是跨 面板 的,因此会复杂一些
查看效果,发现在拖拽到其他面板后,若是原面板为空,拖不回去了。。。因此需要为 DropChild 加一个最小高度
编辑 src\screens\ViewBoard\components\ViewboardCloumn.tsx:
...
export const ViewboardColumn = React.forwardRef<...>((...) => {...return (<Container {...props} ref={ref}>...<TasksContainer><Drop {...}><DropChild style={{minHeight: '5px'}}>...</DropChild></Drop><CreateTask kanbanId={viewboard.id} /></TasksContainer></Container>);
});
...
至此拖拽大功告成!
12.任务组页面 (上)
看板页面开发完,接下来是任务组页面
新建 src\types\TaskGroup.ts:
export interface TaskGroup {id: number;name: string;projectId: number;// 开始时间start: number;// 结束时间end: number;
}
新建 src\utils\taskGroup.ts(与看板 Viewboard(kanban) 类似,可以复制后修改):
import { cleanObject } from "utils";
import { useHttp } from "./http";
import { TaskGroup } from "types/TaskGroup";
import { QueryKey, useMutation, useQuery } from "react-query";
import {useAddConfig,useDeleteConfig,
} from "./use-optimistic-options";export const useTaskGroups = (param?: Partial<TaskGroup>) => {const client = useHttp();return useQuery<TaskGroup[]>(["taskgroups", param], () =>client("epics", { data: cleanObject(param || {}) }));
};export const useAddTaskGroup = (queryKey: QueryKey) => {const client = useHttp();return useMutation((params: Partial<TaskGroup>) =>client(`epics`, {method: "POST",data: params,}),useAddConfig(queryKey));
};export const useDeleteTaskGroup = (queryKey: QueryKey) => {const client = useHttp();return useMutation((id?: number) =>client(`epics/${id}`, {method: "DELETE",}),useDeleteConfig(queryKey));
};
新建 src\screens\TaskGroup\utils.ts:
import { useProjectIdInUrl } from "screens/ViewBoard/utils";export const useTaskGroupSearchParams = () => ({projectId: useProjectIdInUrl(),
});export const useTaskGroupsQueryKey = () => ["taskgroups",useTaskGroupSearchParams(),
];
修改 src\types\Task.ts(属性字段需要和实际数据一致。。。):
export interface Task {id: number;name: string;projectId: number;processorId: number; // 经办人epicId: number; // 任务组(原 taskGroupId)kanbanId: number;typeId: number; // bug or tasknote: string;
}
编辑 src\screens\TaskGroup\index.tsx(之前新建路由时创建过,页面布局有一部分与看板相同,可以拿过来 src\screens\ViewBoard\index.tsx):
import { Row, ViewContainer } from "components/lib";
import { useProjectInUrl } from "screens/ViewBoard/utils";
import { useTaskGroups } from "utils/taskGroup";
import { useTaskGroupSearchParams, useTaskGroupsQueryKey } from "./utils";
import { Button, List, Modal } from "antd";
import dayjs from "dayjs";
import { useTasks } from "utils/task";
import { Link } from "react-router-dom";
import { TaskGroup } from "types/TaskGroup";
import { useState } from "react";export const TaskGroupIndex = () => {const { data: currentProject } = useProjectInUrl();const { data: taskGroups } = useTaskGroups(useTaskGroupSearchParams());const { data: tasks } = useTasks({ projectId: currentProject?.id });return (<ViewContainer><Row between={true}><h1>{currentProject?.name}任务组</h1><Button onClick={() => setEpicCreateOpen(true)} type={"link"}>创建任务组</Button></Row><Liststyle={{ overflow: "scroll" }}dataSource={taskGroups}itemLayout={"vertical"}renderItem={(taskGroup) => (<List.Item><List.Item.Metatitle={<Row between={true}><span>{taskGroup.name}</span><Button onClick={() => {}} type={"link"}>删除</Button></Row>}description={<div><div>开始时间:{dayjs(taskGroup.start).format("YYYY-MM-DD")}</div><div>结束时间:{dayjs(taskGroup.end).format("YYYY-MM-DD")}</div></div>}/><div>{tasks?.filter((task) => task.epicId === taskGroup.id).map((task) => (<Linkto={`/projects/${currentProject?.id}/viewboard?editingTaskId=${task.id}`}key={task.id}>{task.name}</Link>))}</div></List.Item>)}/></ViewContainer>);
};
查看页面效果,点击对应任务会跳转到看板并打开任务编辑窗口
编辑 src\screens\TaskGroup\index.tsx(新增删除任务组功能):
...
import { useDeleteTaskGroup, useTaskGroups } from "utils/taskGroup";export const TaskGroupIndex = () => {...const { mutate: deleteTaskGroup } = useDeleteTaskGroup(useTaskGroupsQueryKey());const confirmDeleteEpic = (taskGroup: TaskGroup) => {Modal.confirm({title: `确定删除项目组:${taskGroup.name}`,content: "点击确定删除",okText: "确定",onOk() {deleteTaskGroup(taskGroup.id);},});};return (<ViewContainer><Row between={true}>...</Row><Liststyle={{ overflow: "scroll" }}dataSource={taskGroups}itemLayout={"vertical"}renderItem={(taskGroup) => (<List.Item><List.Item.Metatitle={<Row between={true}><span>{taskGroup.name}</span><Button onClick={() => confirmDeleteEpic(taskGroup)} type={"link"}>删除</Button></Row>}description={...}/><div>...</div></List.Item>)}/></ViewContainer>);
};
查看页面,可以正常删除任务组(建议功能尝试放到完成创建功能之后再尝试,你懂的。。。)
部分引用笔记还在草稿阶段,敬请期待。。。
相关文章:
【实战】十一、看板页面及任务组页面开发(五) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十七)
文章目录 一、项目起航:项目初始化与配置二、React 与 Hook 应用:实现项目列表三、TS 应用:JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook&…...
mac m1 docker 安装kafka和zookeeper
获取本地ip地址 ifconfig en0 192.168.0.105. 下面的ip都会使用到 1、拉取镜像 docker pull wurstmeister/zookeeper docker pull wurstmeister/kafka 2、启动容器 启动 zookeeper docker run -d --name zookeeper -p 2181:2181 映射 3、 启动 kafka 注意ÿ…...
宏观经济和风电预测误差分析(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
GO学习之 搜索引擎(ElasticSearch)
GO系列 1、GO学习之Hello World 2、GO学习之入门语法 3、GO学习之切片操作 4、GO学习之 Map 操作 5、GO学习之 结构体 操作 6、GO学习之 通道(Channel) 7、GO学习之 多线程(goroutine) 8、GO学习之 函数(Function) 9、GO学习之 接口(Interface) 10、GO学习之 网络通信(Net/Htt…...
Sentinel —实时监控
Sentinel 提供对所有资源的实时监控。如果需要实时监控,客户端需引入以下依赖(以 Maven 为例): <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-transport-simple-http</artif…...
接口优化通用方案
目录 批量异步、回调缓存预取池化并行锁粒度索引大事务海量数据 批量 批量思想:批量操作数据库 优化前: //for循环单笔入库 for(TransDetail detail:transDetailList){ insert(detail); } 优化后: batchInsert(transDetailList); 异步、回…...
用Visual Studio 2022的.map文件来查看C++变量在内存中的布局情况
先看几个实例 代码1 #include <iostream> int data_arr[32768]; int main() {data_arr[1] 11;std::cout<<"data_arr[1]: " << data_arr[1] << std::endl;return data_arr[1]; } 上述代码在Win10 X64,MSVC Release模式下编译&…...
使用代理突破浏览器IP限制
一、实验目的: 主要时了解代理服务器的概念,同时如何突破浏览器IP限制 二、预备知识: 代理服务器英文全称是Proxy Server,其功能就是代理网络用户去取得网络信息。形象的说:它是网络信息的中转站,特别是它具有一个cac…...
HuggingFace中的 Files and versions 如何优雅下载到本地?(Python requests,tqdm)
前言 在使用huggingface把玩各种大模型时,如果选择从远程加载模型,这个过程可能因为网络问题而非常耗时甚至直接失败,所以把模型、分词器等相关文件下载到本地,再直接从本地加载就成了不可回避的流程。 在进入具体版本的模型后&…...
三、原型模式
一、什么是原型模式 原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效&a…...
transformer实现词性标注
1、self-attention 1.1、self-attention结构图 上图是 Self-Attention 的结构,在计算的时候需要用到矩阵 Q(查询), K(键值), V(值)。在实际中,Self-Attention 接收的是输入(单词的表示向量 x组成的矩阵 X) 或者上一个 Encoder block 的输出。而 Q, K, V…...
Java中异或操作和OTP算法
最近在研究加密算法,发现异或操作在加密算法中用途特别广,也特别好用。下面以Java语言为例,简单记录一下异或操作,以及在算法中的使用,包括常用的OTP算法。 一,异或操作特征 1, 相同出0&#…...
K8S最新版本集群部署(v1.28) + 容器引擎Docker部署(下)
温故知新 📚第三章 Kubernetes各组件部署📗安装kubectl(可直接跳转到安装kubeadm章节,直接全部安装了)📕下载kubectl安装包📕执行kubectl安装📕验证kubectl 📗安装kubead…...
女子垒球运动的发展·垒球1号位
女子垒球运动的发展 1. 女子垒球运动的起源和发展概述 女子垒球运动,诞生于19世纪末的美国,作为棒球运动的衍生品,经过百年的积淀,已在全球范围内广泛传播,形成了丰富的赛事文化。她的起源,可以追溯到19世…...
Debian 30 周年,生日快乐!
导读近日是 Debian 日,也是由伊恩-默多克(Ian Murdock)创立的 Debian GNU/Linux 通用操作系统和社区支持的 Debian 项目 30 周年纪念日。 不管你信不信,从已故的伊恩-默多克于 1993 年 8 月 16 日宣布成立 Debian 项目,…...
字符串匹配的Rabin–Karp算法
leetcode-28 实现strStr() 更熟悉的字符串匹配算法可能是KMP算法, 但在Golang中,使用的是Rabin–Karp算法 一般中文译作 拉宾-卡普算法,由迈克尔拉宾与理查德卡普于1987年提出 “ 要在一段文本中找出单个模式串的一个匹配,此算法具有线性时间的平均复杂度࿰…...
傅里叶变换(FFT)笔记存档
参考博客:https://www.luogu.com.cn/blog/command-block/fft-xue-xi-bi-ji 目录: FFT引入复数相关知识单位根及其相关性质DFT过程(难点)DFT结论(重要)IDFT结论(重要)IDFT结论证明&…...
ELK安装、部署、调试 (二) ES的安装部署
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口操作ES,也可以利用Java API。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业…...
Android 13 - Media框架(8)- MediaExtractor
上一篇我们了解了 GenericSource 需要依赖 IMediaExtractor 完成 demux 工作,这一篇我们就来学习 android media 框架中的第二个服务 media.extractor,看看 IMediaExtractor 是如何创建与工作的。 1、MediaExtractorService media.extractor 和 media.p…...
Flutter 混合开发调试
针对Flutter开发的同学来说,大部分的应用还是Native Flutter的混合开发,所以每次改完Flutter代码,运行整个项目无疑是很费时间的。所以Flutter官方也给我们提供了混合调试的方案【在混合开发模式下进行调试】,这里以Android Stud…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
SpringAI实战:ChatModel智能对话全解
一、引言:Spring AI 与 Chat Model 的核心价值 🚀 在 Java 生态中集成大模型能力,Spring AI 提供了高效的解决方案 🤖。其中 Chat Model 作为核心交互组件,通过标准化接口简化了与大语言模型(LLM࿰…...
