[Tatsächlicher Kampf] Elf. Entwicklung von Kanban-Seiten und Aufgabengruppenseiten (3) – React17 + React Hook + TS4 Best Practice, Nachahmung von Jira-Projekten auf Unternehmensebene (25)


Quelle der Lerninhalte: React + React Hook + TS Best Practice – MOOC


Im Vergleich zum Original-Tutorial habe ich zu Beginn meines Studiums die neueste Version (2023.03) verwendet:

Artikel Ausführung
reagieren & reagieren-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
heiser ^8.0.3
fusselfrei ^13.1.2
hübscher 2.8.4
JSON-Server 0,17,2
craco-los ^2.0.0
@craco/craco ^7.1.0
qs ^6.11.0
dayjs ^1.11.7
Reaktionshelm ^6.1.0
@types/react-helmet ^6.1.6
Reaktionsabfrage ^6.1.0
@welldone-software/why-did-you-render ^7.0.1
@emotion/react & @emotion/styled ^11.10.6

Die spezifische Konfiguration, Bedienung und der Inhalt werden unterschiedlich sein, und auch die „Grube“ wird unterschiedlich sein. . .


1. Projektstart: Projektinitialisierung und -konfiguration

2. React- und Hook-Anwendung: Implementieren Sie die Projektliste

3. TS-Anwendung: JS God Assist – Starker Typ

4. JWT, Benutzerauthentifizierung und asynchrone Anfrage


5. CSS ist eigentlich ganz einfach: Fügen Sie Stile mit CSS-in-JS hinzu


6. Optimierung der Benutzererfahrung – Laden und Fehlerstatusbehandlung



7. Hook-, Routing- und URL-Statusverwaltung



8. Benutzerauswahl und Elementbearbeitungsfunktion


9. Detaillierte React-Statusverwaltung und Redux-Mechanismus





10. Verwenden Sie React-Query, um Daten abzurufen und den Cache zu verwalten


11. Entwicklung von Kanban-Seiten und Aufgabengruppenseiten

1~3

4~6

7. Aufgabenfunktion bearbeiten

Erstellen Sie als Nächstes eine Komponente zum Bearbeiten von Aufgaben:

Wenn Sie bereit sind, die Benutzeroberfläche für Bearbeitungsaufgaben aufzurufen und Aufgabendetails abzurufen Hook, bearbeiten Sie Folgendes src\utils\task.ts:

...
import {
    
     useAddConfig, useEditConfig } from "./use-optimistic-options";

export const useEditTask = (queryKey: QueryKey) => {
    
    
  const client = useHttp();
  return useMutation(
    (params: Partial<Task>) =>
      client(`tasks/${
      
      params.id}`, {
    
    
        method: "PATCH",
        data: params,
      }),
    useEditConfig(queryKey)
  );
};

export const useTask = (id?: number) => {
    
    
  const client = useHttp();
  return useQuery<Task>(["task", id], () => client(`tasks/${
      
      id}`), {
    
    
    enabled: Boolean(id),
  });
};

BEARBEITEN src\screens\ViewBoard\utils.ts(hinzugefügt useTasksModal):

...
// import { useDebounce } from "utils";
import {
    
     useTask } from "utils/task";
...

export const useTasksSearchParams = () => {
    
    
  const [param] = useUrlQueryParam([
    "name",
    "typeId",
    "processorId",
    "tagId",
  ]);
  const projectId = useProjectIdInUrl();
  // const debouncedName = useDebounce(param.name)
  return useMemo(
    () => ({
    
    
      projectId,
      typeId: Number(param.typeId) || undefined,
      processorId: Number(param.processorId) || undefined,
      tagId: Number(param.tagId) || undefined,
      // name: debouncedName,
      name: param.name,
    }),
    // [projectId, param, debouncedName]
    [projectId, param]
  );
};

...

export const useTasksModal = () => {
    
    
  const [{
    
     editingTaskId }, setEditingTaskId] = useUrlQueryParam(['editingTaskId'])
  const {
    
     data: editingTask, isLoading } = useTask(Number(editingTaskId))
  const startEdit = useCallback((id: number) => {
    
    
    setEditingTaskId({
    
    editingTaskId: id})
  }, [setEditingTaskId])
  const close = useCallback(() => {
    
    
    setEditingTaskId({
    
    editingTaskId: ''})
  }, [setEditingTaskId])
  return {
    
    
    editingTaskId,
    editingTask,
    startEdit,
    close,
    isLoading
  }
}

Durch die Verwendung von im Video useDebouncewird die Suche gestartet, nachdem die Eingabe vollständig gestoppt wurde. Dadurch wird die Verschwendung von Systemressourcen durch häufige Suchvorgänge während des Eingabevorgangs vermieden und die Benutzererfahrung beeinträchtigt. Nachdem der Blogger solche Änderungen vorgenommen hat, ist dies bei der chinesischen Eingabemethode nicht mehr möglich normal verwendet werden. . . Nachverfolgen

Neue Komponente: src\screens\ViewBoard\components\taskModal.tsx:

import {
    
     useForm } from "antd/lib/form/Form"
