#
Junjie
4 天以前 494d383f5d48d8986ec2890fb37eb3973ac487fe
#
3个文件已添加
2个文件已修改
937 ■■■■■ 已修改文件
src/main/resources/sql/20260321_add_wrk_batch_out_menu.sql 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/wrkBatchOut/wrkBatchOut.js 371 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/wrkMast/wrkMast.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/wrkBatchOut/wrkBatchOut.html 488 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/wrkMast/wrkMast.html 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/sql/20260321_add_wrk_batch_out_menu.sql
New file
@@ -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;
src/main/webapp/static/js/wrkBatchOut/wrkBatchOut.js
New file
@@ -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;
                    }
                });
            }
        }
    });
})();
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);
src/main/webapp/views/wrkBatchOut/wrkBatchOut.html
New file
@@ -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>
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>