diff --git a/src/components/IdSelect.tsx b/src/components/IdSelect.tsx new file mode 100644 index 0000000..dd0822d --- /dev/null +++ b/src/components/IdSelect.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { Select } from "antd"; + +const { Option } = Select; + +type SelectProps = React.ComponentProps; + +interface IdSelectProps + extends Omit { + 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 ( + + ); +}; + +export default React.memo(IdSelect); diff --git a/src/components/Pin.tsx b/src/components/Pin.tsx new file mode 100644 index 0000000..da7d17e --- /dev/null +++ b/src/components/Pin.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { Rate } from "antd"; + +interface PinProps extends React.ComponentProps { + checked: boolean; + onCheckedChange?: (checked: boolean) => void; +} + +const Pin = ({ checked, onCheckedChange, ...restProps }: PinProps) => { + return ( + onCheckedChange?.(!!num)} + {...restProps} + /> + ); +}; + +export default Pin; diff --git a/src/components/UserSelect.tsx b/src/components/UserSelect.tsx new file mode 100644 index 0000000..1028b67 --- /dev/null +++ b/src/components/UserSelect.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import useUser from "../hook/useUsers"; +import IdSelect from "./IdSelect"; + +const UserSelect = (props: React.ComponentProps) => { + const { data: users } = useUser(); + return ; +}; + +export default React.memo(UserSelect); diff --git a/src/context/auth-context.tsx b/src/context/auth-context.tsx index eb4a017..c4d6475 100644 --- a/src/context/auth-context.tsx +++ b/src/context/auth-context.tsx @@ -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; diff --git a/src/hook/useProjects.ts b/src/hook/useProjects.ts index 744ebf1..c96e366 100644 --- a/src/hook/useProjects.ts +++ b/src/hook/useProjects.ts @@ -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) => { const request = useRequest(); const { run, ...result } = useAsync(); @@ -23,4 +15,24 @@ const useProjects = (param?: Partial) => { return result; }; -export default useProjects; +const useEditProject = () => { + const { run, ...asyncResult } = useAsync(); + const request = useRequest(); + const mutate = (params: Partial) => { + 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) => { + return run(request(`/projects/${params.id}`, { data: params, method: "POST" })); + }; + + return { mutate, ...asyncResult }; +}; + +export { useProjects, useEditProject, useAddProject }; diff --git a/src/hook/useProjectsSearchParams.ts b/src/hook/useProjectsSearchParams.ts new file mode 100644 index 0000000..9ada378 --- /dev/null +++ b/src/hook/useProjectsSearchParams.ts @@ -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; diff --git a/src/hook/useRequest.ts b/src/hook/useRequest.ts new file mode 100644 index 0000000..cc04b21 --- /dev/null +++ b/src/hook/useRequest.ts @@ -0,0 +1,9 @@ +import { useAuth } from "../context/auth-context"; +import { request } from "../utils/request"; + +const useRequest = () => { + const { user } = useAuth(); + return (...[endpoint, config]: Parameters) => request(endpoint, { ...config, token: user?.token }); +}; + +export default useRequest; diff --git a/src/hook/useUsers.ts b/src/hook/useUsers.ts index a64cac6..82a8607 100644 --- a/src/hook/useUsers.ts +++ b/src/hook/useUsers.ts @@ -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) => { const request = useRequest(); const { run, ...result } = useAsync(); diff --git a/src/pages/ProjectList/List/index.tsx b/src/pages/ProjectList/List/index.tsx index 2fe125d..aed20ff 100644 --- a/src/pages/ProjectList/List/index.tsx +++ b/src/pages/ProjectList/List/index.tsx @@ -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 { users: User[]; } const ProjectList = ({ users, ...props }: ListProps) => { + const { mutate } = useEditProject(); + const pinProject = (id: number) => (pin: boolean) => mutate({ id, pin }); return ( , + render: (value, project) => { + return ( + + ); + }, + }, { title: "名称", sorter: (a, b) => a.name.localeCompare(b.name), diff --git a/src/pages/ProjectList/Search/index.tsx b/src/pages/ProjectList/Search/index.tsx index 17e1f10..3393b88 100644 --- a/src/pages/ProjectList/Search/index.tsx +++ b/src/pages/ProjectList/Search/index.tsx @@ -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>; 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 (
- + setParam({ ...param, name: evt.target.value })} /> - - - - + /> + ); }; diff --git a/src/pages/ProjectList/index.tsx b/src/pages/ProjectList/index.tsx index 340b779..076db65 100644 --- a/src/pages/ProjectList/index.tsx +++ b/src/pages/ProjectList/index.tsx @@ -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 (

项目列表

- + {error ? ( {error.message} ) : null} diff --git a/src/type/index.ts b/src/type/index.ts new file mode 100644 index 0000000..3d97dca --- /dev/null +++ b/src/type/index.ts @@ -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; +} diff --git a/src/utils/request.ts b/src/utils/request.ts index 1e7161b..98f9503 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -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) => request(endpoint, { ...config, token: user?.token }) -} \ No newline at end of file + 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); + }); +}; diff --git a/src/utils/token.ts b/src/utils/token.ts index d3cf370..23de567 100644 --- a/src/utils/token.ts +++ b/src/utils/token.ts @@ -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 }) => {