import {
    
     useTasksModal, useTasksQueryKey } from "../utils"
import {
    
     useEditTask } from "utils/task"
import {
    
     useEffect } from "react"
import {
    
     Form, Input, Modal } from "antd"
import {
    
     UserSelect } from "components/user-select"
import {
    
     TaskTypeSelect } from "components/task-type-select"

const layout = {
    
    
  labelCol: {
    
    span: 8},
  wrapperCol: {
    
    span: 16}
}

export const TaskModal = () => {
    
    
  const [form] = useForm()
  const {
    
     editingTaskId, editingTask, close } = useTasksModal()
  const {
    
     mutateAsync: editTask, isLoading: editLoading } = useEditTask(useTasksQueryKey())

  const onCancel = () => {
    
    
    close()
    form.resetFields()
  }

  const onOk = async () => {
    
    
    await editTask({
    
    ...editingTask, ...form.getFieldsValue()})
    close()
  }

  useEffect(() => {
    
    
    form.setFieldsValue(editingTask)
  }, [form, editingTask])

  return <Modal
    forceRender={
    
    true}
    onCancel={
    
    onCancel}
    onOk={
    
    onOk}
    okText={
    
    "确认"}
    cancelText={
    
    "取消"}
    confirmLoading={
    
    editLoading}
    title={
    
    "编辑任务"}
    open={
    
    !!editingTaskId}
  >
    <Form {
    
    ...layout} initialValues={
    
    editingTask} form={
    
    form}>
      <Form.Item
        label={
    
    "任务名"}
        name={
    
    "name"}
        rules={
    
    [{
    
     required: true, message: "请输入任务名" }]}
      >
        <Input />
      </Form.Item>
      <Form.Item label={
    
    "经办人"} name={
    
    "processorId"}>
        <UserSelect defaultOptionName={
    
    "经办人"} />
      </Form.Item>
      <Form.Item label={
    
    "类型"} name={
    
    "typeId"}>
        <TaskTypeSelect />
      </Form.Item>
    </Form>
  </Modal>
}

Hinweis: Wie Drawerbei ModalVerwendung der useForm()extrahierten formBindung in der Komponente Formmüssen Sie forceRenderdie Eigenschaft hinzufügen. Andernfalls wird beim Öffnen der Seite ein Fehler gemeldet, wenn die Bindung nicht verfügbar ist. useForm' ist mit keinem Form-Element verbunden . Vergessen…

EDIT: src\screens\ViewBoard\index.tsx(eingeführt TaskModal):

...
import {
    
     TaskModal } from "./components/taskModal";

export const ViewBoard = () => {
    
    
  ...

  return (
    <ViewContainer>
      ...
      <TaskModal/>
    </ViewContainer>
  );
};
...

BEARBEITEN: src\screens\ViewBoard\components\ViewboardCloumn.tsx(eingeführt useTasksModal, damit durch Klicken auf eine Aufgabenkarte die TaskModalBearbeitung geöffnet wird):

...
import {
    
     useTasksModal, useTasksSearchParams } from "../utils";
...

export const ViewboardColumn = ({
     
      viewboard }: {
     
      viewboard: Viewboard }) => {
    
    
  ...
  const {
    
     startEdit } = useTasksModal()
  return (
    <Container>
      ...
      <TasksContainer>
        {
    
    tasks?.map((task) => (
          <Card onClick={
    
    () => startEdit(task.id)} style={
    
    {
    
     marginBottom: "0.5rem", cursor: 'pointer' }} key={
    
    task.id}>
            ...
          </Card>
        ))}
        ...
      </TasksContainer>
    </Container>
  );
};
...

Sehen Sie sich die Funktionen und Effekte an, klicken Sie auf die Aufgabenkarte, um TaskModalsie anzuzeigen, bearbeiten und bestätigen Sie, um die geänderte Aufgabe anzuzeigen (mit optimistischem Update, völlig gefühllos):
Fügen Sie hier eine Bildbeschreibung ein

8. Kanban- und Aufgabenlöschfunktion

Als nächstes implementieren Sie eine kleine Funktion, um Schlüsselwörter in den Suchergebnissen hervorzuheben

Neu src\screens\ViewBoard\components\mark.tsx:

export const Mark = ({
     
     name, keyword}: {
     
     name: string, keyword: string}) => {
    
    
  if(!keyword) {
    
    
    return <>{
    
    name}</>
  }
  const arr = name.split(keyword)
  return <>
    {
    
    
      arr.map((str, index) => <span key={
    
    index}>
        {
    
    str}
        {
    
    
          index === arr.length -1 ? null : <span style={
    
    {
    
     color: '#257AFD' }}>{
    
    keyword}</span>
        }
      </span>)
    }
  </>
}

EDIT ( separat src\screens\ViewBoard\components\ViewboardCloumn.tsxeingeführt Taskund extrahiert):TaskCard

...
import {
    
     Task } from "types/Task";
import {
    
     Mark } from "./mark";

...

