React表格 - 切断侧分页,搜索,排序/顺序
#javascript #react #node #prisma

React-Table-Sever-Side-Pagination-Search-Sort-Order

tldr

这是我使用react-tableprisma Orm在React JS项目中管理DataTable的完整指南。让我们开始!

客户端

// Component Filename: TablePagination.js

import {
    ArrowLongDownIcon,
    ArrowLongUpIcon,
    FunnelIcon,
} from "@heroicons/react/24/outline"
import { ClockIcon } from "@heroicons/react/24/solid"
import React from "react"
import {
    useAsyncDebounce,
    useGlobalFilter,
    usePagination,
    useSortBy,
    useTable,
} from "react-table"

function TablePagination({
    columns,
    data,
    fetchData,
    loading,
    pageCount: controlledPageCount,
    totalRow,
    actions: Actions,
}) {
    const {
        getTableProps,
        getTableBodyProps,
        headerGroups,
        prepareRow,
        page,
        canPreviousPage,
        canNextPage,
        pageOptions,
        pageCount,
        gotoPage,
        nextPage,
        previousPage,
        setPageSize,
        state: { pageIndex, pageSize, globalFilter, sortBy },
        preGlobalFilteredRows,
        setGlobalFilter,
    } = useTable(
        {
            columns,
            data,
            manualPagination: true,
            manualGlobalFilter: true,
            manualSortBy: true,
            initialState: {
                pageIndex: 0,
                pageSize: 10,
            }, // Pass our hoisted table state
            pageCount: controlledPageCount,
            autoResetSortBy: false,
            autoResetExpanded: false,
            autoResetPage: false,
        },
        useGlobalFilter,
        useSortBy,
        usePagination
    )

    const GlobalFilter = ({
        preGlobalFilteredRows,
        globalFilter,
        setGlobalFilter,
    }) => {
        const count = preGlobalFilteredRows
        const [value, setValue] = React.useState(globalFilter)
        const onChange = useAsyncDebounce((value) => {
            setGlobalFilter(value || undefined)
        }, 700)

        return (
            <div
                className={
                    Actions !== undefined
                        ? "flex flex-row justify-between"
                        : "flex flex-col"
                }
            >
                {Actions !== undefined ? <Actions /> : null}
                <input
                    value={value || ""}
                    onChange={(e) => {
                        setValue(e.target.value)
                        onChange(e.target.value)
                    }}
                    placeholder={`${count} records...`}
                    type="search"
                    className={`input input-bordered input-sm w-full max-w-xs focus:outline-0 mb-2 ${
                        Actions !== undefined ? "" : "self-end"
                    }`}
                />
            </div>
        )
    }

    React.useEffect(() => {
        let search = globalFilter === undefined ? "" : globalFilter
        fetchData(pageSize, pageIndex, search, sortBy)
    }, [fetchData, pageIndex, pageSize, globalFilter, sortBy])

    return (
        <>
            <GlobalFilter
                preGlobalFilteredRows={totalRow}
                globalFilter={globalFilter}
                setGlobalFilter={setGlobalFilter}
            />
            <div className="overflow-x-auto relative">
                <table
                    {...getTableProps()}
                    className="table table-compact table-zebra w-full"
                >
                    <thead>
                        {headerGroups.map((headerGroup) => (
                            <tr {...headerGroup.getHeaderGroupProps()}>
                                {headerGroup.headers.map((column) => (
                                    <th
                                        {...column.getHeaderProps(
                                            column.getSortByToggleProps()
                                        )}
                                    >
                                        <span>
                                            {column.isSorted ? (
                                                column.isSortedDesc ? (
                                                    <ArrowLongDownIcon className="h-4 w-4 inline mr-1" />
                                                ) : (
                                                    <ArrowLongUpIcon className="h-4 w-4 inline mr-1" />
                                                )
                                            ) : (
                                                <FunnelIcon className="h-4 w-4 inline mr-1" />
                                            )}
                                        </span>
                                        {column.render("Header")}
                                    </th>
                                ))}
                            </tr>
                        ))}
                    </thead>
                    <tbody {...getTableBodyProps()}>
                        {page.length > 0 ? (
                            page.map((row, i) => {
                                prepareRow(row)
                                return (
                                    <tr
                                        {...row.getRowProps()}
                                        className="hover"
                                    >
                                        {row.cells.map((cell) => {
                                            return (
                                                <td {...cell.getCellProps()}>
                                                    {cell.render("Cell")}
                                                </td>
                                            )
                                        })}
                                    </tr>
                                )
                            })
                        ) : (
                            <tr className="hover">
                                <td colSpan={10000} className="text-center">
                                    Data not found!
                                </td>
                            </tr>
                        )}
                    </tbody>
                </table>
                {loading ? (
                    <div className="absolute top-0 bottom-0 left-0 right-0 bg-black bg-opacity-5 rounded-md z-20 flex items-center justify-center">
                        <div className="absolute p-3 bg-white w-36 shadow-md rounded-md text-center">
                            <div className="flex animate-pulse">
                                <ClockIcon className="w-6 h-6 mr-1" />{" "}
                                <span>Loading...</span>
                            </div>
                        </div>
                    </div>
                ) : null}
            </div>
            <div className="flex flex-row justify-between">
                <div className="mt-2">
                    <span>
                        Halaman{" "}
                        <strong>
                            {pageIndex + 1} dari {pageOptions.length}
                        </strong>{" "}
                        Total <strong>{preGlobalFilteredRows.length}</strong>{" "}
                    </span>
                    <span>
                        | Lompat ke halaman:{" "}
                        <input
                            type="number"
                            defaultValue={pageIndex + 1}
                            onChange={(e) => {
                                const page = e.target.value
                                    ? Number(e.target.value) - 1
                                    : 0
                                gotoPage(page)
                            }}
                            className="input input-bordered input-sm w-20 max-w-xs focus:outline-0"
                        />
                    </span>{" "}
                    <select
                        value={pageSize}
                        onChange={(e) => {
                            setPageSize(Number(e.target.value))
                        }}
                        className="select select-bordered select-sm w-30 max-w-xs focus:outline-0"
                    >
                        {[10, 20, 30, 40, 50].map((pageSize) => (
                            <option key={pageSize} value={pageSize}>
                                Tampilkan {pageSize} baris
                            </option>
                        ))}
                    </select>
                </div>
                <div className="mt-2">
                    <button
                        className="btn btn-xs"
                        onClick={() => gotoPage(0)}
                        disabled={!canPreviousPage}
                    >
                        {"<<"}
                    </button>{" "}
                    <button
                        className="btn btn-xs"
                        onClick={() => previousPage()}
                        disabled={!canPreviousPage}
                    >
                        {"<"}
                    </button>{" "}
                    <button
                        className="btn btn-xs"
                        onClick={() => nextPage()}
                        disabled={!canNextPage}
                    >
                        {">"}
                    </button>{" "}
                    <button
                        className="btn btn-xs"
                        onClick={() => gotoPage(pageCount - 1)}
                        disabled={!canNextPage}
                    >
                        {">>"}
                    </button>{" "}
                </div>
            </div>
        </>
    )
}

