封装通用组件
- 一、封装思想
- 二、react代码
- 三、css代码
- 四、实现效果
- 五、使用文档 BasicTableModal 表格模态框组件
- 1.组件简介
- 2.功能特点
- 3.使用方法
- 基础用法
- 宽度控制示例
- 带筛选功能
- 搜索功能示例
- 自定义单元格渲染
- 4.API 说明
- Props
- Column 配置项
- Filter 配置项
- 5.注意事项
一、封装思想
1.通用性:可以适用于多种表格展示场景,样式设计更灵活
2.可配置性:提供丰富的配置选项
3.易用性:使用方式简单直观
4.可维护性:代码结构清晰,逻辑分明
5.可扩展性:预留了自定义渲染等扩展接口
二、react代码
import React from 'react';
import PropTypes from 'prop-types';
import 'src/css/basicTableModal.css';
class BasicTableModal extends React.Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
filterValues: {},
filteredData: props.data || [],
visible: props.visible || false
};
}
getColumnsWithIndex = () => {
const indexColumn = {
key: '_index',
title: '序号',
render: (text, record, index) => index + 1
};
return [indexColumn, ...this.props.columns];
}
componentDidMount() {
this.filterAndSearchData();
}
componentDidUpdate(prevProps) {
if (prevProps.data !== this.props.data) {
this.filterAndSearchData();
}
if (prevProps.visible !== this.props.visible) {
this.setState({ visible: this.props.visible });
}
}
filterAndSearchData = () => {
const { data, searchableKeys = [] } = this.props;
const { searchText, filterValues } = this.state;
let result = [...data];
if (searchText.trim()) {
result = result.filter(item => {
return searchableKeys.some(key => {
const cellValue = item[key];
return cellValue &&
String(cellValue)
.toLowerCase()
.includes(searchText.toLowerCase());
});
});
}
Object.entries(filterValues).forEach(([key, value]) => {
if (value) {
result = result.filter(item => item[key] === value);
}
});
this.setState({ filteredData: result });
}
handleSearchChange = (e) => {
this.setState({ searchText: e.target.value }, this.filterAndSearchData);
}
handleFilterChange = (key, value) => {
this.setState(prevState => ({
filterValues: {
...prevState.filterValues,
[key]: value
}
}), this.filterAndSearchData);
}
handleClose = () => {
const { onClose } = this.props;
this.setState({ visible: false });
if (onClose) {
onClose();
}
}
renderFilterDropdown = (column) => {
if (!column.filters) return null;
return (
<select
className="filter-select"
value={this.state.filterValues[column.key] || ''}
onChange={(e) => this.handleFilterChange(column.key, e.target.value)}
>
<option value="">全部</option>
{column.filters.map((filter, index) => (
<option key={index} value={filter.value}>
{filter.text}
</option>
))}
</select>
);
}
render() {
const { title, searchPlaceholder = "输入关键字搜索...", width } = this.props;
const { searchText, filteredData, visible } = this.state;
const columnsWithIndex = this.getColumnsWithIndex();
if (!visible) return null;
const modalStyle = {
width: width || '80%',
maxWidth: '1000px'
};
return (
<div className="modal-overlay">
<div className="modal-content" style={modalStyle}>
<div className="modal-header">
<h3>{title || '表格详情'}</h3>
<button className="close-button" onClick={this.handleClose}>×</button>
</div>
<div className="basic-table-container">
<div className="table-toolbar">
{(this.props.searchableKeys && this.props.searchableKeys.length > 0) && (
<input
type="text"
placeholder={searchPlaceholder}
value={searchText}
onChange={this.handleSearchChange}
className="search-input"
/>
)}
<div className="filter-container">
{this.props.columns.map(column => (
column.filters && (
<div key={column.key} className="filter-item">
<span className="filter-label">{column.title}:</span>
{this.renderFilterDropdown(column)}
</div>
)
))}
</div>
</div>
<div className="table-content-wrapper">
<table className="basic-table">
<thead>
<tr>
{columnsWithIndex.map(column => (
<th key={column.key} style={{ width: column.width || 'auto' }}>{column.title}</th>
))}
</tr>
</thead>
<tbody>
{filteredData.map((item, index) => (
<tr key={index} className={index % 2 === 0 ? 'table-row-light' : 'table-row-dark'}>
{columnsWithIndex.map(column => (
<td key={column.key} style={{ width: column.width || 'auto' }}>
{column.render
? column.render(item[column.key], item, index)
: item[column.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<div className="table-footer">
<span className="data-count">数量({filteredData.length})</span>
</div>
</div>
</div>
</div>
);
}
}
BasicTableModal.propTypes = {
columns: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
render: PropTypes.func,
width: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
filters: PropTypes.arrayOf(PropTypes.shape({
text: PropTypes.string.isRequired,
value: PropTypes.any.isRequired
}))
})).isRequired,
data: PropTypes.array.isRequired,
visible: PropTypes.bool,
onClose: PropTypes.func,
title: PropTypes.string,
searchableKeys: PropTypes.arrayOf(PropTypes.string),
searchPlaceholder: PropTypes.string,
width: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
])
};
BasicTableModal.defaultProps = {
data: [],
visible: false,
searchableKeys: [],
width: '80%'
};
export default BasicTableModal;
三、css代码
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background-color: white;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
max-height: 90vh;
display: flex;
flex-direction: column;
position: relative;
}
.modal-header {
background-color: #eaeaea;
border-bottom: 1px solid #e8e8e8;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
border-radius: 4px 4px 0 0;
}
.modal-header::before {
content: '';
position: absolute;
top: 9px;
left: 10px;
height: 14px;
border-left: 2px solid #f95e34;
}
.modal-header h3 {
margin: 0;
font-size: 12px;
font-family: Microsoft Yahei;
color: rgba(0, 0, 0, 0.85);
margin-left: 15px;
}
.close-button {
background: none !important;
cursor: pointer;
line-height: 33px;
}
.basic-table-container {
padding: 5px;
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
position: relative;
}
.table-content-wrapper {
overflow: auto;
flex: 1;
min-height: 0;
}
.basic-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
background-color: #fff;
font-size: 12px;
font-family: Microsoft Yahei;
line-height: 0.5;
}
.basic-table th,
.basic-table td {
padding: 12px 8px;
border: 1px solid #e8e8e8;
text-align: left;
}
.basic-table thead {
position: sticky;
top: 0;
z-index: 2;
background-color: #fafafa;
}
.basic-table th {
background-color: #fafafa;
font-weight: 500;
box-shadow: 0 1px 0 #e8e8e8;
}
.basic-table tbody {
overflow-y: auto;
}
.table-row-light {
background-color: #f0f0f0;
}
.table-row-dark {
background-color: #ffffff;
}
.basic-table-container::-webkit-scrollbar {
display: none;
}
.table-content-wrapper::-webkit-scrollbar {
width: 4px;
height: 8px;
}
.table-content-wrapper::-webkit-scrollbar-thumb {
background-color: #c1c1c1;
border-radius: 20px;
transition: background-color 0.3s;
}
.table-content-wrapper::-webkit-scrollbar-thumb:hover {
background-color: #a8a8a8;
}
.table-content-wrapper::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 20px;
}
.table-content-wrapper::-webkit-scrollbar-corner {
background: transparent;
}
.table-toolbar {
display: flex;
align-items: center;
padding: 8px;
gap: 16px;
position: sticky;
top: 0;
background-color: white;
z-index: 1;
border-bottom: 1px solid #e8e8e8;
}
.search-input {
min-width: 200px;
max-width: 300px;
height: 28px;
padding: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 2px;
font-size: 12px;
}
.search-input:focus {
border-color: #f95e34;
outline: none;
box-shadow: 0 0 0 2px rgba(249, 94, 52, 0.2);
}
.filter-container {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.filter-item {
display: flex;
align-items: center;
gap: 4px;
}
.filter-label {
font-size: 12px;
color: rgba(0, 0, 0, 0.85);
}
.filter-select {
height: 28px;
padding: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 2px;
font-size: 12px;
min-width: 100px;
}
.filter-select:focus {
border-color: #f95e34;
outline: none;
box-shadow: 0 0 0 2px rgba(249, 94, 52, 0.2);
}
.table-footer {
padding: 8px;
display: flex;
align-items: center;
position: sticky;
bottom: 0;
background-color: white;
z-index: 1;
border-top: 1px solid #e8e8e8;
}
.data-count {
font-size: 12px;
color: rgba(0, 0, 0, 0.65);
font-family: Microsoft Yahei;
}
四、实现效果

五、使用文档 BasicTableModal 表格模态框组件
1.组件简介
BasicTableModal 是一个表格模态框组件,提供了搜索、筛选、自动序号、数据量统计等功能。它适用于需要在模态框中展示表格数据的场景。
2.功能特点
- 支持表格数据展示
- 自动添加序号列
- 支持关键字搜索
- 支持多列筛选
- 支持自定义单元格渲染
- 支持奇偶行样式区分
- 响应式设计
- 支持数据量统计显示
- 支持模态框宽度自定义
- 支持列宽自定义
3.使用方法
基础用法
import BasicTableModal from './basicTableModal';
const columns = [
{
key: 'name',
title: '姓名'
},
{
key: 'age',
title: '年龄'
}
];
const data = [
{ name: '张三', age: 25 },
{ name: '李四', age: 30 }
];
function MyComponent() {
return (
<BasicTableModal
columns={columns}
data={data}
visible={true}
onClose={() => {}}
/>
);
}
宽度控制示例
// 设置模态框宽度
<BasicTableModal
width="90%"
columns={columns}
data={data}
/>
// 设置列宽度
const columns = [
{
key: 'index',
title: '序号',
width: 80 // 固定像素宽度
},
{
key: 'name',
title: '姓名',
width: '150px' // 带单位的宽度
},
{
key: 'description',
title: '描述',
width: '40%' // 百分比宽度
}
];
带筛选功能
const columns = [
{
key: 'name',
title: '姓名'
},
{
key: 'status',
title: '状态',
filters: [
{ text: '在线', value: 'online' },
{ text: '离线', value: 'offline' }
]
}
];
搜索功能示例
// 多字段组合搜索示例
const columns = [
{
key: 'name',
title: '姓名'
},
{
key: 'code',
title: '编号'
},
{
key: 'description',
title: '描述'
}
];
<BasicTableModal
columns={columns}
data={data}
searchableKeys={['name', 'code', 'description']} // 可以同时搜索多个字段
searchPlaceholder="输入姓名/编号/描述搜索..."
/>
自定义单元格渲染
const columns = [
{
key: 'name',
title: '姓名',
render: (text, record, index) => <span style={{color: 'red'}}>{text}</span>
}
];
4.API 说明
Props
参数 | 说明 | 类型 | 必填 | 默认值 |
---|
columns | 表格列配置 | Array | 是 | - |
data | 表格数据 | Array | 是 | [] |
visible | 是否显示模态框 | boolean | 否 | false |
onClose | 关闭模态框的回调函数 | function | 否 | - |
title | 模态框标题 | string | 否 | ‘表格详情’ |
searchableKeys | 可搜索的字段键名数组 | string[] | 否 | [] |
searchPlaceholder | 搜索框占位文本 | string | 否 | ‘输入关键字搜索…’ |
width | 模态框宽度 | number/string | 否 | ‘80%’ |
Column 配置项
参数 | 说明 | 类型 | 必填 | 默认值 |
---|
key | 列数据对应的键名 | string | 是 | - |
title | 列标题 | string | 是 | - |
render | 自定义渲染函数 | function(text, record, index) | 否 | - |
filters | 筛选选项配置 | Array | 否 | - |
width | 列宽度 | number/string | 否 | ‘auto’ |
Filter 配置项
参数 | 说明 | 类型 | 必填 |
---|
text | 筛选项显示文本 | string | 是 |
value | 筛选项对应的值 | any | 是 |
5.注意事项
- 组件会自动在表格最左侧添加序号列
- 搜索功能仅在设置 searchableKeys 且不为空数组时显示搜索框
- 筛选和搜索可以同时使用
- 表格支持奇偶行样式区分,便于数据查看
- 模态框宽度可以使用数字(默认像素)或带单位的字符串(如:‘80%’、‘800px’)
- 列宽度同样支持数字和带单位的字符串,不设置时自动适应内容宽度
- 数据总量显示会实时反映当前筛选/搜索后的数据条数