import * as React from 'react';
|
import {
|
Stack,
|
Chip,
|
Dialog,
|
DialogTitle,
|
DialogContent,
|
IconButton,
|
CircularProgress
|
} from '@mui/material';
|
import { useRecordContext } from 'react-admin';
|
import CloseIcon from '@mui/icons-material/Close';
|
import request from '@/utils/request';
|
|
/**
|
* 通用芯片数组字段组件
|
* 用于展示从 API 获取的数组数据,以芯片形式显示
|
*
|
* @param {Object} props
|
* @param {string|string[]} props.source - 数据源字段名,支持多个字段名(如 ['areaIds', 'areas'])
|
* @param {string} props.apiEndpoint - API 端点模板,使用 {ids} 作为占位符,例如: '/warehouseAreas/many/{ids}'
|
* @param {string} props.labelField - 用于显示的字段名,默认为 'label'
|
* @param {string} props.dialogTitle - 弹窗标题
|
* @param {number} props.initialDisplayCount - 初始显示的数量,默认为 1
|
* @param {string} props.placeholderText - 占位符文本模板,使用 {count} 作为占位符,默认为 '{count} 项'
|
* @param {function} props.dataTransform - 可选的数据转换函数,用于处理特殊格式的数据源
|
*/
|
const ChipArrayField = ({
|
source,
|
apiEndpoint,
|
labelField = 'label',
|
dialogTitle = '详情',
|
initialDisplayCount = 1,
|
placeholderText = '{count} 项',
|
dataTransform = null
|
}) => {
|
const record = useRecordContext();
|
const [open, setOpen] = React.useState(false);
|
const [items, setItems] = React.useState([]);
|
const [loading, setLoading] = React.useState(false);
|
|
const handleOpen = () => {
|
setOpen(true);
|
};
|
|
const handleClose = () => {
|
setOpen(false);
|
};
|
|
// 使用 useMemo 缓存数据源值
|
const sourceData = React.useMemo(() => {
|
if (!record) return null;
|
|
// 支持多个字段名
|
if (Array.isArray(source)) {
|
for (const field of source) {
|
if (record[field]) {
|
return record[field];
|
}
|
}
|
return null;
|
}
|
|
return record[source];
|
}, [record, source]);
|
|
// 使用 useCallback 缓存 fetchItems 函数
|
const fetchItems = React.useCallback(async () => {
|
let data = sourceData;
|
if (!data || data.length === 0) {
|
setItems([]);
|
return;
|
}
|
|
// 应用自定义数据转换函数
|
if (dataTransform) {
|
data = dataTransform(data);
|
}
|
|
setLoading(true);
|
try {
|
// 提取 ID 和排序信息
|
const isObjectArray = data.length > 0 &&
|
typeof data[0] === 'object' &&
|
data[0] !== null &&
|
'id' in data[0];
|
|
let ids = [];
|
let sortMap = new Map(); // 存储 id -> sort 的映射
|
|
if (isObjectArray) {
|
// 对象数组格式,提取 ID 和排序信息
|
ids = data.map(item => {
|
const id = item.id;
|
sortMap.set(id, item.sort || 0);
|
return id;
|
});
|
} else {
|
// 纯 ID 数组格式
|
ids = data.map(id => Number(id));
|
}
|
|
// 替换 API 端点中的占位符
|
const url = apiEndpoint.replace('{ids}', ids.join(','));
|
const res = await request.post(url);
|
|
if (res?.data?.code === 200) {
|
let fetchedItems = res.data.data || [];
|
|
// 如果有排序信息,按排序值排序
|
if (sortMap.size > 0) {
|
fetchedItems = fetchedItems.sort((a, b) => {
|
const sortA = sortMap.get(a.id) || 0;
|
const sortB = sortMap.get(b.id) || 0;
|
return sortA - sortB;
|
});
|
}
|
|
setItems(fetchedItems);
|
}
|
} catch (error) {
|
console.error('获取数据失败:', error);
|
} finally {
|
setLoading(false);
|
}
|
}, [sourceData, apiEndpoint, dataTransform]);
|
|
React.useEffect(() => {
|
if (sourceData && sourceData.length > 0) {
|
fetchItems();
|
} else {
|
setItems([]);
|
}
|
}, [sourceData, fetchItems]);
|
|
if (loading) {
|
return <CircularProgress size={20} />;
|
}
|
|
return (
|
<>
|
<Stack
|
direction="row"
|
gap={1}
|
flexWrap="wrap"
|
onClick={handleOpen}
|
sx={{ cursor: 'pointer' }}
|
>
|
{items.slice(0, initialDisplayCount).map((item) => (
|
<Chip
|
size="small"
|
key={item.id}
|
label={item[labelField] || item.id}
|
/>
|
))}
|
{items.length > initialDisplayCount && (
|
<Chip
|
size="small"
|
label={`+${items.length - initialDisplayCount}`}
|
/>
|
)}
|
{items.length === 0 && sourceData && sourceData.length > 0 && (
|
<Chip
|
size="small"
|
label={placeholderText.replace('{count}', sourceData.length)}
|
/>
|
)}
|
</Stack>
|
|
<Dialog
|
open={open}
|
onClose={handleClose}
|
maxWidth="md"
|
fullWidth
|
>
|
<DialogTitle>
|
{dialogTitle}
|
<IconButton
|
aria-label="close"
|
onClick={handleClose}
|
sx={{
|
position: 'absolute',
|
right: 8,
|
top: 8,
|
}}
|
>
|
<CloseIcon />
|
</IconButton>
|
</DialogTitle>
|
<DialogContent>
|
{loading ? (
|
<CircularProgress />
|
) : (
|
<Stack direction="row" gap={1} flexWrap="wrap" sx={{ mt: 1 }}>
|
{items.map((item) => (
|
<Chip
|
size="small"
|
key={item.id}
|
label={item[labelField] || item.id}
|
/>
|
))}
|
</Stack>
|
)}
|
</DialogContent>
|
</Dialog>
|
</>
|
);
|
};
|
|
export default ChipArrayField;
|