export default TablePagination

依赖性

上面的组件生成一个具有全局过滤,分页,排序和加载功能的表。它使用以下依赖项:图标的@heroicons/react,基本库的reactreact-table用于生成表。

表实例

TablePagination是一个接收用于显示表的道具的函数。这些道具是表列的columns,要显示的数据的data,在更改分页时获取数据的fetchDataloading以显示加载图标,pageCountpageCounttotalRow for the Row Count和actions for actions for actions for actions for actions for actions过滤器行上的额外动作按钮。

该函数使用useTable创建一个表实例,并将表的状态初始化为第一页,每页十行。它还将manualPaginationmanualGlobalFiltermanualSortBy设置为true,以便组件可以控制这些功能。

全局过滤

GlobalFilter组件显示用于过滤表数据的输入搜索框。它还收到预先过滤的行计数,并使用useAsyncDebounce延迟搜索过滤器,直到用户停止键入。这有助于减少搜索时对服务器的不必要调用。

桌子主体

然后,使用React-Table库中的getTablePropsgetTableBodyProps方法创建表主体和标头。 headerGroups和页面用于使用MAP函数分别在标题列和表数据上映射。每行都调用prepareRow方法,以实现getRowPropsgetCellProps方法的使用来定型行和单元格。

排序

通过将getHeaderProps方法添加到列标题并使用column.getSortByToggleProps()方法来启用排序功能。此方法在表状态中更新sortBy对象,并将适当的类和图标添加到排序的列中。

分页

使用usePaginationpageCountcanPreviousPagecanNextPagepageOptionspageOptionsgotoPagenextPagenextPagepreviousPagesetPageSize方法启用了分页功能。这些方法用于生成分页控件并在用户与它们交互时更新表状态。

加载

最后,通过检查loading是否为true并在从服务器获取数据时在表中显示加载图标。

,启用了加载功能。

帮助者

当我们使用API​​进行分页时,我们还需要一个助手到发送到服务器之前的序列化端点URL。

// Filename: uriSerialized.js
const Util = {
    isArray: function (val) {
        return Object.prototype.toString.call(val) === "[object Array]"
    },
    isNil: function (val) {
        return val === null || Util.typeOf(val)
    },
    typeOf: function (val, type) {
        return (type || "undefined") === typeof val
    },
    funEach: function (obj, fun) {
        if (Util.isNil(obj)) return // empty value

        if (!Util.typeOf(obj, "object")) obj = [obj] // Convert to array

        if (Util.isArray(obj)) {
            // Iterate over array
            for (var i = 0, l = obj.length; i < l; i++)
                fun.call(null, obj[i], i, obj)
        } else {
            // Iterate over object
            for (var key in obj)
                Object.prototype.hasOwnProperty.call(obj, key) &&
                    fun.call(null, obj[key], key, obj)
        }
    },
}

export const uriSerialized = (params) => {
    let pair = []

    const encodeValue = (v) => {
        if (Util.typeOf(v, "object")) v = JSON.stringify(v)

        return encodeURIComponent(v)
    }

    Util.funEach(params, (val, key) => {
        let isNil = Util.isNil(val)

        if (!isNil && Util.isArray(val)) key = `${key}[]`
        else val = [val]

        Util.funEach(val, (v) => {
            pair.push(`${key}=${isNil ? "" : encodeValue(v)}`)
        })
    })

    return pair.join("&")
}

