/**
|
*
|
* @author chen.lin
|
* @time 2026-02-02
|
* 库区排序编辑组件
|
* 功能:显示已选中的库区,允许修改排序值
|
* 数据格式:[{"id": 1, "sort": 1}, {"id": 2, "sort": 2}]
|
*
|
* 使用隐藏字段存储排序信息,保存时合并到 areas 字段
|
*/
|
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
import { useFormContext } from 'react-hook-form';
|
import {
|
Box,
|
TextField,
|
Chip,
|
IconButton,
|
Paper,
|
Typography,
|
Stack,
|
} from '@mui/material';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
import request from '@/utils/request';
|
const AreasSortInput = ({ source }) => {
|
const { setValue, watch } = useFormContext();
|
const sortSource = `${source}Sort`; // 隐藏字段名:areasSort
|
const [areas, setAreas] = useState([]);
|
const [selectedAreas, setSelectedAreas] = useState([]);
|
const [loading, setLoading] = useState(false);
|
const currentValue = watch(source) || [];
|
const prevSelectedAreasRef = useRef([]);
|
|
// 加载所有库区选项(用于获取名称)
|
useEffect(() => {
|
const loadAreas = async () => {
|
setLoading(true);
|
try {
|
const res = await request.post('/warehouseAreas/list', {});
|
if (res?.data?.code === 200) {
|
setAreas(res.data.data || []);
|
} else {
|
console.error('加载库区失败:', res?.data?.msg);
|
}
|
} catch (error) {
|
console.error('加载库区失败:', error);
|
} finally {
|
setLoading(false);
|
}
|
};
|
loadAreas();
|
}, []);
|
|
// 初始化选中项:将后端数据格式转换为前端格式
|
useEffect(() => {
|
// currentValue 现在应该是纯ID数组 [1, 2, 3],因为 ReferenceArrayInput 的 format 已经处理了
|
if (currentValue && currentValue.length > 0) {
|
// 检查是否是纯ID数组
|
const isIdArray = currentValue.every(item => typeof item === 'number' || typeof item === 'string');
|
|
if (isIdArray) {
|
// 纯ID数组格式 [1, 2, 3],需要与现有数据合并
|
const currentIds = new Set(currentValue.map(id => Number(id)));
|
|
// 保留已有的排序信息(如果存在)
|
const existingAreas = prevSelectedAreasRef.current.filter(item =>
|
currentIds.has(Number(item.id))
|
);
|
const existingIds = new Set(existingAreas.map(item => Number(item.id)));
|
|
// 找出新增的ID
|
const newIds = currentValue
|
.map(id => Number(id))
|
.filter(id => !existingIds.has(id));
|
|
// 为新增的ID创建排序项(默认排序为已有最大排序值+1)
|
const maxSort = existingAreas.length > 0
|
? Math.max(...existingAreas.map(item => item.sort || 1), 0)
|
: 0;
|
const newItems = newIds.map((id, index) => ({
|
id: id,
|
sort: maxSort + index + 1,
|
}));
|
|
// 合并已有项和新项
|
const converted = [...existingAreas, ...newItems];
|
setSelectedAreas(converted);
|
prevSelectedAreasRef.current = converted;
|
// 保存排序信息到隐藏字段
|
setValue(sortSource, converted, { shouldValidate: false });
|
} else {
|
// 如果已经是对象数组格式 [{id, sort}](从后端直接加载的情况)
|
const sorted = [...currentValue].sort((a, b) => {
|
const sortA = a.sort || 0;
|
const sortB = b.sort || 0;
|
return sortA - sortB;
|
});
|
setSelectedAreas(sorted);
|
prevSelectedAreasRef.current = sorted;
|
// 保存排序信息到隐藏字段
|
setValue(sortSource, sorted, { shouldValidate: false });
|
// 同时更新 ReferenceArrayInput 的值为纯ID数组
|
const ids = sorted.map(item => item.id);
|
setValue(source, ids, { shouldValidate: false });
|
}
|
} else {
|
setSelectedAreas([]);
|
prevSelectedAreasRef.current = [];
|
// 清空排序信息
|
setValue(sortSource, [], { shouldValidate: false });
|
}
|
}, [currentValue, source, setValue]);
|
|
// 处理删除库区
|
const handleDeleteArea = (id) => {
|
const filtered = selectedAreas.filter(item => item.id !== id);
|
setSelectedAreas(filtered);
|
prevSelectedAreasRef.current = filtered;
|
// 更新表单值为纯ID数组(ReferenceArrayInput 需要)
|
const ids = filtered.map(item => item.id);
|
setValue(source, ids, { shouldValidate: true, shouldDirty: true, shouldTouch: true });
|
// 更新排序信息
|
setValue(sortSource, filtered, { shouldValidate: false, shouldDirty: true, shouldTouch: true });
|
};
|
|
// 处理修改排序值
|
const handleSortChange = (id, newSort) => {
|
const sortValue = parseInt(newSort) || 1;
|
const updated = selectedAreas.map(item => {
|
if (item.id === id) {
|
return { ...item, sort: sortValue };
|
}
|
return item;
|
});
|
// 按排序值排序
|
const sorted = [...updated].sort((a, b) => {
|
const sortA = a.sort || 0;
|
const sortB = b.sort || 0;
|
return sortA - sortB;
|
});
|
setSelectedAreas(sorted);
|
prevSelectedAreasRef.current = sorted;
|
// 更新排序信息到隐藏字段,设置 shouldDirty: true 以触发表单的 dirty 状态
|
setValue(sortSource, sorted, { shouldValidate: false, shouldDirty: true, shouldTouch: true });
|
};
|
|
// 获取库区名称
|
const getAreaName = (id) => {
|
if (!id) return '未知';
|
const area = areas.find(a => a.id === id);
|
return area ? area.name : `ID: ${id}`;
|
};
|
|
// 确保列表始终按排序值排序
|
const sortedAreas = useMemo(() => {
|
if (!selectedAreas || selectedAreas.length === 0) {
|
return [];
|
}
|
return [...selectedAreas].sort((a, b) => {
|
const sortA = a.sort || 0;
|
const sortB = b.sort || 0;
|
return sortA - sortB;
|
});
|
}, [selectedAreas]);
|
|
// 如果没有选中的库区,不显示
|
if (!sortedAreas || sortedAreas.length === 0) {
|
return null;
|
}
|
|
return (
|
<Box sx={{ mt: 2 }}>
|
<Typography
|
variant="body2"
|
sx={{
|
mb: 1,
|
color: 'text.secondary',
|
width: '100%',
|
}}
|
>
|
已选库区(可修改排序):
|
</Typography>
|
|
{/* 表头 */}
|
<Paper
|
elevation={0}
|
sx={{
|
p: 1,
|
display: 'flex',
|
alignItems: 'center',
|
gap: 2,
|
width: '100%',
|
bgcolor: 'grey.100',
|
mb: 0.5,
|
}}
|
>
|
<Box sx={{ flex: 1, minWidth: 0 }}>
|
<Typography variant="body2" sx={{ fontWeight: 'medium' }}>
|
库区
|
</Typography>
|
</Box>
|
<Box sx={{ width: 120, textAlign: 'center' }}>
|
<Typography variant="body2" sx={{ fontWeight: 'medium' }}>
|
排序
|
</Typography>
|
</Box>
|
<Box sx={{ width: 48 }}></Box> {/* 删除按钮占位 */}
|
</Paper>
|
|
<Stack spacing={1}>
|
{sortedAreas.map((item) => {
|
// 确保 item.id 存在且有效
|
if (!item || item.id === undefined || item.id === null) {
|
return null;
|
}
|
return (
|
<Paper
|
key={item.id}
|
elevation={1}
|
sx={{
|
p: 1.5,
|
display: 'flex',
|
alignItems: 'center',
|
gap: 2,
|
width: '100%',
|
height: '30px',
|
minHeight: 'auto',
|
maxWidth: '100%',
|
boxSizing: 'border-box',
|
}}
|
>
|
{/* 左边:库区名称 */}
|
<Box sx={{ flex: 1, minWidth: 0 }}>
|
<Chip
|
label={getAreaName(item.id)}
|
size="small"
|
sx={{ maxWidth: '100%' }}
|
/>
|
</Box>
|
|
{/* 右边:排序输入框(无 label) */}
|
<TextField
|
type="number"
|
value={item.sort || ''}
|
onChange={(e) => handleSortChange(item.id, e.target.value)}
|
size="small"
|
placeholder="排序"
|
sx={{
|
width: 120,
|
'& .MuiInputBase-root': {
|
height: '30px',
|
},
|
'& .MuiInputBase-input': {
|
height: '30px',
|
padding: '8.5px 14px',
|
}
|
}}
|
inputProps={{
|
min: 1,
|
step: 1
|
}}
|
/>
|
|
{/* 删除按钮 */}
|
<IconButton
|
size="small"
|
onClick={() => handleDeleteArea(item.id)}
|
color="error"
|
>
|
<DeleteIcon fontSize="small" />
|
</IconButton>
|
</Paper>
|
);
|
})}
|
</Stack>
|
</Box>
|
);
|
};
|
|
export default AreasSortInput;
|