From 494d383f5d48d8986ec2890fb37eb3973ac487fe Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期六, 21 三月 2026 20:12:07 +0800
Subject: [PATCH] #

---
 src/main/webapp/views/wrkMast/wrkMast.html                 |    3 
 src/main/resources/sql/20260321_add_wrk_batch_out_menu.sql |   72 ++++
 src/main/webapp/views/wrkBatchOut/wrkBatchOut.html         |  488 ++++++++++++++++++++++++++++++
 src/main/webapp/static/js/wrkMast/wrkMast.js               |    3 
 src/main/webapp/static/js/wrkBatchOut/wrkBatchOut.js       |  371 +++++++++++++++++++++++
 5 files changed, 936 insertions(+), 1 deletions(-)

diff --git a/src/main/resources/sql/20260321_add_wrk_batch_out_menu.sql b/src/main/resources/sql/20260321_add_wrk_batch_out_menu.sql
new file mode 100644
index 0000000..cd3b411
--- /dev/null
+++ b/src/main/resources/sql/20260321_add_wrk_batch_out_menu.sql
@@ -0,0 +1,72 @@
+-- 灏� 鐢熸垚鍑哄簱浠诲姟 鑿滃崟鎸傝浇鍒帮細浣滀笟娴佺▼
+-- 璇存槑锛氭墽琛屾湰鑴氭湰鍚庯紝璇峰湪鈥滆鑹叉巿鏉冣�濋噷缁欏搴旇鑹插嬀閫夋柊鑿滃崟鍜屸�滄煡鐪嬧�濇潈闄愩��
+
+SET @wrk_batch_out_parent_id := (
+  SELECT id
+  FROM sys_resource
+  WHERE code = 'workFlow' AND level = 1
+  ORDER BY id
+  LIMIT 1
+);
+
+SET @wrk_batch_out_sort := COALESCE(
+  (
+    SELECT MAX(COALESCE(sort, 0)) + 1
+    FROM sys_resource
+    WHERE resource_id = @wrk_batch_out_parent_id
+      AND level = 2
+  ),
+  1
+);
+
+INSERT INTO sys_resource(code, name, resource_id, level, sort, status)
+SELECT 'wrkBatchOut/wrkBatchOut.html', '鐢熸垚鍑哄簱浠诲姟', @wrk_batch_out_parent_id, 2, @wrk_batch_out_sort, 1
+FROM dual
+WHERE @wrk_batch_out_parent_id IS NOT NULL
+  AND NOT EXISTS (
+    SELECT 1
+    FROM sys_resource
+    WHERE code = 'wrkBatchOut/wrkBatchOut.html' AND level = 2
+  );
+
+UPDATE sys_resource
+SET name = '鐢熸垚鍑哄簱浠诲姟',
+    resource_id = @wrk_batch_out_parent_id,
+    level = 2,
+    sort = @wrk_batch_out_sort,
+    status = 1
+WHERE code = 'wrkBatchOut/wrkBatchOut.html' AND level = 2;
+
+SET @wrk_batch_out_id := (
+  SELECT id
+  FROM sys_resource
+  WHERE code = 'wrkBatchOut/wrkBatchOut.html' AND level = 2
+  ORDER BY id
+  LIMIT 1
+);
+
+INSERT INTO sys_resource(code, name, resource_id, level, sort, status)
+SELECT 'wrkBatchOut/wrkBatchOut.html#view', '鏌ョ湅', @wrk_batch_out_id, 3, 1, 1
+FROM dual
+WHERE @wrk_batch_out_id IS NOT NULL
+  AND NOT EXISTS (
+    SELECT 1
+    FROM sys_resource
+    WHERE code = 'wrkBatchOut/wrkBatchOut.html#view' AND level = 3
+  );
+
+UPDATE sys_resource
+SET name = '鏌ョ湅',
+    resource_id = @wrk_batch_out_id,
+    level = 3,
+    sort = 1,
+    status = 1
+WHERE code = 'wrkBatchOut/wrkBatchOut.html#view' AND level = 3;
+
+SELECT id, code, name, resource_id, level, sort, status
+FROM sys_resource
+WHERE code IN (
+  'wrkBatchOut/wrkBatchOut.html',
+  'wrkBatchOut/wrkBatchOut.html#view'
+)
+ORDER BY level, sort, id;
diff --git a/src/main/webapp/static/js/wrkBatchOut/wrkBatchOut.js b/src/main/webapp/static/js/wrkBatchOut/wrkBatchOut.js
new file mode 100644
index 0000000..53f203b
--- /dev/null
+++ b/src/main/webapp/static/js/wrkBatchOut/wrkBatchOut.js
@@ -0,0 +1,371 @@
+(function () {
+    function createSearchForm() {
+        return {
+            loc_no: "",
+            barcode: "",
+            row1: "",
+            bay1: "",
+            lev1: "",
+            loc_type: "",
+            qr_code_value: ""
+        };
+    }
+
+    function createForm() {
+        return {
+            staNo: "",
+            taskPri: 100
+        };
+    }
+
+    function createBatchNo() {
+        var now = new Date();
+        var pad = function (value) {
+            return value < 10 ? "0" + value : String(value);
+        };
+        return "MANUAL_OUT_" + now.getFullYear()
+            + pad(now.getMonth() + 1)
+            + pad(now.getDate())
+            + pad(now.getHours())
+            + pad(now.getMinutes())
+            + pad(now.getSeconds());
+    }
+
+    function listSelectedLocNos(selectedLocMap, selectedLocOrder) {
+        return selectedLocOrder.filter(function (locNo) {
+            return !!selectedLocMap[locNo];
+        });
+    }
+
+    function buildOutStationsFromDevps(records) {
+        var stationMap = {};
+        var stationList = [];
+
+        (records || []).forEach(function (record) {
+            var outStations = record["outStationList$"] || [];
+            outStations.forEach(function (station) {
+                var stationId = station && station.stationId;
+                if (!stationId || stationMap[stationId]) {
+                    return;
+                }
+                stationMap[stationId] = true;
+                stationList.push({
+                    stationId: stationId,
+                    deviceNo: station.deviceNo,
+                    stationLev: station.stationLev
+                });
+            });
+        });
+
+        stationList.sort(function (left, right) {
+            return Number(left.stationId) - Number(right.stationId);
+        });
+        return stationList;
+    }
+
+    new Vue({
+        el: "#app",
+        data: function () {
+            return {
+                loading: false,
+                stationLoading: false,
+                submitting: false,
+                advancedVisible: false,
+                tableData: [],
+                outStationOptions: [],
+                searchForm: createSearchForm(),
+                form: createForm(),
+                page: {
+                    curr: 1,
+                    limit: 30,
+                    total: 0
+                },
+                selectedLocMap: {},
+                selectedLocOrder: [],
+                restoringSelection: false,
+                layoutTimer: null
+            };
+        },
+        computed: {
+            selectedLocNos: function () {
+                return listSelectedLocNos(this.selectedLocMap, this.selectedLocOrder);
+            },
+            selectedCount: function () {
+                return this.selectedLocNos.length;
+            },
+            selectedPreviewLocNos: function () {
+                return this.selectedLocNos.slice(0, 18);
+            },
+            selectedPreviewOverflow: function () {
+                return Math.max(0, this.selectedCount - this.selectedPreviewLocNos.length);
+            },
+            tableHeight: function () {
+                return this.advancedVisible ? "calc(100vh - 410px)" : "calc(100vh - 350px)";
+            }
+        },
+        created: function () {
+            this.loadOutStations();
+            this.loadTable();
+        },
+        beforeDestroy: function () {
+            if (this.layoutTimer) {
+                clearTimeout(this.layoutTimer);
+                this.layoutTimer = null;
+            }
+        },
+        methods: {
+            requestTableLayout: function (delay) {
+                var vm = this;
+                if (vm.layoutTimer) {
+                    clearTimeout(vm.layoutTimer);
+                }
+                vm.$nextTick(function () {
+                    vm.layoutTimer = setTimeout(function () {
+                        if (vm.$refs.dataTable && typeof vm.$refs.dataTable.doLayout === "function") {
+                            vm.$refs.dataTable.doLayout();
+                        }
+                        vm.restoreTableSelection();
+                    }, delay || 40);
+                });
+            },
+            buildQueryParams: function () {
+                var params = {
+                    curr: this.page.curr,
+                    limit: this.page.limit,
+                    status: 1,
+                    loc_sts: "F",
+                    orderByField: "locNo",
+                    orderByType: "asc"
+                };
+                var key;
+
+                for (key in this.searchForm) {
+                    if (Object.prototype.hasOwnProperty.call(this.searchForm, key) && this.searchForm[key] !== "" && this.searchForm[key] !== null) {
+                        params[key] = this.searchForm[key];
+                    }
+                }
+                return params;
+            },
+            loadTable: function () {
+                var vm = this;
+                vm.loading = true;
+                $.ajax({
+                    url: baseUrl + "/locMast/list/auth",
+                    headers: { token: localStorage.getItem("token") },
+                    method: "GET",
+                    data: vm.buildQueryParams(),
+                    success: function (res) {
+                        if (res.code === 200) {
+                            vm.tableData = (res.data && res.data.records) || [];
+                            vm.page.total = (res.data && res.data.total) || 0;
+                            vm.requestTableLayout(80);
+                            return;
+                        }
+                        if (res.code === 403) {
+                            top.location.href = baseUrl + "/";
+                            return;
+                        }
+                        vm.$message.error(res.msg || "搴撲綅鍔犺浇澶辫触");
+                    },
+                    error: function () {
+                        vm.$message.error("搴撲綅鍔犺浇澶辫触");
+                    },
+                    complete: function () {
+                        vm.loading = false;
+                    }
+                });
+            },
+            loadOutStations: function () {
+                var vm = this;
+                vm.stationLoading = true;
+                $.ajax({
+                    url: baseUrl + "/basDevp/list/auth",
+                    headers: { token: localStorage.getItem("token") },
+                    method: "GET",
+                    data: {
+                        curr: 1,
+                        limit: 200,
+                        status: 1,
+                        orderByField: "devpNo",
+                        orderByType: "asc"
+                    },
+                    success: function (res) {
+                        if (res.code === 200) {
+                            vm.outStationOptions = buildOutStationsFromDevps((res.data && res.data.records) || []);
+                            return;
+                        }
+                        if (res.code === 403) {
+                            top.location.href = baseUrl + "/";
+                            return;
+                        }
+                        vm.$message.error(res.msg || "鍑哄簱绔欑偣鍔犺浇澶辫触");
+                    },
+                    error: function () {
+                        vm.$message.error("鍑哄簱绔欑偣鍔犺浇澶辫触");
+                    },
+                    complete: function () {
+                        vm.stationLoading = false;
+                    }
+                });
+            },
+            formatStationOptionLabel: function (station) {
+                var parts = [];
+                if (!station) {
+                    return "";
+                }
+                if (station.stationId !== null && station.stationId !== undefined && station.stationId !== "") {
+                    parts.push("绔欑偣" + station.stationId);
+                }
+                if (station.deviceNo !== null && station.deviceNo !== undefined) {
+                    parts.push("璁惧" + station.deviceNo);
+                }
+                if (station.stationLev !== null && station.stationLev !== undefined) {
+                    parts.push(station.stationLev + "灞�");
+                }
+                return parts.join(" / ");
+            },
+            toggleAdvanced: function () {
+                this.advancedVisible = !this.advancedVisible;
+                this.requestTableLayout(180);
+            },
+            handleSearch: function () {
+                this.page.curr = 1;
+                this.loadTable();
+            },
+            handleReset: function () {
+                this.searchForm = createSearchForm();
+                this.advancedVisible = false;
+                this.page.curr = 1;
+                this.loadTable();
+            },
+            handleCurrentChange: function (curr) {
+                this.page.curr = curr;
+                this.loadTable();
+            },
+            handleSizeChange: function (limit) {
+                this.page.limit = limit;
+                this.page.curr = 1;
+                this.loadTable();
+            },
+            handleSelectionChange: function (rows) {
+                var vm = this;
+                var selectedOnPage = {};
+
+                if (vm.restoringSelection) {
+                    return;
+                }
+
+                (rows || []).forEach(function (row) {
+                    selectedOnPage[row.locNo] = row;
+                });
+
+                vm.tableData.forEach(function (row) {
+                    if (!row || !row.locNo) {
+                        return;
+                    }
+                    if (selectedOnPage[row.locNo]) {
+                        vm.selectedLocMap[row.locNo] = row;
+                        if (vm.selectedLocOrder.indexOf(row.locNo) === -1) {
+                            vm.selectedLocOrder.push(row.locNo);
+                        }
+                    } else if (vm.selectedLocMap[row.locNo]) {
+                        vm.$delete(vm.selectedLocMap, row.locNo);
+                    }
+                });
+
+                vm.selectedLocOrder = vm.selectedLocOrder.filter(function (locNo) {
+                    return !!vm.selectedLocMap[locNo];
+                });
+            },
+            restoreTableSelection: function () {
+                var vm = this;
+                if (!vm.$refs.dataTable) {
+                    return;
+                }
+                vm.restoringSelection = true;
+                vm.$refs.dataTable.clearSelection();
+                vm.tableData.forEach(function (row) {
+                    if (row && row.locNo && vm.selectedLocMap[row.locNo]) {
+                        vm.$refs.dataTable.toggleRowSelection(row, true);
+                    }
+                });
+                vm.restoringSelection = false;
+            },
+            clearSelectedLocs: function () {
+                this.selectedLocMap = {};
+                this.selectedLocOrder = [];
+                if (this.$refs.dataTable) {
+                    this.$refs.dataTable.clearSelection();
+                }
+            },
+            removeSelectedLoc: function (locNo) {
+                if (!locNo || !this.selectedLocMap[locNo]) {
+                    return;
+                }
+                this.$delete(this.selectedLocMap, locNo);
+                this.selectedLocOrder = this.selectedLocOrder.filter(function (item) {
+                    return item !== locNo;
+                });
+                this.restoreTableSelection();
+            },
+            submitBatchOutTask: function () {
+                var vm = this;
+                var batchNo;
+                var taskList;
+                var locNos = vm.selectedLocNos;
+
+                if (!vm.form.staNo) {
+                    vm.$message.warning("璇烽�夋嫨鐩爣绔欑偣");
+                    return;
+                }
+                if (!locNos.length) {
+                    vm.$message.warning("璇峰厛鍕鹃�夎嚦灏戜竴涓簱浣�");
+                    return;
+                }
+
+                batchNo = createBatchNo();
+                taskList = locNos.map(function (locNo, index) {
+                    return {
+                        locNo: locNo,
+                        staNo: Number(vm.form.staNo),
+                        taskPri: vm.form.taskPri,
+                        batch: batchNo,
+                        batchSeq: index + 1
+                    };
+                });
+
+                vm.submitting = true;
+                $.ajax({
+                    url: baseUrl + "/openapi/createOutTaskBatch",
+                    contentType: "application/json",
+                    headers: { token: localStorage.getItem("token") },
+                    data: JSON.stringify({ taskList: taskList }),
+                    method: "POST",
+                    success: function (res) {
+                        if (res.code === 200) {
+                            vm.$message.success("宸茬敓鎴�" + locNos.length + "鏉″嚭搴撲换鍔�");
+                            vm.clearSelectedLocs();
+                            vm.loadTable();
+                            return;
+                        }
+                        if (res.code === 403) {
+                            top.location.href = baseUrl + "/";
+                            return;
+                        }
+                        vm.$message.error(res.msg || "鎵归噺鐢熸垚鍑哄簱浠诲姟澶辫触");
+                    },
+                    error: function (xhr) {
+                        var message = "鎵归噺鐢熸垚鍑哄簱浠诲姟澶辫触";
+                        if (xhr && xhr.responseJSON && xhr.responseJSON.msg) {
+                            message = xhr.responseJSON.msg;
+                        }
+                        vm.$message.error(message);
+                    },
+                    complete: function () {
+                        vm.submitting = false;
+                    }
+                });
+            }
+        }
+    });
+})();
diff --git a/src/main/webapp/static/js/wrkMast/wrkMast.js b/src/main/webapp/static/js/wrkMast/wrkMast.js
index bb926fb..adfe52f 100644
--- a/src/main/webapp/static/js/wrkMast/wrkMast.js
+++ b/src/main/webapp/static/js/wrkMast/wrkMast.js
@@ -235,6 +235,9 @@
                 saveVisibleColumns(this.visibleColumnKeys);
                 this.scheduleTableLayout();
             },
