封装通用组件
一、封装思想 二、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’) 列宽度同样支持数字和带单位的字符串,不设置时自动适应内容宽度 数据总量显示会实时反映当前筛选/搜索后的数据条数