Browse Source

抽离代码逻辑。编写 Hook

redux
YuJian920 3 years ago
parent
commit
7a60d44f6d
  1. 36
      src/components/IdSelect.tsx
  2. 20
      src/components/Pin.tsx
  3. 10
      src/components/UserSelect.tsx
  4. 10
      src/context/auth-context.tsx
  5. 34
      src/hook/useProjects.ts
  6. 13
      src/hook/useProjectsSearchParams.ts
  7. 9
      src/hook/useRequest.ts
  8. 12
      src/hook/useUsers.ts
  9. 34
      src/pages/ProjectList/List/index.tsx
  10. 42
      src/pages/ProjectList/Search/index.tsx
  11. 24
      src/pages/ProjectList/index.tsx
  12. 17
      src/type/index.ts
  13. 65
      src/utils/request.ts
  14. 11
      src/utils/token.ts

36
src/components/IdSelect.tsx

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
import React from "react";
import { Select } from "antd";
const { Option } = Select;
type SelectProps = React.ComponentProps<typeof Select>;
interface IdSelectProps
extends Omit<SelectProps, "value" | "onChange" | "options"> {
value: number | string | null | undefined;
onChange: (value?: number) => void;
defaultOptionName?: string;
options?: { name: string; id: number }[];
}
const toNumber = (value: unknown) => (isNaN(Number(value)) ? 0 : Number(value));
const IdSelect = (props: IdSelectProps) => {
const { value, onChange, defaultOptionName, options, ...restProps } = props;
return (
<Select
value={options?.length ? toNumber(value) : 0}
onChange={(value) => onChange(toNumber(value) || undefined)}
{...restProps}
>
{defaultOptionName ? (
<Option value={0}>{defaultOptionName}</Option>
) : null}
{options?.map((option) => (
<Option value={option.id} key={option.id}>{option.name}</Option>
))}
</Select>
);
};
export default React.memo(IdSelect);

20
src/components/Pin.tsx

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
import React from "react";
import { Rate } from "antd";
interface PinProps extends React.ComponentProps<typeof Rate> {
checked: boolean;
onCheckedChange?: (checked: boolean) => void;
}
const Pin = ({ checked, onCheckedChange, ...restProps }: PinProps) => {
return (
<Rate
count={1}
value={checked ? 1 : 0}
onChange={(num) => onCheckedChange?.(!!num)}
{...restProps}
/>
);
};
export default Pin;

10
src/components/UserSelect.tsx

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
import React from "react";
import useUser from "../hook/useUsers";
import IdSelect from "./IdSelect";
const UserSelect = (props: React.ComponentProps<typeof IdSelect>) => {
const { data: users } = useUser();
return <IdSelect options={users || []} {...props} />;
};
export default React.memo(UserSelect);

10
src/context/auth-context.tsx