+            openBatchOutPage: function () {
+                window.open(baseUrl + "/views/wrkBatchOut/wrkBatchOut.html", "_blank");
+            },
             handleRowCommand: function (command, row) {
                 if (command === "complete") {
                     this.completeTask(row);
diff --git a/src/main/webapp/views/wrkBatchOut/wrkBatchOut.html b/src/main/webapp/views/wrkBatchOut/wrkBatchOut.html
new file mode 100644
index 0000000..71e23f6
--- /dev/null
+++ b/src/main/webapp/views/wrkBatchOut/wrkBatchOut.html
@@ -0,0 +1,488 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <title>鎵归噺鍑哄簱浠诲姟</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="../../static/vue/element/element.css">
+    <link rel="stylesheet" href="../../static/css/cool.css">
+    <style>
+        :root {
+            --card-bg: rgba(255, 255, 255, 0.94);
+            --card-border: rgba(216, 226, 238, 0.95);
+            --text-main: #243447;
+        }
+
+        [v-cloak] {
+            display: none;
+        }
+
+        html,
+        body {
+            margin: 0;
+            min-height: 100%;
+            color: var(--text-main);
+            font-family: "Avenir Next", "PingFang SC", "Microsoft YaHei", sans-serif;
+            background:
+                radial-gradient(1000px 420px at 0% -10%, rgba(44, 107, 193, 0.12), transparent 56%),
+                radial-gradient(900px 400px at 100% 0%, rgba(28, 150, 126, 0.10), transparent 58%),
+                linear-gradient(180deg, #f2f6fb 0%, #f8fafc 100%);
+        }
+
+        .page-shell {
+            max-width: 1700px;
+            margin: 0 auto;
+            padding: 14px;
+            box-sizing: border-box;
+        }
+
+        .card-shell {
+            position: relative;
+            border-radius: 24px;
+            border: 1px solid var(--card-border);
+            background:
+                radial-gradient(760px 220px at -8% 0%, rgba(43, 117, 196, 0.05), transparent 55%),
+                radial-gradient(680px 200px at 108% 10%, rgba(24, 150, 129, 0.05), transparent 58%),
+                var(--card-bg);
+            box-shadow: 0 16px 32px rgba(44, 67, 96, 0.08);
+            overflow: hidden;
+        }
+
+        .card-body {
+            position: relative;
+            z-index: 1;
+        }
+
+        .list-toolbar {
+            padding: 12px 16px 10px;
+            border-bottom: 1px solid rgba(222, 230, 239, 0.92);
+        }
+
+        .toolbar-main {
+            display: flex;
+            align-items: flex-start;
+            justify-content: space-between;
+            gap: 8px;
+            flex-wrap: wrap;
+        }
+
+        .toolbar-left {
+            flex: 1 1 960px;
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            flex-wrap: wrap;
+        }
+
+        .toolbar-search {
+            flex: 1 1 auto;
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            flex-wrap: wrap;
+        }
+
+        .toolbar-search-item {
+            flex: 0 0 152px;
+            min-width: 152px;
+        }
+
+        .toolbar-search-item.keyword {
+            flex: 0 0 220px;
+            min-width: 220px;
+        }
+
+        .toolbar-search-item.station {
+            flex: 0 0 230px;
+            min-width: 230px;
+        }
+
+        .toolbar-search-item.priority {
+            flex: 0 0 150px;
+            min-width: 150px;
+        }
+
+        .toolbar-query-actions,
+        .toolbar-ops {
+            display: flex;
+            gap: 8px;
+            flex-wrap: wrap;
+        }
+
+        .toolbar-ops {
+            justify-content: flex-end;
+            align-items: center;
+        }
+
+        .list-toolbar .el-input__inner,
+        .advanced-panel .el-input__inner,
+        .list-toolbar .el-input-number,
+        .list-toolbar .el-input-number .el-input__inner,
+        .advanced-panel .el-input-number .el-input__inner {
+            height: 32px;
+            line-height: 32px;
+        }
+
+        .list-toolbar .el-input__icon,
+        .advanced-panel .el-input__icon {
+            line-height: 32px;
+        }
+
+        .list-toolbar .el-button,
+        .advanced-panel .el-button {
+            padding: 8px 12px;
+            border-radius: 8px;
+        }
+
+        .advanced-panel {
+            padding: 10px 16px 12px;
+            border-bottom: 1px solid rgba(222, 230, 239, 0.92);
+            background: rgba(248, 251, 254, 0.78);
+        }
+
+        .advanced-grid {
+            display: grid;
+            grid-template-columns: repeat(5, minmax(0, 1fr));
+            gap: 8px;
+        }
+
+        .advanced-item {
+            min-width: 0;
+        }
+
+        .selection-panel {
+            padding: 12px 16px;
+            border-bottom: 1px solid rgba(222, 230, 239, 0.92);
+            background: rgba(248, 251, 254, 0.78);
+        }
+
+        .selection-head {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 12px;
+            flex-wrap: wrap;
+            margin-bottom: 10px;
+        }
+
+        .selection-title {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            color: #53677d;
+            font-size: 13px;
+            font-weight: 700;
+        }
+
+        .selection-tags {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+        }
+
+        .selection-empty {
+            color: #8a97a8;
+            font-size: 13px;
+            line-height: 20px;
+        }
+
+        .selection-count {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+            min-width: 24px;
+            padding: 0 8px;
+            height: 24px;
+            border-radius: 12px;
+            background: rgba(64, 158, 255, 0.12);
+            color: #2b6cb0;
+            font-size: 12px;
+            font-weight: 700;
+        }
+
+        .table-wrap {
+            padding: 10px 16px;
+        }
+
+        .table-shell {
+            border-radius: 20px;
+            overflow: hidden;
+            border: 1px solid rgba(217, 227, 238, 0.98);
+            background: rgba(255, 255, 255, 0.95);
+        }
+
+        .table-shell .el-table {
+            border-radius: 20px;
+            overflow: hidden;
+        }
+
+        .table-shell .el-table th {
+            background: #f7fafc;
+            color: #53677d;
+            font-weight: 700;
+        }
+
+        .payload-cell {
+            display: inline-block;
+            max-width: 280px;
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+        }
+
+        .pager-bar {
+            padding: 0 16px 16px;
+            display: flex;
+            align-items: center;
+            justify-content: flex-end;
+        }
+
+        @media (max-width: 1480px) {
+            .advanced-grid {
+                grid-template-columns: repeat(4, minmax(0, 1fr));
+            }
+        }
+
+        @media (max-width: 1180px) {
+            .advanced-grid {
+                grid-template-columns: repeat(3, minmax(0, 1fr));
+            }
+        }
+
+        @media (max-width: 900px) {
+            .page-shell {
+                padding: 10px;
+            }
+
+            .list-toolbar,
+            .advanced-panel,
+            .selection-panel,
+            .table-wrap,
+            .pager-bar {
+                padding-left: 12px;
+                padding-right: 12px;
+            }
+
+            .toolbar-search-item,
+            .toolbar-search-item.keyword,
+            .toolbar-search-item.station,
+            .toolbar-search-item.priority {
+                flex: 1 1 100%;
+                min-width: 100%;
+            }
+
+            .advanced-grid {
+                grid-template-columns: repeat(2, minmax(0, 1fr));
+            }
+        }
+
+        @media (max-width: 560px) {
+            .advanced-grid {
+                grid-template-columns: 1fr;
+            }
+        }
+    </style>
+</head>
+<body>
+<div id="app" class="page-shell" v-cloak>
+    <section class="card-shell">
+        <div class="card-body">
+            <div class="list-toolbar">
+                <div class="toolbar-main">
+                    <div class="toolbar-left">
+                        <div class="toolbar-search">
+                            <div class="toolbar-search-item keyword">
+                                <el-input
+                                    v-model.trim="searchForm.loc_no"
+                                    clearable
+                                    size="small"
+                                    placeholder="搴撲綅鍙�"
+                                    @keyup.enter.native="handleSearch">
+                                </el-input>
+                            </div>
+                            <div class="toolbar-search-item">
+                                <el-input
+                                    v-model.trim="searchForm.barcode"
+                                    clearable
+                                    size="small"
+                                    placeholder="鎵樼洏鐮�"
+                                    @keyup.enter.native="handleSearch">
+                                </el-input>
+                            </div>
+                            <div class="toolbar-search-item">
+                                <el-input
+                                    v-model.trim="searchForm.row1"
+                                    clearable
+                                    size="small"
+                                    placeholder="鎺�"
+                                    @keyup.enter.native="handleSearch">
+                                </el-input>
+                            </div>
+                            <div class="toolbar-search-item">
+                                <el-input
+                                    v-model.trim="searchForm.bay1"
+                                    clearable
+                                    size="small"
+                                    placeholder="鍒�"
+                                    @keyup.enter.native="handleSearch">
+                                </el-input>
+                            </div>
+                            <div class="toolbar-search-item">
+                                <el-input
+                                    v-model.trim="searchForm.lev1"
+                                    clearable
+                                    size="small"
+                                    placeholder="灞�"
+                                    @keyup.enter.native="handleSearch">
+                                </el-input>
+                            </div>
+                            <div class="toolbar-query-actions">
+                                <el-button size="small" type="primary" icon="el-icon-search" @click="handleSearch">鎼滅储</el-button>
+                                <el-button size="small" plain icon="el-icon-refresh-left" @click="handleReset">閲嶇疆</el-button>
+                                <el-button size="small" plain :icon="advancedVisible ? 'el-icon-arrow-up' : 'el-icon-arrow-down'" @click="toggleAdvanced">
+                                    {{ advancedVisible ? '鏀惰捣' : '绛涢��' }}
+                                </el-button>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="toolbar-ops">
+                        <div class="toolbar-search-item station">
+                            <el-select
+                                v-model="form.staNo"
+                                size="small"
+                                clearable
+                                filterable
+                                :loading="stationLoading"
+                                placeholder="鐩爣绔欑偣"
+                                style="width: 100%;">
+                                <el-option
+                                    v-for="station in outStationOptions"
+                                    :key="'station-' + station.stationId"
+                                    :label="formatStationOptionLabel(station)"
+                                    :value="station.stationId">
+                                </el-option>
+                            </el-select>
+                        </div>
+                        <div class="toolbar-search-item priority">
+                            <el-input-number
+                                v-model="form.taskPri"
+                                size="small"
+                                :min="1"
+                                :max="9999"
+                                controls-position="right"
+                                style="width: 100%;">
+                            </el-input-number>
+                        </div>
+                        <el-button size="small" plain icon="el-icon-refresh" :loading="loading" @click="loadTable">鍒锋柊搴撲綅</el-button>
+                        <el-button size="small" plain :disabled="selectedCount === 0" @click="clearSelectedLocs">娓呯┖宸查��</el-button>
+                        <el-button size="small" type="primary" icon="el-icon-s-promotion" :loading="submitting" @click="submitBatchOutTask">鐢熸垚鍑哄簱浠诲姟</el-button>
+                    </div>
+                </div>
+            </div>
+
+            <div v-show="advancedVisible" class="advanced-panel">
+                <div class="advanced-grid">
+                    <div class="advanced-item">
+                        <el-input
+                            v-model.trim="searchForm.loc_type"
+                            clearable
+                            size="small"
+                            placeholder="搴撲綅绫诲瀷"
+                            @keyup.enter.native="handleSearch">
+                        </el-input>
+                    </div>
+                    <div class="advanced-item">
+                        <el-input
+                            v-model.trim="searchForm.qr_code_value"
+                            clearable
+                            size="small"
+                            placeholder="浜岀淮鐮�"
+                            @keyup.enter.native="handleSearch">
+                        </el-input>
+                    </div>
+                </div>
+            </div>
+
+            <div class="selection-panel">
+                    <div class="selection-head">
+                        <div class="selection-title">
+                            <span>宸查�夊簱浣�</span>
+                            <span class="selection-count">{{ selectedCount }}</span>
+                        </div>
+                    </div>
+                <div v-if="selectedPreviewLocNos.length" class="selection-tags">
+                    <el-tag
+                        v-for="locNo in selectedPreviewLocNos"
+                        :key="'selected-' + locNo"
+                        closable
+                        size="small"
+                        @close="removeSelectedLoc(locNo)">
+                        {{ locNo }}
+                    </el-tag>
+                    <el-tag v-if="selectedPreviewOverflow > 0" size="small" type="info">
+                        杩樻湁 {{ selectedPreviewOverflow }} 涓�
+                    </el-tag>
+                </div>
+                <div v-else class="selection-empty">
+                    鏆傛棤宸查�夊簱浣�
+                </div>
+            </div>
+
+            <div class="table-wrap">
+                <div class="table-shell">
+                    <el-table
+                        ref="dataTable"
+                        v-loading="loading"
+                        :data="tableData"
+                        border
+                        stripe
+                        row-key="locNo"
+                        :height="tableHeight"
+                        @selection-change="handleSelectionChange">
+                        <el-table-column type="selection" width="52" align="center" :reserve-selection="true"></el-table-column>
+                        <el-table-column prop="locNo" label="搴撲綅鍙�" min-width="150" show-overflow-tooltip></el-table-column>
+                        <el-table-column prop="barcode" label="鎵樼洏鐮�" min-width="180" show-overflow-tooltip></el-table-column>
+                        <el-table-column label="搴撲綅鐘舵��" width="110" align="center">
+                            <template slot-scope="scope">
+                                <el-tag size="mini" type="success">{{ scope.row['locSts$'] || scope.row.locSts || '-' }}</el-tag>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="row1" label="鎺�" width="90" align="center"></el-table-column>
+                        <el-table-column prop="bay1" label="鍒�" width="90" align="center"></el-table-column>
+                        <el-table-column prop="lev1" label="灞�" width="90" align="center"></el-table-column>
+                        <el-table-column prop="locType" label="搴撲綅绫诲瀷" min-width="140" show-overflow-tooltip></el-table-column>
+                        <el-table-column prop="qrCodeValue" label="浜岀淮鐮�" min-width="180" show-overflow-tooltip></el-table-column>
+                        <el-table-column label="寮傚父璇存槑" min-width="220" show-overflow-tooltip>
+                            <template slot-scope="scope">
+                                <span class="payload-cell">{{ scope.row.errorMemo || '-' }}</span>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+                </div>
+            </div>
+
+            <div class="pager-bar">
+                <el-pagination
+                    small
+                    background
+                    layout="total, sizes, prev, pager, next, jumper"
+                    :current-page="page.curr"
+                    :page-size="page.limit"
+                    :page-sizes="[30, 50, 100, 200, 500]"
+                    :total="page.total"
+                    @current-change="handleCurrentChange"
+                    @size-change="handleSizeChange">
+                </el-pagination>
+            </div>
+        </div>
+    </section>
+</div>
+</body>
+<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
+<script type="text/javascript" src="../../static/js/common.js?v=20260309_i18n_fix1"></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/wrkBatchOut/wrkBatchOut.js?v=20260321_batch_outbound_table_v5"></script>
+</html>
diff --git a/src/main/webapp/views/wrkMast/wrkMast.html b/src/main/webapp/views/wrkMast/wrkMast.html
index 3877200..c9dc25c 100644
--- a/src/main/webapp/views/wrkMast/wrkMast.html
+++ b/src/main/webapp/views/wrkMast/wrkMast.html
@@ -319,6 +319,7 @@
                         </div>
                         <el-button slot="reference" size="small" plain icon="el-icon-setting">鍒楄缃�</el-button>
                     </el-popover>
+                    <el-button size="small" type="primary" plain icon="el-icon-s-promotion" @click="openBatchOutPage">鎵归噺鍑哄簱</el-button>
                     <el-button size="small" plain icon="el-icon-refresh" :loading="loading" @click="loadList">鍒锋柊</el-button>
                 </div>
             </div>
@@ -420,5 +421,5 @@
 <script type="text/javascript" src="../../static/js/common.js?v=20260309_i18n_fix1"></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/wrkMast/wrkMast.js?v=20260311_wrk_mast_vue"></script>
+<script type="text/javascript" src="../../static/js/wrkMast/wrkMast.js?v=20260321_batch_outbound_page"></script>
 </html>

--
Gitblit v1.9.1