目录

file-20250402085119376 conponents放这个页面相关的组件 context用于创建user的上下文context与封装自定义hook data存放user数据结构相关的文件

准备工作data

schema.ts 用于存放数据模式 Schema 是对数据结构或数据组织方式的描述,定义了数据的格式、字段和关系。而里面的zod依赖是用来检查其类型安全的

import { z } from "zod";
export const UserSchema = z.object({
    id: z.number(),
    userName: z.string(),
    password: z.string(),
    createDate: z.date(),
})
export type User = z.infer<typeof UserSchema>
export const UserListSchema = z.array(UserSchema)

user-data-ts 用于存放静态user数据 然后我们需要复制粘贴columns到componts,再自行根据自己的数据结构进行修改,这是data-table所需的一个参数columns,来定义表格的行:

  • 可以用复选框多选
  • 展示哪些列
  • 对某行进行哪些操作
import { ColumnDef } from "@tanstack/react-table";
import { Checkbox } from "@/components/ui/checkbox";
 
import { DataTableRowActions } from "./data-table-row-actions";//这里会爆红,因为没有创建
import { User } from "../data/schema";
 
export const columns: ColumnDef<User>[] = [
    {
        id: "select",//多选框,详情见shadcnUI文档中的data-table
        header: ({ table }) => (
          <Checkbox
            checked={
              table.getIsAllPageRowsSelected() ||
              (table.getIsSomePageRowsSelected() && "indeterminate")
            }
            onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
            aria-label="Select all"
          />
        ),
        cell: ({ row }) => (
          <Checkbox
            checked={row.getIsSelected()}
            onCheckedChange={(value) => row.toggleSelected(!!value)}
            aria-label="Select row"
          />
        ),
        enableSorting: false,
        enableHiding: false,
      },
      {
        accessorKey: "userName",
        header: "用户名",
      },
      {
        accessorKey: "password",
        header: "密码",
      },
      {
        accessorKey: "createDate",
        header: "创建日期",
        
      },
      {
        id: "actions",//对行进行操作,详情见shadcnUI文档
        cell: ({row}) => {
          return (
            <DataTableRowActions row={row} />
          )
        },
      },
]
 

我们再来粘贴,修改一下data-table-row-actions

·······
import { useUser } from '../../users/context/userContext'//这里还需要一个文件
import { User } from '../data/schema'
 
interface DataTableRowActionsProps {
  row: Row<User>
}
 
export function DataTableRowActions({ row }: DataTableRowActionsProps) {
  const { setOpen, setCurrentRow } = useUser()
  return (
    <>
     ····
    </>
  )
}
 

再粘贴修改一下userContext.tsx

import React, { useState } from 'react'
import useDialogState from '@/hooks/use-dialog-state'
import { User } from '../data/schema'
 
type UserDialogType = 'create' | 'update' | 'delete'|'import'
 
interface UserContextType {
  open: UserDialogType | null
  setOpen: (str: UserDialogType | null) => void
  currentRow: User | null
  setCurrentRow: React.Dispatch<React.SetStateAction<User | null>>
}
 
const UserContext = React.createContext<UserContextType | null>(null)
 
interface Props {
  children: React.ReactNode
}
 
 
export default function UserProvider({ children }: Props) {
  const [open, setOpen] = useDialogState<UserDialogType>(null)
  const [currentRow, setCurrentRow] = useState<User | null>(null)
  
  return (
    <UserContext.Provider value={{ open, setOpen, currentRow, setCurrentRow }}>
      {children}
    </UserContext.Provider>
  )
}
 
// eslint-disable-next-line react-refresh/only-export-components
export const useUser = () => {
  const userContext = React.useContext(UserContext)
 
  if (!userContext) {
    throw new Error('useUser has to be used within <UserContext>')
  }
 
  return userContext
}
 

此时data-table-row-actions不会爆红了

colums这个参数也准备好了

入口文件user.tsx

export function User() {
    return (
        <UserProvider> //提供UserContext
            <UserHeader />//数据表格之外的部分
            <DataTable columns={columns} data={UserData} />//数据表格的主题部分
            <UserDialog />//根据条件弹出来的对话框
        </UserProvider>
 
    )
 
}

