1个文件已添加
8个文件已修改
1305 ■■■■■ 已修改文件
src/main/java/com/zy/core/utils/station/StationRerouteProcessor.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/basCrnp/basCrnp.js 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/basDevp/basDevp.js 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/basDualCrnp/basDualCrnp.js 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/jsonEditors.js 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/basCrnp/basCrnp.html 256 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/basDevp/basDevp.html 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/basDualCrnp/basDualCrnp.html 256 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/station/StationRerouteProcessor.java
@@ -57,6 +57,9 @@
    private static final int RUN_BLOCK_DIRECT_REASSIGN_NEAREST_CACHE_SECONDS = 60 * 60 * 24;
    private static final long CHECK_STATION_OUT_ORDER_SLOW_THRESHOLD_MS = 200L;
    private static final long EXECUTE_REROUTE_PLAN_SLOW_THRESHOLD_MS = 200L;
    private static final String RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING_PREFIX = "堵塞重分配请求WMS失败";
    private static final String RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING =
            RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING_PREFIX + ",请检查WMS重新分配库位接口";
    @Autowired
    private WrkMastService wrkMastService;
@@ -684,16 +687,31 @@
        }
        String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId());
        if (Cools.isEmpty(response)) {
            appendStationSystemWarning(stationProtocol, RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING);
            News.taskError(wrkMast.getWrkNo(), "请求WMS重新分配库位接口失败,接口未响应!!!response:{}", response);
            return;
        }
        JSONObject jsonObject = JSON.parseObject(response);
        if (!jsonObject.getInteger("code").equals(200)) {
            News.error("请求WMS接口失败!!!response:{}", response);
        JSONObject jsonObject;
        try {
            jsonObject = JSON.parseObject(response);
        } catch (Exception e) {
            appendStationSystemWarning(stationProtocol, RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING);
            News.taskError(wrkMast.getWrkNo(), "请求WMS重新分配库位接口响应解析异常!!!response:{}", response, e);
            return;
        }
        if (jsonObject == null || !Integer.valueOf(200).equals(jsonObject.getInteger("code"))) {
            appendStationSystemWarning(stationProtocol, RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING);
            News.taskError(wrkMast.getWrkNo(), "请求WMS接口失败!!!response:{}", response);
            return;
        }
        StartupDto dto = jsonObject.getObject("data", StartupDto.class);
        if (dto == null || Cools.isEmpty(dto.getLocNo())) {
            appendStationSystemWarning(stationProtocol, RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING);
            News.taskError(wrkMast.getWrkNo(), "请求WMS重新分配库位接口失败,WMS未返回目标库位!!!response:{}", response);
            return;
        }
        clearStationSystemWarningByPrefix(stationProtocol, RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING_PREFIX);
        String sourceLocNo = wrkMast.getLocNo();
        String locNo = dto.getLocNo();
@@ -780,6 +798,36 @@
        }
    }
    private void appendStationSystemWarning(StationProtocol stationProtocol, String warning) {
        if (stationProtocol == null || Cools.isEmpty(warning)) {
            return;
        }
        String currentWarning = stationProtocol.getSystemWarning();
        if (Cools.isEmpty(currentWarning)) {
            stationProtocol.setSystemWarning(warning);
            return;
        }
        if (currentWarning.contains(warning)) {
            return;
        }
        stationProtocol.setSystemWarning(currentWarning + ";" + warning);
    }
    private void clearStationSystemWarningByPrefix(StationProtocol stationProtocol, String warningPrefix) {
        if (stationProtocol == null || Cools.isEmpty(warningPrefix) || Cools.isEmpty(stationProtocol.getSystemWarning())) {
            return;
        }
        String[] warningParts = stationProtocol.getSystemWarning().split(";");
        List<String> keepWarningList = new ArrayList<>();
        for (String warningPart : warningParts) {
            if (Cools.isEmpty(warningPart) || warningPart.startsWith(warningPrefix)) {
                continue;
            }
            keepWarningList.add(warningPart);
        }
        stationProtocol.setSystemWarning(String.join(";", keepWarningList));
    }
    private int countCurrentTaskBufferCommands(List<StationTaskBufferItem> taskBufferItems, Integer currentTaskNo) {
        if (taskBufferItems == null || taskBufferItems.isEmpty() || currentTaskNo == null || currentTaskNo <= 0) {
            return 0;
src/main/resources/application.yml
@@ -1,6 +1,6 @@
# 系统版本信息
app:
  version: 3.0.1.3
  version: 3.0.1.5
  version-type: prd  # prd 或 dev
  i18n:
    default-locale: zh-CN
src/main/webapp/static/js/basCrnp/basCrnp.js
@@ -194,7 +194,7 @@
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        textarea: true,
        minWidth: 134,
        enumOptions: [],
        foreignQuery: '',
@@ -248,7 +248,7 @@
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        textarea: true,
        minWidth: 110,
        enumOptions: [],
        foreignQuery: '',
@@ -266,7 +266,7 @@
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        textarea: true,
        minWidth: 110,
        enumOptions: [],
        foreignQuery: '',
@@ -777,6 +777,10 @@
                    dialogForm: createFormDefaults(),
                    dialogDisplay: createDisplayDefaults(),
                    rowMapRows: [],
                    stationEditorFields: ['inStationList', 'outStationList'],
                    intArray2dFields: ['controlRows'],
                    intArrayFields: [],
                    jsonEditorRows: {},
                    dialogRules: createFormRules()
                };
            },
@@ -1011,6 +1015,7 @@
                    this.dialogForm = createFormDefaults();
                    this.dialogDisplay = createDisplayDefaults();
                    this.rowMapRows = [];
                    this.jsonEditorRows = {};
                    if (this.$refs.dialogForm) {
                        this.$refs.dialogForm.clearValidate();
                    }
@@ -1028,6 +1033,7 @@
                        self.resetDialogState();
                        fillFormFromRow(row, self.dialogForm, self.dialogDisplay);
                        self.rowMapRows = parseRowMapRows(self.dialogForm.rowMap);
                        self.initJsonEditorRows();
                        if (self.$refs.dialogForm) {
                            self.$refs.dialogForm.clearValidate();
                        }
