【实战】十一、看板页面及任务组页面开发(二) —— 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 || viewBoardIsLoading
  return (
    <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>
    <Input
      size="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>
      <Input
        onBlur={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>
  );
};
...

查看功能和效果,点击 +创建任务 输入框出现,点击输入框以外的地方输入框隐藏,输入新增任务名后回车,即可看到新任务:
效果图


部分引用笔记还在草稿阶段,敬请期待。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/80297.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023+HuggingGPT: Solving AI Tasks with ChatGPT and itsFriends in Hugging Face

摘要&#xff1a; 语言是llm(例如ChatGPT)连接众多AI模型(例如hugs Face)的接口&#xff0c;用于解决复杂的AI任务。在这个概念中&#xff0c;llms作为一个控制器&#xff0c;管理和组织专家模型的合作。LLM首先根据用户请求规划任务列表&#xff0c;然后为每个任务分配专家模…

LeetCode 热题 100(四):48. 旋转图像、240. 搜索二维矩阵 II、234. 回文链表

一.48. 旋转图像 题目要求&#xff1a;就是一个顺时针的旋转过程。 思路&#xff1a;观察矩阵&#xff0c;得出翻转前第i行的第J个元素 等于 翻转后倒数第i列的第J个元素&#xff0c;举例说明&#xff0c;第1行第2个元素为“2”&#xff0c;翻转后到了 倒数第1列的第2个元素…

git 回滚相关问题

原本用as自带的git执行回滚任务&#xff0c; 但是提交之后发现并没有成功&#xff0c; 后面通过命令行的方式重新回滚并且提交上去&#xff0c;就可以了 说明as的git还是有点小瑕疵&#xff0c;还是命令行最稳妥 相关博文&#xff1a; git代码回滚操作_imkaifan的博客-CSDN博…

数据采集器丨高效传输、集中管理

随时科技的发展&#xff0c;数据采集器成为各行各业中的常用重要仪器&#xff0c;为人们对各种不同数据的采集处理&#xff0c;提供了无可比拟的便利。 数据采集器能够接入不同种类的RS485设备&#xff0c;洞察世间万象&#xff0c;探测大千世界的细微脉动&#xff0c;并将监测…

SNMP简单介绍

SNMP SNMP是广泛应用于TCP/IP网络的网络管理标准协议&#xff0c;该协议能够支持网络管理系统&#xff0c;用以监测连接到网络上的设备是否有任何引起管理上关注的情况。SNMP采用轮询机制&#xff0c;提供最基本的功能集&#xff0c;适合小型、快速、低价格的环境使用&#xf…

Springboot 实践(3)配置DataSource及创建数据库

前文讲述了利用MyEclipse2019开发工具&#xff0c;创建maven工程、加载springboot、swagger-ui功能。本文讲述创建数据库&#xff0c;为项目配置数据源&#xff0c;实现数据的增删改查服务&#xff0c;并通过swagger-ui界面举例调试服务控制器 创建数据库 项目使用MySQL 8.0.…

感觉和身边其他人有差距怎么办?

虽然清楚知识需要靠时间沉淀&#xff0c;但在看到自己做不出来的题别人会做&#xff0c;自己写不出的代码别人会写时还是会感到焦虑怎么办&#xff1f; 你是否也因为自身跟周围人的差距而产生过迷茫&#xff0c;这份迷茫如今是被你克服了还是仍旧让你感到困扰&#xff1f; 下…

关于docker-compose up -d在文件下无法运行的原因以及解决方法

一、确认文件下有docker-compose.yml文件 二、解决方法 检查 Docker 服务是否运行&#xff1a; 使用以下命令检查 Docker 服务是否正在运行&#xff1a; systemctl status docker 如果 Docker 未运行&#xff0c;可以使用以下命令启动它&#xff1a; systemctl start docker …

【C语言】字符串函数的介绍二( strcmp、strncpy、strncat、strncmp)

前言 上篇文章我们介绍了strlen、strcpy、stract这三个函数&#xff0c;接下来我们会学习新的函数&#xff0c;话不多说&#xff0c;让我们直接开始吧 上一篇文章 strcmp 引入&#xff1a; 在进行字符串比较时&#xff0c;不可以直接使用符号&#xff0c; 这是在比较二者的…

五、修改官方FreeRTOS例程(STM32F1)

