react 常用组件searchTable 组合式
·
话不多说,上代码,就不介绍。
加上注释了 ,大家日常要写好注释哈,目前我是开发阶段,这个项目 0-1得第三天,所以封装了一些复用性组件,我今天 发了 好多组件,可以进我主页看看,vue,react都有。回归主题,目前开发阶段,接口feactData 这里用的mock 你们要用的话 ,改一下接口就可以了,我把less 也发了把 ,再发个使用得 ,再发个图片,感兴趣,觉得有需要的可以copy微改一下,使用
import React, { useState, useEffect, useRef } from 'react';
import {
Form,
Input,
Button,
Table,
Space,
Row,
Col,
Pagination,
Empty,
Spin,
message
} from 'antd';
import {
SearchOutlined,
ReloadOutlined,
SyncOutlined
} from '@ant-design/icons';
import { getAllTable } from "@/services/index"
import './index.less';
const SearchTable = ({
searchFields = [], // 头部 search
operations, // btn
columns = [], // table
style = {},
tableHeight = 400, // table 高度
onRowClick, // 当前点击行
watchData = null, // 监听的数据 ,数据变化更新列表
TableUrl = null, // 请求的 url
initialFetch = true, // 是否初始化加载数据 默认为false
rowKey = 'id', // 点击行数据 根据 什么作为唯一值
emptyText = '暂无数据', // 缺省
showSearch = true, // 是否 需要 serarch
showPagination = true, // 是否需要分页
clearSelection = true, // 数据刷新后 清空选择,默认为true 不进行存储 跨分页储存多选时 可以设置为false (需配置onSelectionChange联调使用 )
onSelectionChange = null // table check
}) => {
const [form] = Form.useForm();
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
});
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [selectedRow, setSelectedRow] = useState(null);
// 使用ref标记是否已初始化
const initializedRef = useRef(false);
// 使用ref存储上一次的watchData值
const prevWatchDataRef = useRef(null);
// 存储取消请求的函数
const cancelRequestRef = useRef(null);
const fetchData = async (params, signal) => {
try {
// 调用 API 服务
const result = await getAllTable(TableUrl, params, signal);
// 模拟数据格式转换
const data = result.map(item => ({
...item,
key: item.id,
}));
return {
data,
total: result.length,
};
} catch (error) {
console.error('获取数据失败:', error);
return {
data: [],
total: 0,
};
}
};
// 获取数据
const getData = async (page = pagination.current, pageSize = pagination.pageSize) => {
// 取消之前的请求
if (cancelRequestRef.current) {
cancelRequestRef.current();
}
setLoading(true);
const mockData = Array.from({ length: 10 }, (_, i) => ({
key: i,
status: i % 3 === 0 ? '已完成' : '进行中',
orderNo: `56565${i}`,
model: `FT14HC-${i}`,
description: `4.5 1° BSP ${i}`,
orderCount: Math.floor(Math.random() * 100),
estimatedStart: `2024/10/${10 + i}`,
estimatedEnd: `2024/10/${20 + i}`,
actualStart: `2024/10/${11 + i}`,
actualEnd: `2024/10/${21 + i}`,
processName: `FT14-${i}`,
remark: `FLOAT ${i}`,
purchaseOrder: `066400${i}`,
id: `1111${i}`
}));
setData(mockData);
try {
const values = form.getFieldsValue();
const abortController = new AbortController();
cancelRequestRef.current = () => abortController.abort();
const result = await fetchData({
...values,
page,
pageSize
}, abortController.signal)
if (result && !abortController.signal.aborted) {
// setData(result.data || []);
setData(mockData);
setPagination({
current: page,
pageSize: pageSize,
total: result.total || 0,
});
// 数据刷新后清空选择
if (clearSelection && typeof onSelectionChange === 'function') {
setSelectedRowKeys([]);
onSelectionChange([], []);
}
}
} catch (error) {
if (error.name !== 'AbortError') {
console.error('获取数据失败:', error);
message.error('数据加载失败,请稍后重试');
}
} finally {
setLoading(false);
cancelRequestRef.current = null;
}
};
// 初始化加载数据
useEffect(() => {
if (initialFetch && !initializedRef.current) {
initializedRef.current = true;
getData();
}
}, [initialFetch]);
// 监听watchData变化
useEffect(() => {
// 如果watchData未定义或为null,则不触发
if (watchData === undefined || watchData === null) return;
// 检查watchData是否变化
const hasChanged = JSON.stringify(watchData) !== JSON.stringify(prevWatchDataRef.current);
if (hasChanged) {
// 重置分页到第一页
setSelectedRow(null)
setPagination(prev => ({
...prev,
current: 1,
}));
getData(1);
}
// 更新上一次的值
prevWatchDataRef.current = watchData;
}, [watchData]);
// 处理分页变化
const handlePageChange = (page, pageSize) => {
getData(page, pageSize);
};
// 处理查询
const handleSearch = () => {
getData(1);
};
// 处理重置
const handleReset = () => {
form.resetFields();
getData(1);
};
// 处理行点击
const handleRowClick = (record) => {
// 设置当前选中的行
setSelectedRow(record);
// 调用父组件回调函数
if (onRowClick) {
onRowClick(record);
}
};
// 组件卸载时取消请求
useEffect(() => {
return () => {
if (cancelRequestRef.current) {
cancelRequestRef.current('组件卸载');
}
};
}, []);
// 渲染查询表单
const renderSearchForm = () => {
if (!showSearch || searchFields.length === 0) return null;
return (
<div className="search-form-container">
<Form
form={form}
layout="horizontal"
labelAlign="right"
>
<Row gutter={16} align="middle">
{searchFields.map(field => (
<Col key={field.name} span={6}>
<Form.Item
name={field.name}
label={field.label}
rules={field.rules || []}
style={{ marginBottom: 12 }}
>
{field.render ? field.render() : (
<Input
placeholder={field.label ? `请输入${field.label}` : field.placeholder || ''}
{
...field?.config
}
size="middle"
/>
)}
</Form.Item>
</Col>
))}
{/* 操作按钮紧跟最后一个搜索条件 */}
<Col span={6}>
<Space style={{ height: '44px', alignItems: 'start' }}>
<Button onClick={handleReset} icon={<ReloadOutlined />}>
重置
</Button>
<Button
type="primary"
icon={<SearchOutlined />}
onClick={handleSearch}
>
查询
</Button>
</Space>
</Col>
</Row>
</Form>
</div>
);
};
// 渲染操作按钮区域
const renderOperations = () => {
if (!operations) return null;
return (
<div className="operations-container">
{operations}
</div>
);
};
// 渲染表格
const renderTable = () => {
// 计算表格滚动区域高度
const scrollY = tableHeight - (showPagination ? 55 : 0) - 55; // 减去表头高度和分页高度
let rowSelectionConfig = null;
if (typeof onSelectionChange === 'function') {
rowSelectionConfig = {
selectedRowKeys,
onChange: (selectedKeys, selectedRows) => {
setSelectedRowKeys(selectedKeys);
if (onSelectionChange) {
onSelectionChange(selectedKeys, selectedRows);
}
},
};
}
return (
<div className="table-container">
<Spin spinning={loading} tip="加载中...">
<Table
rowKey={record => record[rowKey]}
rowSelection={rowSelectionConfig}
columns={columns}
dataSource={data}
pagination={false}
loading={loading}
scroll={{
x: 'max-content',
y: scrollY
}}
size="middle"
style={{ flex: 1 }}
bordered={false}
onRow={(record) => ({
onClick: (event) => {
if (event.target.closest('.ant-table-selection-column')) {
return;
}
console.log(selectedRow);
handleRowClick(record)
}
})}
rowClassName={(record) =>
selectedRow && selectedRow[rowKey] === record[rowKey] ? 'highlight-row' : ''
}
/>
</Spin>
{/* {showPagination && data.length > 0 && ( */}
<div className="pagination-container">
<Pagination
current={pagination.current}
pageSize={pagination.pageSize}
total={pagination.total}
showSizeChanger
showQuickJumper
showTotal={(total, range) => (
`第 ${range[0]}-${range[1]} 条,共 ${total} 条`
)}
onChange={handlePageChange}
onShowSizeChange={handlePageChange}
size="small"
/>
<div className='pagination-iocn'>
<ReloadOutlined onClick={() => {
getData(1)
}} />
</div>
</div>
{/* )} */}
</div>
);
};
return (
<div className="search-table-wrapper" style={style}>
{renderSearchForm()}
{renderOperations()}
{renderTable()}
</div>
);
};
SearchTable.defaultProps = {
searchFields: [],
style: {},
tableHeight: 400,
initialFetch: true,
rowKey: 'id',
emptyText: '暂无数据',
showSearch: true,
showPagination: true,
};
export default SearchTable;
以下是less
// SearchTable.less
.search-table-wrapper {
background: #fff;
border-radius: 2px;
padding: 10px 10px 0 10px ;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
.search-form-container {
.search-form-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: #fafafa;
.search-form-title {
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
}
.toggle-button {
color: #1890ff;
padding: 0;
height: auto;
}
}
.form-divider {
margin: 0;
}
.search-form {
padding: 12px;
.form-item {
margin-bottom: 12px;
.ant-form-item-label {
padding-bottom: 4px;
label {
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
}
}
}
.form-actions {
padding-top: 8px;
text-align: right;
}
}
}
.operations-container {
margin-top: 10px;
margin-bottom: 5px;
.ant-btn {
margin-right: 7px;
}
}
.table-container {
.data-table {
.ant-table-thead > tr > th {
background: #fafafa;
padding: 8px 12px;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
}
.ant-table-tbody > tr > td {
padding: 8px 12px;
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
}
.table-row-even {
background-color: #fff;
}
.table-row-odd {
background-color: #fafafa;
}
.ant-table-tbody > tr:hover > td {
background: #e6f7ff;
}
}
.pagination-container {
padding: 12px;
background: #fafafa;
border-top: 1px solid #e8e8e8;
position: relative;
padding-right: 50px;
.pagination-iocn{
position: absolute;
right: 15px;
top: 14px;
cursor: pointer;
}
}
}
// .ant-table-row-selected{
// background-color: #fff !im;
// }
// 高亮行样式 - 提高优先级
.ant-table-wrapper .ant-table-tbody .ant-table-row.ant-table-row-selected > td {
background: transparent !important;
}
.ant-table-tbody {
// .ant-table-row.ant-table-row-selected {
// background-color: inherit;
// }
// .ant-table-row.ant-table-row-selected > td {
// background-color: inherit;
// }
.highlight-row {
background-color: #e6f7ff !important;
// 高亮行的悬停效果
&:hover > td {
background-color: #e6f7ff !important;
}
}
}
// 普通行的悬停效果
.ant-table-tbody > tr:not(.highlight-row):hover > td {
// background-color: #f5f5f5 !important;
cursor: pointer;
}
}
// 响应式调整
@media (max-width: 768px) {
.search-table-wrapper {
.search-form-container {
.search-form {
.ant-col-6 {
span: 12;
margin-bottom: 8px;
}
}
}
}
}
<SearchTable
searchFields={searchFields}
columns={columns}
TableUrl={'/postsdadsadasdsa'}
operations={operations}
onRowClick={handleRowClick}
rowKey="id"
onSelectionChange={(ids, list) => {
setSelectedRowKeys(ids)
}}
/>
// 查询字段配置
const searchFields = [
{ name: 'orderNo', label: '工单号' },
{ name: 'purchaseOrder', label: '订单号' },
{ name: 'model', label: '型号' },
{ name: 'description', label: '描述' },
];
const columns = [
{ title: '名称', dataIndex: 'status', key: 'status', width: 100, },
{ title: '工单号', dataIndex: 'orderNo', key: 'orderNo', width: 120 },
{ title: '型号', dataIndex: 'model', key: 'model', width: 150 },
{ title: '描述', dataIndex: 'description', key: 'description', width: 200 },
{ title: '工单数', dataIndex: 'orderCount', key: 'orderCount', width: 80 },
{ title: '预计开始', dataIndex: 'estimatedStart', key: 'estimatedStart', width: 120 },
{ title: '预计结束', dataIndex: 'estimatedEnd', key: 'estimatedEnd', width: 120 },
{ title: '实际开始时间', dataIndex: 'actualStart', key: 'actualStart', width: 140 },
{ title: '实际结束时间', dataIndex: 'actualEnd', key: 'actualEnd', width: 140 },
{ title: '工艺名称', dataIndex: 'processName', key: 'processName', width: 120 },
{ title: '备注', dataIndex: 'remark', key: 'remark', width: 150 },
{ title: '订单号', dataIndex: 'purchaseOrder', key: 'purchaseOrder', width: 120 },
];
TableUrl={'/postsdadsadasdsa'} 这里是 url 到时候 你们要和后端统一 统一接口类型,返回值 后面数据都会在searchTable 处理
operations={operations} 按钮
const operations_t = (
<>
<Button
type="primary"
onClick={handelEditOrder}
>
编辑工单
</Button>
</>
);
onRowClick={handleRowClick}
rowKey="id"
onSelectionChange={(ids, list) => {
setSelectedRowKeys(ids)
}} 这三个是 tbale操作
一个是行点击
一个是 checkout 点击

更多推荐


所有评论(0)