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