此代码定义了一个名为Util的对象,该对象包含多个实用程序函数:

  1. isArray检查给定值是否为数组。它通过使用Object.prototype.toString.call方法来完成此操作,该方法返回代表对象类型的字符串。如果字符串匹配预期值“ [object Array]”,则该值被视为数组。
  2. isNil检查给定值是null还是未定义。它通过使用typeOf方法来检查该值的类型是否为“未定义”。
  3. typeOf检查给定值是否为某种类型。它通过将值的类型与提供的类型作为参数进行比较来做到这一点。如果类型匹配,则返回true。
  4. funEach是一个实用程序函数,可以在数组或对象上迭代并为每个元素执行给定函数。如果给定值为null或未定义,则该函数仅返回。如果该值不是对象,则将其转换为数组。如果值是数组,则它会在每个元素上迭代,并以元素,索引和数组为参数调用给定函数。如果值是一个对象,则它会在每个键值对上迭代,并以值,键和对象为参数的给定函数。

代码然后导出一个名为uriSerialized的函数。此函数将对象params作为输入,并返回代表对象为尿道编码的字符串的字符串。

该函数使用Util.funEach迭代对象并创建一个键值对数组,其中每个值都均为尿道编码。如果值是数组,则通过将“ []”附加到末端来修改键。然后将键值对与“&”作为分离器串联成一个字符串,然后返回。

服务

例如,当我们需要为系统上的角色指定创建数据时。

import axios from "axios"
import { uriSerialized } from "../Utils/uriSerialized"

export const getRoleDatatable = async (queryOptions = null) => {
    try {
        const query = queryOptions ? "?" + uriSerialized(queryOptions) : ""
        const request = await axios({
            method: "GET",
            url: `/roles/datatable${query}`,
        })
        const response = request.data
        return response
    } catch (error) {
        console.log(`getRoleDatatable error: ${error}`)
        return false
    }
}

此函数使使用Axios HTTP客户端库的角色数据登录到API端点。

该函数接受可选参数queryOptions,可用于将查询参数传递到API端点。如果queryOptions未零,则将使用从“ ../ utils/uriserializatized导入的uriSerialized函数”将其转换为序列化的URI字符串。然后将序列化的URI字符串附加到API端点URL。

然后,该函数将HTTP请求发送到AXIO的API端点,并等待响应数据。如果请求成功,则返回响应数据。如果请求失败,则错误消息将记录到控制台,并且函数返回false

角色数据表

woooooooooooooooooooooooooooo ....在RoleDatatable组件中实现

import React, { useState, useCallback, useMemo } from "react"
import { getRoleDatatable } from "../../../services/roles"
import TablePagination from "../../TablePagination"
import { PencilSquareIcon, TrashIcon } from "@heroicons/react/24/solid"

function RoleDatatable() {
    const [data, setData] = useState([])
    const [loading, setLoading] = useState(false)
    const [pageCount, setPageCount] = useState(0)
    const [totalRow, setTotalRow] = useState(0)

    const fetchData = useCallback(
        async (pageSize, pageIndex, search, order) => {
            setLoading(true)
            const queryOptions = {
                page: pageIndex,
                limit: pageSize,
                search: search,
                order: order,
            }
            const items = await getRoleDatatable(queryOptions)

            setData(items.data)
            setPageCount(items.pagination.totalPage)
            setTotalRow(items.pagination.totalRow)
            setLoading(false)
        },
        []
    )

    const columns = useMemo(
        () => [
            {
                Header: "#",
                accessor: "roleId",
                Cell: ({ row }) => `R#${row.original.roleId}`,
                disableSortBy: true,
            },
            {
                Header: "Role Name",
                accessor: "roleName",
            },
            {
                Header: "Role Key",
                accessor: "roleKey",
                Cell: ({ row }) => row.original.roleKey,
            },
            {
                Header: "Action",
                accessor: ({ row }) => {
                    return (
                        <div className="flex gap-2">
                            <button className="btn btn-xs btn-info">
                                <PencilSquareIcon className="w-4 h-4" />
                            </button>
                            <button className="btn btn-xs btn-error">
                                <TrashIcon className="w-4 h-4" />
                            </button>
                        </div>
                    )
                },
            },
        ],
        []
    )

    return (
        <section>
            <TablePagination
                columns={columns}
                data={data}
                fetchData={fetchData}
                loading={loading}
                pageCount={pageCount}
                totalRow={totalRow}
            />
        </section>
    )
}

export default RoleDatatable

这是一个功能性的反应组件,可从服务器获取数据,将其显示在打页表中,并为用户提供每个项目的某些操作按钮。

该组件使用useState钩子维护其内部状态,其中包括dataloadingpageCounttotalRowfetchData函数是一个useCallback挂钩,它使用一些查询参数对服务器进行API调用,以获取数据,更新状态变量并设置加载标志。

该组件还使用useMemo钩记录包含一系列对象的columns对象,该对象定义了表列的标题,其访问者函数和Cell函数,该函数返回每行的相应值。该表的最后一列有两个按钮PencilSquareIconTrashIcon,可允许用户编辑或删除项目。

TablePagination组件是一种自定义组件,可接收columnsdatafetchDataloadingpageCounttotalRowtotalRow。该组件负责渲染表,插入它,并在获取数据时显示loading旋转器。当用户单击“分页链接”时,fetchData与新页面索引和页面大小一起调用,该页面大小触发了带有更新的查询参数到服务器的新API调用。

最后,将组件导出为默认导出,可以在应用程序的其他部分导入和使用。

