import React, { useState, useEffect, useRef } from 'react';
|
import {
|
Dialog,
|
DialogTitle,
|
DialogContent,
|
DialogActions,
|
TextField,
|
Button as MuiButton,
|
Box,
|
Stack,
|
Chip,
|
CircularProgress,
|
InputAdornment,
|
} from '@mui/material';
|
import { DataGrid } from '@mui/x-data-grid';
|
import SearchIcon from '@mui/icons-material/Search';
|
import DialogCloseButton from '../../components/DialogCloseButton';
|
import request from '@/utils/request';
|
|
/**
|
* 通用选择弹窗组件
|
* @param {Object} props
|
* @param {boolean} props.open - 是否打开
|
* @param {Function} props.onClose - 关闭回调
|
* @param {Function} props.onConfirm - 确认回调,参数为选中的项数组
|
* @param {string} props.title - 弹窗标题
|
* @param {string} props.apiUrl - API地址
|
* @param {Function} props.apiMethod - API方法,默认'post'
|
* @param {Function} props.transformData - 数据转换函数,将API返回的数据转换为表格格式
|
* @param {Array} props.columns - 表格列定义
|
* @param {string} props.searchField - 搜索字段名
|
* @param {boolean} props.multiple - 是否多选,默认true
|
* @param {Array} props.selectedItems - 已选中的项(用于回显)
|
* @param {string} props.idField - ID字段名,默认'id'
|
*/
|
const SelectModal = ({
|
open,
|
onClose,
|
onConfirm,
|
title,
|
apiUrl,
|
apiMethod = 'post',
|
transformData,
|
columns,
|
searchField = 'code',
|
multiple = true,
|
selectedItems = [],
|
idField = 'id',
|
}) => {
|
const [data, setData] = useState([]);
|
const [loading, setLoading] = useState(false);
|
const [searchKeyword, setSearchKeyword] = useState('');
|
const [paginationModel, setPaginationModel] = useState({
|
page: 0,
|
pageSize: 20,
|
});
|
const [rowCount, setRowCount] = useState(0);
|
const [selectedRows, setSelectedRows] = useState([]);
|
const searchTimeoutRef = useRef(null);
|
|
// 初始化选中项
|
useEffect(() => {
|
if (open && selectedItems.length > 0) {
|
const ids = selectedItems.map(item => item[idField] || item);
|
setSelectedRows(ids);
|
} else if (open) {
|
setSelectedRows([]);
|
}
|
}, [open, selectedItems, idField]);
|
|
// 加载数据
|
const loadData = async (page = 0, pageSize = 20, keyword = '') => {
|
setLoading(true);
|
try {
|
const params = {
|
current: page + 1,
|
pageSize: pageSize,
|
};
|
|
// 添加搜索条件
|
if (keyword) {
|
// 支持多个搜索字段(物料支持code和name)
|
if (searchField === 'code' && apiUrl.includes('/matnr/')) {
|
// 物料搜索:支持编码和名称
|
params.code = keyword;
|
params.name = keyword;
|
} else {
|
params[searchField] = keyword;
|
}
|
}
|
|
const response = await request[apiMethod](apiUrl, params);
|
|
if (response?.data?.code === 200) {
|
const result = response.data.data;
|
let items = [];
|
let total = 0;
|
|
// 处理分页数据
|
if (result.records) {
|
items = result.records;
|
total = result.total || 0;
|
} else if (Array.isArray(result)) {
|
items = result;
|
total = result.length;
|
} else {
|
items = [];
|
total = 0;
|
}
|
|
// 转换数据
|
const transformedData = transformData ? items.map(transformData) : items;
|
setData(transformedData);
|
setRowCount(total);
|
} else {
|
console.error('加载数据失败:', response?.data?.msg);
|
setData([]);
|
setRowCount(0);
|
}
|
} catch (error) {
|
console.error('加载数据失败:', error);
|
setData([]);
|
setRowCount(0);
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
// 搜索防抖
|
useEffect(() => {
|
if (searchTimeoutRef.current) {
|
clearTimeout(searchTimeoutRef.current);
|
}
|
|
searchTimeoutRef.current = setTimeout(() => {
|
if (open) {
|
loadData(0, paginationModel.pageSize, searchKeyword);
|
setPaginationModel(prev => ({ ...prev, page: 0 }));
|
}
|
}, 500);
|
|
return () => {
|
if (searchTimeoutRef.current) {
|
clearTimeout(searchTimeoutRef.current);
|
}
|
};
|
}, [searchKeyword, open]);
|
|
// 打开弹窗时加载数据
|
useEffect(() => {
|
if (open) {
|
loadData(paginationModel.page, paginationModel.pageSize, searchKeyword);
|
} else {
|
// 关闭时重置
|
setSearchKeyword('');
|
setSelectedRows([]);
|
setPaginationModel({ page: 0, pageSize: 20 });
|
}
|
}, [open]);
|
|
// 分页变化
|
const handlePaginationModelChange = (newModel) => {
|
setPaginationModel(newModel);
|
loadData(newModel.page, newModel.pageSize, searchKeyword);
|
};
|
|
// 确认选择
|
const handleConfirm = () => {
|
const selectedData = data.filter(item => selectedRows.includes(item[idField]));
|
onConfirm(selectedData);
|
onClose();
|
};
|
|
// 表格列定义(带复选框)
|
const gridColumns = [
|
...columns,
|
];
|
|
return (
|
<Dialog
|
open={open}
|
onClose={onClose}
|
maxWidth="md"
|
fullWidth
|
PaperProps={{
|
sx: { height: '80vh' }
|
}}
|
>
|
<DialogTitle>
|
{title}
|
<DialogCloseButton onClose={onClose} />
|
</DialogTitle>
|
<DialogContent>
|
<Stack spacing={2} sx={{ mt: 1 }}>
|
{/* 搜索框 */}
|
<TextField
|
fullWidth
|
size="small"
|
placeholder={
|
apiUrl.includes('/matnr/')
|
? '搜索物料编码或名称...'
|
: apiUrl.includes('/loc/')
|
? '搜索库位号...'
|
: `搜索${searchField === 'code' ? '编码' : '名称'}...`
|
}
|
value={searchKeyword}
|
onChange={(e) => setSearchKeyword(e.target.value)}
|
InputProps={{
|
startAdornment: (
|
<InputAdornment position="start">
|
<SearchIcon />
|
</InputAdornment>
|
),
|
}}
|
/>
|
|
{/* 已选中的项 */}
|
{selectedRows.length > 0 && (
|
<Box>
|
<Box sx={{ mb: 1, fontSize: '0.875rem', color: 'text.secondary' }}>
|
{multiple ? `已选择 ${selectedRows.length} 项:` : '已选择:'}
|
</Box>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
{data
|
.filter(item => selectedRows.includes(item[idField]))
|
.map((item) => (
|
<Chip
|
key={item[idField]}
|
label={item.label || item.code || item.name || item[idField]}
|
size="small"
|
color={multiple ? 'default' : 'primary'}
|
onDelete={multiple ? () => {
|
setSelectedRows(prev =>
|
prev.filter(id => id !== item[idField])
|
);
|
} : () => {
|
setSelectedRows([]);
|
}}
|
/>
|
))}
|
</Box>
|
</Box>
|
)}
|
|
{/* 数据表格 */}
|
<Box sx={{ height: 400, width: '100%' }}>
|
<DataGrid
|
rows={data}
|
columns={gridColumns}
|
loading={loading}
|
checkboxSelection={multiple}
|
disableRowSelectionOnClick={!multiple}
|
rowSelectionModel={selectedRows}
|
onRowSelectionModelChange={(newSelection) => {
|
if (multiple) {
|
setSelectedRows(newSelection);
|
} else {
|
// 单选模式:只保留最后一个选中的项
|
setSelectedRows(newSelection.length > 0 ? [newSelection[newSelection.length - 1]] : []);
|
}
|
}}
|
onRowClick={(params) => {
|
if (!multiple) {
|
// 单选模式:点击行直接选择
|
const rowId = params.row[idField];
|
setSelectedRows([rowId]);
|
}
|
}}
|
paginationMode="server"
|
paginationModel={paginationModel}
|
onPaginationModelChange={handlePaginationModelChange}
|
pageSizeOptions={[10, 20, 50, 100]}
|
rowCount={rowCount}
|
getRowId={(row) => row[idField]}
|
sx={{
|
'& .MuiDataGrid-cell': {
|
fontSize: '0.875rem',
|
},
|
'& .MuiDataGrid-row': {
|
cursor: multiple ? 'default' : 'pointer',
|
},
|
}}
|
/>
|
</Box>
|
</Stack>
|
</DialogContent>
|
<DialogActions>
|
<MuiButton onClick={onClose}>取消</MuiButton>
|
<MuiButton
|
onClick={handleConfirm}
|
variant="contained"
|
disabled={selectedRows.length === 0}
|
>
|
确认选择 {multiple ? `(${selectedRows.length})` : ''}
|
</MuiButton>
|
</DialogActions>
|
</Dialog>
|
);
|
};
|
|
export default SelectModal;
|