使用 Material UI 和一丝喜悦来响应数据表⚛️
如何构建数据表
准备数据库中的数据💾
启动 API 来处理数据🚀
使用 React ⚛️ 创建应用程序
构建基本数据表🏗
当你想要可视化大量统一的数据时,图表效果不佳,因为它们会有效地隐藏单个数据项的信息。然而,数据表就派上用场了! 😇
在本教程中,我们将学习如何在 React 中从头构建的数据表中显示海量数据。我们将探索如何通过 API 从数据库获取数据,并使用过滤、排序等基本功能在数据表中进行可视化。
我们将使用Material UI,因为它是 React 最流行的 UI 框架。它的设计灵感源自 Google 的Material Design,提供了许多组件,可用于创建美观的用户界面。
如何构建数据表
这是我们今天的计划!
听起来不错吧?走吧!
在深入研究之前,请先查看我们将要构建的数据表的屏幕截图。此外,请查看GitHub 上提供的完整源代码。
准备数据库中的数据💾
我想,我们会使用最流行的 SQL 数据存储之一——PostgreSQL数据库。请确保你已安装 PostgreSQL。(否则,它可能有一天会变得不再流行😛。)
现在,我们可以下载并导入一个精心准备的 PostgreSQL 示例电商数据集。该数据集与一家虚拟电商公司相关,该公司希望跟踪其订单及其状态:
$ curl <http://cube.dev/downloads/ecom-dump.sql> > ecom-dump.sql
$ createdb ecom
$ psql --dbname ecom -f ecom-dump.sql
好了,数据库已经准备好了!让我们继续……
启动 API 来处理数据🚀
我们将使用Cube.js作为 API。Cube.js 是一个开源分析 API 平台,可帮助您创建用于 SQL 数据存储的 API 并构建分析应用程序。它免除了构建 API 层、生成 SQL 和查询数据库的所有繁琐步骤。它还提供了许多生产级功能,例如用于优化性能的多级缓存、多租户、安全性等等。
因此,让我们通过几个简单的步骤使用 Cube.js 在数据库上启动 API。
首先,我们需要安装 Cube.js 命令行工具(CLI)。为了方便起见,我们先在机器上全局安装它。
$ npm install -g cubejs-cli
然后,安装 CLI 后,我们可以通过运行单个命令创建一个基本的后端。Cube.js 支持所有流行的数据库,因此我们可以预先配置后端以使用 PostgreSQL:
$ cubejs create <project name> -d <database type>
要创建后端,我们运行以下命令:
$ cubejs create react-data-table -d postgres
现在我们需要将它连接到数据库。为了实现这一点,我们通过.env
Cube.js 项目根目录下的文件 ( react-data-table
) 提供了一些选项:
CUBEJS_DB_NAME=ecom
CUBEJS_DB_TYPE=postgres
CUBEJS_API_SECRET=secret
现在我们可以运行后端了!
在开发模式下,后端还会运行 Cube.js Playground。这真的很酷。🤘 Cube.js Playground 是一款省时的 Web 应用程序,可帮助您创建数据模式、测试查询并生成 React 仪表板样板。在 Cube.js 项目文件夹中运行以下命令:
$ node index.js
接下来,在浏览器中打开http://localhost:4000 。
我们将使用 Cube.js Playground 创建数据模式。它本质上是一段 JavaScript 代码,以声明式的方式描述数据,定义度量和维度等分析实体,并将它们映射到 SQL 查询。以下是可用于描述产品数据的模式示例。
cube(`Products`, {
sql: `SELECT * FROM public.products`,
measures: {
count: {
type: `count`
}
},
dimensions: {
name: {
sql: `name`,
type: `string`
},
id: {
sql: `id`,
type: `number`,
primaryKey: true,
shown: true
},
description: {
sql: `description`,
type: `string`
},
createdAt: {
sql: `created_at`,
type: `time`
}
}
});
Cube.js 可以根据数据库表生成简单的数据模式。如果您的数据库中已经有一组重要的表,可以考虑使用数据模式生成功能,因为它可以节省大量时间。
因此,让我们导航到 Cube.js Playground 的 Schema 选项卡,public
在树视图中选择组,选择line_items
、orders
、products
和users
表,然后单击“生成 Schema”。结果,我们将在schema
文件夹中生成 4 个文件 - 每个表恰好一个模式文件。
生成模式后,我们就可以通过 Cube.js Playground 查询数据了。具体操作如下:导航到“Build”选项卡,从模式中选择一些度量和维度。看起来很神奇,不是吗?
在“构建”选项卡中,您可以使用不同的可视化库构建示例图表,并检查图表创建的各个方面,从生成的 SQL 一直到用于渲染图表的 JavaScript 代码。您还可以检查发送到 Cube.js 后端的 JSON 编码的 Cube.js 查询。
好的,一切就绪。API 已准备就绪,现在我们……
使用 React ⚛️ 创建应用程序
大新闻!😛
Cube.js 园地可以为你生成任何所选前端框架和图表库的模板。要为我们的应用程序创建模板,请导航到“仪表板应用”并使用以下选项:
- 框架:
React
- 主模板:
React Material UI Static
- 图表库:
Chart.js
恭喜!现在我们的项目中有了dashboard-app
文件夹。此文件夹包含我们要扩展的所有前端代码。
在继续之前,让我们做出最重要的改变——在标题中显示我们正在构建数据表。😝
为此,请更改public/index.html
文件中的几行dashboard-app
,如下所示:
// ...
- <title>React App</title>
+ <title>React Data Table</title>
+ <style>
+ body {
+ background-color: #eeeeee;
+ margin: 0;
+ }
+ </style>
// ...
此外,让我们安装一些依赖项,dashboard-app
这将使我们构建数据表的任务更容易:
$ npm install --save react-perfect-scrollbar @material-ui/pickers
那么,现在我们准备好了...
构建基本数据表🏗
表中有大量数据真是太棒了,对吧?那么,让我们通过 API 获取它们。
为此,我们将定义一些新指标:订单中的商品数量(即订单大小)、订单价格以及用户的全名。使用 Cube.js,这非常简单:
首先,让我们在schema/Users.js
文件的“Users”模式中添加全名。要创建全名,我们使用 SQL 函数连接名字和姓氏CONCAT
:
cube(`Users`, {
sql: `SELECT * FROM public.users`,
// ...
dimensions: {
// ...
id: {
+ shown: true,
sql: `id`,
type: `number`,
primaryKey: true
},
firstName: {
sql: `first_name`,
type: `string`
},
lastName: {
sql: `last_name`,
type: `string`
},
+ fullName: {
+ sql: `CONCAT(${firstName}, ' ', ${lastName})`,
+ type: `string`
+ },
// ...
然后,让我们向文件中的“订单”模式添加其他措施schema/Orders.js
。
对于这些度量,我们将使用Cube.js 的子查询功能。您可以使用子查询维度来引用维度内其他多维数据集的度量。定义此类维度的方法如下:
cube(`Orders`, {
sql: `SELECT * FROM public.orders`,
dimensions: {
// ...
id: {
+ shown: true,
sql: `id`,
type: `number`,
primaryKey: true
},
createdAt: {
sql: `created_at`,
type: `time`
},
+ size: {
+ sql: `${LineItems.count}`,
+ subQuery: true,
+ type: 'number'
+ },
+
+ price: {
+ sql: `${LineItems.price}`,
+ subQuery: true,
+ type: 'number'
+ },
completedAt: {
sql: `completed_at`,
type: `time`
}
}
});
快完成了!为了显示数据表,我们将src/pages/DashboardPage.js
文件替换为以下内容:
import React from "react";
import { makeStyles } from "@material-ui/styles";
import Table from "../components/Table.js";
const useStyles = makeStyles(theme => ({
root: { padding: 15 },
content: { marginTop: 15 },
}));
const Dashboard = () => {
const classes = useStyles();
const query = {
timeDimensions: [
{
dimension: 'Orders.createdAt',
granularity: 'day'
}
],
dimensions: [
'Users.id',
'Orders.id',
'Orders.size',
'Users.fullName',
'Users.city',
'Orders.price',
'Orders.status',
'Orders.createdAt',
]
};
return (
<div className={classes.root}>
<div className={classes.content}>
<Table query={query}/>
</div>
</div>
);
};
export default Dashboard;
请注意,现在该文件包含一个 Cube.js 查询,该查询非常不言自明:我们只是要求 API 返回一些维度,然后它就会执行该操作。
然而,对的变化Dashboard
很小,渲染数据表的所有神奇之处都发生在<Table />
组件内部,并且查询结果的变化反映在表中。
让我们在文件<Table />
中创建这个组件src/components/Table.js
,其内容如下:
import React, { useState } from "react";
import clsx from "clsx";
import PropTypes from "prop-types";
import moment from "moment";
import PerfectScrollbar from "react-perfect-scrollbar";
import { makeStyles } from "@material-ui/styles";
import Typography from "@material-ui/core/Typography";
import { useCubeQuery } from "@cubejs-client/react";
import CircularProgress from "@material-ui/core/CircularProgress";
import {
Card,
CardActions,
CardContent,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TablePagination
} from "@material-ui/core";
const useStyles = makeStyles(theme => ({
root: {
padding: 0
},
content: {
padding: 0
},
inner: {
minWidth: 1050
},
nameContainer: {
display: "flex",
alignItems: "baseline"
},
status: {
marginRight: 15
},
actions: {
justifyContent: "flex-end"
},
}));
const TableComponent = props => {
const { className, query, cubejsApi, ...rest } = props;
const classes = useStyles();
const [rowsPerPage, setRowsPerPage] = useState(10);
const [page, setPage] = useState(0);
const tableHeaders = [
{ text: "Full Name", value: "Users.fullName" },
{ text: "User city", value: "Users.city" },
{ text: "Order price", value: "Orders.price" },
{ text: "Status", value: "Orders.status" },
{ text: "Created at", value: "Orders.createdAt" }
];
const { resultSet, error, isLoading } = useCubeQuery(query, { cubejsApi });
if (isLoading) {
return <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}><CircularProgress color="secondary" /></div>;
}
if (error) {
return <pre>{error.toString()}</pre>;
}
if (resultSet) {
let orders = resultSet.tablePivot();
const handlePageChange = (event, page) => {
setPage(page);
};
const handleRowsPerPageChange = event => {
setRowsPerPage(event.target.value);
};
return (
<Card
{...rest}
padding={"0"}
className={clsx(classes.root, className)}
>
<CardContent className={classes.content}>
<PerfectScrollbar>
<div className={classes.inner}>
<Table>
<TableHead className={classes.head}>
<TableRow>
{tableHeaders.map((item) => (
<TableCell key={item.value + Math.random()}
className={classes.hoverable}
>
<span>{item.text}</span>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{orders.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map(obj => (
<TableRow
className={classes.tableRow}
hover
key={obj["Orders.id"]}
>
<TableCell>
{obj["Orders.id"]}
</TableCell>
<TableCell>
{obj["Orders.size"]}
</TableCell>
<TableCell>
{obj["Users.fullName"]}
</TableCell>
<TableCell>
{obj["Users.city"]}
</TableCell>
<TableCell>
{"$ " + obj["Orders.price"]}
</TableCell>
<TableCell>
{obj["Orders.status"]}
</TableCell>
<TableCell>
{moment(obj["Orders.createdAt"]).format("DD/MM/YYYY")}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</PerfectScrollbar>
</CardContent>
<CardActions className={classes.actions}>
<TablePagination
component="div"
count={orders.length}
onChangePage={handlePageChange}
onChangeRowsPerPage={handleRowsPerPageChange}
page={page}
rowsPerPage={rowsPerPage}
rowsPerPageOptions={[5, 10, 25, 50, 100]}
/>
</CardActions>
</Card>
);
} else {
return null
}
};
TableComponent.propTypes = {
className: PropTypes.string,
query: PropTypes.object.isRequired
};
export default TableComponent;
终于!这是我们期待已久的数据表:
看起来很棒,是吗?
请注意,它实际上并不是那么基础!😜 你有一个内置的分页功能,可以显示和浏览大量数据。
然而,它看起来有点灰暗。所以,让我们添加一些颜色,并扩展表格……
自定义单元格格式
该表包含订单状态,目前以文本形式显示。让我们用自定义组件替换它们!
我们的想法是用一个彩色圆点来可视化订单状态。为此,我们将创建一个自定义<StatusBullet />
组件。让我们在src/components/StatusBullet.js
文件中创建此组件,其内容如下:
import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { makeStyles } from '@material-ui/styles';
const useStyles = makeStyles(theme => ({
root: {
display: 'inline-block',
borderRadius: '50%',
flexGrow: 0,
flexShrink: 0
},
sm: {
height: 15,
width: 15
},
md: {
height: 15,
width: 15
},
lg: {
height: 15,
width: 15
},
neutral: { backgroundColor: '#fff' },
primary: { backgroundColor: '#ccc' },
info: { backgroundColor: '#3cc' },
warning: { backgroundColor: '#cc3' },
danger: { backgroundColor: '#c33' },
success: { backgroundColor: '#3c3' }
}));
const StatusBullet = props => {
const { className, size, color, ...rest } = props;
const classes = useStyles();
return (
<span
{...rest}
className={clsx(
{
[classes.root]: true,
[classes[size]]: size,
[classes[color]]: color
},
className
)}
/>
);
};
StatusBullet.propTypes = {
className: PropTypes.string,
color: PropTypes.oneOf([
'neutral',
'primary',
'info',
'success',
'warning',
'danger'
]),
size: PropTypes.oneOf(['sm', 'md', 'lg'])
};
StatusBullet.defaultProps = {
size: 'md',
color: 'default'
};
export default StatusBullet;
为了使其正常工作,我们需要对数据表进行一些细微的更改。让我们修改src/components/Table.js
如下:
// ...
} from "@material-ui/core";
import StatusBullet from "./StatusBullet";
const statusColors = {
completed: "success",
processing: "info",
shipped: "danger"
};
const useStyles = makeStyles(theme => ({
// ...
<TableCell>
+ <StatusBullet
+ className={classes.status}
+ color={statusColors[obj["Orders.status"]]}
+ size="sm"
+ />
{obj["Orders.status"]}
</TableCell>
// ...
太棒了!🎉 现在我们有了一个表格,它显示所有订单的信息,并且带有一些彩色元素:
过滤数据
然而,仅使用提供的控件很难探索这些订单。为了解决这个问题,我们将添加一个带有过滤器的综合工具栏,并使表格具有交互性。
首先,让我们添加一些依赖项。在文件夹中运行以下命令dashboard-app
:
npm install --save @date-io/date-fns@1.x date-fns @date-io/moment@1.x moment
然后,<Toolbar />
在src/components/Toolbar.js
文件中创建具有以下内容的组件:
import "date-fns";
import React from "react";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/styles";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import Tab from "@material-ui/core/Tab";
import Tabs from "@material-ui/core/Tabs";
import withStyles from "@material-ui/core/styles/withStyles";
const AntTabs = withStyles({
indicator: {},
})(Tabs);
const AntTab = withStyles((theme) => ({
root: {
textTransform: 'none',
minWidth: 25,
fontSize: 12,
fontWeight: theme.typography.fontWeightRegular,
marginRight: 0,
opacity: 0.6,
'&:hover': {
opacity: 1,
},
'&$selected': {
fontWeight: theme.typography.fontWeightMedium,
outline: 'none',
},
'&:focus': {
outline: 'none',
},
},
selected: {},
}))((props) => <Tab disableRipple {...props} />);
const useStyles = makeStyles(theme => ({
root: {},
row: {
marginTop: 15
},
spacer: {
flexGrow: 1
},
importButton: {
marginRight: 15
},
exportButton: {
marginRight: 15
},
searchInput: {
marginRight: 15
},
formControl: {
margin: 25,
fullWidth: true,
display: "flex",
wrap: "nowrap"
},
date: {
marginTop: 3
},
range: {
marginTop: 13
}
}));
const Toolbar = props => {
const { className,
statusFilter,
setStatusFilter,
tabs,
...rest } = props;
const [tabValue, setTabValue] = React.useState(statusFilter);
const classes = useStyles();
const handleChangeTab = (e, value) => {
setTabValue(value);
setStatusFilter(value);
};
return (
<div
{...rest}
className={className}
>
<Grid container spacing={4}>
<Grid
item
lg={3}
sm={6}
xl={3}
xs={12}
m={2}
>
<div className={classes}>
<AntTabs value={tabValue} onChange={(e,value) => {handleChangeTab(e,value)}} aria-label="ant example">
{tabs.map((item) => (<AntTab key={item} label={item} />))}
</AntTabs>
<Typography className={classes.padding} />
</div>
</Grid>
</Grid>
</div>
);
};
Toolbar.propTypes = {
className: PropTypes.string
};
export default Toolbar;
让我们修改这个src/pages/DashboardPage
文件:
import React from "react";
import { makeStyles } from "@material-ui/styles";
+ import Toolbar from "../components/Toolbar.js";
import Table from "../components/Table.js";
const useStyles = makeStyles(theme => ({
root: {
padding: 15
},
content: {
marginTop: 15
},
}));
const DashboardPage = () => {
const classes = useStyles();
+ const tabs = ['All', 'Shipped', 'Processing', 'Completed'];
+ const [statusFilter, setStatusFilter] = React.useState(0);
const query = {
"timeDimensions": [
{
"dimension": "Orders.createdAt",
"granularity": "day"
}
],
"dimensions": [
"Users.id",
"Orders.id",
"Orders.size",
"Users.fullName",
"Users.city",
"Orders.price",
"Orders.status",
"Orders.createdAt"
],
+ "filters": [
+ {
+ "dimension": "Orders.status",
+ "operator": tabs[statusFilter] !== 'All' ? "equals" : "set",
+ "values": [
+ `${tabs[statusFilter].toLowerCase()}`
+ ]
+ }
+ ]
};
return (
<div className={classes.root}>
+ <Toolbar
+ statusFilter={statusFilter}
+ setStatusFilter={setStatusFilter}
+ tabs={tabs}
+ />
<div className={classes.content}>
<Table
query={query}/>
</div>
</div>
);
};
export default DashboardPage;
完美!🎉 现在数据表新增了一个过滤器,可以在不同类型的订单之间切换:
但是,订单还有其他参数,例如价格和日期。让我们为这些参数创建过滤器。为此,请修改src/components/Toolbar.js
文件:
import "date-fns";
import React from "react";
import PropTypes from "prop-types";
import clsx from "clsx";
import { makeStyles } from "@material-ui/styles";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import Tab from "@material-ui/core/Tab";
import Tabs from "@material-ui/core/Tabs";
import withStyles from "@material-ui/core/styles/withStyles";
+ import DateFnsUtils from "@date-io/date-fns";
+ import {
+ MuiPickersUtilsProvider,
+ KeyboardDatePicker
+ } from "@material-ui/pickers";
+ import Slider from "@material-ui/core/Slider";
// ...
const Toolbar = props => {
const { className,
+ startDate,
+ setStartDate,
+ finishDate,
+ setFinishDate,
+ priceFilter,
+ setPriceFilter,
statusFilter,
setStatusFilter,
tabs,
...rest } = props;
const [tabValue, setTabValue] = React.useState(statusFilter);
+ const [rangeValue, rangeSetValue] = React.useState(priceFilter);
const classes = useStyles();
const handleChangeTab = (e, value) => {
setTabValue(value);
setStatusFilter(value);
};
+ const handleDateChange = (date) => {
+ setStartDate(date);
+ };
+ const handleDateChangeFinish = (date) => {
+ setFinishDate(date);
+ };
+ const handleChangeRange = (event, newValue) => {
+ rangeSetValue(newValue);
+ };
+ const setRangeFilter = (event, newValue) => {
+ setPriceFilter(newValue);
+ };
return (
<div
{...rest}
className={clsx(classes.root, className)}
>
<Grid container spacing={4}>
<Grid
item
lg={3}
sm={6}
xl={3}
xs={12}
m={2}
>
<div className={classes}>
<AntTabs value={tabValue} onChange={(e,value) => {handleChangeTab(e,value)}} aria-label="ant example">
{tabs.map((item) => (<AntTab key={item} label={item} />))}
</AntTabs>
<Typography className={classes.padding} />
</div>
</Grid>
+ <Grid
+ className={classes.date}
+ item
+ lg={3}
+ sm={6}
+ xl={3}
+ xs={12}
+ m={2}
+ >
+ <MuiPickersUtilsProvider utils={DateFnsUtils}>
+ <Grid container justify="space-around">
+ <KeyboardDatePicker
+ id="date-picker-dialog"
+ label={<span style={{opacity: 0.6}}>Start Date</span>}
+ format="MM/dd/yyyy"
+ value={startDate}
+ onChange={handleDateChange}
+ KeyboardButtonProps={{
+ "aria-label": "change date"
+ }}
+ />
+ </Grid>
+ </MuiPickersUtilsProvider>
+ </Grid>
+ <Grid
+ className={classes.date}
+ item
+ lg={3}
+ sm={6}
+ xl={3}
+ xs={12}
+ m={2}
+ >
+ <MuiPickersUtilsProvider utils={DateFnsUtils}>
+ <Grid container justify="space-around">
+ <KeyboardDatePicker
+ id="date-picker-dialog-finish"
+ label={<span style={{opacity: 0.6}}>Finish Date</span>}
+ format="MM/dd/yyyy"
+ value={finishDate}
+ onChange={handleDateChangeFinish}
+ KeyboardButtonProps={{
+ "aria-label": "change date"
+ }}
+ />
+ </Grid>
+ </MuiPickersUtilsProvider>
+ </Grid>
+ <Grid
+ className={classes.range}
+ item
+ lg={3}
+ sm={6}
+ xl={3}
+ xs={12}
+ m={2}
+ >
+ <Typography id="range-slider">
+ Order price range
+ </Typography>
+ <Slider
+ value={rangeValue}
+ onChange={handleChangeRange}
+ onChangeCommitted={setRangeFilter}
+ aria-labelledby="range-slider"
+ valueLabelDisplay="auto"
+ min={0}
+ max={2000}
+ />
+ </Grid>
</Grid>
</div>
);
};
Toolbar.propTypes = {
className: PropTypes.string
};
export default Toolbar;
为了使这些过滤器正常工作,我们需要将它们连接到父组件:添加状态、修改查询,并向<Toolbar />
组件添加新的 props。此外,我们还需要为数据表添加排序功能。因此,请src/pages/DashboardPage.js
按如下方式修改文件:
// ...
const DashboardPage = () => {
const classes = useStyles();
const tabs = ['All', 'Shipped', 'Processing', 'Completed'];
const [statusFilter, setStatusFilter] = React.useState(0);
+ const [startDate, setStartDate] = React.useState(new Date("2019-01-01T00:00:00"));
+ const [finishDate, setFinishDate] = React.useState(new Date("2022-01-01T00:00:00"));
+ const [priceFilter, setPriceFilter] = React.useState([0, 200]);
+ const [sorting, setSorting] = React.useState(['Orders.createdAt', 'desc']);
const query = {
timeDimensions: [
{
"dimension": "Orders.createdAt",
+ "dateRange": [startDate, finishDate],
"granularity": "day"
}
],
+ order: {
+ [`${sorting[0]}`]: sorting[1]
+ },
"dimensions": [
"Users.id",
"Orders.id",
"Orders.size",
"Users.fullName",
"Users.city",
"Orders.price",
"Orders.status",
"Orders.createdAt"
],
"filters": [
{
"dimension": "Orders.status",
"operator": tabs[statusFilter] !== 'All' ? "equals" : "set",
"values": [
`${tabs[statusFilter].toLowerCase()}`
]
},
+ {
+ "dimension": "Orders.price",
+ "operator": "gt",
+ "values": [
+ `${priceFilter[0]}`
+ ]
+ },
+ {
+ "dimension": "Orders.price",
+ "operator": "lt",
+ "values": [
+ `${priceFilter[1]}`
+ ]
+ },
]
};
return (
<div className={classes.root}>
<Toolbar
+ startDate={startDate}
+ setStartDate={setStartDate}
+ finishDate={finishDate}
+ setFinishDate={setFinishDate}
+ priceFilter={priceFilter}
+ setPriceFilter={setPriceFilter}
statusFilter={statusFilter}
setStatusFilter={setStatusFilter}
tabs={tabs}
/>
<div className={classes.content}>
<Table
+ sorting={sorting}
+ setSorting={setSorting}
query={query}/>
</div>
</div>
);
};
export default DataTablePage;
太棒了!🎉 我们添加了一些实用的过滤器。事实上,您还可以添加更多带有自定义逻辑的过滤器。请参阅文档,了解过滤器格式选项。
还有一件事!我们已经向工具栏添加了排序属性,但我们还需要将它们传递给<Table />
组件。为了解决这个问题,让我们修改src/components/Table.js
文件:
// ...
+ import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
+ import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
import { useCubeQuery } from "@cubejs-client/react";
import CircularProgress from "@material-ui/core/CircularProgress";
// ...
const useStyles = makeStyles(theme => ({
// ...
actions: {
justifyContent: "flex-end"
},
+ tableRow: {
+ padding: '0 5px',
+ cursor: "pointer",
+ '.MuiTableRow-root.MuiTableRow-hover&:hover': {
+ }
+ },
+ hoverable: {
+ "&:hover": {
+ cursor: `pointer`
+ }
+ },
+ arrow: {
+ fontSize: 10,
+ position: "absolute"
+ }
}));
const statusColors = {
completed: "success",
processing: "info",
shipped: "danger"
};
const TableComponent = props => {
- const { className, query, cubejsApi, ...rest } = props;
+ const { className, sorting, setSorting, query, cubejsApi, ...rest } = props;
// ...
if (resultSet) {
//...
+ const handleSetSorting = str => {
+ setSorting([str, sorting[1] === "desc" ? "asc" : "desc"]);
+ };
return (
// ...
<TableHead className={classes.head}>
<TableRow>
{tableHeaders.map((item) => (
<TableCell key={item.value + Math.random()} className={classes.hoverable}
+ onClick={() => {
+ handleSetSorting(`${item.value}`);
+ }}
>
<span>{item.text}</span>
+ <Typography
+ className={classes.arrow}
+ variant="body2"
+ component="span"
+ >
+ {(sorting[0] === item.value) ? (sorting[1] === "desc" ? <KeyboardArrowUpIcon/> :
+ <KeyboardArrowDownIcon/>) : null}
+ </Typography>
</TableCell>
))}
</TableRow>
</TableHead>
// ...
太棒了!🎉 现在我们有了完全支持过滤和排序的数据表:
就这样!😇 恭喜你完成本教程!🎉
另外,请检查GitHub 上提供的完整源代码。
现在,您应该能够使用 React 和 Material UI 创建由 Cube.js 提供支持的自定义数据表,以在应用程序中显示任意数量的数据。
欢迎随意探索使用 Cube.js 可以实现的其他示例,例如实时仪表板指南和开源 Web 分析平台指南。
文章来源:https://dev.to/cubejs/react-data-table-with-material-ui-and-a-spark-of-joy-50o1