那是客户方面的事!!!露台完成了!

服务器端

现在在服务器端移动,我们将在Express API中使用prisma作为ORM。

依赖项:

  • lodash
  • Prisma
  • 一杯咖啡

角色数据表模型

// Filename: Roles.js
const { PrismaClient } = require('@prisma/client')
const db = new PrismaClient()
const _ = require('lodash')

exports.roleDatatable = async (
    page = 0,
    limit = 10,
    search = '',
    order = []
) => {
    try {
        var paginate = limit * page - 1
        var offset = paginate < 0 ? 0 : paginate
        const sort = _.isEmpty(order) ? [] : JSON.parse(_.first(order))
        const orderKey = _.isEmpty(sort) ? 'roleName' : sort.id
        const orderDirection = _.isEmpty(sort)
            ? 'desc'
            : sort.desc
            ? 'desc'
            : 'asc'

        const roles = await db.roles.findMany({
            where: {
                OR: [
                    {
                        roleName: {
                            contains: search,
                        },
                    },
                ],
                isDeleted: false,
            },
            skip: Number(offset),
            take: Number(limit),
            orderBy: {
                [orderKey]: orderDirection,
            },
        })

        const countTotal = await db.roles.count({
            where: {
                OR: [
                    {
                        roleName: {
                            contains: search,
                        },
                    },
                ],
                isDeleted: false,
            },
        })

        return {
            data: roles,
            totalRow: countTotal,
            totalPage: Math.ceil(countTotal / limit),
        }
    } catch (error) {
        console.log(`Error on roleDatatable: ${error}`)
        return false
    }
}

函数称为roleDatatable,该函数根据给定的搜索条件,排序和分页询问数据库以检索角色的分页列表。

该功能采用四个可选参数:pagelimitsearchorderpagelimit用于确定页面大小和要返回的记录的数量,而搜索用于根据文本字符串过滤记录。订单是指定​​order的数组。

在函数内部,使用paginateoffset变量来计算要跳过和采取的记录。 sortorderKeyorderDirection变量用于指定应对记录进行排序的顺序。

然后,功能使用db.roles.findMany()查询数据库,传递搜索条件,分页和排序选项。它还查询与搜索标准相匹配的角色的总数,该角色用于计算页面总数。

该函数返回包含分页角色,行总数和页面总数的对象。如果发生错误,它将记录错误并返回false。

帮助者

我需要格式化使用formatResponse助手发送给客户端的服务器响应

// Filename: helpers/formatResponse.js
module.exports = (
    type,
    message = 'No desription',
    data = [],
    pagination = null
) => {
    const ALLOWED_TYPES = [
        'VALID',
        'INVALID',
        'FOUND',
        'NOT_FOUND',
        'INTERNAL_ERROR',
        'CREATED',
        'NOT_MODIFIED',
        'NOT_AUTHORIZED',
        'FORBIDDEN',
    ]
    if (!ALLOWED_TYPES.includes(type)) {
        throw `${type} is not allowed. Available type is ${ALLOWED_TYPES.join(
            ', '
        )}`
    }
    return pagination === null
        ? { type, message, data }
        : { type, message, data, pagination }
}

该函数包含四个参数:type(字符串),message(默认值为“无描述”的字符串),data(一个具有默认值为空数组的数组)和pagination(可选的对象)。

该函数返回带有属性typemessagedata的对象,如果pagination不是null,则还包括pagination对象。

返回对象之前,该函数通过将其与允许类型的数组进行比较,检查type参数是否是允许类型之一。如果不允许使用type,则会丢弃错误,指示允许哪种类型。

路由器

让我们创建角色datatable的API路由

// Filename: routes/roles.js
const express = require('express')
const formatResponse = require('../../helpers/formatResponse')
const { roleDatatable } = require('../../models/Roles')

const router = express.Router()

router.get('/datatable', async (req, res) => {
    const { page, limit, search, order } = req.query
    const roles = await roleDatatable(page, limit, search, order)
    if (roles) {
        res.status(200).json(
            formatResponse('FOUND', 'Roles datatable', roles.data, {
                totalRow: roles.totalRow,
                totalPage: roles.totalPage,
            })
        )
    } else {
        res.status(404).json(formatResponse('NOT_FOUND', 'No data roles'))
    }
})

module.exports = router

他的代码为端点设置了一个路由器,该路由器返回角色的数据表。端点会听取“/datatable”路径的获取请求。收到请求后,将从请求中提取查询参数(页面,限制,搜索和订单)。然后,它使用查询参数从角色模型中调用roleDatatable函数。如果roleDatatable返回数据,则端点将发送带有200个状态代码的响应和包含数据表数据和分页信息的JSON对象。如果roleDatatable返回没有数据,则端点将发送带有404状态代码和包含错误消息的JSON对象的响应。

formatResponse函数用于将响应格式化为标准结构。它采用四个参数:type(一个指示响应类型的字符串),消息(提供有关响应的其他详细信息的字符串),数据(响应中包含的数据)和分页(包含的可选对象分页信息)。它返回一个包含四个参数的对象。

fiuh !!!!就是这样...没有旁观者...

如果此隐喻可以帮助您,并且想对Buy me coffee进行帮助,或者说谢谢或只是阅读。

来源隐喻:

Add [React Table - Sever Side Pagination, Search, Sort/Order] #10

