- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
其他章节请看:
react 高效高质量搭建后台系统 系列 。
有一种页面在后台系统中比较常见:页面分上下两部分,上部分是 input、select、时间等查询项,下部分是查询项对应的表格数据。包含 增删改查 ,例如点击 新建 进行新增操作。就像这样:
本篇将对 ant 的表格进行封装。效果如下:
我们选择 spug 比较简单的模块( 角色管理 )进行分析.
进入角色管理模块入口,发现表格区封装到模块当前目录的 Table.js 中:
// spug\src\pages\system\role\index.js
import ComTable from './Table';
export default observer(function () {
return (
<AuthDiv auth="system.role.view">
<Breadcrumb>
<Breadcrumb.Item>首页</Breadcrumb.Item>
<Breadcrumb.Item>系统管理</Breadcrumb.Item>
<Breadcrumb.Item>角色管理</Breadcrumb.Item>
</Breadcrumb>
{/* 查询区域 */}
<SearchForm>
<SearchForm.Item span={8} title="角色名称">
<Input allowClear value={store.f_name} onChange={e => store.f_name = e.target.value} placeholder="请输入"/>
</SearchForm.Item>
</SearchForm>
{/* 将表格区域封装到了 Table.js 中 */}
<ComTable/>
</AuthDiv>
);
})
查阅 Table.js 发现表格使用的是 components 中的 TableCard .
// spug\src\pages\system\role\Table.js
import { TableCard, ... } from 'components';
@observer
class ComTable extends React.Component {
...
render() {
return (
<TableCard
rowKey="id"
title="角色列表"
loading={store.isFetching}
dataSource={store.dataSource}
onReload={store.fetchRecords}
actions={[
<AuthButton type="primary" icon={<PlusOutlined/>} onClick={() => store.showForm()}>新建</AuthButton>
]}
pagination={{
showSizeChanger: true,
showLessItems: true,
showTotal: total => `共 ${total} 条`,
pageSizeOptions: ['10', '20', '50', '100']
}}
columns={this.columns}/>
)
}
}
export default ComTable
进一步跟进不难发现 TableCard.js 就是 spug 中 封装好的 Table 组件.
Tip : vscode 搜索 TableCard, 发现有 17 处,推测至少有 16 个模块使用的这个封装好的 Table 组件 。
下面我们来分析spug 中表格分装组件:TableCard.
TableCard 从界面上分三部分: 头部 、 表格主体 (包含分页器)、 Footer 。请看代码:
// spug\src\components\TableCard.js
return (
<div ref={rootRef} className={styles.tableCard} style={{ ...props.customStyles }}>
{/* 头部。例如表格标题 */}
<Header
title={props.title}
columns={columns}
actions={props.actions}
fields={fields}
rootRef={rootRef}
defaultFields={defaultFields}
onFieldsChange={handleFieldsChange}
onReload={props.onReload} />
{/* 表格主体,包含分页。如果没数据分页器页不会显示 */}
<Table
tableLayout={props.tableLayout}
scroll={props.scroll}
rowKey={props.rowKey}
loading={props.loading}
columns={columns.filter((_, index) => fields.includes(index))}
dataSource={props.dataSource}
rowSelection={props.rowSelection}
expandable={props.expandable}
size={props.size}
onChange={props.onChange}
// 分页器
pagination={props.pagination} />
{/* Footer 根据props.selected 来显示,里面显示`选择了几项...` */}
{selected.length ? <Footer selected={selected} actions={batchActions} /> : null}
</div>
)
头部分三部分,左侧是表格的 标题 ,中间是是一些 操作 ,例如新增、批量删除等,右侧是 表格的操作 。如下图所示:
右侧表格操作也有三部分:刷新表格、列展示、表格全屏.
Tip :表格刷新很简单,就是调用父组件的 reload 重新发请求.
表格全屏也很简单,利用的是浏览器原生支持的功能.
// 全屏操作。使用浏览器自带全屏功能
function handleFullscreen() {
// props.rootRef.current 是表格组件的原始 Element
// fullscreenEnabled 属性提供了启用全屏模式的可能性。当它的值是 false 的时候,表示全屏模式不可用(可能的原因有 "fullscreen" 特性不被允许,或全屏模式不被支持等)。
if (props.rootRef.current && document.fullscreenEnabled) {
// 如果处在全屏。
// fullscreenElement 返回当前文档中正在以全屏模式显示的Element节点,如果没有使用全屏模式,则返回null.
if (document.fullscreenElement) {
document.exitFullscreen()
} else {
props.rootRef.current.requestFullscreen()
}
}
}
比如取消 描述信息 ,表格中将不会显示该列。效果如下图所示:
这个过程不会发送请求.
整个逻辑如下:
<Header>
组件传入 columns、fields、onFieldsChange、defaultFields等属性方法。
<Header
title={props.title}
columns={columns}
actions={props.actions}
fields={fields}
rootRef={rootRef}
defaultFields={defaultFields}
onFieldsChange={handleFieldsChange}
onReload={props.onReload} />
绿框
的 checkbox 由传入的 columns 决定 列展示
由传入的 columns 和 fields 决定,当选中的个数(fields)等于 columns 的个数,则全选 重置
主要针对 fields,页面一进来就会取到默认选中字段。 表格主体就是调用 antd 中的 Table 组件:
注 : antd 中的 Table 有许多属性,这里只对外暴露有限个 antd 表格属性,这种做法不是很好.
<Table
// 表格元素的 table-layout 属性,例如可以实现`固定表头/列`
tableLayout={props.tableLayout}
// 表格是否可滚动
scroll={props.scroll}
// 表格行 key 的取值,可以是字符串或一个函数。spug 中 `rowKey="id"` 重现出现在 29 个文件中。
rowKey={props.rowKey}
// 加载中的 loading 效果
loading={props.loading}
// 表格的列。用户可以选择哪些列不显示
columns={columns.filter((_, index) => fields.includes(index))}
// 数据源
dataSource={props.dataSource}
// 表格行是否可选择,配置项(object)。可以不传
rowSelection={props.rowSelection}
// 展开功能的配置。可以不传
expandable={props.expandable}
// 表格大小 default | middle | small
size={props.size}
// 分页、排序、筛选变化时触发
onChange={props.onChange}
// 分页器,参考配置项或 pagination 文档,设为 false 时不展示和进行分页
pagination={props.pagination} />
根据父组件的 selected 决定是否显示 Footer:
{/* selected 来自 props,在 Footer 组件中显示选中了多少项等信息,spug 中没有使用到 */}
{selected.length ? <Footer selected={selected} actions={batchActions} /> : null}
Footer 主要显示 已选择... ,spug 中出现得很少:
function Footer(props) {
const actions = props.actions || [];
const length = props.selected.length;
return length > 0 ? (
<div className={styles.tableFooter}>
<div className={styles.left}>已选择 <span>{length}</span> 项</div>
<Space size="middle">
{actions.map((item, index) => (
<React.Fragment key={index}>{item}</React.Fragment>
))}
</Space>
</div>
) : null
}
spug 中表格封装的完整代码如下:
// spug\src\components\TableCard.js
import React, { useState, useEffect, useRef } from 'react';
import { Table, Space, Divider, Popover, Checkbox, Button, Input, Select } from 'antd';
import { ReloadOutlined, SettingOutlined, FullscreenOutlined, SearchOutlined } from '@ant-design/icons';
import styles from './index.module.less';
// 从缓存中取得之前设置的列。记录要隐藏的字段。比如之前将 `状态` 这列隐藏
let TableFields = localStorage.getItem('TableFields')
TableFields = TableFields ? JSON.parse(TableFields) : {}
function Search(props) {
// ...
}
// 已选择多少项。
function Footer(props) {
const actions = props.actions || [];
const length = props.selected.length;
return length > 0 ? (
<div className={styles.tableFooter}>
<div className={styles.left}>已选择 <span>{length}</span> 项</div>
<Space size="middle">
{actions.map((item, index) => (
<React.Fragment key={index}>{item}</React.Fragment>
))}
</Space>
</div>
) : null
}
function Header(props) {
// 表格所有的列
const columns = props.columns || [];
// 例如创建、批量删除等操作
const actions = props.actions || [];
// 选中列,也就是表格要显示的列
const fields = props.fields || [];
// 取消或选中某列时触发
const onFieldsChange = props.onFieldsChange;
// 列展示组件
const Fields = () => {
return (
// value - 指定选中的选项 string[]
// onChange- 变化时的回调函数 function(checkedValue)。
// 例如取消`状态`这列的选中
<Checkbox.Group value={fields} onChange={onFieldsChange}>
{/* 展示所有的列 */}
{columns.map((item, index) => (
// 注:值的选中是根据索引来的,因为 columns 是数组,是有顺序的。
<Checkbox value={index} key={index}>{item.title}</Checkbox>
))}
</Checkbox.Group>
)
}
// 列展示 - 全选或取消全部
function handleCheckAll(e) {
if (e.target.checked) {
// 例如:[0, 1, 2, 3]
// console.log('columns', columns.map((_, index) => index))
onFieldsChange(columns.map((_, index) => index))
} else {
onFieldsChange([])
}
}
// 全屏操作。使用浏览器自带全屏功能
function handleFullscreen() {
// props.rootRef.current 是表格组件的原始 Element
// fullscreenEnabled 属性提供了启用全屏模式的可能性。当它的值是 false 的时候,表示全屏模式不可用(可能的原因有 "fullscreen" 特性不被允许,或全屏模式不被支持等)。
if (props.rootRef.current && document.fullscreenEnabled) {
// 如果处在全屏。
// fullscreenElement 返回当前文档中正在以全屏模式显示的Element节点,如果没有使用全屏模式,则返回null.
if (document.fullscreenElement) {
// console.log('退出全屏')
document.exitFullscreen()
} else {
// console.log('全屏该元素')
props.rootRef.current.requestFullscreen()
}
}
}
// 头部分左右两部分:表格标题 和 options。options 又分两部分:操作项(例如新建、批量删除)、表格操作(刷新表格、表格列显隐控制、表格全屏控制)
return (
<div className={styles.toolbar}>
<div className={styles.title}>{props.title}</div>
<div className={styles.option}>
{/* 新建、删除等项 */}
<Space size="middle" style={{ marginRight: 10 }}>
{actions.map((item, index) => (
// 这种用法有意思
<React.Fragment key={index}>{item}</React.Fragment>
))}
</Space>
{/* 如果有新建等按钮就得加一个分隔符 | */}
{actions.length ? <Divider type="vertical" /> : null}
{/* 表格操作:刷新表格、表格列显隐控制、表格全屏控制 */}
<Space className={styles.icons}>
{/* 刷新表格 */}
<ReloadOutlined onClick={props.onReload} />
{/* 控制表格列的显示,比如让`状态`这列隐藏 */}
<Popover
arrowPointAtCenter
destroyTooltipOnHide={{ keepParent: false }}
// 头部:列展示、重置
title={[
<Checkbox
key="1"
// 全选状态。选中的列数 === 表格中定义的列数
checked={fields.length === columns.length}
// 在实现全选效果时,你可能会用到 indeterminate 属性。
// 设置 indeterminate 状态,只负责样式控制
indeterminate={![0, columns.length].includes(fields.length)}
onChange={handleCheckAll}>列展示</Checkbox>,
// 重置展示最初的列,也就是页面刚进来时列展示的状态。localStorage 会记录对表格列展示的状态。
<Button
key="2"
type="link"
style={{ padding: 0 }}
onClick={() => onFieldsChange(props.defaultFields)}>重置</Button>
]}
overlayClassName={styles.tableFields}
// 触发方式是 click
trigger="click"
placement="bottomRight"
// 卡片内容
content={<Fields />}>
<SettingOutlined />
</Popover>
{/* 表格全屏控制 */}
<FullscreenOutlined onClick={handleFullscreen} />
</Space>
</div>
</div>
)
}
function TableCard(props) {
// 定义一个 ref,用于表格的全屏控制
const rootRef = useRef();
// Footer 组件中使用
const batchActions = props.batchActions || [];
// Footer 组件中使用
const selected = props.selected || [];
// 记录要展示的列
// 例如全选则是 [0, 1, 2, 3 ...],空数组表示不展示任何列
const [fields, setFields] = useState([]);
// 用于列展示中的重置功能。页面一进来就会将选中的列进行保存
const [defaultFields, setDefaultFields] = useState([]);
// 用于保存传入的表格的列数据
const [columns, setColumns] = useState([]);
useEffect(() => {
// _columns - 传入的列数据
let [_columns, _fields] = [props.columns, []];
// `角色名称`这种功能 props.children 是空。
if (props.children) {
if (Array.isArray(props.children)) {
_columns = props.children.filter(x => x.props).map(x => x.props)
} else {
_columns = [props.children.props]
}
}
// 隐藏字段。有 hide 属性的是要隐藏的字段。如果有 tKey 字段,隐藏字段则以缓存的为准
let hideFields = _columns.filter(x => x.hide).map(x => x.title)
// tKey 是表格标识,比如这个表要隐藏 `状态` 字段,另一个表格要隐藏 `地址` 字段,与表格初始列展示对应。
// 如果表格有唯一标识(tKey),再看TableFields(来自localStorage)中是否有数据,如果没有则更新缓存
if (props.tKey) {
if (TableFields[props.tKey]) {
hideFields = TableFields[props.tKey]
} else {
TableFields[props.tKey] = hideFields
localStorage.setItem('TableFields', JSON.stringify(TableFields))
}
}
// Array.prototype.entries() 方法返回一个新的数组迭代器对象,该对象包含数组中每个索引的键/值对。
for (let [index, item] of _columns.entries()) {
// 比如之前将 `状态` 这列隐藏,输出:hideFields ['状态']
// console.log('hideFields', hideFields)
if (!hideFields.includes(item.title)) _fields.push(index)
}
//
setFields(_fields);
// 将传入的列数据保存在 state 中
setColumns(_columns);
// 记录初始展示的列
setDefaultFields(_fields);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// 列展示的操作。
function handleFieldsChange(fields) {
// 更新选中的 fields
setFields(fields)
// tKey 就是一个标识,可以将未选中的fields存入 localStorage。比如用户取消了 `状态` 这列的展示,只要没有清空缓存,下次查看表格中仍旧不会显示`状态`这列
// 将列展示状态保存到缓存
if (props.tKey) {
TableFields[props.tKey] = columns.filter((_, index) => !fields.includes(index)).map(x => x.title)
localStorage.setItem('TableFields', JSON.stringify(TableFields))
// 隐藏三列("频率","描述","操作"),输入: {"hi":["备注信息"],"cb":[],"cg":[],"cc":[],"sa":[],"mi":["频率","描述","操作"]}
// console.log(localStorage.getItem('TableFields'))
}
}
// 分为三部分:Header、Table和 Footer。
return (
<div ref={rootRef} className={styles.tableCard}>
{/* 头部。 */}
<Header
// 表格标题。例如`角色列表`
title={props.title}
// 表格的列
columns={columns}
// 操作。例如新增、批量删除等操作
actions={props.actions}
// 不隐藏的列
fields={fields}
rootRef={rootRef}
defaultFields={defaultFields}
// 所选列变化时触发
onFieldsChange={handleFieldsChange}
onReload={props.onReload} />
{/* antd 的 Table 组件 */}
<Table
// 表格元素的 table-layout 属性,例如可以实现`固定表头/列`
tableLayout={props.tableLayout}
// 表格是否可滚动
scroll={props.scroll}
// 表格行 key 的取值,可以是字符串或一个函数。spug 中 `rowKey="id"` 重现出现在 29 个文件中。
rowKey={props.rowKey}
// 加载中的 loading 效果
loading={props.loading}
// 表格的列。用户可以选择哪些列不显示
columns={columns.filter((_, index) => fields.includes(index))}
// 数据源
dataSource={props.dataSource}
// 表格行是否可选择,配置项(object)。可以不传
rowSelection={props.rowSelection}
// 展开功能的配置。可以不传
expandable={props.expandable}
// 表格大小 default | middle | small
size={props.size}
// 分页、排序、筛选变化时触发
onChange={props.onChange}
// 分页器,参考配置项或 pagination 文档,设为 false 时不展示和进行分页
pagination={props.pagination} />
{/* selected 来自 props,在 Footer 组件中显示选中了多少项等信息,spug 中没有使用到 */}
{selected.length ? <Footer selected={selected} actions={batchActions} /> : null}
</div>
)
}
// spug 没有用到
TableCard.Search = Search;
export default TableCard
笔者这里验证效果时需要使用状态管理器 mobx,目前项目会报如下 2 种错误:
Support for the experimental syntax 'decorators' isn't currently enabled (10:1):
src\pages\system\role\Table.js
Line 10: Parsing error: This experimental syntax requires enabling one of the following parser plugin(s): "decorators", "decorators-legacy". (10:0)
这里需要两处修改即可:
addDecoratorsLegacy
的支持 .babelrc
文件 Tip : 具体细节请看 这里 .
至此 mobx 仍有问题,经过一番折腾,最终才验证表格成功.
笔者在表格中使用一个变量( store.isFetching )控制 loading 效果,但页面一直显示加载效果。而加载完毕将 isFetching 置为 false 的语句也执行了,怀疑是 store.isFetching 变量没有同步到组件。折腾了一番...,最后将 mobx和 mobx-react 包版本改成和 spug 中相同:
- "mobx": "^6.7.0",
- "mobx-react": "^7.6.0",
+ "mobx": "^5.15.7",
+ "mobx-react": "^6.3.1",
期间无意发现我的组件加载完毕后输出两次 。
componentDidMount(){
// 执行2次
console.log('hi')
}
删除 <React.StrictMode> .
笔者在新建页面( 角色管理 )中验证封装的表格组件,效果如下:
有关导航的配置,路由、mock数据、样式都无需讲解,这里主要说一下表格模块的封装( TableCard.js )和表格的使用( store.js 、 Table.js ).
前面我们已经分析过了 spug 中表格的封装,这里与之类似,不在冗余.
// myspug\src\components\TableCard.js
import React, { useState, useEffect, useRef } from 'react';
import { Table, Space, Divider, Popover, Checkbox, Button, Input, Select } from 'antd';
import { ReloadOutlined, SettingOutlined, FullscreenOutlined, SearchOutlined } from '@ant-design/icons';
import styles from './index.module.less';
// 从缓存中取得之前设置的列。记录要隐藏的字段。比如之前将 `状态` 这列隐藏
let TableFields = localStorage.getItem('TableFields')
TableFields = TableFields ? JSON.parse(TableFields) : {}
// 已选择多少项。
function Footer(props) {
const actions = props.actions || [];
const length = props.selected.length;
return length > 0 ? (
<div className={styles.tableFooter}>
<div className={styles.left}>已选择 <span>{length}</span> 项</div>
<Space size="middle">
{actions.map((item, index) => (
<React.Fragment key={index}>{item}</React.Fragment>
))}
</Space>
</div>
) : null
}
function Header(props) {
const columns = props.columns || [];
const actions = props.actions || [];
// 选中列,也就是表格要显示的列
const fields = props.fields || [];
const onFieldsChange = props.onFieldsChange;
// 列展示组件
const Fields = () => {
return (
// value - 指定选中的选项 string[]
// onChange- 变化时的回调函数 function(checkedValue)。
// 例如取消`状态`这列的选中
<Checkbox.Group value={fields} onChange={onFieldsChange}>
{/* 展示所有的列 */}
{columns.map((item, index) => (
// 注:值的选中是根据索引来的,因为 columns 是数组,是有顺序的。
<Checkbox value={index} key={index}>{item.title}</Checkbox>
))}
</Checkbox.Group>
)
}
// 列展示 - 全选或取消全部
function handleCheckAll(e) {
if (e.target.checked) {
// 例如:[0, 1, 2, 3]
// console.log('columns', columns.map((_, index) => index))
onFieldsChange(columns.map((_, index) => index))
} else {
onFieldsChange([])
}
}
// 全屏操作。使用浏览器自带全屏功能
function handleFullscreen() {
// props.rootRef.current 是表格组件的原始 Element
// fullscreenEnabled 属性提供了启用全屏模式的可能性。当它的值是 false 的时候,表示全屏模式不可用(可能的原因有 "fullscreen" 特性不被允许,或全屏模式不被支持等)。
if (props.rootRef.current && document.fullscreenEnabled) {
// 如果处在全屏。
// fullscreenElement 返回当前文档中正在以全屏模式显示的Element节点,如果没有使用全屏模式,则返回null.
if (document.fullscreenElement) {
// console.log('退出全屏')
document.exitFullscreen()
} else {
// console.log('全屏该元素')
props.rootRef.current.requestFullscreen()
}
}
}
// 头部分左右两部分:表格标题 和 options。options 又分两部分:操作项(例如新建、批量删除)、表格操作(刷新表格、表格列显隐控制、表格全屏控制)
return (
<div className={styles.toolbar}>
<div className={styles.title}>{props.title}</div>
<div className={styles.option}>
{/* 新建、删除等项 */}
<Space size="middle" style={{ marginRight: 10 }}>
{actions.map((item, index) => (
// 这种用法有意思
<React.Fragment key={index}>{item}</React.Fragment>
))}
</Space>
{/* 如果有新建等按钮就得加一个分隔符 | */}
{actions.length ? <Divider type="vertical" /> : null}
{/* 表格操作:刷新表格、表格列显隐控制、表格全屏控制 */}
<Space className={styles.icons}>
{/* 刷新表格 */}
<ReloadOutlined onClick={props.onReload} />
{/* 控制表格列的显示,比如让`状态`这列隐藏 */}
<Popover
arrowPointAtCenter
destroyTooltipOnHide={{ keepParent: false }}
// 头部:列展示、重置
title={[
<Checkbox
key="1"
// 全选状态。选中的列数 === 表格中定义的列数
checked={fields.length === columns.length}
// 在实现全选效果时,你可能会用到 indeterminate 属性。
// 设置 indeterminate 状态,只负责样式控制
indeterminate={![0, columns.length].includes(fields.length)}
onChange={handleCheckAll}>列展示</Checkbox>,
// 重置展示最初的列,也就是页面刚进来时列展示的状态。localStorage 会记录对表格列展示的状态。
<Button
key="2"
type="link"
style={{ padding: 0 }}
onClick={() => onFieldsChange(props.defaultFields)}>重置</Button>
]}
overlayClassName={styles.tableFields}
// 触发方式是 click
trigger="click"
placement="bottomRight"
// 卡片内容
content={<Fields />}>
<SettingOutlined />
</Popover>
{/* 表格全屏控制 */}
<FullscreenOutlined onClick={handleFullscreen} />
</Space>
</div>
</div>
)
}
function TableCard(props) {
// 定义一个 ref,用于表格的全屏控制
const rootRef = useRef();
// Footer 组件中使用
const batchActions = props.batchActions || [];
// Footer 组件中使用
const selected = props.selected || [];
// 记录要展示的列
// 例如全选则是 [0, 1, 2, 3 ...],空数组表示不展示任何列
const [fields, setFields] = useState([]);
const [defaultFields, setDefaultFields] = useState([]);
// 用于保存传入的表格的列数据
const [columns, setColumns] = useState([]);
useEffect(() => {
// _columns - 传入的列数据
let [_columns, _fields] = [props.columns, []];
if (props.children) {
if (Array.isArray(props.children)) {
_columns = props.children.filter(x => x.props).map(x => x.props)
} else {
_columns = [props.children.props]
}
}
// 隐藏字段。有 hide 属性的是要隐藏的字段。如果有 tKey 字段,隐藏字段则以缓存的为准
let hideFields = _columns.filter(x => x.hide).map(x => x.title)
// tKey 是表格标识,比如这个表要隐藏 `状态` 字段,另一个表格要隐藏 `地址` 字段,与表格初始列展示对应。
// 如果表格有唯一标识(tKey),再看TableFields(来自localStorage)中是否有数据,如果没有则更新缓存
if (props.tKey) {
if (TableFields[props.tKey]) {
hideFields = TableFields[props.tKey]
} else {
TableFields[props.tKey] = hideFields
localStorage.setItem('TableFields', JSON.stringify(TableFields))
}
}
// Array.prototype.entries() 方法返回一个新的数组迭代器对象,该对象包含数组中每个索引的键/值对。
for (let [index, item] of _columns.entries()) {
// 比如之前将 `状态` 这列隐藏,输出:hideFields ['状态']
// console.log('hideFields', hideFields)
if (!hideFields.includes(item.title)) _fields.push(index)
}
//
setFields(_fields);
// 将传入的列数据保存在 state 中
setColumns(_columns);
// 记录初始展示的列
setDefaultFields(_fields);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// 列展示的操作。
function handleFieldsChange(fields) {
// 更新选中的 fields
setFields(fields)
// tKey 就是一个标识,可以将未选中的fields存入 localStorage。比如用户取消了 `状态` 这列的展示,只要没有清空缓存,下次查看表格中仍旧不会显示`状态`这列
// 将列展示状态保存到缓存
if (props.tKey) {
TableFields[props.tKey] = columns.filter((_, index) => !fields.includes(index)).map(x => x.title)
localStorage.setItem('TableFields', JSON.stringify(TableFields))
// 隐藏三列("频率","描述","操作"),输入: {"hi":["备注信息"],"cb":[],"cg":[],"cc":[],"sa":[],"mi":["频率","描述","操作"]}
// console.log(localStorage.getItem('TableFields'))
}
}
// 分为三部分:Header、Table和 Footer。
return (
<div ref={rootRef} className={styles.tableCard}>
{/* 头部。 */}
<Header
// 表格标题。例如`角色列表`
title={props.title}
// 表格的列
columns={columns}
// 操作。例如新增、批量删除等操作
actions={props.actions}
// 不隐藏的列
fields={fields}
rootRef={rootRef}
defaultFields={defaultFields}
// 所选列变化时触发
onFieldsChange={handleFieldsChange}
onReload={props.onReload} />
{/* antd 的 Table 组件 */}
<Table
// 表格元素的 table-layout 属性,例如可以实现`固定表头/列`
tableLayout={props.tableLayout}
// 表格是否可滚动
scroll={props.scroll}
// 表格行 key 的取值,可以是字符串或一个函数。spug 中 `rowKey="id"` 重现出现在 29 个文件中。
rowKey={props.rowKey}
// 加载中的 loading 效果
loading={props.loading}
// 表格的列。用户可以选择哪些列不显示
columns={columns.filter((_, index) => fields.includes(index))}
// 数据源
dataSource={props.dataSource}
// 表格行是否可选择,配置项(object)。可以不传
rowSelection={props.rowSelection}
// 展开功能的配置。可以不传
expandable={props.expandable}
// 表格大小 default | middle | small
size={props.size}
// 分页、排序、筛选变化时触发
onChange={props.onChange}
// 分页器,参考配置项或 pagination 文档,设为 false 时不展示和进行分页
pagination={props.pagination} />
{/* selected 来自 props,在 Footer 组件中显示选中了多少项等信息,spug 中没有使用到 */}
{selected.length ? <Footer selected={selected} actions={batchActions} /> : null}
</div>
)
}
// spug 没有用到,我们也删除
// TableCard.Search = Search;
export default TableCard
这里是表格的使用,与 antd Table 类似,主要是 columns(列) 和 dataSource(数据源):
// myspug\src\pages\system\role\Table.js
import React from 'react';
import { observer } from 'mobx-react';
import { Modal, Popover, Button, message } from 'antd';
// PlusOutlined:antd 2.2.8 找到
import { PlusOutlined } from '@ant-design/icons';
import { TableCard, } from '@/components';
import store from './store';
@observer
class ComTable extends React.Component {
componentDidMount() {
store.fetchRecords()
}
columns = [{
title: '角色名称',
dataIndex: 'name',
}, {
title: '关联账户',
render: info => 0
}, {
title: '描述信息',
dataIndex: 'desc',
ellipsis: true
}, {
title: '操作',
width: 400,
render: info => (
'编辑按钮'
)
}];
render() {
return (
<TableCard
rowKey="id"
title="角色列表"
loading={store.isFetching}
dataSource={store.dataSource}
// 刷新表格
onReload={store.fetchRecords}
actions={[
<Button type="primary" icon={<PlusOutlined />}>新增</Button>
]}
pagination={{
showSizeChanger: true,
showLessItems: true,
showTotal: total => `共 ${total} 条`,
pageSizeOptions: ['10', '20', '50', '100']
}}
columns={this.columns} />
)
}
}
export default ComTable
状态管理。例如表格的数据的请求,控制表格 loading 效果的 isFetching:
// myspug\src\pages\system\role\store.js
import { observable, computed, } from 'mobx';
import http from '@/libs/http';
class Store {
@observable records = [];
@observable isFetching = false;
@computed get dataSource() {
let records = this.records;
return records
}
fetchRecords = () => {
// 加载中
this.isFetching = true;
http.get('/api/account/role/')
.then(res => {
this.records = res
})
.finally(() => this.isFetching = false)
};
}
export default new Store()
spug 中的表格数据是一次性加载出来的,点击 上下翻页 不会发请求给后端。配合表格上方的过滤条件,体验不错,因为无需请求,数据都在前端。就像这样:
但是如果数据量很大,按照常规做法,翻页、查询等操作都需要从后端重新请求数据.
要实现表格翻页时重新请求数据也很简单,使用 antd Table 的 onChange 属性(分页、排序、筛选变化时触发)即可.
前面我们已经在 TableCard.js 中增加了该属性(即 onChange={props.onChange} ) 。
下面我们将 角色管理 页面的表格改为分页请求数据:
首先我们回顾下目前这种一次请求表格所有数据,纯前端分页效果。请看代码:
render() {
return (
<TableCard
rowKey="id"
title="角色列表"
loading={store.isFetching}
// 后端的数据源
dataSource={store.dataSource}
onReload={store.fetchRecords}
actions={[
<Button type="primary" icon={<PlusOutlined />}>新增</Button>
]}
// 分页器
pagination={{
showSizeChanger: true,
showLessItems: true,
showTotal: total => `共 ${total} 条`,
pageSizeOptions: ['10', '20', '50', '100']
}}
columns={this.columns} />
)
}
只需要给表格传入数据源(dataSource),antd Table 自动完成前端分页效果.
接着我们修改代码如下:
// myspug\src\pages\system\role\Table.js
...
import { TableCard, } from '@/components';
import store from './store';
@observer
class ComTable extends React.Component {
componentDidMount() {
store.fetchRecords()
}
columns = [...];
handleTableChange = ({current}, filters, sorter) => {
store.current = current
store.tableOptions = {
// 排序:好像只支持单个排序
sortField: sorter.field,
sortOrder: sorter.order,
...filters
}
store.fetchRecords();
};
render() {
return (
<TableCard
rowKey="id"
title="角色列表"
loading={store.isFetching}
// 后端的数据源
dataSource={store.dataSource}
onReload={store.fetchRecords}
onChange={this.handleTableChange}
// 分页器
pagination={{
showSizeChanger: true,
showLessItems: true,
showTotal: total => `共 ${total} 条`,
pageSizeOptions: ['10', '20', '50', '100'],
// 如果不传 total,则以后端返回数据条数作为 total 的值
total: store.total,
// 如果不传,则默认是第一条,如果需要默认显示第3条,则必须传
current: store.current,
}}
columns={this.columns} />
)
}
}
export default ComTable
// myspug\src\pages\system\role\store.js
class Store {
...
// 默认第1页
@observable current = 1;
// 总共多少页
@observable total = '';
// 其他参数,例如排序、过滤等等
@observable tableOptions = {}
fetchRecords = () => {
const realParams = {current: this.current, ...this.tableOptions}
this.isFetching = true;
http.get('/api/account/role/', {params: realParams})
.then(res => {
// 可以这么赋值
// ({data: this.records, total: this.pagination.total} = res)
this.total = res.total
this.records = res.data
})
.finally(() => this.isFetching = false)
}
}
export default new Store()
最终效果如下图所示:
Tip :本地 mock 模拟数据如下 。
const getNum = () => String(+new Date()).slice(-3)
// 注:第三个参数必须不能是对象,否则 getNum 不会重新执行
Mock.mock(/\/api\/account\/role\/.*/, 'get', function () {
return {
"data": {
data: new Array(10).fill(0).map((item, index) => ({
"id": index + getNum(), "name": 'name' + index + getNum(), "desc": null,
})),
total: 10000,
}
, "error": ""
}
})
试试删除 <React.StrictMode> (官网说:这仅适用于开发模式。生产模式下生命周期不会被调用两次) 。
疑惑:笔者验证表格时使用了 mobx,表格没渲染出来,删除 <React.StrictMode> 后表格正常,不知是否是 <React.StrictMode> 的副作用.
antd Table 组件某些属性无法使用:spug 中表格是对 antd Table 组件的封装,但是现在封装的组件对外的接口只提供了 antd Table 中有限的几个属性。例如上文提到的翻页请求后端数据需要使用 antd Table 中的 onChange 属性就没有提供出来 。
头部一定会有:不需要都不行 。
其他章节请看:
react 高效高质量搭建后台系统 系列 。
最后此篇关于react高效高质量搭建后台系统系列——表格的封装的文章就讲到这里了,如果你想了解更多关于react高效高质量搭建后台系统系列——表格的封装的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
好的,这听起来很简单,但我已经花了几个小时在谷歌上搜索,我只是找不到解决方案,这并不复杂。 我想创建一个包含图像和文本的表格。我希望表格的每一行都具有相同的高度。我希望文本始终从顶部开始。 IE。 \
在我的网站表单上 - 我的出生日期、月份和年份菜单显示在两行上,我希望它们都显示在同一行上。 当我测试代码时,它显示在一行中,所以我相信一定存在宽度问题。 您可以在右侧表格 (incomeprotec
我们需要跟踪和审核生产,本质上我们有很多订单,但我们似乎在途中丢失了一些产品(废品等)。 为了阻止这种情况,我们现在已在 Google 表格上下了订单,并列出了应有的数量,然后员工会写下收到的数量。
我正在转换我的应用程序,以便它适用于 iOS 7。在应用程序的一部分,我有两个搜索栏,每个搜索栏都有一个与之关联的 UISearchDisplayController。当我搜索 UISearchDis
正如标题所说,非固定表格布局是否与类似的 HTML 表格具有相同的性能问题? 最佳答案 非固定表格的问题在于,要确定一列的宽度,必须加载该列的所有单元格。这仅在...... …您有一个包含几千字节或几
我在使用 Javascript 遍历表格并从一行的第一个单元格获取文本时遇到问题。我想获取此单元格的文本,以便我可以将它与其他内容进行比较,如果文本匹配则删除该行。但是,当我尝试获取文本时,实际出现的
我经常发现自己想要制作一个表格表格——一堆行,每一行都是一个单独的表格,有自己的字段和提交按钮。例如,这是一个宠物店应用程序示例——假设这是一个结帐屏幕,您可以选择更新所选宠物的数量和属性,并在结帐前
看过许多UBB代码,包括JS,ASP,JSP的,一直没发现表格的UBB,虽然可以直接用HTML模式实现相同表格功能,但对于某些开放的站点来说开放HTML模式终究是不合适的,故一直想实现表格的UBB。
表格由 table 标签来定义。每个表格均有若干行(由 tr 标签定义),每行被分割为若干单元格(由 td 标签定义)。字母 td 指表格数据(table data),即数据单元格的内容。数据单元格
我有一个 HTML 与 border-radius和使用 position: sticky 的粘性标题看起来像这样: https://codepen.io/muhammadrehansaeed/pen
对于 iPhone 应用程序,我需要以网格格式显示只读表格数据。该数据可能有许多行和列。 我可以使用 UITableView,但问题是数据很可能会非常宽并且需要滚动。 有没有办法将 UITableVi
我知道这里有类似的问题,但我找不到适合我的答案。 我想要的是显示表单“默认”是选择了某些选项(在这种情况下,除了“Ban Appeal”或“Ban Appeal(西类牙语)”之外的所有内容,我希望仅在
天啊! 我想在Flutter中创建以下非常简单的表。基本上是两列文字,左列右对齐,右列左对齐。如果右列具有多个名称,则每一行都将顶部对齐。 左列应自动调整为最大项目的大小(因为每个标题都有翻译字符串)
我们开始构建 SSAS 表格模型,并想知道大多数人是否拥有一个或多个模型。如果有多个,您是否复制每个所需的表,或者是否有办法在模型之间共享表?我想我知道答案,但我希望那些有更多经验的人能够证实我们的发
tl;博士 如何将任意数量的单词分成两列,总是在最后一列中只有最后一个单词,在第一列中包含所有其他单词? =IFS( LEN(C2)-LEN(SUBSTITUTE(C2," ",""))=1, SP
你们知道一个图表或dable,它可以提供一个简短而简洁但仍然完整且相对最新的现有协议(protocol)及其细节的 View ? (即:ZeroMQ、Rendez-Vous、EMS、...所有这些!:
我才刚刚开始开发MFC应用程序,我希望对整个“控件”概念更加熟悉。我在Visual Studio中使用对话框编辑器,到目前为止,我无法找到添加简单表/网格的功能。这对我来说似乎很基础,但是我什至找不到
我需要对一个非常大的表或矩阵执行计算和操作,大约有 7500 行和 30000 列。 矩阵数据将如下所示: 文件编号|字1 |字 2 |字 3 |... |字 30000 |文档类 0032 1 0
我正在使用设计非常糟糕的数据库,我需要在编写查询之前重新调整表格。 以下是我的常见问题: 时间戳已分为两列(一列用于日期,另一列用于时间)。 一些字符串列也被拆分成多个列。 大多数字符串都有固定长度和
我正在尝试显示 $row["name"] 通过 HTML Table 的形式,如下所示: echo " ".$row["name"]." "; 我也从这里获取行变量: $que
我是一名优秀的程序员,十分优秀!