接下来我们将会一步一步生成这些组件

user-header

您可以自定义这些header的内容

export function UserHeader() {
    return (
        <div>
            <h1 className="text-3xl font-bold mb-6">用户管理</h1>
        </div>
    )

data-table

您可以直接复制粘贴data-table过来,这些是非常典型的组件,您可以从employee这个文件夹中的内容进行复制粘贴

复制到conponents文件夹后会有报错,还有一个组件需要我们创建,这个组件是用于操作表格的工具

./data-table-toolbar,小修一下

import { Table } from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { DropdownMenuCheckboxItem } from "@/components/ui/dropdown-menu"
import { TrashIcon } from "lucide-react"
import { useUser } from "../context/userContext"
interface DataTableToolbarProps<TData> {
  table: Table<TData>
}
export default function DateTableToolbar<TData>({
  table,
}: DataTableToolbarProps<TData>) {
    const {setOpen} = useUser()
  return (
    <div className="flex justify-between py-4">
        <div className="flex items-center space-x-2">
          {/* 搜索框 */}
        <Input
          placeholder="搜索用户..."
          value={(table.getColumn("userName")?.getFilterValue() as string) ?? ""}
          onChange={(event) =>
            table.getColumn("userName")?.setFilterValue(event.target.value)
          }
          className="max-w-sm"
        />
         {/*批量选中时,出现的删除按钮 */}
         {table.getFilteredSelectedRowModel().rows.length>0&&(
          <TrashIcon className="w-4 h-4" onClick={()=>{
            setOpen('delete')
          }}/>
         )}
        </div>
        
        
        <div className="flex items-center space-x-2">
         
          
          {/* 添加订单 */}
          <Button variant="outline" onClick={()=>{
            setOpen('create')
          }}>添加用户</Button>
          {/* 表格列选择 */}
 
          <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button variant="outline" className="ml-auto">
              列选择
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            {table
              .getAllColumns()
              .filter(
                (column) => column.getCanHide()
              )
              .map((column) => {
                return (
                  <DropdownMenuCheckboxItem
                    key={column.id}
                    className="capitalize"
                    checked={column.getIsVisible()}
                    onCheckedChange={(value) =>
                      column.toggleVisibility(!!value)
                    }
                  >
                    {column.id}
                  </DropdownMenuCheckboxItem>
                )
              })}
          </DropdownMenuContent>
          </DropdownMenu>
        </div>
        
      </div>
  )
}
 

展示一下

在routes/index.tsx修改一下路由

import {createBrowserRouter} from 'react-router-dom';
import Order from '../features/manageView/order/order.tsx';
import Index from '../features/app/layout';
import Dashboard from '../features/dashboard/dashboard';
import Employee from '../features/manageView/employee/employee';
import { User } from '../features/manageView/users/user';
export const router =createBrowserRouter([
    {
        path:'/',
        element:<Index/>,
        children:[{
            index:true,
            element:<Dashboard />
        },
        {
            path:'manageView/order',
            element:<Order />
        },
        {
            path:'manageView/employee',
            element:<Employee />
        },
        {
            path:'manageView/users',
            element:<User />
        }
    ]
    },
    
])
 

修改一下siderbar的配置

{
          title: 'Users',
          url: '/manageView/users',
          icon: IconUsers,
},

file-20250402100000726 基本的表格就可以展示出来了

user-dialog

这个文件是用来根据条件渲染对话框的,例如点击删除会弹出是否确认删除,点击添加会弹出添加的对话框

import { toast } from '@/hooks/use-toast'
import { ConfirmDialog } from '@/components/confirm-dialog'
import { useUser } from '../context/userContext'
import { UserMutateDrawer } from './user-mutate-drawer'//会爆红,等会儿创建
 
export function UserDialogs() {
  const { open, setOpen, currentRow, setCurrentRow } = useUser()
  return (
    <>
      <UserMutateDrawer
        key='user-create'
        open={open === 'create'}
        onOpenChange={() => setOpen('create')}
      />
      
      {currentRow && (//选中当前行后才会根据条件渲染
        <>
          <UserMutateDrawer
            key={`user-update-${currentRow.id}`}
            open={open === 'update'}
            onOpenChange={() => {
              setOpen('update')
              setTimeout(() => {
                setCurrentRow(null)
              }, 500)
            }}
            currentRow={currentRow}
          />
 
          <ConfirmDialog //当点击当前行删除,才弹出来的确认对话框
            key='user-delete'
            destructive
            open={open === 'delete'}
            onOpenChange={() => {
              setOpen('delete')
              setTimeout(() => {
                setCurrentRow(null)
              }, 500)
            }}
            handleConfirm={() => {
              setOpen(null)
              setTimeout(() => {
                setCurrentRow(null)
              }, 500)
              toast({
                title: 'The following employee has been deleted:',
                description: (
                  <pre className='mt-2 w-[340px] rounded-md bg-slate-950 p-4'>
                    <code className='text-white'>
                      {JSON.stringify(currentRow, null, 2)}
                    </code>
                  </pre>
                ),
              })
            }}
            className='max-w-md'
            title={`Delete this employee: ${currentRow.id} ?`}
            desc={
              <>
                You are about to delete a employee with the ID{' '}
                <strong>{currentRow.id}</strong>. <br />
                This action cannot be undone.
              </>
            }
            confirmText='Delete'
          />
        </>
      )}
    </>
  )
}
 

我们再粘贴修改一下user-mutate-drawer

import { z } from 'zod'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { toast } from '@/hooks/use-toast'
import { Button } from '@/components/ui/button'
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
} from '@/components/ui/sheet'
import { User, UserSchema } from '../data/schema'
 