mentaphore名称

React Table-断开,搜索,排序/顺序

share您的隐喻故事!

React-Table-Sever-Side-Pagination-Search-Sort-Order

tldr

这是我使用react-tableprisma Orm在React JS项目中管理DataTable的完整指南。让我们开始!

client side

  //组件文件名:tablepagination.js 
			 import   {
			 arrowlongdownicon  
			 arrowlongupicon  
			 funnelicon  
			}  来自  “@herioicons/react/react/react/react/24/outline” 
			 import   {  compricicon  }  来自  “@herioicons/react/react/24/solid” 
			 import   react  来自 ” React 
			 import   {
			 useasyncdebouncous  
			 useglobalfilter  
			 usepagination  
			 useortby  
			 usetable  
			}  来自  “ react-table” 
			在pl-kos“> {
			数据
			fetchdata 
			加载
			在
			totalrow }    {
			 const   {
			getTableProps 
			gettabledobyprops 
			headerGroups 
			Preparerow 
			canpreviouspage 
			canNextPage 
			pageoptions 
			pagecount 
			gotopage 
			nextpage 
			上一个page 
			setPagesize 
			 state  { pageIndex  pagesize  globalfilter  sortby }   
			preglobalfilterredrows 
			setGlobalFilter 
			在pl-kos”>(
			 {
			数据
			在
			在
			在
			初始状态 {
			在
			在
			}    //通过我们的吊杆状态
			在
			在
			在
			在
			}  
			 useglobalfilter  
			 useortby  
			 usepagination 
			
			在pl-kos”>(  {
			preglobalfilterredrows 
			GlobalFilter 
			setGlobalFilter 
			在“ PL-KOS”> {
			在PL-S1“> preglobalfilterredrows 
			在pl-kos“>,  setValue  ]    =   react    usestate 在
			在pl-en“> useasyncdebouncous       =>    {
			 setglobalfilter      ||   undefined  
			在pl-kos“>)
			 return   <  div 
			 className   =   {
			在
			? “ flex flex row joanify-weew” “ flex flex-col” 
			} 
			> 
			 {  action  !==   未定义的?在pl-c1“ >>  null  } 
			 <  input 
			在pl-s1“>值  ||  “”  } 
			 onChange   =   {   e    =>   {
			 setValue    e     target    
			 onChange    e     target    
			}  } 
			在pl-s“>`  $ {  count  }  记录...` }  className   =   { `输入输入式输入输入sm w-full max-w-xs焦点:outline-0 mb-2   $ $ {  
			   action  !==  未定义的?态
			在-kos“>} 
			/ > 
			在PL-C1“ >> 
			
			} 
			 react    useffeft       =>   {
			  search   =    GlobalFilter   ===   undefined “”  globalfilter 
			 fetchData    pageSize    pageIndex    搜索   sortby  
			}    [  fetchdata    pageIndex    pagesize    globalfilter 在pl-kos“>)
			 return   < > 
			 <  globalfilter 
			 preglobalfilteredrows   =   {   totalrow  } 
			 globalfilter   =   {  globalfilter  } 
			 setglobalfilter   =    {  setglobalfilter  } 
			/ > 
			 <  div   className  =  ” Overflow-x-auto相对“  >  
			 <  table 
			 { ...  getTableProps     } 
			 className   =  “ table table table table compact-zebra w--完整” 
			>  {  headerGroups    map     headerGroup    =>   
			 <  tr   { ...  headerGroup    getheadergrouprops     }   > << /span>
			 {  headerGroup   标题   map        =>   <  th 
			 { ...    getheaderprops     getsortbyToggleProps   
			 } 
			> 
			 <  span  > 
			 {    issorted 
			   issorteddesc 
			 <  arrowlongdownicon   className   =  ” H-4 W-4内联MR-1“   > 
			 <  arrowlongupicon   className   =  ” H-4 W-4内联MR-1“   > 
			
			 <  funnelicon   className   =  ” H-4 W-4内联MR-1“   > 
			 } 
			在PL-C1“ >> 
			 {   渲染  “ header”   } 
			在PL-C1“ >> 
			  } 
			在PL-C1“ >> 
			  } 
			在PL-C1“ >> 
			 <  tbody   { ...  gettablebodyprops    }  > 
			 {  page   长度 >   0 
			在pl-kos”>(   row     i    =>   {
			在pl-kos“>)
			 return   <  tr 
			 { ...    getrowprops    } 
			 className   =  “ hover” 
			> 
			 {   单元格   map      cell    =>   {
			 return   <  td   { ...  cell    getCellProps    }   > << /span>
			在pl-en“>渲染    } 
			在PL-C1“ >> 
			
			在
			在PL-C1“ >> 
			
			}  
			)
			在PL-C1“> =  “徘徊”  > 
			在pl-c1“> =   {  10000   }   className   =   “ text-center”  > 
			找不到数据!
			在PL-C1“ >> 
			在PL-C1“ >> 
			 } 
			在PL-C1“ >> 
			在PL-C1“ >> 
			 { 加载
			 <  div   className  =  ”绝对top-0底部-0左0左-0右0 bg-bg-bg-opacity-5圆形-MD Z-20 Z-20 Flex项目-center jusify-center“  > 
			 <  div   className  =  ”绝对P-3 BG-WHITE W-36 Shadow-MD圆形MD圆形MD文本中心“  > 
			 <  div   className  =  “ flex animate-pulse”  >  
			 <  clackicon   className   =  ” W-6 H-6 MR-1“  /在“ PL-KOS”>} 
			在span class =“ pl-c1”> < /  span   > 
			在PL-C1“ >> 
			在PL-C1“ >> 
			在PL-C1“ >> 
			在
			在PL-C1“ >> 
			 <  div   className  =  “ flex flex-row jusify-weew”  > 
			 <  div   className  =  ” MT-2“  > 
			 <  span  > 
			Halaman  { “”  } 
			 <  strong  > 
			 {  pageIndex  +  1  }  dari  {  pageOptions    length   } 
			在pl-c1“ >>   { “”  } 
			总计 <  strong  >   {  preglobalfilteredrows  。 >长度 }   <  /  strong  >   { “”  } 
			在PL-C1“ >> 
			 <  span  > 
			|跳到页面: { “”  } 
			 <  input 
			 type   =  “ number” 
			 defaultvalue   =   {  pageIndex  +  1    } 
			 onChange   =   {   e    =>   {
			在PL-S1“> e    target    数字   e    target       -    1  0 
			 gotopage    page  
			}  } 
			 className   =  “输入输入输入输入输入input-sm w- 20 Max-W-XS焦点:概述-0“ 
			/ > 
			在pl-c1“ >>   { “”  } 
			 <  select 
			在pl-s1“> pageize  } 
			 onChange   =   {   e    =>   {
			 setPagesize    number     e   目标    
			}  } 
			 className   =  “ select select-bordered select-bordered select-sm w-- 30 Max-W-XS焦点:概述-0“ 
			> 
			 {  [  10    20    30    40    50  ]    map     pageSize    =>   
			在pl-c1“> =   {    =   {在
			tampilkan  {  pagesize  }  baris
			 < /  option  > 
			  } 
			在PL-C1“ >> 
			在PL-C1“ >> 
			 <  div   className  =  ” MT-2“  > 
			 < 按钮
			 className   =  “ btn btn-xs” 
			 onclick   =   {    =>    gotopage    0   } 
			禁用  =   {   canpreviouspage  } } 
			> 
			 { “ <<”  } 
			 < /  button  >   { “”  } 
			 < 按钮
			 className   =  “ btn btn-xs” 
			 onclick   =   {    =>   上一个page    } 
			禁用  =   {   canpreviouspage  } } 
			> 
			 { “ <”  } 
			 < /  button  >   { “”  } 
			 < 按钮
			 className   =  “ btn btn-xs” 
			 onclick   =   {    =>    nextpage    } 
			禁用  =   {   cannextpage  } 
			> 
			 { “>”  } 
			 < /  button  >   { “”  } 
			 < 按钮
			 className   =  “ btn btn-xs” 
			 onclick   =   {    =>    gotopage    pagecount    -    1   } 
			禁用  =   {   cannextpage  } 
			> 
			 { “ >>”  } 
			 < /  button  >   { “”  } 
			在PL-C1“ >> 
			在PL-C1“ >> 
			 < / > 
			
			} 
			导出 默认  tablepagination  

依赖性

上面的组件生成一个具有全局过滤,分页,排序和加载功能的表。它使用以下依赖项:图标的@heroicons/react,基本库的reactreact-table用于生成表。

table实例

TablePagination是一个接收用于显示表的道具的函数。这些道具是表列的columns,要显示的数据的data,在更改分页时获取数据的fetchDataloading以显示加载图标,pageCountpageCounttotalRow for the Row Count和actions for actions for actions for actions for actions for actions过滤器行上的额外动作按钮。

该函数使用useTable创建一个表实例,并将表的状态初始化为第一页,每页十行。它还将manualPaginationmanualGlobalFiltermanualSortBy设置为true,以便组件可以控制这些功能。

global滤波

GlobalFilter组件显示用于过滤表数据的输入搜索框。它还收到预先过滤的行计数,并使用useAsyncDebounce延迟搜索过滤器,直到用户停止键入。这有助于减少搜索时对服务器的不必要调用。

table身体

然后,使用React-Table库中的getTablePropsgetTableBodyProps方法创建表主体和标头。 headerGroups和页面用于使用MAP函数分别在标题列和表数据上映射。每行都调用prepareRow方法,以实现getRowPropsgetCellProps方法的使用来定型行和单元格。

Sorting

通过将getHeaderProps方法添加到列标题并使用column.getSortByToggleProps()方法来启用排序功能。此方法在表状态中更新sortBy对象,并将适当的类和图标添加到排序的列中。

pagination

使用usePaginationpageCountcanPreviousPagecanNextPagepageOptionspageOptionsgotoPagenextPagenextPagepreviousPagesetPageSize方法启用了分页功能。这些方法用于生成分页控件并在用户与它们交互时更新表状态。

loading

最后,通过检查loading是否为true并在从服务器获取数据时在表中显示加载图标。

,启用了加载功能。

Helpers

当我们使用API​​进行分页时,我们还需要一个助手到发送到服务器之前的序列化端点URL。

  //文件名:uriserialized.js 
			在pl-kos“> {
			在“ pl-s1”> val    {
			 return   object   原型   toString   呼叫   val    ====  “ [对象阵列]跨度>
			}  
			在“ pl-s1”> val    {
			在=“ pl-c1”> null   ||   util     typeof     val  
			}  
			在“ pl-s1”> val    type     {
			 return    type   ||  “ undefined”    ===   typeof   val 
			}  
			在“ PL-S1”> obj    fun     {
			如果   util    isnil     obj      return   //空值
			如果    util    typeof     obj   “ object”     obj    =   [  obj   ]   //转换为阵列
			如果   util    isarray     obj     { {
			 //在数组上迭代
			 for    var    i   =   0     l   =   obj   长度 ;   i   <  l    ;   i   ++  
			在pl-kos”>(  null    obj   [  i   ]    i    obj  
			}   else   {
			 //迭代对象
			 for    var    key   in   obj   
			在pl-kos“>。  hasownproperty   呼叫   obj  在
			在pl-kos”>(  null    obj   [  key   ]    key    obj  
			} 
			}  
			} 
			导出  const   uriserialized   =    params     =>   {
			 配对  =    [ ] 
			 const   encodevalue   =     v    =>   {
			如果   util    typeof    v   “ object”     v   =   json    stringify    v  
			 return   encodeuricomponent    v  
			} 
			在pl-kos”>(  params     val      =>   {
			  isnil   =    $ {span>  error    
			 return   false 
			} 
			}  

此函数使使用Axios HTTP客户端库的角色数据登录到API端点。

该函数接受可选参数queryOptions,可用于将查询参数传递到API端点。如果queryOptions未零,则将使用从“ ../ utils/uriserializatized导入的uriSerialized函数”将其转换为序列化的URI字符串。然后将序列化的URI字符串附加到API端点URL。

然后,该函数将HTTP请求发送到AXIO的API端点,并等待响应数据。如果请求成功,则返回响应数据。如果请求失败,则错误消息将记录到控制台,并且函数返回false

角色datatable

woooooooooooooooooooooooooooo ....在RoleDatatable组件中实现

  import   react    {  usestate    usecallback    usememo  } < /span> 来自 “ react” 
			 import   {  getRoledatabable  }  来自 “ pl-s”>“ ../../ ../ ../ services/wars” < /span>
			 import   tablepagination  来自 ” ../../ tablepagination“ 
			 import   {  pencilssquareicon    trashicon  }   来自 “@heroicons/react/react/24/solid” 
			 function   roledataitable     {
			在pl-kos“>,  setData  ]    =   usestate    [ ]  
			在pl-kos“>,  setloading  ]    =   usestate    false  
			在pl-kos“>,  setPageCount  ]   =   usestate    0  
			在pl-kos“>,  settotalrow  ]   =   usestate    0  
			在pl-en“> usecallback   async    pagesize    pageIndex    搜索  顺序   =>   {
			在pl-kos“>)
			在pl-kos“> {
			在
			在
			在
			在
			} 
			在pl-k“>等待  getRoledatabable    queryOptions  
			 setData    items    data  
			 setPageCount    items   分页    TotalPage  
			 settotalrow   项目  分页    totalrow  
			在pl-kos“>)
			}  
			 [ ] 
			
			在pl-en“> usememo     =>   [
			 {
			 header “#”  单元格  { row }    => =>  `r#  $ {  row    原始   rooleid  }  `  }  
			 {
			 header “角色名称”  }  
			 {
			 header “角色键”  
			 concector “ colekey”  
			单元格  { row }    => =>    原始  。<<<< /span>  roulekey  
			}  
			 {
			 header “ action”  
			 concector   { row }    => =>   {
			 return   <  div   className  =  ” flex gap-2“  > 
			 <  button   className   =  “ btn-btn-xs btn-info”  >  
			 <  pencilsquareicon   className   =  “ W-4 H-4”  / > 
			 < /  button  > 
			 <  button   className   =  “ btn-xs btn-error”  > 
			 <  trashicon   className   =  “ W-4 H-4”  / > 
			 < /  button  > 
			在PL-C1“ >> 
			
			}  
			}  
			]  
			 [ ] 
			
			 return   <  > 
			 <  tablepagination 
			在pl-s1“>列 } 
			 data   =   {  data  } 
			 fetchdata   =   {  fetchdata  } 
			加载  =   { 加载 } 
			在pl-s1“> pagecount  } 
			在pl-s1“> totalrow  } 
			/ > 
			在PL-C1“ >> 
			
			} 
			导出 默认  roledatatable  