const TaskCard = ({
     
     task}: {
     
     task: Task}) => {
    
    
  const {
    
     startEdit } = useTasksModal();
  const {
    
     name: keyword } = useTasksSearchParams()
  return <Card
    onClick={
    
    () => startEdit(task.id)}
    style={
    
    {
    
     marginBottom: "0.5rem", cursor: "pointer" }}
    key={
    
    task.id}
  >
    <p>
      <Mark keyword={
    
    keyword} name={
    
    task.name}/>
    </p>
    <TaskTypeIcon id={
    
    task.id} />
  </Card>
}

export const ViewboardColumn = ({
     
      viewboard }: {
     
      viewboard: Viewboard }) => {
    
    
  const {
    
     data: allTasks } = useTasks(useTasksSearchParams());
  const tasks = allTasks?.filter((task) => task.kanbanId === viewboard.id);
  return (
    <Container>
      <h3>{
    
    viewboard.name}</h3>
      <TasksContainer>
        {
    
    tasks?.map((task) => <TaskCard task={
    
    task}/>)}
        <CreateTask kanbanId={
    
    viewboard.id} />
      </TasksContainer>
    </Container>
  );
};
...

Sehen Sie sich den Effekt an:

Fügen Sie hier eine Bildbeschreibung ein

Beginnen wir mit der Entwicklung der Löschfunktion

BEARBEITEN src\utils\viewboard.ts(erstellt und exportiert useDeleteViewBoard):

...
export const useDeleteViewBoard = (queryKey: QueryKey) => {
    
    
  const client = useHttp();
  return useMutation(
    (id?: number) =>
      client(`kanbans/${
      
      id}`, {
    
    
        method: "DELETE",
      }),
    useDeleteConfig(queryKey)
  );
};

BEARBEITEN src\screens\ViewBoard\components\ViewboardCloumn.tsx:

...
import {
    
     Button, Card, Dropdown, MenuProps, Modal, Row } from "antd";
import {
    
     useDeleteViewBoard } from "utils/viewboard";

...

export const ViewboardColumn = ({
     
      viewboard }: {
     
      viewboard: Viewboard }) => {
    
    
  const {
    
     data: allTasks } = useTasks(useTasksSearchParams());
  const tasks = allTasks?.filter((task) => task.kanbanId === viewboard.id);
  return (
    <Container>
      <Row>
        <h3>{
    
    viewboard.name}</h3>
        <More viewboard={
    
    viewboard}/>
      </Row>
      <TasksContainer>
        {
    
    tasks?.map((task) => <TaskCard task={
    
    task}/>)}
        <CreateTask kanbanId={
    
    viewboard.id} />
      </TasksContainer>
    </Container>
  );
};

const More = ({
     
      viewboard }: {
     
      viewboard: Viewboard }) => {
    
    
  const {
    
    mutateAsync: deleteViewBoard} = useDeleteViewBoard(useViewBoardQueryKey())
  const startDelete = () => {
    
    
    Modal.confirm({
    
    
      okText: '确定',
      cancelText: '取消',
      title: '确定删除看板吗?',
      onOk() {
    
    
        deleteViewBoard(viewboard.id)
      }
    })
  }
  const items: MenuProps["items"] = [
    {
    
    
      key: 1,
      label: "删除",
      onClick: startDelete,
    },
  ];
  return <Dropdown menu={
    
    {
    
     items }}>
    <Button type="link" onClick={
    
    (e) => e.preventDefault()}>
      ...
    </Button>
  </Dropdown>
}
...

Testen Sie, um das Kanban zu löschen, die Funktion ist normal

Das Folgende ist die Funktion zum Löschen von Aufgaben

BEARBEITEN src\utils\task.ts(erstellt und exportiert useDeleteTask):

...
export const useDeleteTask = (queryKey: QueryKey) => {
    
    
  const client = useHttp();
  return useMutation(
    (id?: number) =>
      client(`tasks/${
      
      id}`, {
    
    
        method: "DELETE",
      }),
    useDeleteConfig(queryKey)
  );
};

BEARBEITEN src\screens\ViewBoard\components\taskModal.tsx:

...
import {
    
     useDeleteTask, useEditTask } from "utils/task";

export const TaskModal = () => {
    
    
  ...
  const {
    
     mutateAsync: deleteTask } = useDeleteTask(useTasksQueryKey());
  ...

  const startDelete = () => {
    
    
    close();
    Modal.confirm({
    
    
      okText: '确定',
      cancelText: '取消',
      title: '确定删除看板吗?',
      onOk() {
    
    
        deleteTask(Number(editingTaskId));
      }
    })
  }

  return (
    <Modal {
    
    ...}>
      <Form {
    
    ...}>
        ...
      </Form>
      <div style={
    
    {
    
     textAlign: 'right' }}>
        <Button style={
    
    {
    
    fontSize: '14px'}} size="small" onClick={
    
    startDelete}>删除</Button>
      </div>
    </Modal>
  );
};

Testen Sie, um die Aufgabe zu löschen. Die Funktion ist normal


Einige Referenznotizen befinden sich noch im Entwurfsstadium, also bleiben Sie dran. . .

Supongo que te gusta

Origin blog.csdn.net/qq_32682301/article/details/132420837
Recomendado
Clasificación