interface Props {
  open: boolean
  onOpenChange: (open: boolean) => void
  currentRow?: User
}
 
 
type UserForm = z.infer<typeof UserSchema>
 
export function UserMutateDrawer({ open, onOpenChange, currentRow }: Props) {
  const isUpdate = !!currentRow
 
  const form = useForm<UserForm>({
    resolver: zodResolver(UserSchema),
    defaultValues: currentRow ?? {
      id: 0,
      userName: '',
      password: '',
      createDate: new Date(),
    },
  })
 
  const onSubmit = (data: UserForm) => {
    // do something with the form data
    onOpenChange(false)
    form.reset()
    toast({
      title: 'You submitted the following values:',
      description: (
        <pre className='mt-2 w-[340px] rounded-md bg-slate-950 p-4'>
          <code className='text-white'>{JSON.stringify(data, null, 2)}</code>
        </pre>
      ),
    })
  }
 
  return (
    <Sheet
      open={open}
      onOpenChange={(v) => {
        onOpenChange(v)
        form.reset()
      }}
    >
      <SheetContent className='flex flex-col'>
        <SheetHeader className='text-left'>
          <SheetTitle>{isUpdate ? 'Update' : 'Create'} User</SheetTitle>
          <SheetDescription>
            {isUpdate
              ? 'Update the user by providing necessary info.'
              : 'Add a new user by providing necessary info.'}
            Click save when you&apos;re done.
          </SheetDescription>
        </SheetHeader>
        <Form {...form}>
          <form
            id='employee-form'
            onSubmit={form.handleSubmit(onSubmit)}
            className='flex-1 space-y-5'
          >
            <FormField
              control={form.control}
              name='userName'
              render={({ field }) => (
                <FormItem className='space-y-1'>
                  <FormLabel>用户名</FormLabel>
                  <FormControl>
                    <Input {...field} placeholder='Enter a userName' />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            <FormField
              control={form.control}
              name='password'
              render={({ field }) => (
                <FormItem className='space-y-1'>
                  <FormLabel>密码</FormLabel>
                  <FormControl>
                    <Input {...field} placeholder='Enter a password' />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            
          </form>
        </Form>
        <SheetFooter className='gap-2'>
          <SheetClose asChild>
            <Button variant='outline'>取消</Button>
          </SheetClose>
          <Button form='user-form' type='submit'>
            {isUpdate ? '更新' : '创建'}
          </Button>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  )
}
 

最后再user.tsx入口文件加上 file-20250402101512153 就可以正常使用了

reference