这是一个功能性的反应组件,可从服务器获取数据,将其显示在打页表中,并为用户提供每个项目的某些操作按钮。

该组件使用useState钩子维护其内部状态,其中包括dataloadingpageCounttotalRowfetchData函数是一个useCallback挂钩,它使用一些查询参数对服务器进行API调用,以获取数据,更新状态变量并设置加载标志。

该组件还使用useMemo钩记录包含一系列对象的columns对象,该对象定义了表列的标题,其访问者函数和Cell函数,该函数返回每行的相应值。该表的最后一列有两个按钮PencilSquareIconTrashIcon,可允许用户编辑或删除项目。

TablePagination组件是一种自定义组件,可接收columnsdatafetchDataloadingpageCounttotalRowtotalRow。该组件负责渲染表,插入它,并在获取数据时显示loading旋转器。当用户单击“分页链接”时,fetchData与新页面索引和页面大小一起调用,该页面大小触发了带有更新的查询参数到服务器的新API调用。

最后,将组件导出为默认导出,可以在应用程序的其他部分导入和使用。

那是客户方面的事!!!露台完成了!

Server

现在在服务器端移动,我们将在Express API中使用prisma作为ORM。

依赖项:

  • lodash
  • Prisma
  • 一杯咖啡

role datatable模型

  //文件名:roles.js 
			 const   { prismaclient }    =   require   '@prisma/client' 
			在pl-k“> new   prismaclient   
			在pl-en“> require   'lodash' 
			 enforts    roledatabable   =   async  )
			在pl-kos“>,
			在pl-kos“>,
			在“ PL-KOS”>,
			在PL-KOS“>] 
			  =>   {
			尝试  {
			在pl-s1“> limit  *  page    -    1 
			 var   offset   =   sort    id 
			 const   orderDirection   =    _    isempty     sort  'desc' sort    desc  'desc''asc'
			在pl-k“>等待  db   角色   findmany   {
			其中 {
			 [
			 {
			 rolename  {}  
			}  
			]  }  
			在“ pl-s1”>偏移  
			在“ pl-s1”> limit   
			 orderby  {
			在“ PL-S1”> OrderDirection  
			}  
			}  
			在pl-k“>等待  db   角色   count    {
			其中 {
			 [
			 {
			 rolename  {}  
			}  
			]  }  
			}  
			 return   {
			在
			在
			在“ pl-en”> ceil    counttotal  /  limit    
			} 
			在pl-s1“>错误   {
			 console    log    `roledataTable上的错误:  $ { 错误 }   `  
			 return   false 
			} 
			}  

函数称为roleDatatable,该函数根据给定的搜索条件,排序和分页询问数据库以检索角色的分页列表。

该功能采用四个可选参数:pagelimitsearchorderpagelimit用于确定页面大小和要返回的记录的数量,而搜索用于根据文本字符串过滤记录。订单是指定​​order的数组。

在函数内部,使用paginateoffset变量来计算要跳过和采取的记录。 sortorderKeyorderDirection变量用于指定应对记录进行排序的顺序。

然后,功能使用db.roles.findMany()查询数据库,传递搜索条件,分页和排序选项。它还查询与搜索标准相匹配的角色的总数,该角色用于计算页面总数。

该函数返回包含分页角色,行总数和页面总数的对象。如果发生错误,它将记录错误并返回false。

我需要使用formatResponse助手

格式化服务器响应,该响应

  //文件名:helpers/formatresponse.js 
			在PL-C1“> =   type  
			消息  =  'no deSription' 
			 data   =   [ ]  
			分页  =   null  null 
			  =>   {
			在pl-kos“> [
			'有效' 
			'无效' 
			'找到' 
			'not_found' 
			'internal_error' 
			'创建' 
			'not_modified' 
			'not_authorized' 
			'禁止' 
			] 
			如果    wasse_types   包括    type     {
			在span>  type  }  不允许。可用类型IS   $ {  washe_types    join     
			态
			在`
			} 
			在=“ PL-C1”> null 
			?  { type 消息 data }  { type 消息 data < Span class =“ PL-KOS”>,分页} 
			}  

该函数包含四个参数:type(字符串),message(默认值为“无描述”的字符串),data(一个具有默认值为空数组的数组)和pagination(可选的对象)。

该函数返回带有属性typemessagedata的对象,如果pagination不是null,则还包括pagination对象。

返回对象之前,该函数通过将其与允许类型的数组进行比较,检查type参数是否是允许类型之一。如果不允许使用type,则会丢弃错误,指示允许哪种类型。

Router

让我们创建角色datatable的API路由

  //文件名:路由/roles.js 
			在pl-en“> require   'express'express' 
			在pl-en“> require   '../ helpers/formatresponse' 
			 const   { roledatabable }    =   require   '../../模型/角色' 
			在pl-s1“> express   路由器   
			路由器   get    '/datatable'   async    req   ,< /span>  res    =>   < SPAN类=“ PL-KOS”> {
			在=“ pl-kos”>,搜索 order }   =   req   
			在pl-k“>等待  roledataTable    page    limit在pl-kos“>)
			如果  角色   {
			在pl-kos”>(  200     json  )
			在=“ pl-kos”>, '角色datatable'  角色   data     {
			在“ PL-C1”> Totalrow  
			在“ PL-C1”> TotalPage  
			}  
			
			}   else   {
			在pl-kos”>(  404      json    formatresponse   'not_found'   '无数据角色'  
			} 
			}  
			在pl-c1“> =  路由器 

他的代码为端点设置了一个路由器,该路由器返回角色的数据表。端点会听取“/datatable”路径的获取请求。收到请求后,将从请求中提取查询参数(页面,限制,搜索和订单)。然后,它使用查询参数从角色模型中调用roleDatatable函数。如果roleDatatable返回数据,则端点将发送带有200个状态代码的响应和包含数据表数据和分页信息的JSON对象。如果roleDatatable返回没有数据,则端点将发送带有404状态代码和包含错误消息的JSON对象的响应。

formatResponse函数用于将响应格式化为标准结构。它采用四个参数:type(一个指示响应类型的字符串),消息(提供有关响应的其他详细信息的字符串),数据(响应中包含的数据)和分页(包含的可选对象分页信息)。它返回一个包含四个参数的对象。

fiuh !!!!就是这样...没有旁观者...

a演示/repos链接

无响应