@ -2,18 +2,10 @@ import React, { useState, useContext, ReactNode } from "react"; @@ -2,18 +2,10 @@ import React, { useState, useContext, ReactNode } from "react";
import { FullPageLoading } from "../components/lib";
import useAsync from "../hook/useAsync";
import useMount from "../hook/useMount";
import { User } from "../type";
import { request } from "../utils/request";
import * as auth from "../utils/token";
interface User {
id: string;
name: string;
email: string;
title: string;
organization: string;
token: string;
}
interface AuthForm {
username: string;
password: string;

34
src/hook/useProjects.ts

@ -1,17 +1,9 @@ @@ -1,17 +1,9 @@
import { useEffect } from "react";
import { Project } from "../type";
import { cleanObject } from "../utils";
import { useRequest } from "../utils/request";
import useRequest from "./useRequest";
import useAsync from "./useAsync";
interface Project {
id: string;
name: string;
personId: string;
pin: boolean;
organization: string;
created: number;
}
const useProjects = (param?: Partial<Project>) => {
const request = useRequest();
const { run, ...result } = useAsync<Project[]>();
@ -23,4 +15,24 @@ const useProjects = (param?: Partial<Project>) => { @@ -23,4 +15,24 @@ const useProjects = (param?: Partial<Project>) => {
return result;
};
export default useProjects;
const useEditProject = () => {
const { run, ...asyncResult } = useAsync();
const request = useRequest();
const mutate = (params: Partial<Project>) => {
return run(request(`/projects/${params.id}`, { data: params, method: "PATCH" }));
};
return { mutate, ...asyncResult };
};
const useAddProject = () => {
const { run, ...asyncResult } = useAsync();
const request = useRequest();
const mutate = (params: Partial<Project>) => {
return run(request(`/projects/${params.id}`, { data: params, method: "POST" }));
};
return { mutate, ...asyncResult };
};
export { useProjects, useEditProject, useAddProject };

13
src/hook/useProjectsSearchParams.ts

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
import { useMemo } from "react";
import useUrlQueryParams from "./useUrlQueryParams";
const useProjectsSearchParams = () => {
const [param, setParam] = useUrlQueryParams(["name", "personId"]);
// 转换 URL 中的字符串参数
return [
useMemo(() => ({ ...param, personId: Number(param.personId) || undefined }), [param]),
setParam,
] as const;
};
export default useProjectsSearchParams;

9
src/hook/useRequest.ts

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
import { useAuth } from "../context/auth-context";
import { request } from "../utils/request";
const useRequest = () => {
const { user } = useAuth();
return (...[endpoint, config]: Parameters<typeof request>) => request(endpoint, { ...config, token: user?.token });
};
export default useRequest;

12
src/hook/useUsers.ts

@ -1,17 +1,9 @@ @@ -1,17 +1,9 @@
import { useEffect } from "react";
import { User } from "../type";
import { cleanObject } from "../utils";
import { useRequest } from "../utils/request";
import useRequest from "./useRequest";
import useAsync from "./useAsync";
interface User {
id: string;
name: string;
email: string;
title: string;
token: string;
organization: string;
}
const useUser = (param?: Partial<User>) => {
const request = useRequest();
const { run, ...result } = useAsync<User[]>();

34
src/pages/ProjectList/List/index.tsx

@ -2,33 +2,31 @@ import React from "react"; @@ -2,33 +2,31 @@ import React from "react";
import { Link } from "react-router-dom";
import { Table, TableProps } from "antd";
import dayjs from "dayjs";
interface Project {
id: string;
name: string;
personId: string;
pin: boolean;
organization: string;
created: number;
}
interface User {
id: string;
name: string;
email: string;
title: string;
organization: string;
}
import { Project, User } from "../../../type";
import Pin from "../../../components/Pin";
import { useEditProject } from "../../../hook/useProjects";
interface ListProps extends TableProps<Project> {
users: User[];
}
const ProjectList = ({ users, ...props }: ListProps) => {
const { mutate } = useEditProject();
const pinProject = (id: number) => (pin: boolean) => mutate({ id, pin });
return (
<Table
pagination={false}
columns={[
{
title: <Pin checked={true} disabled={true} />,
render: (value, project) => {
return (
<Pin
checked={project.pin}
onCheckedChange={pinProject(project.id)}
/>
);
},
},
{
title: "名称",
sorter: (a, b) => a.name.localeCompare(b.name),

42
src/pages/ProjectList/Search/index.tsx

@ -1,50 +1,34 @@ @@ -1,50 +1,34 @@
import React from "react";
import { Form, Input, Select } from "antd";
import { Form, Input } from "antd";
import { Project, User } from "../../../type";
import UserSelect from "../../../components/UserSelect";
const { Option } = Select;
const { Item } = Form;
interface SearchPanelProps {
users: User[];
param: {
name: string;
personId: string;
};
param: Partial<Pick<Project, "name" | "personId">>;
setParam: (param: SearchPanelProps["param"]) => void;
}
export interface User {
id: string;
name: string;
email: string;
title: string;
organization: string;
token: string;
}
const Search = ({ param, setParam, users }: SearchPanelProps) => {
const Search = ({ param, setParam }: SearchPanelProps) => {
return (
<Form style={{ marginBottom: "2rem" }} layout="inline">
<Form.Item>
<Item>
<Input
placeholder="项目名"
type="text"
value={param.name}
onChange={(evt) => setParam({ ...param, name: evt.target.value })}
/>
</Form.Item>
<Form.Item>
<Select
</Item>
<Item>
<UserSelect
value={param.personId}
defaultOptionName="负责人"
onChange={(value) => setParam({ ...param, personId: value })}
>
<Option value={""}></Option>
{users.map((user) => (
<Option value={String(user.id)} key={user.id}>
{user.name}
</Option>
))}
</Select>
</Form.Item>
/>
</Item>
</Form>
);
};

24
src/pages/ProjectList/index.tsx

@ -4,32 +4,22 @@ import useDebounce from "../../hook/useDebounce"; @@ -4,32 +4,22 @@ import useDebounce from "../../hook/useDebounce";
import List from "./List";
import SearchPanel from "./Search";
import { Container } from "./style";
import useProjects from "../../hook/useProjects";
import { useProjects } from "../../hook/useProjects";
import useUsers from "../../hook/useUsers";
import useDocumentTitle from "../../hook/useDocumentTitle";
import useUrlQueryParams from "../../hook/useUrlQueryParams";
interface Project {
id: string;
name: string;
personId: string;
pin: boolean;
organization: string;
created: number;
}
import useProjectsSearchParams from "../../hook/useProjectsSearchParams";
const ProjectList = () => {
const [param, setParam] = useUrlQueryParams(["name", "personId"]);
const debounceParams = useDebounce(param, 200);
const { isLoading, error, data: list } = useProjects(debounceParams);
const { data: users } = useUsers();
useDocumentTitle("项目列表", false);
const [params, setParam] = useProjectsSearchParams()
const { isLoading, error, data: list } = useProjects(useDebounce(params, 200));
const { data: users } = useUsers();
return (
<Container>
<h1></h1>
<SearchPanel users={users || []} param={param} setParam={setParam} />
<SearchPanel users={users || []} param={params} setParam={setParam} />
{error ? (
<Typography.Text type="danger">{error.message}</Typography.Text>
) : null}

17
src/type/index.ts

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
export interface User {
id: number;
name: string;
email: string;
title: string;
organization: string;
token: string;
}
export interface Project {
id: number;
name: string;
personId: number;
pin: boolean;
organization: string;
created: number;
}

65
src/utils/request.ts

@ -1,39 +1,44 @@ @@ -1,39 +1,44 @@
import qs from 'qs'
import { logout } from './token'
import { useAuth } from '../context/auth-context'
import qs from "qs";
import { logout } from "./token";
import { message } from "antd";
const baseURL = process.env.REACT_APP_API_URL
const baseURL = process.env.REACT_APP_API_URL;
interface Config extends RequestInit {
data?: object,
token?: string
data?: object;
token?: string;
}
export const request = async (endpoint: string, { data, token, headers, ...customConfig }: Config) => {
export const request = async (
endpoint: string,
{ data, token, headers, ...customConfig }: Config
) => {
const config = {
method: 'GET',
method: "GET",
headers: {
Authorization: token ? `Bearer ${token}` : '',
'Content-Type': data ? 'application/json' : ''
Authorization: token ? `Bearer ${token}` : "",
"Content-Type": data ? "application/json" : "",
},
...customConfig
}
if (config.method.toUpperCase() === 'GET') endpoint += `?${qs.stringify(data)}`
else config.body = JSON.stringify(data || {})
...customConfig,
};
if (config.method.toUpperCase() === "GET")
endpoint += `?${qs.stringify(data)}`;
else config.body = JSON.stringify(data || {});
return window.fetch(`${baseURL}${endpoint}`, config).then(async (response) => {
if (response.status === 401) {
await logout()
window.location.reload()
return Promise.reject({ message: "请重新登录" })
}
const data = await response.json()
if (response.ok) return data
else return Promise.reject(data)
})
}
export const useRequest = () => {
const { user } = useAuth()
return (...[endpoint, config]: Parameters<typeof request>) => request(endpoint, { ...config, token: user?.token })
}
return window
.fetch(`${baseURL}${endpoint}`, config)
.then(async (response) => {
if (response.status === 401) {
await logout();
window.location.reload();
return Promise.reject({ message: "请重新登录" });
}
const data = await response.json();
if (response.ok) return data;
else return Promise.reject(data);
})
.catch((error) => {
message.error("请求异常");
return Promise.reject(error);
});
};

11
src/utils/token.ts

@ -1,15 +1,8 @@ @@ -1,15 +1,8 @@
import { User } from "../type"
const baseURL = process.env.REACT_APP_API_URL
const localStorageKey = '__auth_provider_token__'
interface User {
id: string;
name: string;
email: string;
title: string;
organization: string;
token: string
}
export const getToken = () => window.localStorage.getItem(localStorageKey)
export const handleUserResponse = ({ user }: { user: User }) => {

Loading…
Cancel
Save