1、官方源码下载 (1)进入FreeRTOS官网&#xff1a;FreeRTOS官网 (2)下载FreeRTOS。(选择带示例的下载) 2、删减目录 (1)下载后解压的FreeRTOS文件如下图所示。 (2)删除下图中红框勾选的文件。 FreeRTOS-Plus&#xff0c;FreeRTOS的生态文件&#xff0c;非必需的。tools&…

Docker vs. Kubernetes:选择合适的场景

在决定使用 Docker 还是 Kubernetes 之前&#xff0c;让我们看看一些实际的场景&#xff0c;以便更好地理解它们的适用性。 使用 Docker 的场景 假设您正在开发一个微服务应用程序&#xff0c;其中每个微服务都需要一些特定的依赖项和环境。在这种情况下&#xff0c;Docker 是一…

C语言中常见的一些语法概念和功能

常用代码&#xff1a; 程序入口&#xff1a;int main() 函数用于定义程序的入口点。 输出&#xff1a;使用 printf() 函数可以在控制台打印输出。 输入&#xff1a;使用 scanf() 函数可以接收用户的输入。 条件判断&#xff1a;使用 if-else 语句可以根据条件执行不同的代码…

Spring-4-掌握Spring事务传播机制

今日目标 能够掌握Spring事务配置 Spring事务管理 1 Spring事务简介【重点】 1.1 Spring事务作用 事务作用&#xff1a;在数据层保障一系列的数据库操作同成功同失败 Spring事务作用&#xff1a;在数据层或业务层保障一系列的数据库操作同成功同失败 1.2 案例分析Spring…

从零开始,快速打造租车服务小程序的分享

随着移动互联网的发展&#xff0c;小程序成为了企业推广和服务的重要手段之一。租车服务行业也不例外&#xff0c;通过打造一款租车服务小程序&#xff0c;企业可以更好地与用户进行互动和交流&#xff0c;提供更方便快捷的租车服务。本文将介绍如何利用第三方制作平台/工具快速…

Linux/centos上如何配置管理NFS服务器?

Linux/centos上如何配置管理NFS服务器&#xff1f; 1 NFS基础了解1.1 NFS概述1.2 NFS工作流程 2 安装和启动NFS服务2.1 安装NFS服务器2.2 启动NFS服务 3 配置NFS服务器和客户端3.1 配置NFS服务器3.2 配置NFS客户端 4 实际示例4.1 基本要求4.2 案例实现 1 NFS基础了解 NFS&…

openGauss学习笔记-44 openGauss 高级数据管理-存储过程

文章目录 openGauss学习笔记-44 openGauss 高级数据管理-存储过程44.1 语法格式44.2 参数说明44.3 示例 openGauss学习笔记-44 openGauss 高级数据管理-存储过程 存储过程是能够完成特定功能的SQL语句集。用户可以进行反复调用&#xff0c;从而减少SQL语句的重复编写数量&…

QT的布局与间隔器介绍

布局与间隔器 1、概述 QT中使用绝对定位的布局方式&#xff0c;无法适用窗口的变化&#xff0c;但是&#xff0c;也可以通过尺寸策略来进行 调整&#xff0c;使得 可以适用窗口变化。 布局管理器作用最主要用来在qt设计师中进行控件的排列&#xff0c;另外&#xff0c;布局管理…

通讯协议044——全网独有的OPC HDA知识一之聚合(十二)持续坏值时间

本文简单介绍OPC HDA规范的基本概念&#xff0c;更多通信资源请登录网信智汇(wangxinzhihui.com)。 本节旨在详细说明HDA聚合的要求和性能。其目的是使HDA聚合标准化&#xff0c;以便HDA客户端能够可靠地预测聚合计算的结果并理解其含义。如果用户需要聚合中的自定义功能&…

ide internal errors【bug】

ide internal errors【bug】 前言版权ide internal errors错误产生相关资源解决1解决2 设置虚拟内存最后 前言 2023-8-15 12:36:59 以下内容源自《【bug】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是h…

No view found for id 0x7f0901c3 for fragment解决以及线上bug排查技巧

情景再现 开发这么久&#xff0c;不知道你们是否也经历过这样的情况&#xff0c;测试或者用户&#xff0c;反馈app闪退&#xff0c;结果你自己打开开发工具&#xff0c;去调试&#xff0c;一切正常&#xff0c;然后闪退还是存在&#xff0c;只是在开发环境中不能重现。这种情况…