@@ -1047,6 +1053,118 @@
                syncRowMapJson: function () {
                    this.$set(this.dialogForm, 'rowMap', buildRowMapJson(this.rowMapRows));
                },
                initJsonEditorRows: function () {
                    var self = this;
                    var editors = window.WcsJsonEditors;
                    self.stationEditorFields.forEach(function (field) {
                        self.$set(self.jsonEditorRows, field, editors.parseStationList(self.dialogForm[field]));
                    });
                    self.intArray2dFields.forEach(function (field) {
                        self.$set(self.jsonEditorRows, field, editors.parseIntArray2D(self.dialogForm[field]));
                    });
                    self.intArrayFields.forEach(function (field) {
                        self.$set(self.jsonEditorRows, field, editors.parseIntArray(self.dialogForm[field]));
                    });
                },
                syncAllJsonEditors: function () {
                    var self = this;
                    var editors = window.WcsJsonEditors;
                    self.stationEditorFields.forEach(function (field) {
                        self.$set(self.dialogForm, field, editors.buildStationListJson(self.jsonEditorRows[field]));
                    });
                    self.intArray2dFields.forEach(function (field) {
                        self.$set(self.dialogForm, field, editors.buildIntArray2DJson(self.jsonEditorRows[field]));
                    });
                    self.intArrayFields.forEach(function (field) {
                        self.$set(self.dialogForm, field, editors.buildIntArrayJson(self.jsonEditorRows[field]));
                    });
                },
                validateAllJsonEditors: function () {
                    var self = this;
                    var editors = window.WcsJsonEditors;
                    var error = '';
                    self.stationEditorFields.forEach(function (field) {
                        if (error) return;
                        error = editors.validateStationList(self.jsonEditorRows[field]);
                    });
                    self.intArray2dFields.forEach(function (field) {
                        if (error) return;
                        error = editors.validateIntArray2D(self.jsonEditorRows[field]);
                    });
                    self.intArrayFields.forEach(function (field) {
                        if (error) return;
                        error = editors.validateIntArray(self.jsonEditorRows[field]);
                    });
                    return error;
                },
                addStationRow: function (field) {
                    if (!this.jsonEditorRows[field]) {
                        this.$set(this.jsonEditorRows, field, []);
                    }
                    this.jsonEditorRows[field].push(window.WcsJsonEditors.createEmptyStation());
                    this.syncAllJsonEditors();
                },
                removeStationRow: function (field, index) {
                    this.jsonEditorRows[field].splice(index, 1);
                    this.syncAllJsonEditors();
                },
                toggleNested: function (field, rowIndex, nestedKey) {
                    var row = this.jsonEditorRows[field][rowIndex];
                    var showKey = '_show' + nestedKey.charAt(0).toUpperCase() + nestedKey.slice(1);
                    this.$set(row, showKey, !row[showKey]);
                },
                addNestedStation: function (field, rowIndex, nestedKey) {
                    var row = this.jsonEditorRows[field][rowIndex];
                    var nested = {};
                    window.WcsJsonEditors.STATION_OBJ_FIELDS.forEach(function (k) { nested[k] = ''; });
                    this.$set(row, nestedKey, nested);
                    var showKey = '_show' + nestedKey.charAt(0).toUpperCase() + nestedKey.slice(1);
                    this.$set(row, showKey, true);
                    this.syncAllJsonEditors();
                },
                removeNestedStation: function (field, rowIndex, nestedKey) {
                    this.$set(this.jsonEditorRows[field][rowIndex], nestedKey, null);
                    this.syncAllJsonEditors();
                },
                syncStationField: function (field) {
                    this.$set(this.dialogForm, field, window.WcsJsonEditors.buildStationListJson(this.jsonEditorRows[field]));
                },
                addIntArray2DGroup: function (field) {
                    if (!this.jsonEditorRows[field]) {
                        this.$set(this.jsonEditorRows, field, []);
                    }
                    this.jsonEditorRows[field].push({ values: [''] });
                    this.syncAllJsonEditors();
                },
                removeIntArray2DGroup: function (field, index) {
                    this.jsonEditorRows[field].splice(index, 1);
                    this.syncAllJsonEditors();
                },
                addIntArray2DValue: function (field, groupIndex) {
                    this.jsonEditorRows[field][groupIndex].values.push('');
                    this.syncAllJsonEditors();
                },
                removeIntArray2DValue: function (field, groupIndex, valueIndex) {
                    this.jsonEditorRows[field][groupIndex].values.splice(valueIndex, 1);
                    this.syncAllJsonEditors();
                },
                syncIntArray2DField: function (field) {
                    this.$set(this.dialogForm, field, window.WcsJsonEditors.buildIntArray2DJson(this.jsonEditorRows[field]));
                },
                addIntArrayValue: function (field) {
                    if (!this.jsonEditorRows[field]) {
                        this.$set(this.jsonEditorRows, field, []);
                    }
                    this.jsonEditorRows[field].push('');
                    this.syncAllJsonEditors();
                },
                removeIntArrayValue: function (field, index) {
                    this.jsonEditorRows[field].splice(index, 1);
                    this.syncAllJsonEditors();
                },
                syncIntArrayField: function (field) {
                    this.$set(this.dialogForm, field, window.WcsJsonEditors.buildIntArrayJson(this.jsonEditorRows[field]));
                },
                submitDialog: function () {
                    var self = this;
                    if (!self.$refs.dialogForm) {
@@ -1058,6 +1176,12 @@
                        self.$message.warning(rowMapMessage);
                        return;
                    }
                    self.syncAllJsonEditors();
                    var jsonError = self.validateAllJsonEditors();
                    if (jsonError) {
                        self.$message.warning(jsonError);
                        return;
                    }
                    self.$refs.dialogForm.validate(function (valid) {
                        if (!valid) {
                            return false;
src/main/webapp/static/js/basDevp/basDevp.js
@@ -2268,6 +2268,8 @@
                    tableResizeHandler: null,
                    dialogForm: createFormDefaults(),
                    dialogDisplay: createDisplayDefaults(),
                    stationEditorFields: ['stationList', 'barcodeStationList', 'inStationList', 'outStationList', 'runBlockReassignLocStationList', 'isOutOrderList', 'isLiftTransferList'],
                    jsonEditorRows: {},
                    dialogRules: createFormRules()
                };
            },
@@ -2501,6 +2503,7 @@
                resetDialogState: function () {
                    this.dialogForm = createFormDefaults();
                    this.dialogDisplay = createDisplayDefaults();
                    this.jsonEditorRows = {};
                    if (this.$refs.dialogForm) {
                        this.$refs.dialogForm.clearValidate();
                    }
@@ -2517,6 +2520,7 @@
                    self.$nextTick(function () {
                        self.resetDialogState();
                        fillFormFromRow(row, self.dialogForm, self.dialogDisplay);
                        self.initJsonEditorRows();
                        if (self.$refs.dialogForm) {
                            self.$refs.dialogForm.clearValidate();
                        }
@@ -2525,6 +2529,12 @@
                submitDialog: function () {
                    var self = this;
                    if (!self.$refs.dialogForm) {
                        return;
                    }
                    self.syncAllJsonEditors();
                    var jsonError = self.validateAllJsonEditors();
                    if (jsonError) {
                        self.$message.warning(jsonError);
                        return;
                    }
                    self.$refs.dialogForm.validate(function (valid) {
@@ -2558,6 +2568,62 @@
                        return true;
                    });
                },
                initJsonEditorRows: function () {
                    var self = this;
                    var editors = window.WcsJsonEditors;
                    self.stationEditorFields.forEach(function (field) {
                        self.$set(self.jsonEditorRows, field, editors.parseStationList(self.dialogForm[field]));
                    });
                },
                syncAllJsonEditors: function () {
                    var self = this;
                    var editors = window.WcsJsonEditors;
                    self.stationEditorFields.forEach(function (field) {
                        self.$set(self.dialogForm, field, editors.buildStationListJson(self.jsonEditorRows[field]));
                    });
                },
                validateAllJsonEditors: function () {
                    var self = this;
                    var editors = window.WcsJsonEditors;
                    var error = '';
                    self.stationEditorFields.forEach(function (field) {
                        if (error) return;
                        error = editors.validateStationList(self.jsonEditorRows[field]);
                    });
                    return error;
                },
                addStationRow: function (field) {
                    if (!this.jsonEditorRows[field]) {
                        this.$set(this.jsonEditorRows, field, []);
                    }
                    this.jsonEditorRows[field].push(window.WcsJsonEditors.createEmptyStation());
                    this.syncAllJsonEditors();
                },
                removeStationRow: function (field, index) {
                    this.jsonEditorRows[field].splice(index, 1);
                    this.syncAllJsonEditors();
                },
                toggleNested: function (field, rowIndex, nestedKey) {
                    var row = this.jsonEditorRows[field][rowIndex];
                    var showKey = '_show' + nestedKey.charAt(0).toUpperCase() + nestedKey.slice(1);
                    this.$set(row, showKey, !row[showKey]);
                },
                addNestedStation: function (field, rowIndex, nestedKey) {
                    var row = this.jsonEditorRows[field][rowIndex];
                    var nested = {};
                    window.WcsJsonEditors.STATION_OBJ_FIELDS.forEach(function (k) { nested[k] = ''; });
                    this.$set(row, nestedKey, nested);
                    var showKey = '_show' + nestedKey.charAt(0).toUpperCase() + nestedKey.slice(1);
                    this.$set(row, showKey, true);
                    this.syncAllJsonEditors();
                },
                removeNestedStation: function (field, rowIndex, nestedKey) {
                    this.$set(this.jsonEditorRows[field][rowIndex], nestedKey, null);
                    this.syncAllJsonEditors();
                },
                syncStationField: function (field) {
                    this.$set(this.dialogForm, field, window.WcsJsonEditors.buildStationListJson(this.jsonEditorRows[field]));
                },
                removeSelection: function () {
                    var self = this;
                    var ids = self.selection.map(function (row) {
src/main/webapp/static/js/basDualCrnp/basDualCrnp.js
@@ -194,7 +194,7 @@
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        textarea: true,
        minWidth: 134,
        enumOptions: [],
        foreignQuery: '',
@@ -212,7 +212,7 @@
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        textarea: true,
        minWidth: 116,
        enumOptions: [],
        foreignQuery: '',
@@ -230,7 +230,7 @@
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        textarea: true,
        minWidth: 110,
        enumOptions: [],
        foreignQuery: '',
@@ -248,7 +248,7 @@
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        textarea: true,
        minWidth: 110,
        enumOptions: [],
        foreignQuery: '',
@@ -302,7 +302,7 @@
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        textarea: true,
        minWidth: 170,
        enumOptions: [],
        foreignQuery: '',
@@ -320,7 +320,7 @@
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        textarea: true,
        minWidth: 170,
        enumOptions: [],
        foreignQuery: '',
@@ -735,6 +735,10 @@
                    tableResizeHandler: null,
                    dialogForm: createFormDefaults(),
                    dialogDisplay: createDisplayDefaults(),
                    stationEditorFields: ['inStationList', 'outStationList'],
                    intArray2dFields: ['controlRows'],
                    intArrayFields: ['disableStationOneBays', 'disableStationTwoBays', 'deepRows'],
                    jsonEditorRows: {},
                    dialogRules: createFormRules()
                };
            },
@@ -968,6 +972,7 @@
                resetDialogState: function () {
                    this.dialogForm = createFormDefaults();
                    this.dialogDisplay = createDisplayDefaults();
                    this.jsonEditorRows = {};
                    if (this.$refs.dialogForm) {
                        this.$refs.dialogForm.clearValidate();
                    }
@@ -984,6 +989,7 @@
                    self.$nextTick(function () {
                        self.resetDialogState();
                        fillFormFromRow(row, self.dialogForm, self.dialogDisplay);
                        self.initJsonEditorRows();
                        if (self.$refs.dialogForm) {
                            self.$refs.dialogForm.clearValidate();
                        }
@@ -992,6 +998,12 @@
                submitDialog: function () {
                    var self = this;
                    if (!self.$refs.dialogForm) {
                        return;
                    }
                    self.syncAllJsonEditors();
                    var jsonError = self.validateAllJsonEditors();
                    if (jsonError) {
                        self.$message.warning(jsonError);
                        return;
                    }
                    self.$refs.dialogForm.validate(function (valid) {
@@ -1025,6 +1037,118 @@
                        return true;
                    });
                },
                initJsonEditorRows: function () {
                    var self = this;
                    var editors = window.WcsJsonEditors;
                    self.stationEditorFields.forEach(function (field) {
                        self.$set(self.jsonEditorRows, field, editors.parseStationList(self.dialogForm[field]));
                    });
                    self.intArray2dFields.forEach(function (field) {
                        self.$set(self.jsonEditorRows, field, editors.parseIntArray2D(self.dialogForm[field]));
                    });
                    self.intArrayFields.forEach(function (field) {
                        self.$set(self.jsonEditorRows, field, editors.parseIntArray(self.dialogForm[field]));
                    });
                },
                syncAllJsonEditors: function () {
                    var self = this;
                    var editors = window.WcsJsonEditors;
                    self.stationEditorFields.forEach(function (field) {
                        self.$set(self.dialogForm, field, editors.buildStationListJson(self.jsonEditorRows[field]));
                    });
                    self.intArray2dFields.forEach(function (field) {
                        self.$set(self.dialogForm, field, editors.buildIntArray2DJson(self.jsonEditorRows[field]));
                    });
                    self.intArrayFields.forEach(function (field) {
                        self.$set(self.dialogForm, field, editors.buildIntArrayJson(self.jsonEditorRows[field]));
                    });
                },
                validateAllJsonEditors: function () {
                    var self = this;
                    var editors = window.WcsJsonEditors;
                    var error = '';
                    self.stationEditorFields.forEach(function (field) {
                        if (error) return;
                        error = editors.validateStationList(self.jsonEditorRows[field]);
                    });
                    self.intArray2dFields.forEach(function (field) {
                        if (error) return;
                        error = editors.validateIntArray2D(self.jsonEditorRows[field]);
                    });
                    self.intArrayFields.forEach(function (field) {
                        if (error) return;
                        error = editors.validateIntArray(self.jsonEditorRows[field]);
                    });
                    return error;
                },
                addStationRow: function (field) {
                    if (!this.jsonEditorRows[field]) {
                        this.$set(this.jsonEditorRows, field, []);
                    }
                    this.jsonEditorRows[field].push(window.WcsJsonEditors.createEmptyStation());
                    this.syncAllJsonEditors();
                },
                removeStationRow: function (field, index) {
                    this.jsonEditorRows[field].splice(index, 1);
                    this.syncAllJsonEditors();
                },
                toggleNested: function (field, rowIndex, nestedKey) {
                    var row = this.jsonEditorRows[field][rowIndex];
                    var showKey = '_show' + nestedKey.charAt(0).toUpperCase() + nestedKey.slice(1);
                    this.$set(row, showKey, !row[showKey]);
                },
                addNestedStation: function (field, rowIndex, nestedKey) {
                    var row = this.jsonEditorRows[field][rowIndex];
                    var nested = {};
                    window.WcsJsonEditors.STATION_OBJ_FIELDS.forEach(function (k) { nested[k] = ''; });
                    this.$set(row, nestedKey, nested);
                    var showKey = '_show' + nestedKey.charAt(0).toUpperCase() + nestedKey.slice(1);
                    this.$set(row, showKey, true);
                    this.syncAllJsonEditors();
                },
                removeNestedStation: function (field, rowIndex, nestedKey) {
                    this.$set(this.jsonEditorRows[field][rowIndex], nestedKey, null);
                    this.syncAllJsonEditors();
                },
                syncStationField: function (field) {
                    this.$set(this.dialogForm, field, window.WcsJsonEditors.buildStationListJson(this.jsonEditorRows[field]));
                },
                addIntArray2DGroup: function (field) {
                    if (!this.jsonEditorRows[field]) {
                        this.$set(this.jsonEditorRows, field, []);
                    }
                    this.jsonEditorRows[field].push({ values: [''] });
                    this.syncAllJsonEditors();
                },
                removeIntArray2DGroup: function (field, index) {
                    this.jsonEditorRows[field].splice(index, 1);
                    this.syncAllJsonEditors();
                },
                addIntArray2DValue: function (field, groupIndex) {
                    this.jsonEditorRows[field][groupIndex].values.push('');
                    this.syncAllJsonEditors();
                },
                removeIntArray2DValue: function (field, groupIndex, valueIndex) {
                    this.jsonEditorRows[field][groupIndex].values.splice(valueIndex, 1);
                    this.syncAllJsonEditors();
                },
                syncIntArray2DField: function (field) {
                    this.$set(this.dialogForm, field, window.WcsJsonEditors.buildIntArray2DJson(this.jsonEditorRows[field]));
                },
                addIntArrayValue: function (field) {
                    if (!this.jsonEditorRows[field]) {
                        this.$set(this.jsonEditorRows, field, []);
                    }
                    this.jsonEditorRows[field].push('');
                    this.syncAllJsonEditors();
                },
                removeIntArrayValue: function (field, index) {
                    this.jsonEditorRows[field].splice(index, 1);
                    this.syncAllJsonEditors();
                },
                syncIntArrayField: function (field) {
                    this.$set(this.dialogForm, field, window.WcsJsonEditors.buildIntArrayJson(this.jsonEditorRows[field]));
                },
                removeSelection: function () {
                    var self = this;
                    var ids = self.selection.map(function (row) {
src/main/webapp/static/js/jsonEditors.js
New file
@@ -0,0 +1,252 @@
/**
 * WCS JSON 可视化编辑器 - 共享工具函数
 * 用于 basCrnp、basDevp、basDualCrnp 页面的 JSON 字段编辑
 */
(function () {
    'use strict';
    var STATION_OBJ_FIELDS = [
        'deviceNo', 'stationId', 'deviceRow', 'deviceBay',
        'deviceLev', 'stationLev', 'barcodeIdx', 'dualCrnExecuteStation'
    ];
    // ========== StationObjModel 数组 ==========
    function createEmptyStation() {
        return {
            deviceNo: '', stationId: '', deviceRow: '', deviceBay: '',
            deviceLev: '', stationLev: '', barcodeIdx: '', dualCrnExecuteStation: '',
            barcodeStation: null,
            backStation: null,
            _showBarcode: false,
            _showBack: false
        };
    }
    function stationObjToRow(obj) {
        var row = createEmptyStation();
        STATION_OBJ_FIELDS.forEach(function (key) {
            if (obj[key] !== null && obj[key] !== undefined) {
                row[key] = String(obj[key]);
            }
        });
        if (obj.barcodeStation && typeof obj.barcodeStation === 'object') {
            row.barcodeStation = {};
            STATION_OBJ_FIELDS.forEach(function (key) {
                row.barcodeStation[key] = (obj.barcodeStation[key] !== null && obj.barcodeStation[key] !== undefined)
                    ? String(obj.barcodeStation[key]) : '';
            });
            row._showBarcode = false;
        }
        if (obj.backStation && typeof obj.backStation === 'object') {
            row.backStation = {};
            STATION_OBJ_FIELDS.forEach(function (key) {
                row.backStation[key] = (obj.backStation[key] !== null && obj.backStation[key] !== undefined)
                    ? String(obj.backStation[key]) : '';
            });
            row._showBack = false;
        }
        return row;
    }
    function parseStationList(jsonText) {
        if (!jsonText || typeof jsonText !== 'string' || !jsonText.trim()) {
            return [];
        }
        try {
            var arr = JSON.parse(jsonText);
            if (!Array.isArray(arr)) return [];
            return arr.map(function (item) {
                return stationObjToRow(item);
            });
        } catch (e) {
            return [];
        }
    }
    function buildStationObj(row) {
        var obj = {};
        var hasValue = false;
        STATION_OBJ_FIELDS.forEach(function (key) {
            var val = row[key] ? String(row[key]).trim() : '';
            if (val !== '') {
                obj[key] = Number(val);
                hasValue = true;
            } else {
                obj[key] = null;
            }
        });
        if (row.barcodeStation && typeof row.barcodeStation === 'object') {
            var bs = {};
            var bsHasValue = false;
            STATION_OBJ_FIELDS.forEach(function (key) {
                var val = row.barcodeStation[key] ? String(row.barcodeStation[key]).trim() : '';
                if (val !== '') {
                    bs[key] = Number(val);
                    bsHasValue = true;
                } else {
                    bs[key] = null;
                }
            });
            obj.barcodeStation = bsHasValue ? bs : null;
        } else {
            obj.barcodeStation = null;
        }
        if (row.backStation && typeof row.backStation === 'object') {
            var bk = {};
            var bkHasValue = false;
            STATION_OBJ_FIELDS.forEach(function (key) {
                var val = row.backStation[key] ? String(row.backStation[key]).trim() : '';
                if (val !== '') {
                    bk[key] = Number(val);
                    bkHasValue = true;
                } else {
                    bk[key] = null;
                }
            });
            obj.backStation = bkHasValue ? bk : null;
        } else {
            obj.backStation = null;
        }
        return hasValue ? obj : null;
    }
    function buildStationListJson(rows) {
        if (!rows || !rows.length) return '';
        var result = [];
        rows.forEach(function (row) {
            var obj = buildStationObj(row);
            if (obj) result.push(obj);
        });
        return result.length > 0 ? JSON.stringify(result) : '';
    }
    function validateStationList(rows) {
        if (!rows || !rows.length) return '';
        for (var i = 0; i < rows.length; i++) {
            var row = rows[i];
            var hasAny = false;
            STATION_OBJ_FIELDS.forEach(function (key) {
                if (row[key] && String(row[key]).trim()) hasAny = true;
            });
            if (!hasAny) continue;
            if (!row.deviceNo || !String(row.deviceNo).trim()) {
                return '第' + (i + 1) + '个站点:设备号不能为空';
            }
            if (!row.stationId || !String(row.stationId).trim()) {
                return '第' + (i + 1) + '个站点:站点ID不能为空';
            }
            for (var j = 0; j < STATION_OBJ_FIELDS.length; j++) {
                var key = STATION_OBJ_FIELDS[j];
                var val = row[key] ? String(row[key]).trim() : '';
                if (val !== '' && !/^-?\d+$/.test(val)) {
                    return '第' + (i + 1) + '个站点:' + key + ' 必须为整数';
                }
            }
        }
        return '';
    }
    // ========== 二维整数数组 ==========
    function parseIntArray2D(jsonText) {
        if (!jsonText || typeof jsonText !== 'string' || !jsonText.trim()) {
            return [];
        }
        try {
            var arr = JSON.parse(jsonText);
            if (!Array.isArray(arr)) return [];
            return arr.map(function (group) {
                if (!Array.isArray(group)) return { values: [] };
                return {
                    values: group.map(function (v) { return String(v); })
                };
            });
        } catch (e) {
            return [];
        }
    }
    function buildIntArray2DJson(rows) {
        if (!rows || !rows.length) return '';
        var result = [];
        rows.forEach(function (group) {
            if (!group.values || !group.values.length) return;
            var nums = [];
            group.values.forEach(function (v) {
                var trimmed = v ? String(v).trim() : '';
                if (trimmed !== '') nums.push(Number(trimmed));
            });
            if (nums.length > 0) result.push(nums);
        });
        return result.length > 0 ? JSON.stringify(result) : '';
    }
    function validateIntArray2D(rows) {
        if (!rows || !rows.length) return '';
        for (var i = 0; i < rows.length; i++) {
            var group = rows[i];
            if (!group.values) continue;
            for (var j = 0; j < group.values.length; j++) {
                var val = group.values[j] ? String(group.values[j]).trim() : '';
                if (val !== '' && !/^-?\d+$/.test(val)) {
                    return '第' + (i + 1) + '组第' + (j + 1) + '个值必须为整数';
                }
            }
        }
        return '';
    }
    // ========== 整数数组 ==========
    function parseIntArray(jsonText) {
        if (!jsonText || typeof jsonText !== 'string' || !jsonText.trim()) {
            return [];
        }
        try {
            var arr = JSON.parse(jsonText);
            if (!Array.isArray(arr)) return [];
            return arr.map(function (v) { return String(v); });
        } catch (e) {
            return [];
        }
    }
    function buildIntArrayJson(rows) {
        if (!rows || !rows.length) return '';
        var result = [];
        rows.forEach(function (v) {
            var trimmed = v ? String(v).trim() : '';
            if (trimmed !== '') result.push(Number(trimmed));
        });
        return result.length > 0 ? JSON.stringify(result) : '';
    }
    function validateIntArray(rows) {
        if (!rows || !rows.length) return '';
        for (var i = 0; i < rows.length; i++) {
            var val = rows[i] ? String(rows[i]).trim() : '';
            if (val !== '' && !/^-?\d+$/.test(val)) {
                return '第' + (i + 1) + '个值必须为整数';
            }
        }
        return '';
    }
    // ========== 暴露全局对象 ==========
    window.WcsJsonEditors = {
        STATION_OBJ_FIELDS: STATION_OBJ_FIELDS,
        createEmptyStation: createEmptyStation,
        parseStationList: parseStationList,
        buildStationListJson: buildStationListJson,
        validateStationList: validateStationList,
        parseIntArray2D: parseIntArray2D,
        buildIntArray2DJson: buildIntArray2DJson,
        validateIntArray2D: validateIntArray2D,
        parseIntArray: parseIntArray,
        buildIntArrayJson: buildIntArrayJson,
        validateIntArray: validateIntArray
    };
})();
src/main/webapp/views/basCrnp/basCrnp.html
@@ -239,6 +239,143 @@
            text-overflow: ellipsis;
        }
        .station-list-editor,
        .int-array2d-editor,
        .int-array-editor {
            padding: 10px 12px;
            border: 1px solid #dfe7f1;
            border-radius: 10px;
            background: #f8fbff;
            max-height: 400px;
            overflow-y: auto;
        }
        .station-list-editor-row {
            padding: 10px;
            border: 1px solid #e8eef5;
            border-radius: 8px;
            background: #fff;
            margin-bottom: 8px;
        }
        .station-list-editor-row-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 8px;
            font-size: 12px;
            font-weight: 700;
            color: #66788f;
        }
        .station-fields-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 6px;
        }
        .station-fields-grid label {
            display: block;
            font-size: 11px;
            color: #66788f;
            margin-bottom: 2px;
        }
        .station-fields-grid .el-input__inner {
            height: 28px;
            font-size: 12px;
        }
        .nested-section {
            margin-top: 8px;
            padding: 8px;
            border: 1px dashed #d0dbe8;
            border-radius: 6px;
            background: #f5f8fc;
        }
        .nested-section-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 6px;
            font-size: 12px;
            font-weight: 700;
            color: #66788f;
        }
        .json-editor-footer {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 8px;
            margin-top: 10px;
        }
        .json-preview {
            min-width: 0;
            color: #8a98ac;
            font-size: 12px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            font-family: Menlo, Monaco, Consolas, monospace;
            max-width: 400px;
        }
        .int-array2d-group {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 8px;
            flex-wrap: wrap;
        }
        .int-array2d-group-label {
            font-size: 12px;
            font-weight: 700;
            color: #66788f;
            min-width: 36px;
        }
        .int-array2d-values {
            display: flex;
            gap: 6px;
            flex-wrap: wrap;
            align-items: center;
        }
        .int-array2d-values .el-input {
            width: 70px;
        }
        .int-array2d-values .el-input__inner {
            height: 28px;
            font-size: 12px;
        }
        .int-array-items {
            display: flex;
            gap: 8px;
            flex-wrap: wrap;
            align-items: center;
        }
        .int-array-item {
            display: flex;
            align-items: center;
            gap: 4px;
        }
        .int-array-item .el-input {
            width: 80px;
        }
        .int-array-item .el-input__inner {
            height: 28px;
            font-size: 12px;
        }
        .pager-bar {
            padding: 0 16px 16px;
            display: flex;
@@ -713,6 +850,124 @@
                                <span class="row-map-json-preview mono" :title="dialogForm.rowMap">{{ dialogForm.rowMap || '未配置时使用库位排原值' }}</span>
                            </div>
                        </div>
                        <!-- StationObjModel 数组编辑器 -->
                        <div
                            v-else-if="stationEditorFields.indexOf(field.field) !== -1"
                            class="station-list-editor">
                            <div
                                v-for="(station, sIdx) in jsonEditorRows[field.field] || []"
                                :key="field.field + '-station-' + sIdx"
                                class="station-list-editor-row">
                                <div class="station-list-editor-row-head">
                                    <span>站点 {{ sIdx + 1 }}</span>
                                    <el-button type="text" style="color:#f56c6c;" @click="removeStationRow(field.field, sIdx)">删除</el-button>
                                </div>
                                <div class="station-fields-grid">
                                    <div><label>设备号</label><el-input v-model.trim="station.deviceNo" placeholder="如 1" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>站点ID</label><el-input v-model.trim="station.stationId" placeholder="如 11" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>排</label><el-input v-model.trim="station.deviceRow" placeholder="排" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>列</label><el-input v-model.trim="station.deviceBay" placeholder="列" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>层</label><el-input v-model.trim="station.deviceLev" placeholder="层" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>站点层</label><el-input v-model.trim="station.stationLev" placeholder="站点层" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>条码索引</label><el-input v-model.trim="station.barcodeIdx" placeholder="条码索引" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>双工位工位</label><el-input v-model.trim="station.dualCrnExecuteStation" placeholder="双工位工位" @input="syncStationField(field.field)"></el-input></div>
                                </div>
                                <!-- 条码站点 -->
                                <div class="nested-section">
                                    <div class="nested-section-head">
                                        <span>条码站点 {{ station.barcodeStation ? '(已配置)' : '' }}</span>
                                        <span>
                                            <el-button v-if="!station.barcodeStation" type="text" size="mini" @click="addNestedStation(field.field, sIdx, 'barcodeStation')">添加</el-button>
                                            <el-button v-if="station.barcodeStation" type="text" size="mini" @click="toggleNested(field.field, sIdx, 'barcodeStation')">{{ station._showBarcode ? '收起' : '展开' }}</el-button>
                                            <el-button v-if="station.barcodeStation" type="text" size="mini" style="color:#f56c6c;" @click="removeNestedStation(field.field, sIdx, 'barcodeStation')">移除</el-button>
                                        </span>
                                    </div>
                                    <div v-if="station.barcodeStation && station._showBarcode" class="station-fields-grid">
                                        <div><label>设备号</label><el-input v-model.trim="station.barcodeStation.deviceNo" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点ID</label><el-input v-model.trim="station.barcodeStation.stationId" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>排</label><el-input v-model.trim="station.barcodeStation.deviceRow" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>列</label><el-input v-model.trim="station.barcodeStation.deviceBay" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>层</label><el-input v-model.trim="station.barcodeStation.deviceLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点层</label><el-input v-model.trim="station.barcodeStation.stationLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>条码索引</label><el-input v-model.trim="station.barcodeStation.barcodeIdx" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>双工位工位</label><el-input v-model.trim="station.barcodeStation.dualCrnExecuteStation" @input="syncStationField(field.field)"></el-input></div>
                                    </div>
                                </div>
                                <!-- 回库站点 -->
                                <div class="nested-section">
                                    <div class="nested-section-head">
                                        <span>回库站点 {{ station.backStation ? '(已配置)' : '' }}</span>
                                        <span>
                                            <el-button v-if="!station.backStation" type="text" size="mini" @click="addNestedStation(field.field, sIdx, 'backStation')">添加</el-button>
                                            <el-button v-if="station.backStation" type="text" size="mini" @click="toggleNested(field.field, sIdx, 'backStation')">{{ station._showBack ? '收起' : '展开' }}</el-button>
                                            <el-button v-if="station.backStation" type="text" size="mini" style="color:#f56c6c;" @click="removeNestedStation(field.field, sIdx, 'backStation')">移除</el-button>
                                        </span>
                                    </div>
                                    <div v-if="station.backStation && station._showBack" class="station-fields-grid">
                                        <div><label>设备号</label><el-input v-model.trim="station.backStation.deviceNo" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点ID</label><el-input v-model.trim="station.backStation.stationId" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>排</label><el-input v-model.trim="station.backStation.deviceRow" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>列</label><el-input v-model.trim="station.backStation.deviceBay" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>层</label><el-input v-model.trim="station.backStation.deviceLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点层</label><el-input v-model.trim="station.backStation.stationLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>条码索引</label><el-input v-model.trim="station.backStation.barcodeIdx" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>双工位工位</label><el-input v-model.trim="station.backStation.dualCrnExecuteStation" @input="syncStationField(field.field)"></el-input></div>
                                    </div>
                                </div>
                            </div>
                            <div class="json-editor-footer">
                                <el-button size="mini" plain icon="el-icon-plus" @click="addStationRow(field.field)">新增站点</el-button>
                                <span class="json-preview mono" :title="dialogForm[field.field]">{{ dialogForm[field.field] || '空' }}</span>
                            </div>
                        </div>
                        <!-- 二维整数数组编辑器 -->
                        <div
                            v-else-if="intArray2dFields.indexOf(field.field) !== -1"
                            class="int-array2d-editor">
                            <div
                                v-for="(group, gIdx) in jsonEditorRows[field.field] || []"
                                :key="field.field + '-group-' + gIdx"
                                class="int-array2d-group">
                                <span class="int-array2d-group-label">组 {{ gIdx + 1 }}</span>
                                <div class="int-array2d-values">
                                    <el-input
                                        v-for="(val, vIdx) in group.values"
                                        :key="field.field + '-g' + gIdx + '-v' + vIdx"
                                        v-model.trim="group.values[vIdx]"
                                        placeholder="值"
                                        @input="syncIntArray2DField(field.field)">
                                    </el-input>
                                    <el-button type="text" icon="el-icon-plus" @click="addIntArray2DValue(field.field, gIdx)"></el-button>
                                </div>
                                <el-button type="text" style="color:#f56c6c;" @click="removeIntArray2DGroup(field.field, gIdx)">删除组</el-button>
                            </div>
                            <div class="json-editor-footer">
                                <el-button size="mini" plain icon="el-icon-plus" @click="addIntArray2DGroup(field.field)">新增组</el-button>
                                <span class="json-preview mono" :title="dialogForm[field.field]">{{ dialogForm[field.field] || '空' }}</span>
                            </div>
                        </div>
                        <!-- 整数数组编辑器 -->
                        <div
                            v-else-if="intArrayFields.indexOf(field.field) !== -1"
                            class="int-array-editor">
                            <div class="int-array-items">
                                <div
                                    v-for="(val, idx) in jsonEditorRows[field.field] || []"
                                    :key="field.field + '-item-' + idx"
                                    class="int-array-item">
                                    <el-input
                                        v-model.trim="jsonEditorRows[field.field][idx]"
                                        placeholder="值"
                                        @input="syncIntArrayField(field.field)">
                                    </el-input>
                                    <el-button type="text" style="color:#f56c6c;" @click="removeIntArrayValue(field.field, idx)">删除</el-button>
                                </div>
                            </div>
                            <div class="json-editor-footer">
                                <el-button size="mini" plain icon="el-icon-plus" @click="addIntArrayValue(field.field)">新增</el-button>
                                <span class="json-preview mono" :title="dialogForm[field.field]">{{ dialogForm[field.field] || '空' }}</span>
                            </div>
                        </div>
                        <el-input
                            v-else-if="field.textarea"
                            v-model.trim="dialogForm[field.field]"
@@ -738,6 +993,7 @@
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/jsonEditors.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
<script type="text/javascript" src="../../static/vue/element/element.js"></script>
<script type="text/javascript" src="../../static/js/basCrnp/basCrnp.js?v=20260310" charset="utf-8"></script>
src/main/webapp/views/basDevp/basDevp.html
@@ -196,6 +196,88 @@
            font-family: Menlo, Monaco, Consolas, "Liberation Mono", monospace;
        }
        .station-list-editor {
            padding: 10px 12px;
            border: 1px solid #dfe7f1;
            border-radius: 10px;
            background: #f8fbff;
            max-height: 400px;
            overflow-y: auto;
        }
        .station-list-editor-row {
            padding: 10px;
            border: 1px solid #e8eef5;
            border-radius: 8px;
            background: #fff;
            margin-bottom: 8px;
        }
        .station-list-editor-row-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 8px;
            font-size: 12px;
            font-weight: 700;
            color: #66788f;
        }
        .station-fields-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 6px;
        }
        .station-fields-grid label {
            display: block;
            font-size: 11px;
            color: #66788f;
            margin-bottom: 2px;
        }
        .station-fields-grid .el-input__inner {
            height: 28px;
            font-size: 12px;
        }
        .nested-section {
            margin-top: 8px;
            padding: 8px;
            border: 1px dashed #d0dbe8;
            border-radius: 6px;
            background: #f5f8fc;
        }
        .nested-section-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 6px;
            font-size: 12px;
            font-weight: 700;
            color: #66788f;
        }
        .json-editor-footer {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 8px;
            margin-top: 10px;
        }
        .json-preview {
            min-width: 0;
            color: #8a98ac;
            font-size: 12px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            font-family: Menlo, Monaco, Consolas, monospace;
            max-width: 400px;
        }
        .pager-bar {
            padding: 0 16px 16px;
            display: flex;
@@ -638,6 +720,76 @@
                            active-color="#13ce66"
                            inactive-color="#c0c4cc">
                        </el-switch>
                        <!-- StationObjModel 数组编辑器 -->
                        <div
                            v-else-if="stationEditorFields.indexOf(field.field) !== -1"
                            class="station-list-editor">
                            <div
                                v-for="(station, sIdx) in jsonEditorRows[field.field] || []"
                                :key="field.field + '-station-' + sIdx"
                                class="station-list-editor-row">
                                <div class="station-list-editor-row-head">
                                    <span>站点 {{ sIdx + 1 }}</span>
                                    <el-button type="text" style="color:#f56c6c;" @click="removeStationRow(field.field, sIdx)">删除</el-button>
                                </div>
                                <div class="station-fields-grid">
                                    <div><label>设备号</label><el-input v-model.trim="station.deviceNo" placeholder="如 1" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>站点ID</label><el-input v-model.trim="station.stationId" placeholder="如 11" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>排</label><el-input v-model.trim="station.deviceRow" placeholder="排" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>列</label><el-input v-model.trim="station.deviceBay" placeholder="列" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>层</label><el-input v-model.trim="station.deviceLev" placeholder="层" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>站点层</label><el-input v-model.trim="station.stationLev" placeholder="站点层" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>条码索引</label><el-input v-model.trim="station.barcodeIdx" placeholder="条码索引" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>双工位工位</label><el-input v-model.trim="station.dualCrnExecuteStation" placeholder="双工位工位" @input="syncStationField(field.field)"></el-input></div>
                                </div>
                                <!-- 条码站点 -->
                                <div class="nested-section">
                                    <div class="nested-section-head">
                                        <span>条码站点 {{ station.barcodeStation ? '(已配置)' : '' }}</span>
                                        <span>
                                            <el-button v-if="!station.barcodeStation" type="text" size="mini" @click="addNestedStation(field.field, sIdx, 'barcodeStation')">添加</el-button>
                                            <el-button v-if="station.barcodeStation" type="text" size="mini" @click="toggleNested(field.field, sIdx, 'barcodeStation')">{{ station._showBarcode ? '收起' : '展开' }}</el-button>
                                            <el-button v-if="station.barcodeStation" type="text" size="mini" style="color:#f56c6c;" @click="removeNestedStation(field.field, sIdx, 'barcodeStation')">移除</el-button>
                                        </span>
                                    </div>
                                    <div v-if="station.barcodeStation && station._showBarcode" class="station-fields-grid">
                                        <div><label>设备号</label><el-input v-model.trim="station.barcodeStation.deviceNo" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点ID</label><el-input v-model.trim="station.barcodeStation.stationId" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>排</label><el-input v-model.trim="station.barcodeStation.deviceRow" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>列</label><el-input v-model.trim="station.barcodeStation.deviceBay" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>层</label><el-input v-model.trim="station.barcodeStation.deviceLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点层</label><el-input v-model.trim="station.barcodeStation.stationLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>条码索引</label><el-input v-model.trim="station.barcodeStation.barcodeIdx" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>双工位工位</label><el-input v-model.trim="station.barcodeStation.dualCrnExecuteStation" @input="syncStationField(field.field)"></el-input></div>
                                    </div>
                                </div>
                                <!-- 回库站点 -->
                                <div class="nested-section">
                                    <div class="nested-section-head">
                                        <span>回库站点 {{ station.backStation ? '(已配置)' : '' }}</span>
                                        <span>
                                            <el-button v-if="!station.backStation" type="text" size="mini" @click="addNestedStation(field.field, sIdx, 'backStation')">添加</el-button>
                                            <el-button v-if="station.backStation" type="text" size="mini" @click="toggleNested(field.field, sIdx, 'backStation')">{{ station._showBack ? '收起' : '展开' }}</el-button>
                                            <el-button v-if="station.backStation" type="text" size="mini" style="color:#f56c6c;" @click="removeNestedStation(field.field, sIdx, 'backStation')">移除</el-button>
                                        </span>
                                    </div>
                                    <div v-if="station.backStation && station._showBack" class="station-fields-grid">
                                        <div><label>设备号</label><el-input v-model.trim="station.backStation.deviceNo" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点ID</label><el-input v-model.trim="station.backStation.stationId" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>排</label><el-input v-model.trim="station.backStation.deviceRow" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>列</label><el-input v-model.trim="station.backStation.deviceBay" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>层</label><el-input v-model.trim="station.backStation.deviceLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点层</label><el-input v-model.trim="station.backStation.stationLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>条码索引</label><el-input v-model.trim="station.backStation.barcodeIdx" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>双工位工位</label><el-input v-model.trim="station.backStation.dualCrnExecuteStation" @input="syncStationField(field.field)"></el-input></div>
                                    </div>
                                </div>
                            </div>
                            <div class="json-editor-footer">
                                <el-button size="mini" plain icon="el-icon-plus" @click="addStationRow(field.field)">新增站点</el-button>
                                <span class="json-preview mono" :title="dialogForm[field.field]">{{ dialogForm[field.field] || '空' }}</span>
                            </div>
                        </div>
                        <el-input
                            v-else-if="field.textarea"
                            v-model.trim="dialogForm[field.field]"
@@ -663,6 +815,7 @@
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/jsonEditors.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
<script type="text/javascript" src="../../static/vue/element/element.js"></script>
<script type="text/javascript" src="../../static/js/basDevp/basDevp.js?v=20260310" charset="utf-8"></script>
src/main/webapp/views/basDualCrnp/basDualCrnp.html
@@ -196,6 +196,143 @@
            font-family: Menlo, Monaco, Consolas, "Liberation Mono", monospace;
        }
        .station-list-editor,
        .int-array2d-editor,
        .int-array-editor {
            padding: 10px 12px;
            border: 1px solid #dfe7f1;
            border-radius: 10px;
            background: #f8fbff;
            max-height: 400px;
            overflow-y: auto;
        }
        .station-list-editor-row {
            padding: 10px;
            border: 1px solid #e8eef5;
            border-radius: 8px;
            background: #fff;
            margin-bottom: 8px;
        }
        .station-list-editor-row-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 8px;
            font-size: 12px;
            font-weight: 700;
            color: #66788f;
        }
        .station-fields-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 6px;
        }
        .station-fields-grid label {
            display: block;
            font-size: 11px;
            color: #66788f;
            margin-bottom: 2px;
        }
        .station-fields-grid .el-input__inner {
            height: 28px;
            font-size: 12px;
        }
        .nested-section {
            margin-top: 8px;
            padding: 8px;
            border: 1px dashed #d0dbe8;
            border-radius: 6px;
            background: #f5f8fc;
        }
        .nested-section-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 6px;
            font-size: 12px;
            font-weight: 700;
            color: #66788f;
        }
        .json-editor-footer {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 8px;
            margin-top: 10px;
        }
        .json-preview {
            min-width: 0;
            color: #8a98ac;
            font-size: 12px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            font-family: Menlo, Monaco, Consolas, monospace;
            max-width: 400px;
        }
        .int-array2d-group {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 8px;
            flex-wrap: wrap;
        }
        .int-array2d-group-label {
            font-size: 12px;
            font-weight: 700;
            color: #66788f;
            min-width: 36px;
        }
        .int-array2d-values {
            display: flex;
            gap: 6px;
            flex-wrap: wrap;
            align-items: center;
        }
        .int-array2d-values .el-input {
            width: 70px;
        }
        .int-array2d-values .el-input__inner {
            height: 28px;
            font-size: 12px;
        }
        .int-array-items {
            display: flex;
            gap: 8px;
            flex-wrap: wrap;
            align-items: center;
        }
        .int-array-item {
            display: flex;
            align-items: center;
            gap: 4px;
        }
        .int-array-item .el-input {
            width: 80px;
        }
        .int-array-item .el-input__inner {
            height: 28px;
            font-size: 12px;
        }
        .pager-bar {
            padding: 0 16px 16px;
            display: flex;
@@ -638,6 +775,124 @@
                            active-color="#13ce66"
                            inactive-color="#c0c4cc">
                        </el-switch>
                        <!-- StationObjModel 数组编辑器 -->
                        <div
                            v-else-if="stationEditorFields.indexOf(field.field) !== -1"
                            class="station-list-editor">
                            <div
                                v-for="(station, sIdx) in jsonEditorRows[field.field] || []"
                                :key="field.field + '-station-' + sIdx"
                                class="station-list-editor-row">
                                <div class="station-list-editor-row-head">
                                    <span>站点 {{ sIdx + 1 }}</span>
                                    <el-button type="text" style="color:#f56c6c;" @click="removeStationRow(field.field, sIdx)">删除</el-button>
                                </div>
                                <div class="station-fields-grid">
                                    <div><label>设备号</label><el-input v-model.trim="station.deviceNo" placeholder="如 1" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>站点ID</label><el-input v-model.trim="station.stationId" placeholder="如 11" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>排</label><el-input v-model.trim="station.deviceRow" placeholder="排" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>列</label><el-input v-model.trim="station.deviceBay" placeholder="列" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>层</label><el-input v-model.trim="station.deviceLev" placeholder="层" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>站点层</label><el-input v-model.trim="station.stationLev" placeholder="站点层" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>条码索引</label><el-input v-model.trim="station.barcodeIdx" placeholder="条码索引" @input="syncStationField(field.field)"></el-input></div>
                                    <div><label>双工位工位</label><el-input v-model.trim="station.dualCrnExecuteStation" placeholder="双工位工位" @input="syncStationField(field.field)"></el-input></div>
                                </div>
                                <!-- 条码站点 -->
                                <div class="nested-section">
                                    <div class="nested-section-head">
                                        <span>条码站点 {{ station.barcodeStation ? '(已配置)' : '' }}</span>
                                        <span>
                                            <el-button v-if="!station.barcodeStation" type="text" size="mini" @click="addNestedStation(field.field, sIdx, 'barcodeStation')">添加</el-button>
                                            <el-button v-if="station.barcodeStation" type="text" size="mini" @click="toggleNested(field.field, sIdx, 'barcodeStation')">{{ station._showBarcode ? '收起' : '展开' }}</el-button>
                                            <el-button v-if="station.barcodeStation" type="text" size="mini" style="color:#f56c6c;" @click="removeNestedStation(field.field, sIdx, 'barcodeStation')">移除</el-button>
                                        </span>
                                    </div>
                                    <div v-if="station.barcodeStation && station._showBarcode" class="station-fields-grid">
                                        <div><label>设备号</label><el-input v-model.trim="station.barcodeStation.deviceNo" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点ID</label><el-input v-model.trim="station.barcodeStation.stationId" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>排</label><el-input v-model.trim="station.barcodeStation.deviceRow" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>列</label><el-input v-model.trim="station.barcodeStation.deviceBay" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>层</label><el-input v-model.trim="station.barcodeStation.deviceLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点层</label><el-input v-model.trim="station.barcodeStation.stationLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>条码索引</label><el-input v-model.trim="station.barcodeStation.barcodeIdx" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>双工位工位</label><el-input v-model.trim="station.barcodeStation.dualCrnExecuteStation" @input="syncStationField(field.field)"></el-input></div>
                                    </div>
                                </div>
                                <!-- 回库站点 -->
                                <div class="nested-section">
                                    <div class="nested-section-head">
                                        <span>回库站点 {{ station.backStation ? '(已配置)' : '' }}</span>
                                        <span>
                                            <el-button v-if="!station.backStation" type="text" size="mini" @click="addNestedStation(field.field, sIdx, 'backStation')">添加</el-button>
                                            <el-button v-if="station.backStation" type="text" size="mini" @click="toggleNested(field.field, sIdx, 'backStation')">{{ station._showBack ? '收起' : '展开' }}</el-button>
                                            <el-button v-if="station.backStation" type="text" size="mini" style="color:#f56c6c;" @click="removeNestedStation(field.field, sIdx, 'backStation')">移除</el-button>
                                        </span>
                                    </div>
                                    <div v-if="station.backStation && station._showBack" class="station-fields-grid">
                                        <div><label>设备号</label><el-input v-model.trim="station.backStation.deviceNo" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点ID</label><el-input v-model.trim="station.backStation.stationId" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>排</label><el-input v-model.trim="station.backStation.deviceRow" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>列</label><el-input v-model.trim="station.backStation.deviceBay" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>层</label><el-input v-model.trim="station.backStation.deviceLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>站点层</label><el-input v-model.trim="station.backStation.stationLev" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>条码索引</label><el-input v-model.trim="station.backStation.barcodeIdx" @input="syncStationField(field.field)"></el-input></div>
                                        <div><label>双工位工位</label><el-input v-model.trim="station.backStation.dualCrnExecuteStation" @input="syncStationField(field.field)"></el-input></div>
                                    </div>
                                </div>
                            </div>
                            <div class="json-editor-footer">
                                <el-button size="mini" plain icon="el-icon-plus" @click="addStationRow(field.field)">新增站点</el-button>
                                <span class="json-preview mono" :title="dialogForm[field.field]">{{ dialogForm[field.field] || '空' }}</span>
                            </div>
                        </div>
                        <!-- 二维整数数组编辑器 -->
                        <div
                            v-else-if="intArray2dFields.indexOf(field.field) !== -1"
                            class="int-array2d-editor">
                            <div
                                v-for="(group, gIdx) in jsonEditorRows[field.field] || []"
                                :key="field.field + '-group-' + gIdx"
                                class="int-array2d-group">
                                <span class="int-array2d-group-label">组 {{ gIdx + 1 }}</span>
                                <div class="int-array2d-values">
                                    <el-input
                                        v-for="(val, vIdx) in group.values"
                                        :key="field.field + '-g' + gIdx + '-v' + vIdx"
                                        v-model.trim="group.values[vIdx]"
                                        placeholder="值"
                                        @input="syncIntArray2DField(field.field)">
                                    </el-input>
                                    <el-button type="text" icon="el-icon-plus" @click="addIntArray2DValue(field.field, gIdx)"></el-button>
                                </div>
                                <el-button type="text" style="color:#f56c6c;" @click="removeIntArray2DGroup(field.field, gIdx)">删除组</el-button>
                            </div>
                            <div class="json-editor-footer">
                                <el-button size="mini" plain icon="el-icon-plus" @click="addIntArray2DGroup(field.field)">新增组</el-button>
                                <span class="json-preview mono" :title="dialogForm[field.field]">{{ dialogForm[field.field] || '空' }}</span>
                            </div>
                        </div>
                        <!-- 整数数组编辑器 -->
                        <div
                            v-else-if="intArrayFields.indexOf(field.field) !== -1"
                            class="int-array-editor">
                            <div class="int-array-items">
                                <div
                                    v-for="(val, idx) in jsonEditorRows[field.field] || []"
                                    :key="field.field + '-item-' + idx"
                                    class="int-array-item">
                                    <el-input
                                        v-model.trim="jsonEditorRows[field.field][idx]"
                                        placeholder="值"
                                        @input="syncIntArrayField(field.field)">
                                    </el-input>
                                    <el-button type="text" style="color:#f56c6c;" @click="removeIntArrayValue(field.field, idx)">删除</el-button>
                                </div>
                            </div>
                            <div class="json-editor-footer">
                                <el-button size="mini" plain icon="el-icon-plus" @click="addIntArrayValue(field.field)">新增</el-button>
                                <span class="json-preview mono" :title="dialogForm[field.field]">{{ dialogForm[field.field] || '空' }}</span>
                            </div>
                        </div>
                        <el-input
                            v-else-if="field.textarea"
                            v-model.trim="dialogForm[field.field]"
@@ -663,6 +918,7 @@
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/jsonEditors.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
<script type="text/javascript" src="../../static/vue/element/element.js"></script>
<script type="text/javascript" src="../../static/js/basDualCrnp/basDualCrnp.js?v=20260310" charset="utf-8"></script>