From 7972683f56368cd8ce7ef8bb7f1d087416f4a3e7 Mon Sep 17 00:00:00 2001
From: chen.lin <1442464845@qq.com>
Date: 星期一, 02 二月 2026 13:23:35 +0800
Subject: [PATCH] 容器管理-编辑可入库区 选中数据排序 选择文字排序 兼容修改后的其他调用方法
---
rsf-admin/src/page/components/AreasSortInput.jsx | 279 ++++++++++++++++
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java | 2
rsf-admin/src/page/basicInfo/basStation/CrossZoneAreaField.jsx | 38 ++
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java | 4
rsf-admin/src/page/basicInfo/basContainer/BasContainerPanel.jsx | 11
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/BasContainer.java | 66 +++
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java | 2
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasDeserializer.java | 68 ++++
rsf-admin/src/page/components/SortableAreasInput.jsx | 208 ++++++++++++
rsf-admin/src/config/MyDataProvider.js | 32 +
rsf-admin/src/page/basicInfo/basContainer/BasContainerEdit.jsx | 71 ++++
rsf-admin/src/page/components/DictionarySelect.jsx | 30 +
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasTypeHandler.java | 115 ++++++
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java | 12
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasSerializer.java | 57 +++
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java | 2
16 files changed, 971 insertions(+), 26 deletions(-)
diff --git a/rsf-admin/src/config/MyDataProvider.js b/rsf-admin/src/config/MyDataProvider.js
index 897d7ab..7f77510 100644
--- a/rsf-admin/src/config/MyDataProvider.js
+++ b/rsf-admin/src/config/MyDataProvider.js
@@ -38,7 +38,15 @@
// get a list of records based on an array of ids
getMany: async (resource, params) => {
// console.log("getMany", resource, params);
- const res = await request.post(resource + "/many/" + params.ids);
+ // Old format: [1, 2, 3] (array of integers)
+ // New format: [{id: 1, sort: 1}, {id: 2, sort: 2}] (array of objects)
+ const ids = params.ids.map(id => {
+ if (typeof id === 'object' && id !== null && 'id' in id) {
+ return id.id;
+ }
+ return id;
+ });
+ const res = await request.post(resource + "/many/" + ids.join(','));
const { code, msg, data } = res.data;
if (code === 200) {
return Promise.resolve({
@@ -90,9 +98,18 @@
// update a list of records based on an array of ids and a common patch
updateMany: async (resource, params) => {
console.log("updateMany", resource, params);
+ // Extract IDs from params.ids - handle both formats:
+ // Old format: [1, 2, 3] (array of integers)
+ // New format: [{id: 1, sort: 1}, {id: 2, sort: 2}] (array of objects)
+ const ids = params.ids.map(id => {
+ if (typeof id === 'object' && id !== null && 'id' in id) {
+ return id.id;
+ }
+ return id; // Already a number
+ });
const res = await request.post(
resource + "/update/many",
- params.ids.map((id) => ({ id, ...params.data })),
+ ids.map((id) => ({ id, ...params.data })),
);
const { code, msg, data } = res.data;
if (code === 200) {
@@ -121,7 +138,16 @@
// delete a list of records based on an array of ids
deleteMany: async (resource, params) => {
console.log("deleteMany", resource, params);
- const res = await request.post(resource + "/remove/" + params?.ids);
+ // Extract IDs from params.ids - handle both formats:
+ // Old format: [1, 2, 3] (array of integers)
+ // New format: [{id: 1, sort: 1}, {id: 2, sort: 2}] (array of objects)
+ const ids = params?.ids ? params.ids.map(id => {
+ if (typeof id === 'object' && id !== null && 'id' in id) {
+ return id.id;
+ }
+ return id; // Already a number
+ }) : [];
+ const res = await request.post(resource + "/remove/" + ids.join(','));
const { code, msg, data } = res.data;
if (code === 200) {
return Promise.resolve({
diff --git a/rsf-admin/src/page/basicInfo/basContainer/BasContainerEdit.jsx b/rsf-admin/src/page/basicInfo/basContainer/BasContainerEdit.jsx
index b493002..6d9ed5c 100644
--- a/rsf-admin/src/page/basicInfo/basContainer/BasContainerEdit.jsx
+++ b/rsf-admin/src/page/basicInfo/basContainer/BasContainerEdit.jsx
@@ -30,6 +30,7 @@
import MemoInput from "../../components/MemoInput";
import StatusSelectInput from "../../components/StatusSelectInput";
import DictionarySelect from "../../components/DictionarySelect";
+import AreasSortInput from "../../components/AreasSortInput";
const FormToolbar = () => {
const { getValues } = useFormContext();
@@ -51,6 +52,51 @@
mutationMode={EDIT_MODE}
actions={<CustomerTopToolBar />}
aside={<EditBaseAside />}
+ transform={(data) => {
+ // 淇濆瓨鍓嶈浆鎹細灏� areas 浠庣函ID鏁扮粍杞崲涓� [{id, sort}] 鏍煎紡
+ // 浠庨殣钘忓瓧娈� areasSort 鑾峰彇鎺掑簭淇℃伅
+ const areas = data.areas || [];
+ const areasSort = data.areasSort || [];
+
+ if (areas.length > 0) {
+ if (typeof areas[0] === 'number') {
+ // 濡傛灉鏄函ID鏁扮粍锛屼娇鐢� areasSort 涓殑鎺掑簭淇℃伅
+ if (areasSort.length > 0 && typeof areasSort[0] === 'object') {
+ // 浣跨敤 areasSort 涓殑鎺掑簭淇℃伅锛屼絾鍙繚鐣� areas 涓瓨鍦ㄧ殑ID
+ const areaIds = new Set(areas);
+ const sortedAreas = areasSort
+ .filter(item => areaIds.has(item.id))
+ .sort((a, b) => (a.sort || 0) - (b.sort || 0));
+
+ // 濡傛灉 areasSort 涓湁鎵�鏈塈D鐨勬帓搴忎俊鎭紝浣跨敤瀹�
+ if (sortedAreas.length === areas.length) {
+ data.areas = sortedAreas;
+ } else {
+ // 鍚﹀垯锛屼负缂哄け鐨処D娣诲姞榛樿鎺掑簭
+ const existingIds = new Set(sortedAreas.map(item => item.id));
+ const missingIds = areas.filter(id => !existingIds.has(id));
+ const maxSort = sortedAreas.length > 0
+ ? Math.max(...sortedAreas.map(item => item.sort || 1))
+ : 0;
+ const newItems = missingIds.map((id, index) => ({
+ id: id,
+ sort: maxSort + index + 1,
+ }));
+ data.areas = [...sortedAreas, ...newItems];
+ }
+ } else {
+ // 濡傛灉娌℃湁鎺掑簭淇℃伅锛屼娇鐢ㄩ粯璁ゆ帓搴�
+ data.areas = areas.map((id, index) => ({
+ id: id,
+ sort: index + 1,
+ }));
+ }
+ }
+ // 鍒犻櫎涓存椂瀛楁
+ delete data.areasSort;
+ }
+ return data;
+ }}
>
<SimpleForm
shouldUnregister
@@ -82,7 +128,26 @@
/>
</Stack>
<Stack direction='row' gap={2}>
- <ReferenceArrayInput source="areas" reference="warehouseAreas">
+ <ReferenceArrayInput
+ source="areas"
+ reference="warehouseAreas"
+ sort={{ field: 'name', order: 'ASC' }}
+ format={(value) => {
+ // 浠庡悗绔帴鏀舵椂锛氬皢 [{id, sort}] 杞崲涓� [id, id, ...]
+ if (!value || !Array.isArray(value)) return [];
+ if (value.length === 0) return [];
+ // 濡傛灉鏄璞℃暟缁勶紝鎻愬彇id
+ if (typeof value[0] === 'object' && value[0] !== null && value[0].id !== undefined) {
+ return value.map(item => item.id);
+ }
+ // 濡傛灉宸茬粡鏄函ID鏁扮粍锛岀洿鎺ヨ繑鍥�
+ return value;
+ }}
+ parse={(value) => {
+ // 淇濆瓨鏃讹細淇濇寔鍘熷�硷紝鐢� AreasSortInput 澶勭悊杞崲
+ return value;
+ }}
+ >
<SelectArrayInput
label="table.field.basContainer.areas"
optionText="name"
@@ -92,6 +157,10 @@
/>
</ReferenceArrayInput>
</Stack>
+ {/* 涓嬫柟鏄剧ず宸查�夊簱鍖哄拰鎺掑簭缂栬緫 */}
+ <AreasSortInput source="areas" />
+ {/* 闅愯棌瀛楁锛氬瓨鍌ㄦ帓搴忎俊鎭� */}
+ <TextInput source="areasSort" style={{ display: 'none' }} />
</Grid>
<Grid item xs={12} md={4}>
diff --git a/rsf-admin/src/page/basicInfo/basContainer/BasContainerPanel.jsx b/rsf-admin/src/page/basicInfo/basContainer/BasContainerPanel.jsx
index ee57ec9..002b0be 100644
--- a/rsf-admin/src/page/basicInfo/basContainer/BasContainerPanel.jsx
+++ b/rsf-admin/src/page/basicInfo/basContainer/BasContainerPanel.jsx
@@ -55,7 +55,16 @@
<Grid item xs={6}>
<PanelTypography
title="table.field.basContainer.areas"
- property={record.areas}
+ property={
+ record.areas && Array.isArray(record.areas)
+ ? record.areas.map(area => {
+ if (typeof area === 'object' && area !== null && 'id' in area) {
+ return area.id;
+ }
+ return area;
+ }).join(', ')
+ : record.areas
+ }
/>
</Grid>
diff --git a/rsf-admin/src/page/basicInfo/basStation/CrossZoneAreaField.jsx b/rsf-admin/src/page/basicInfo/basStation/CrossZoneAreaField.jsx
index 97b0746..708d31b 100644
--- a/rsf-admin/src/page/basicInfo/basStation/CrossZoneAreaField.jsx
+++ b/rsf-admin/src/page/basicInfo/basStation/CrossZoneAreaField.jsx
@@ -24,9 +24,43 @@
setLoading(true);
try {
- const res = await request.post(`/warehouseAreas/many/${record.areas.join(',')}`);
+ // 鎻愬彇鎺掑簭淇℃伅鍜孖D
+ // Old format: [1, 2, 3] (array of integers)
+ // New format: [{id: 1, sort: 1}, {id: 2, sort: 2}] (array of objects)
+ const isObjectArray = record.areas.length > 0 &&
+ typeof record.areas[0] === 'object' &&
+ record.areas[0] !== null &&
+ 'id' in record.areas[0];
+
+ let areaIds = [];
+ let sortMap = new Map(); // 瀛樺偍 id -> sort 鐨勬槧灏�
+
+ if (isObjectArray) {
+ // 瀵硅薄鏁扮粍鏍煎紡锛屾彁鍙朓D鍜屾帓搴忎俊鎭�
+ areaIds = record.areas.map(area => {
+ const id = area.id;
+ sortMap.set(id, area.sort || 0);
+ return id;
+ });
+ } else {
+ // 绾疘D鏁扮粍鏍煎紡
+ areaIds = record.areas.map(id => Number(id));
+ }
+
+ const res = await request.post(`/warehouseAreas/many/${areaIds.join(',')}`);
if (res?.data?.code === 200) {
- setAreaNames(res.data.data || []);
+ let areas = res.data.data || [];
+
+ // 濡傛灉鏈夋帓搴忎俊鎭紝鎸夋帓搴忓�兼帓搴�
+ if (sortMap.size > 0) {
+ areas = areas.sort((a, b) => {
+ const sortA = sortMap.get(a.id) || 0;
+ const sortB = sortMap.get(b.id) || 0;
+ return sortA - sortB;
+ });
+ }
+
+ setAreaNames(areas);
}
} catch (error) {
console.error('鑾峰彇鍖哄煙鍚嶇О澶辫触:', error);
diff --git a/rsf-admin/src/page/components/AreasSortInput.jsx b/rsf-admin/src/page/components/AreasSortInput.jsx
new file mode 100644
index 0000000..9f6fb1f
--- /dev/null
+++ b/rsf-admin/src/page/components/AreasSortInput.jsx
@@ -0,0 +1,279 @@
+/**
+ *
+ * @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) {
+ // 妫�鏌ユ槸鍚︽槸绾疘D鏁扮粍
+ const isIdArray = currentValue.every(item => typeof item === 'number' || typeof item === 'string');
+
+ if (isIdArray) {
+ // 绾疘D鏁扮粍鏍煎紡 [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)));
+
+ // 鎵惧嚭鏂板鐨処D
+ 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 鐨勫�间负绾疘D鏁扮粍
+ 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;
+ // 鏇存柊琛ㄥ崟鍊间负绾疘D鏁扮粍锛圧eferenceArrayInput 闇�瑕侊級
+ 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;
diff --git a/rsf-admin/src/page/components/DictionarySelect.jsx b/rsf-admin/src/page/components/DictionarySelect.jsx
index aa489d2..41101ee 100644
--- a/rsf-admin/src/page/components/DictionarySelect.jsx
+++ b/rsf-admin/src/page/components/DictionarySelect.jsx
@@ -1,10 +1,11 @@
import EditIcon from '@mui/icons-material/Edit';
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useMemo } from 'react';
import {
Button, useListContext, SelectInput,
required, SelectArrayInput,
useTranslate, useNotify
} from 'react-admin';
+import { useFormContext } from 'react-hook-form';
import request from '@/utils/request';
const DictionarySelect = (props) => {
@@ -19,8 +20,12 @@
} = props;
const translate = useTranslate();
const notify = useNotify();
+ const { watch } = useFormContext();
const [list, setList] = useState([]);
const [loading, setLoading] = useState(false);
+
+ // 鑾峰彇褰撳墠琛ㄥ崟鍊�
+ const currentValue = watch(name);
useEffect(() => {
http();
@@ -54,12 +59,33 @@
}
};
+ // 纭繚褰撳墠鍊煎湪閫夐」涓紝濡傛灉涓嶅湪涓旀鍦ㄥ姞杞斤紝娣诲姞鍗犱綅閫夐」
+ const choices = useMemo(() => {
+ if (!list || list.length === 0) {
+ // 濡傛灉鍒楄〃涓虹┖浣嗗綋鍓嶆湁鍊硷紝娣诲姞鍗犱綅閫夐」浠ラ伩鍏嶈鍛�
+ if (currentValue !== undefined && currentValue !== null && currentValue !== '') {
+ return [{ id: currentValue, name: String(currentValue) }];
+ }
+ return [];
+ }
+
+ // 妫�鏌ュ綋鍓嶅�兼槸鍚﹀湪閫夐」涓�
+ const valueExists = list.some(item => String(item.id) === String(currentValue));
+
+ // 濡傛灉褰撳墠鍊间笉鍦ㄩ�夐」涓紝娣诲姞瀹冿紙鍙兘鏄姞杞藉欢杩熷鑷寸殑锛�
+ if (currentValue !== undefined && currentValue !== null && currentValue !== '' && !valueExists) {
+ return [...list, { id: currentValue, name: String(currentValue) }];
+ }
+
+ return list;
+ }, [list, currentValue]);
+
const InputComponent = multiple ? SelectArrayInput : SelectInput;
return (
<InputComponent
source={name}
- choices={list}
+ choices={choices}
isLoading={loading}
{...parmas}
/>
diff --git a/rsf-admin/src/page/components/SortableAreasInput.jsx b/rsf-admin/src/page/components/SortableAreasInput.jsx
new file mode 100644
index 0000000..c04ce6f
--- /dev/null
+++ b/rsf-admin/src/page/components/SortableAreasInput.jsx
@@ -0,0 +1,208 @@
+import React, { useState, useEffect } from 'react';
+import { useFormContext } from 'react-hook-form';
+import {
+ Box,
+ TextField,
+ Autocomplete,
+ Chip,
+ IconButton,
+ Paper,
+ Typography,
+ Stack,
+} from '@mui/material';
+import DeleteIcon from '@mui/icons-material/Delete';
+import request from '@/utils/request';
+
+/**
+ * @author chen.lin
+ * @time 2026-02-02
+ * 鍙帓搴忕殑搴撳尯閫夋嫨缁勪欢
+ * 鏁版嵁鏍煎紡锛歔{"id": 1, "sort": 1}, {"id": 2, "sort": 2}]
+ *
+ * 鍔熻兘锛�
+ * 1. 涓婃柟锛氬閫夋閫夋嫨搴撳尯
+ * 2. 涓嬫柟锛氭樉绀哄凡閫変腑鐨勫簱鍖猴紝姣忚鏄剧ず搴撳尯鍚嶇О鍜屾帓搴忚緭鍏ユ
+ * 3. 榛樿鎺掑簭涓�1锛屽彲浠ヤ慨鏀规帓搴忓��
+ */
+const SortableAreasInput = ({ source, label, validate, ...props }) => {
+ const { setValue, watch } = useFormContext();
+ const [areas, setAreas] = useState([]);
+ const [selectedAreas, setSelectedAreas] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const currentValue = watch(source) || [];
+
+ // 鍔犺浇鎵�鏈夊簱鍖洪�夐」
+ 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(() => {
+ if (currentValue && currentValue.length > 0) {
+ // 濡傛灉宸茬粡鏄璞℃暟缁勬牸寮� [{id, sort}]
+ if (typeof currentValue[0] === 'object' && currentValue[0].id !== undefined) {
+ const sorted = [...currentValue].sort((a, b) => {
+ const sortA = a.sort || 0;
+ const sortB = b.sort || 0;
+ return sortA - sortB;
+ });
+ setSelectedAreas(sorted);
+ } else {
+ // 濡傛灉鏄棫鏍煎紡 [1, 2, 3]锛岃浆鎹负鏂版牸寮忥紝榛樿鎺掑簭涓�1
+ const converted = currentValue.map((id, index) => ({
+ id: id,
+ sort: index + 1,
+ }));
+ setSelectedAreas(converted);
+ // 鍚屾鏇存柊琛ㄥ崟鍊�
+ setValue(source, converted, { shouldValidate: false });
+ }
+ } else {
+ setSelectedAreas([]);
+ }
+ }, [currentValue, source, setValue]);
+
+ // 澶勭悊娣诲姞搴撳尯
+ const handleAddArea = (event, newValue) => {
+ if (newValue && !selectedAreas.find(item => item.id === newValue.id)) {
+ // 榛樿鎺掑簭涓�1锛屽鏋滃凡鏈夋暟鎹紝浣跨敤鏈�澶ф帓搴忓��+1
+ const maxSort = selectedAreas.length > 0
+ ? Math.max(...selectedAreas.map(item => item.sort || 1))
+ : 0;
+ const newItem = {
+ id: newValue.id,
+ sort: maxSort + 1,
+ };
+ const updated = [...selectedAreas, newItem];
+ setSelectedAreas(updated);
+ setValue(source, updated, { shouldValidate: true });
+ }
+ };
+
+ // 澶勭悊鍒犻櫎搴撳尯
+ const handleDeleteArea = (id) => {
+ const filtered = selectedAreas.filter(item => item.id !== id);
+ setSelectedAreas(filtered);
+ setValue(source, filtered, { shouldValidate: 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;
+ });
+ setSelectedAreas(updated);
+ setValue(source, updated, { shouldValidate: true });
+ };
+
+ // 鑾峰彇搴撳尯鍚嶇О
+ const getAreaName = (id) => {
+ const area = areas.find(a => a.id === id);
+ return area ? area.name : `ID: ${id}`;
+ };
+
+ // 杩囨护宸查�変腑鐨勯�夐」
+ const availableOptions = areas.filter(
+ area => !selectedAreas.find(item => item.id === area.id)
+ );
+
+ return (
+ <Box>
+ {/* 澶氶�夋 */}
+ <Autocomplete
+ multiple
+ options={availableOptions}
+ getOptionLabel={(option) => option.name || ''}
+ onChange={handleAddArea}
+ loading={loading}
+ value={[]} // 濮嬬粓涓虹┖锛屽洜涓哄凡閫変腑鐨勫湪涓嬫柟闈㈡澘鏄剧ず
+ renderInput={(params) => (
+ <TextField
+ {...params}
+ label={label}
+ placeholder="閫夋嫨搴撳尯..."
+ variant="outlined"
+ size="small"
+ />
+ )}
+ sx={{ mb: 2 }}
+ />
+
+ {/* 宸查�変腑鐨勫簱鍖哄垪琛紝鏄剧ず閿�煎锛堝簱鍖哄悕绉帮細鎺掑簭鍊硷級 */}
+ {selectedAreas.length > 0 && (
+ <Box sx={{ mt: 2 }}>
+ <Typography variant="body2" sx={{ mb: 1, color: 'text.secondary' }}>
+ 宸查�夊簱鍖猴紙鍙慨鏀规帓搴忥級锛�
+ </Typography>
+ <Stack spacing={1}>
+ {selectedAreas.map((item) => (
+ <Paper
+ key={item.id}
+ elevation={1}
+ sx={{
+ p: 1.5,
+ display: 'flex',
+ alignItems: 'center',
+ gap: 2,
+ }}
+ >
+ {/* 宸﹁竟锛氬簱鍖哄悕绉� */}
+ <Box sx={{ flex: 1, minWidth: 0 }}>
+ <Chip
+ label={getAreaName(item.id)}
+ size="small"
+ sx={{ maxWidth: '100%' }}
+ />
+ </Box>
+
+ {/* 鍙宠竟锛氭帓搴忚緭鍏ユ */}
+ <TextField
+ type="number"
+ label="鎺掑簭"
+ value={item.sort || ''}
+ onChange={(e) => handleSortChange(item.id, e.target.value)}
+ size="small"
+ sx={{ width: 120 }}
+ inputProps={{
+ min: 1,
+ step: 1
+ }}
+ />
+
+ {/* 鍒犻櫎鎸夐挳 */}
+ <IconButton
+ size="small"
+ onClick={() => handleDeleteArea(item.id)}
+ color="error"
+ >
+ <DeleteIcon fontSize="small" />
+ </IconButton>
+ </Paper>
+ ))}
+ </Stack>
+ </Box>
+ )}
+ </Box>
+ );
+};
+
+export default SortableAreasInput;
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java
index 396b249..adbcaeb 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java
@@ -345,7 +345,7 @@
for (BasContainer container : containers) {
String codeType = container.getCodeType(); // 鑾峰彇姝e垯琛ㄨ揪寮�
if (barcode.matches(codeType)) { // 鍒ゆ柇鏉$爜鏄惁绗﹀悎杩欎釜姝e垯
- List<Integer> areaList2 = container.getAreas();
+ List<Integer> areaList2 = container.getAreasIds();
if (!areaList2.contains(Integer.valueOf(area))) {
matches2 = false;
continue;
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
index a30fffd..9a4f334 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
@@ -141,7 +141,7 @@
for (BasContainer container : containers) {
String codeType = container.getCodeType(); // 鑾峰彇姝e垯琛ㄨ揪寮�
if (barcode.matches(codeType)) { // 鍒ゆ柇鏉$爜鏄惁绗﹀悎杩欎釜姝e垯
- List<Integer> areaList2 = container.getAreas();
+ List<Integer> areaList2 = container.getAreasIds();
if (!areaList2.contains(Integer.parseInt(area))) {
matches2 = false;
continue;
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
index 99f0a4d..157ca1b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
@@ -1026,7 +1026,7 @@
.map(WarehouseRoleMenu::getMenuId)
.collect(Collectors.toSet());
// 鑾峰彇 areaList 骞惰浆鎹负 Long 绫诲瀷鐨� Set
- List<Integer> areaList = container.getAreas();
+ List<Integer> areaList = container.getAreasIds();
Set<Long> areaSet = new HashSet<>();
if (areaList != null) {
areaList.forEach(area -> areaSet.add(area.longValue()));
@@ -1066,7 +1066,7 @@
.map(WarehouseRoleMenu::getMenuId)
.collect(Collectors.toSet());
// 鑾峰彇 areaList 骞惰浆鎹负 Long 绫诲瀷鐨� Set
- List<Integer> areaList = container.getAreas();
+ List<Integer> areaList = container.getAreasIds();
Set<Long> areaSet = new HashSet<>();
if (areaList != null) {
areaList.forEach(area -> areaSet.add(area.longValue()));
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java
index 0c06dcd..1ba4d16 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java
@@ -51,6 +51,10 @@
@GetMapping("/basContainer/{id}")
public R get(@PathVariable("id") Long id) {
BasContainer basContainer = basContainerService.getById(id);
+ // 纭繚杩斿洖鐨刟reas鎸塻ort瀛楁鎺掑簭
+ if (basContainer != null) {
+ basContainer.sortAreas();
+ }
return R.ok().add(basContainer);
}
@@ -62,6 +66,10 @@
basContainer.setCreateTime(new Date());
basContainer.setUpdateBy(getLoginUserId());
basContainer.setUpdateTime(new Date());
+
+ // 纭繚areas鎸塻ort瀛楁鎺掑簭
+ basContainer.sortAreas();
+
BasContainer container = basContainerService.getOne(new LambdaQueryWrapper<BasContainer>().eq(BasContainer::getContainerType, basContainer.getContainerType()));
if (null != container) {
return R.error("璇ョ被鍨嬪凡琚垵濮嬪寲");
@@ -78,6 +86,10 @@
public R update(@RequestBody BasContainer basContainer) {
basContainer.setUpdateBy(getLoginUserId());
basContainer.setUpdateTime(new Date());
+
+ // 纭繚areas鎸塻ort瀛楁鎺掑簭
+ basContainer.sortAreas();
+
if (!basContainerService.updateById(basContainer)) {
return R.error("Update Fail");
}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/BasContainer.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/BasContainer.java
index 6635df2..25b5b78 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/BasContainer.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/BasContainer.java
@@ -6,17 +6,16 @@
import java.util.Date;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import com.vincent.rsf.server.manager.utils.AreasDeserializer;
+import com.vincent.rsf.server.manager.utils.AreasSerializer;
+import com.vincent.rsf.server.manager.utils.AreasTypeHandler;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.vincent.rsf.server.system.entity.DictData;
import com.vincent.rsf.server.system.service.DictDataService;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import com.vincent.rsf.framework.common.Cools;
@@ -24,8 +23,10 @@
import com.vincent.rsf.server.system.service.UserService;
import com.vincent.rsf.server.system.entity.User;
import java.io.Serializable;
-import java.util.Date;
import java.util.List;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.stream.Collectors;
@Data
@Accessors(chain = true)
@@ -57,11 +58,14 @@
private String codeType;
/**
- * 鍙叆搴撳尯
+ * 鍙叆搴撳尯锛堝寘鍚帓搴忎俊鎭級
+ * 鏍煎紡: [{"id": 1, "sort": 1}, {"id": 2, "sort": 2}]
*/
- @ApiModelProperty(value = "鍙叆搴撳尯")
- @TableField(typeHandler = JacksonTypeHandler.class)
- private List<Integer> areas;
+ @ApiModelProperty(value = "鍙叆搴撳尯锛堝寘鍚帓搴忎俊鎭級")
+ @TableField(typeHandler = AreasTypeHandler.class)
+ @JsonDeserialize(using = AreasDeserializer.class)
+ @JsonSerialize(using = AreasSerializer.class)
+ private List<Map<String, Object>> areas;
/**
* 鏄惁鍒犻櫎 1: 鏄� 0: 鍚�
@@ -118,7 +122,7 @@
public BasContainer() {
}
- public BasContainer(Long containerType, String codeType, List<Integer> areas, Integer deleted, Integer status,
+ public BasContainer(Long containerType, String codeType, List<Map<String, Object>> areas, Integer deleted, Integer status,
Integer tenantId, Long createBy, Date createTime, Long updateBy, Date updateTime, String memo) {
this.containerType = containerType;
this.codeType = codeType;
@@ -207,4 +211,42 @@
}
}
+ /**
+ * 鑾峰彇鎺掑簭鍚庣殑搴撳尯ID鍒楄〃锛堝悜鍚庡吋瀹规柟娉曪級
+ * @return 鎺掑簭鍚庣殑搴撳尯ID鍒楄〃
+ */
+ public List<Integer> getAreasIds() {
+ if (Cools.isEmpty(this.areas)) {
+ return new ArrayList<>();
+ }
+ return this.areas.stream()
+ .sorted((a, b) -> {
+ Integer sortA = a.get("sort") != null ? ((Number) a.get("sort")).intValue() : Integer.MAX_VALUE;
+ Integer sortB = b.get("sort") != null ? ((Number) b.get("sort")).intValue() : Integer.MAX_VALUE;
+ return sortA.compareTo(sortB);
+ })
+ .map(area -> {
+ Object id = area.get("id");
+ if (id instanceof Number) {
+ return ((Number) id).intValue();
+ }
+ return null;
+ })
+ .filter(id -> id != null)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 瀵筧reas鎸塻ort瀛楁杩涜鎺掑簭
+ */
+ public void sortAreas() {
+ if (this.areas != null && !this.areas.isEmpty()) {
+ this.areas.sort((a, b) -> {
+ Integer sortA = a.get("sort") != null ? ((Number) a.get("sort")).intValue() : Integer.MAX_VALUE;
+ Integer sortB = b.get("sort") != null ? ((Number) b.get("sort")).intValue() : Integer.MAX_VALUE;
+ return sortA.compareTo(sortB);
+ });
+ }
+ }
+
}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
index c921c1a..1daacaa 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
@@ -509,7 +509,7 @@
for (BasContainer container : containers) {
String codeType = container.getCodeType(); // 鑾峰彇姝e垯琛ㄨ揪寮�
if (barcode.matches(codeType)) { // 鍒ゆ柇鏉$爜鏄惁绗﹀悎杩欎釜姝e垯
- List<Integer> areaList2 = container.getAreas();
+ List<Integer> areaList2 = container.getAreasIds();
if (!areaList2.contains(Integer.parseInt(area))) {
matches2 = false;
continue;
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasDeserializer.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasDeserializer.java
new file mode 100644
index 0000000..8c55b61
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasDeserializer.java
@@ -0,0 +1,68 @@
+package com.vincent.rsf.server.manager.utils;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * @author chen.lin
+ * @time 2026-02-02
+ * Areas 瀛楁鑷畾涔夊弽搴忓垪鍖栧櫒
+ * 鏀寔涓ょ鏍煎紡锛�
+ * 1. [1, 2, 3] - 绾疘D鏁扮粍锛堝悜鍚庡吋瀹癸級
+ * 2. [{"id": 1, "sort": 1}, {"id": 2, "sort": 2}] - 瀵硅薄鏁扮粍锛堟柊鏍煎紡锛�
+ *
+ *
+ */
+public class AreasDeserializer extends JsonDeserializer<List<Map<String, Object>>> {
+
+ @Override
+ public List<Map<String, Object>> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ JsonToken currentToken = p.getCurrentToken();
+
+ // 澶勭悊 null 鍊�
+ if (currentToken == JsonToken.VALUE_NULL) {
+ return new ArrayList<>();
+ }
+
+ // 澶勭悊鏁扮粍
+ if (currentToken == JsonToken.START_ARRAY) {
+ List<Map<String, Object>> result = new ArrayList<>();
+ JsonToken token = p.nextToken();
+
+ while (token != null && token != JsonToken.END_ARRAY) {
+ if (token == JsonToken.VALUE_NUMBER_INT) {
+ // 澶勭悊绾疘D鏁扮粍鏍煎紡 [1, 2, 3]
+ int id = p.getIntValue();
+ Map<String, Object> area = new HashMap<>();
+ area.put("id", id);
+ area.put("sort", result.size() + 1); // 榛樿鎺掑簭
+ result.add(area);
+ token = p.nextToken();
+ } else if (token == JsonToken.START_OBJECT) {
+ // 澶勭悊瀵硅薄鏁扮粍鏍煎紡 [{"id": 1, "sort": 1}]
+ ObjectMapper mapper = (ObjectMapper) p.getCodec();
+ @SuppressWarnings("unchecked")
+ Map<String, Object> area = mapper.readValue(p, Map.class);
+ result.add(area);
+ token = p.nextToken();
+ } else {
+ token = p.nextToken();
+ }
+ }
+ return result;
+ }
+
+ // 濡傛灉鏃笉鏄暟缁勪篃涓嶆槸null锛岃繑鍥炵┖鍒楄〃
+ return new ArrayList<>();
+ }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasSerializer.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasSerializer.java
new file mode 100644
index 0000000..c86bd7f
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasSerializer.java
@@ -0,0 +1,57 @@
+package com.vincent.rsf.server.manager.utils;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author chen.lin
+ * @time 2026-02-02
+ * Areas 瀛楁鑷畾涔夊簭鍒楀寲鍣�
+ * 灏� List<Map<String, Object>> 搴忓垪鍖栦负 JSON 鏁扮粍
+ * 鏀寔娣峰悎绫诲瀷锛圛nteger 鍜� Map锛夌殑鍚戝悗鍏煎
+ *
+ */
+public class AreasSerializer extends JsonSerializer<Object> {
+
+ @Override
+ public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ if (value == null) {
+ gen.writeNull();
+ return;
+ }
+
+ if (!(value instanceof List)) {
+ gen.writeObject(value);
+ return;
+ }
+
+ @SuppressWarnings("unchecked")
+ List<Object> list = (List<Object>) value;
+
+ gen.writeStartArray();
+ for (Object item : list) {
+ if (item instanceof Map) {
+ // 宸茬粡鏄� Map 鏍煎紡
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map = (Map<String, Object>) item;
+ gen.writeObject(map);
+ } else if (item instanceof Number) {
+ // 濡傛灉鏄� Number锛堝悜鍚庡吋瀹癸級锛岃浆鎹负 Map 鏍煎紡
+ Map<String, Object> areaMap = new HashMap<>();
+ areaMap.put("id", ((Number) item).intValue());
+ areaMap.put("sort", 1); // 榛樿鎺掑簭
+ gen.writeObject(areaMap);
+ } else {
+ // 鍏朵粬绫诲瀷锛屽皾璇曠洿鎺ュ啓鍏�
+ gen.writeObject(item);
+ }
+ }
+ gen.writeEndArray();
+ }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasTypeHandler.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasTypeHandler.java
new file mode 100644
index 0000000..19ca21a
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasTypeHandler.java
@@ -0,0 +1,115 @@
+package com.vincent.rsf.server.manager.utils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author chen.lin
+ * @time 2026-02-02
+ * Areas 瀛楁鑷畾涔� TypeHandler
+ * 澶勭悊鏁版嵁搴撳拰 Java 瀵硅薄涔嬮棿鐨勮浆鎹�
+ * 鏀寔涓ょ鏍煎紡锛�
+ * 1. [1, 2, 3] - 绾疘D鏁扮粍锛堝悜鍚庡吋瀹癸級
+ * 2. [{"id": 1, "sort": 1}, {"id": 2, "sort": 2}] - 瀵硅薄鏁扮粍锛堟柊鏍煎紡锛�
+ *
+ */
+@MappedTypes({List.class})
+@MappedJdbcTypes(JdbcType.VARCHAR)
+public class AreasTypeHandler extends BaseTypeHandler<List<Map<String, Object>>> {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ private static final TypeReference<List<Object>> LIST_TYPE_REF = new TypeReference<List<Object>>() {};
+
+ /**
+ * 瑙f瀽 JSON 瀛楃涓蹭负 List<Map<String, Object>>
+ */
+ private List<Map<String, Object>> parse(String json) {
+ if (json == null || json.trim().isEmpty()) {
+ return new ArrayList<>();
+ }
+
+ try {
+ // 鍏堣В鏋愪负 List<Object>
+ List<Object> rawList = OBJECT_MAPPER.readValue(json, LIST_TYPE_REF);
+
+ if (rawList == null || rawList.isEmpty()) {
+ return new ArrayList<>();
+ }
+
+ List<Map<String, Object>> result = new ArrayList<>();
+
+ // 閬嶅巻鎵�鏈夊厓绱犲苟杞崲
+ for (int i = 0; i < rawList.size(); i++) {
+ Object item = rawList.get(i);
+
+ if (item instanceof Map) {
+ // 宸茬粡鏄璞℃暟缁勬牸寮� [{"id": 1, "sort": 1}]
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map = (Map<String, Object>) item;
+ result.add(map);
+ } else if (item instanceof Number) {
+ // 绾疘D鏁扮粍鏍煎紡 [1, 2, 3]锛岃浆鎹负瀵硅薄鏁扮粍
+ Map<String, Object> area = new HashMap<>();
+ area.put("id", ((Number) item).intValue());
+ area.put("sort", i + 1);
+ result.add(area);
+ }
+ // 蹇界暐鍏朵粬绫诲瀷
+ }
+
+ return result;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to parse areas JSON: " + json, e);
+ }
+ }
+
+ /**
+ * 灏� List<Map<String, Object>> 杞崲涓� JSON 瀛楃涓�
+ */
+ private String toJson(List<Map<String, Object>> obj) {
+ if (obj == null) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.writeValueAsString(obj);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to serialize areas to JSON", e);
+ }
+ }
+
+ @Override
+ public void setNonNullParameter(PreparedStatement ps, int i, List<Map<String, Object>> parameter, JdbcType jdbcType) throws SQLException {
+ ps.setString(i, toJson(parameter));
+ }
+
+ @Override
+ public List<Map<String, Object>> getNullableResult(ResultSet rs, String columnName) throws SQLException {
+ String json = rs.getString(columnName);
+ return parse(json);
+ }
+
+ @Override
+ public List<Map<String, Object>> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+ String json = rs.getString(columnIndex);
+ return parse(json);
+ }
+
+ @Override
+ public List<Map<String, Object>> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+ String json = cs.getString(columnIndex);
+ return parse(json);
+ }
+}
--
Gitblit v1.9.1