|
|
@@ -0,0 +1,1798 @@
|
|
|
+<!-- 领料管理 - 智能仓储风格 -->
|
|
|
+<template>
|
|
|
+ <div class="stock-requisition-page">
|
|
|
+ <!-- 背景层 -->
|
|
|
+ <div class="bg-layer" :style="{ backgroundImage: `url(${bgImg})` }" />
|
|
|
+
|
|
|
+ <!-- 顶部标题区域 -->
|
|
|
+ <div class="header-section">
|
|
|
+ <button class="logout-btn" @click="goHome">
|
|
|
+ <i class="fas fa-home" />
|
|
|
+ <span>主页</span>
|
|
|
+ </button>
|
|
|
+ <h1 class="page-title">领料</h1>
|
|
|
+ <!-- 右侧操作按钮 -->
|
|
|
+ <div class="header-actions">
|
|
|
+ <button class="action-btn regular-btn" @click="openRegularRequisition">
|
|
|
+ <i class="fas fa-star" />
|
|
|
+ <span>常用领料</span>
|
|
|
+ </button>
|
|
|
+ <button class="action-btn cart-btn" @click="openStockOutCar">
|
|
|
+ <van-badge :content="count" :show-zero="true">
|
|
|
+ <i class="fas fa-shopping-cart" />
|
|
|
+ </van-badge>
|
|
|
+ <span>领料车</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 主内容区域 -->
|
|
|
+ <main class="main-content">
|
|
|
+ <!-- 筛选区域 -->
|
|
|
+ <div class="filter-section">
|
|
|
+ <div class="filter-row">
|
|
|
+ <div class="filter-item">
|
|
|
+ <label class="filter-label">类型</label>
|
|
|
+ <v-select
|
|
|
+ v-model="inventoryType" :options="inventoryTypeList" :reduce="item => item.value" label="label"
|
|
|
+ placeholder="请输入类型" :clearable="true" :filterable="true" class="filter-select dark-select"
|
|
|
+ @update:model-value="getDatas"
|
|
|
+ >
|
|
|
+ <template #no-options>
|
|
|
+ <span>无该选项数据</span>
|
|
|
+ </template>
|
|
|
+ </v-select>
|
|
|
+ </div>
|
|
|
+ <div class="filter-item">
|
|
|
+ <label class="filter-label">名称</label>
|
|
|
+ <input
|
|
|
+ v-model="inventoryName" type="text" placeholder="请输入名称" class="filter-input dark-input"
|
|
|
+ @keyup.enter="getDatas"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <!-- <div class="filter-item">
|
|
|
+ <label class="filter-label">编号</label>
|
|
|
+ <input
|
|
|
+ v-model="inventoryNo"
|
|
|
+ type="text"
|
|
|
+ placeholder="请输入编号"
|
|
|
+ class="filter-input dark-input"
|
|
|
+ @keyup.enter="getDatas"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="filter-item">
|
|
|
+ <label class="filter-label">仓库</label>
|
|
|
+ <v-select
|
|
|
+ v-model="warehouseId"
|
|
|
+ :options="warehouseList"
|
|
|
+ :reduce="item => item.id"
|
|
|
+ label="name"
|
|
|
+ placeholder="选择仓库"
|
|
|
+ :clearable="true"
|
|
|
+ :filterable="true"
|
|
|
+ class="filter-select dark-select"
|
|
|
+ @update:model-value="getDatas"
|
|
|
+ >
|
|
|
+ <template #no-options>
|
|
|
+ <span>无该选项数据</span>
|
|
|
+ </template>
|
|
|
+ </v-select>
|
|
|
+ </div> -->
|
|
|
+ <div class="filter-buttons">
|
|
|
+ <button class="search-btn" @click="getDatas">
|
|
|
+ 搜索
|
|
|
+ </button>
|
|
|
+ <button class="reset-btn" @click="handleReset">
|
|
|
+ 重置
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 全选控制栏 -->
|
|
|
+ <div v-if="stockRequisitionList.length > 0" class="select-all-bar">
|
|
|
+ <van-checkbox :model-value="isAllSelected" @update:model-value="toggleSelectAll">
|
|
|
+ <span class="select-all-text">全选当前页(已选 {{ selectedIds.length }} 项)</span>
|
|
|
+ </van-checkbox>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 卡片网格区域 -->
|
|
|
+ <div ref="cardGridWrapper" class="card-grid-wrapper" @scroll="handleScroll">
|
|
|
+ <!-- 空状态 -->
|
|
|
+ <div v-if="stockRequisitionList.length === 0 && !loading" class="empty-state">
|
|
|
+ <i class="fas fa-inbox empty-icon" />
|
|
|
+ <p>暂无数据</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 卡片网格 -->
|
|
|
+ <div v-else class="card-grid">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in stockRequisitionList" :key="item.id || index" class="inventory-card"
|
|
|
+ :class="{ 'selected': selectedIds.includes(item.id) }" @click="toggleSelect(item.id)"
|
|
|
+ >
|
|
|
+ <!-- 卡片序号 -->
|
|
|
+ <div class="card-index">{{ index + 1 }}</div>
|
|
|
+
|
|
|
+ <!-- 图片区域 -->
|
|
|
+ <div class="card-image">
|
|
|
+ <img v-if="item.imageUrl" :src="item.imageUrl" alt="设备图片" />
|
|
|
+ <div v-else class="image-placeholder">
|
|
|
+ <i :class="getInventoryIcon(item.inventoryType)" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 信息区域 -->
|
|
|
+ <div class="card-info">
|
|
|
+ <div class="info-row">
|
|
|
+ <span class="info-label">名称:</span>
|
|
|
+ <span class="info-value">{{ item.inventoryName || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-row">
|
|
|
+ <span class="info-label">编号:</span>
|
|
|
+ <span class="info-value">{{ item.inventoryNo || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-row location-row">
|
|
|
+ <i class="fas fa-map-marker-alt location-icon" />
|
|
|
+ <span class="info-value">{{ item.inventoryActulPosition || item.inventoryPosition || '-' }} / {{
|
|
|
+ item.inventoryWarehouse || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <!-- 配送类型徽章 -->
|
|
|
+ <div class="info-row delivery-type-row">
|
|
|
+ <span class="delivery-type-badge" :class="getDeliveryTypeBadgeClass(item.deliveryType)">
|
|
|
+ {{ item.deliveryType || '未指定' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 配送选择区域(固定高度,始终存在) -->
|
|
|
+ <div class="card-delivery-section" @click.stop>
|
|
|
+ <!-- 选中时显示配送选择 -->
|
|
|
+ <template v-if="selectedIds.includes(item.id)">
|
|
|
+ <div class="delivery-row">
|
|
|
+ <span class="delivery-label">配送:</span>
|
|
|
+ <div class="delivery-method-buttons">
|
|
|
+ <button
|
|
|
+ :class="['delivery-btn', { 'active': item.deliveryMethod === 'AGV_Delivery', 'disabled': item.deliveryType === '人工配送' }]"
|
|
|
+ :disabled="item.deliveryType === '人工配送'" @click="changeDeliveryMethod(item, 'AGV_Delivery')"
|
|
|
+ >
|
|
|
+ <i class="fas fa-robot" /> AGV
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ :class="['delivery-btn', { 'active': item.deliveryMethod === 'Manual_Delivery', 'disabled': item.deliveryType === '强制AGV配送' }]"
|
|
|
+ :disabled="item.deliveryType === '强制AGV配送'"
|
|
|
+ @click="changeDeliveryMethod(item, 'Manual_Delivery')"
|
|
|
+ >
|
|
|
+ <i class="fas fa-user" /> 人工
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- AGV配送位置选择 -->
|
|
|
+ <div class="delivery-location-row">
|
|
|
+ <span class="delivery-label">位置:</span>
|
|
|
+ <v-select
|
|
|
+ v-if="item.deliveryMethod === 'AGV_Delivery'" v-model="item.selectedLocation"
|
|
|
+ :options="locator" :clearable="false" class="location-select dark-select" :append-to-body="true"
|
|
|
+ :calculate-position="withPopper"
|
|
|
+ />
|
|
|
+ <span v-else style="font-size: 16px;">人工配送</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <!-- 未选中时显示占位 -->
|
|
|
+ <template v-else>
|
|
|
+ <div class="delivery-placeholder">
|
|
|
+ <span>点击选择后设置配送方式</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 选中状态指示 -->
|
|
|
+ <div v-if="selectedIds.includes(item.id)" class="selected-indicator">
|
|
|
+ <i class="fas fa-check" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 拣货完成确认弹窗 - 科技感风格 -->
|
|
|
+ <div v-if="completeModalVisible" class="tech-modal-overlay" @click.self="handleCompleteCancel">
|
|
|
+ <div class="tech-modal success-modal">
|
|
|
+ <div class="modal-content-row">
|
|
|
+ <div class="modal-text">
|
|
|
+ <h3>领料完成</h3>
|
|
|
+ <p>领料申请已完成,配送任务已创建!<br />请确认是否返回主页?</p>
|
|
|
+ </div>
|
|
|
+ <div class="modal-icon">
|
|
|
+ <div class="icon-box success-box">
|
|
|
+ <i class="fas fa-truck-loading" />
|
|
|
+ <i class="fas fa-check check-icon" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="modal-footer">
|
|
|
+ <button class="modal-btn cancel-btn" @click="handleCompleteCancel">取消</button>
|
|
|
+ <button class="modal-btn confirm-btn" @click="handleCompleteConfirm">确认</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 加载更多提示 -->
|
|
|
+ <div v-if="loadingMore" class="loading-more">
|
|
|
+ <div class="loading-more-spinner" />
|
|
|
+ <span>加载中...</span>
|
|
|
+ </div>
|
|
|
+ <div v-else-if="noMoreData && stockRequisitionList.length > 0" class="no-more-data">
|
|
|
+ <span>没有更多数据了</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </main>
|
|
|
+
|
|
|
+ <!-- 底部操作按钮 -->
|
|
|
+ <div class="bottom-actions">
|
|
|
+ <button class="submit-btn" :disabled="selectedIds.length === 0" @click="submitStock">
|
|
|
+ 加入领料车
|
|
|
+ </button>
|
|
|
+ <button class="pick-btn" :disabled="selectedIds.length === 0" @click="handleComplete">
|
|
|
+ 领料 ({{ selectedIds.length }})
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ <!-- Loading -->
|
|
|
+ <div v-if="loading" class="loading-overlay">
|
|
|
+ <div class="loading-dots">
|
|
|
+ <div class="dot" />
|
|
|
+ <div class="dot" />
|
|
|
+ <div class="dot" />
|
|
|
+ </div>
|
|
|
+ <span class="loading-text">加载中...</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, onMounted, nextTick, computed } from 'vue';
|
|
|
+import { useRouter } from 'vue-router';
|
|
|
+import { showNotify } from 'vant';
|
|
|
+import vSelect from 'vue-select';
|
|
|
+import 'vue-select/dist/vue-select.css';
|
|
|
+
|
|
|
+// 图片资源
|
|
|
+import bgImg from '../assets/images/bj.png';
|
|
|
+
|
|
|
+import { getWarehouseList, queryFeedAreaStatus } from '../api/stock.js';
|
|
|
+import { findInventory, createStockOutPrepareLine, queryPickingCarNumber, generatePickPaper } from '../api/stockOut.js';
|
|
|
+import { gateController } from '../hardware/GateOperate.js';
|
|
|
+
|
|
|
+const router = useRouter();
|
|
|
+
|
|
|
+// 滚动容器ref
|
|
|
+const cardGridWrapper = ref(null);
|
|
|
+
|
|
|
+const warehouseId = ref(undefined);
|
|
|
+const inventoryName = ref('');
|
|
|
+const inventoryNo = ref('');
|
|
|
+const inventoryType = ref(undefined);
|
|
|
+const count = ref(0);
|
|
|
+const warehouseList = ref([]);
|
|
|
+const stockRequisitionList = ref([]);
|
|
|
+const selectedIds = ref([]);
|
|
|
+
|
|
|
+const loading = ref(false);
|
|
|
+const loadingMore = ref(false);
|
|
|
+const noMoreData = ref(false);
|
|
|
+
|
|
|
+const selectedRows = ref([]);
|
|
|
+
|
|
|
+const inventoryTypeList = ref([
|
|
|
+ { value: 'Clamp', label: '工装' },
|
|
|
+ { value: 'Instrument', label: '设备' },
|
|
|
+ // { value: 'FinishProduct', label: '成品' },
|
|
|
+]);
|
|
|
+
|
|
|
+// 无限滚动参数
|
|
|
+const pageSize = 20;
|
|
|
+const currentStart = ref(0);
|
|
|
+const total = ref(0);
|
|
|
+
|
|
|
+// 可配送位置
|
|
|
+const locator = ref([]);
|
|
|
+
|
|
|
+// 根据 deliveryType 初始化配送方式
|
|
|
+const initDeliveryMethod = item => {
|
|
|
+ if (item.deliveryType === '人工配送') {
|
|
|
+ // 人工配送:只能人工,不可切换
|
|
|
+ item.deliveryMethod = 'Manual_Delivery';
|
|
|
+ item.selectedLocation = '';
|
|
|
+ } else if (item.deliveryType === '强制AGV配送') {
|
|
|
+ // 强制AGV配送:只能AGV,不可切换为人工
|
|
|
+ item.deliveryMethod = 'AGV_Delivery';
|
|
|
+ if (!item.selectedLocation) {
|
|
|
+ item.selectedLocation = '';
|
|
|
+ }
|
|
|
+ } else if (item.deliveryType === '可选AGV配送') {
|
|
|
+ // 可选AGV配送:默认人工,可切换为AGV
|
|
|
+ item.deliveryMethod = item.deliveryMethod || 'Manual_Delivery';
|
|
|
+ item.selectedLocation = item.selectedLocation || '';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 切换选择
|
|
|
+const toggleSelect = id => {
|
|
|
+ const index = selectedIds.value.indexOf(id);
|
|
|
+ if (index > -1) {
|
|
|
+ selectedIds.value.splice(index, 1);
|
|
|
+ // 同时移除 selectedRows 中对应的项
|
|
|
+ selectedRows.value = selectedRows.value.filter(item => item.id !== id);
|
|
|
+ } else {
|
|
|
+ selectedIds.value.push(id);
|
|
|
+ // 添加到 selectedRows 并初始化配送字段
|
|
|
+ const item = stockRequisitionList.value.find(item => item.id === id);
|
|
|
+ if (item) {
|
|
|
+ // 初始化配送字段
|
|
|
+ initDeliveryMethod(item);
|
|
|
+ selectedRows.value.push(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 打开常用领料
|
|
|
+const openRegularRequisition = () => {
|
|
|
+ router.push('/regular-requisition');
|
|
|
+};
|
|
|
+
|
|
|
+// 打开领料车
|
|
|
+const openStockOutCar = () => {
|
|
|
+ router.push('/stock-picking-car?isRegular=false');
|
|
|
+};
|
|
|
+
|
|
|
+// 计算是否全选
|
|
|
+const isAllSelected = computed(() => {
|
|
|
+ if (stockRequisitionList.value.length === 0) return false;
|
|
|
+ return stockRequisitionList.value.every(item => selectedIds.value.includes(item.id));
|
|
|
+});
|
|
|
+
|
|
|
+
|
|
|
+// 全选/取消全选
|
|
|
+const toggleSelectAll = checked => {
|
|
|
+ if (checked) {
|
|
|
+ // 全选当前列表所有项
|
|
|
+ stockRequisitionList.value.forEach(item => {
|
|
|
+ if (!selectedIds.value.includes(item.id)) {
|
|
|
+ selectedIds.value.push(item.id);
|
|
|
+ initDeliveryMethod(item);
|
|
|
+ selectedRows.value.push(item);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 取消全选当前列表
|
|
|
+ const currentListIds = stockRequisitionList.value.map(item => item.id);
|
|
|
+ selectedIds.value = selectedIds.value.filter(id => !currentListIds.includes(id));
|
|
|
+ selectedRows.value = selectedRows.value.filter(row => !currentListIds.includes(row.id));
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 获取设备类型图标
|
|
|
+const getInventoryIcon = type => {
|
|
|
+ const iconMap = {
|
|
|
+ '工装': 'fas fa-cube',
|
|
|
+ '设备': 'fas fa-cogs',
|
|
|
+ '成品': 'fas fa-box',
|
|
|
+ };
|
|
|
+ return iconMap[type] || 'fas fa-cube';
|
|
|
+};
|
|
|
+
|
|
|
+// 获取配送类型徽章样式
|
|
|
+const getDeliveryTypeBadgeClass = deliveryType => {
|
|
|
+ const classMap = {
|
|
|
+ '人工配送': 'badge-manual',
|
|
|
+ '强制AGV配送': 'badge-agv-force',
|
|
|
+ '可选AGV配送': 'badge-agv-optional',
|
|
|
+ };
|
|
|
+ return classMap[deliveryType] || '';
|
|
|
+};
|
|
|
+
|
|
|
+// 滚动事件处理 - 无限滚动
|
|
|
+const handleScroll = e => {
|
|
|
+ const container = e.target;
|
|
|
+ const { scrollTop, scrollHeight, clientHeight } = container;
|
|
|
+
|
|
|
+ // 距离底部100px时触发加载
|
|
|
+ if (scrollHeight - scrollTop - clientHeight < 100) {
|
|
|
+ loadMore();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 检查容器是否需要继续加载(当内容不足以产生滚动条时自动加载更多)
|
|
|
+const checkAndLoadMore = async () => {
|
|
|
+ await nextTick();
|
|
|
+ const container = cardGridWrapper.value;
|
|
|
+ if (!container) return;
|
|
|
+
|
|
|
+ // 如果内容高度不足以产生滚动条,且还有更多数据,继续加载
|
|
|
+ if (container.scrollHeight <= container.clientHeight && !noMoreData.value && !loadingMore.value) {
|
|
|
+ loadMore();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 是否正在执行初次加载(防止重复请求)
|
|
|
+let isInitialLoading = false;
|
|
|
+
|
|
|
+// 完成弹窗控制
|
|
|
+const completeModalVisible = ref(false);
|
|
|
+
|
|
|
+// 加载更多数据
|
|
|
+const loadMore = async () => {
|
|
|
+ // 增加 isInitialLoading 检查,防止初次加载时触发 loadMore
|
|
|
+ if (loadingMore.value || noMoreData.value || loading.value || isInitialLoading) return;
|
|
|
+
|
|
|
+ loadingMore.value = true;
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ inventoryName: inventoryName.value,
|
|
|
+ inventoryNo: inventoryNo.value,
|
|
|
+ inventoryType: inventoryType.value,
|
|
|
+ warehouseId: warehouseId.value,
|
|
|
+ range: {
|
|
|
+ start: currentStart.value,
|
|
|
+ length: pageSize,
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await findInventory(params);
|
|
|
+ if (res.errorCode == 0) {
|
|
|
+ if (res.datas && res.datas.length > 0) {
|
|
|
+ stockRequisitionList.value = [...stockRequisitionList.value, ...res.datas];
|
|
|
+ currentStart.value += res.datas.length;
|
|
|
+ total.value = res.total;
|
|
|
+
|
|
|
+ // 检查是否还有更多数据
|
|
|
+ if (stockRequisitionList.value.length >= res.total) {
|
|
|
+ noMoreData.value = true;
|
|
|
+ } else {
|
|
|
+ // 加载完成后检查是否需要继续加载
|
|
|
+ checkAndLoadMore();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ noMoreData.value = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载更多数据失败:', error);
|
|
|
+ } finally {
|
|
|
+ loadingMore.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 配送方式变更处理
|
|
|
+const changeDeliveryMethod = (item, method) => {
|
|
|
+ item.deliveryMethod = method;
|
|
|
+ // 当选择人工配送时,清空配送位置
|
|
|
+ if (method === 'Manual_Delivery') {
|
|
|
+ item.selectedLocation = '';
|
|
|
+ }
|
|
|
+ console.log('配送方式变更:', item.inventoryName, item.deliveryMethod);
|
|
|
+};
|
|
|
+
|
|
|
+// 获取送料区货位
|
|
|
+const queryFeedArea = async () => {
|
|
|
+ try {
|
|
|
+ const res = await queryFeedAreaStatus();
|
|
|
+
|
|
|
+ if (res.errorCode === 0) {
|
|
|
+ if (res.datas && res.datas.length > 0) {
|
|
|
+ locator.value = res.datas.map(item => {
|
|
|
+ return {
|
|
|
+ label: item.positionName,
|
|
|
+ value: item.positionNo,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ locator.value = [];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ showNotify({ type: 'danger', message: res.errorMessage });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取送料区货位API调用失败:', error);
|
|
|
+ showNotify({ type: 'danger', message: '获取送料区货位API调用失败' });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 初次加载物料数据
|
|
|
+const getStockRequisitionList = async () => {
|
|
|
+ // 防止重复调用
|
|
|
+ if (isInitialLoading) return;
|
|
|
+ isInitialLoading = true;
|
|
|
+
|
|
|
+ loading.value = true;
|
|
|
+ currentStart.value = 0;
|
|
|
+ noMoreData.value = false;
|
|
|
+ // 立即清空列表,防止数据重复
|
|
|
+ stockRequisitionList.value = [];
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ inventoryName: inventoryName.value,
|
|
|
+ inventoryNo: inventoryNo.value,
|
|
|
+ inventoryType: inventoryType.value,
|
|
|
+ warehouseId: warehouseId.value,
|
|
|
+ range: {
|
|
|
+ start: 0,
|
|
|
+ length: pageSize,
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await findInventory(params);
|
|
|
+ if (res.errorCode == 0) {
|
|
|
+ if (res.datas && res.datas.length > 0) {
|
|
|
+ stockRequisitionList.value = res.datas;
|
|
|
+ currentStart.value = res.datas.length;
|
|
|
+ total.value = res.total;
|
|
|
+
|
|
|
+ // 保留已选中物料的配送方式设置
|
|
|
+ stockRequisitionList.value = stockRequisitionList.value.map(item => {
|
|
|
+ // 查找该物料是否在已选中列表中
|
|
|
+ const selectedItem = selectedRows.value.find(selected => selected.id === item.id);
|
|
|
+
|
|
|
+ if (selectedItem) {
|
|
|
+ // 如果已选中,保留其配送方式和位置设置,并更新 selectedRows 中的引用
|
|
|
+ const updatedItem = {
|
|
|
+ ...item,
|
|
|
+ deliveryMethod: selectedItem.deliveryMethod,
|
|
|
+ selectedLocation: selectedItem.selectedLocation,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 更新 selectedRows 中的对应项,保持引用同步
|
|
|
+ const index = selectedRows.value.findIndex(row => row.id === item.id);
|
|
|
+ if (index !== -1) {
|
|
|
+ selectedRows.value[index] = updatedItem;
|
|
|
+ }
|
|
|
+
|
|
|
+ return updatedItem;
|
|
|
+ } else {
|
|
|
+ // 未选中,初始化为空
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ deliveryMethod: '',
|
|
|
+ selectedLocation: '',
|
|
|
+ };
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (stockRequisitionList.value.length >= res.total) {
|
|
|
+ noMoreData.value = true;
|
|
|
+ } else {
|
|
|
+ // 初次加载完成后检查是否需要继续加载
|
|
|
+ checkAndLoadMore();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ stockRequisitionList.value = [];
|
|
|
+ total.value = 0;
|
|
|
+ noMoreData.value = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取物料数据失败:', error);
|
|
|
+ showNotify({ type: 'danger', message: '获取库存数据失败' });
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ isInitialLoading = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 获取仓库列表
|
|
|
+const getWarehouses = async () => {
|
|
|
+ try {
|
|
|
+ const res = await getWarehouseList();
|
|
|
+ if (res.errorCode == 0) {
|
|
|
+ if (res.datas && res.datas.length > 0) {
|
|
|
+ warehouseList.value = res.datas;
|
|
|
+ } else {
|
|
|
+ warehouseList.value = [];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ showNotify({ type: 'danger', message: res.errorMessage });
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取仓库数据失败:', error);
|
|
|
+ showNotify({ type: 'danger', message: '获取仓库数据失败' });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 返回主页
|
|
|
+const goHome = () => {
|
|
|
+ router.push('/home');
|
|
|
+};
|
|
|
+
|
|
|
+// 重置筛选条件
|
|
|
+const handleReset = () => {
|
|
|
+ warehouseId.value = undefined;
|
|
|
+ inventoryName.value = '';
|
|
|
+ inventoryNo.value = '';
|
|
|
+ inventoryType.value = undefined;
|
|
|
+ getDatas();
|
|
|
+};
|
|
|
+
|
|
|
+// 查询(重新搜索时重置列表)
|
|
|
+const getDatas = async () => {
|
|
|
+ // 滚动到顶部
|
|
|
+ if (cardGridWrapper.value) {
|
|
|
+ cardGridWrapper.value.scrollTop = 0;
|
|
|
+ }
|
|
|
+ await getStockRequisitionList();
|
|
|
+};
|
|
|
+
|
|
|
+// 提交中状态(与列表加载分开管理)
|
|
|
+const submitting = ref(false);
|
|
|
+
|
|
|
+// 加入领料车
|
|
|
+const submitStock = async () => {
|
|
|
+ if (selectedIds.value.length == 0) {
|
|
|
+ showNotify({ type: 'danger', message: '请至少选择一个物料' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ submitting.value = true;
|
|
|
+ const params = {
|
|
|
+ inventoryIds: selectedIds.value,
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await createStockOutPrepareLine(params);
|
|
|
+ if (res.errorCode == 0) {
|
|
|
+ selectedIds.value = [];
|
|
|
+ showNotify({ type: 'success', message: '已添加到领料车' });
|
|
|
+ // 先等待提交完成,再刷新列表
|
|
|
+ await getDatas();
|
|
|
+ queryPickingCarCount();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('添加领料车失败:', error);
|
|
|
+ showNotify({ type: 'danger', message: '添加到领料车失败' });
|
|
|
+ } finally {
|
|
|
+ submitting.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 领料申请,直接验证并提交
|
|
|
+const handleComplete = async () => {
|
|
|
+ if (selectedIds.value.length === 0) {
|
|
|
+ showNotify({ type: 'danger', message: '请至少选择一个物料' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证选择了 AGV 配送的物料是否都选择了配送位置
|
|
|
+ const agvItems = selectedRows.value.filter(item => item.deliveryMethod === 'AGV_Delivery');
|
|
|
+ const hasEmptyLocation = agvItems.some(item => !item.selectedLocation);
|
|
|
+
|
|
|
+ if (hasEmptyLocation) {
|
|
|
+ showNotify({ type: 'danger', message: '请为所有 AGV 配送的物料选择配送位置' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const params = [];
|
|
|
+ selectedRows.value.forEach(item => {
|
|
|
+ params.push({
|
|
|
+ inventoryId: item.id,
|
|
|
+ deliveryMethod: item.deliveryMethod,
|
|
|
+ positionEndNo: item.selectedLocation.value,
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log('提交参数:', params);
|
|
|
+ const res = await generatePickPaper(params);
|
|
|
+ if (res.errorCode == 0) {
|
|
|
+ showNotify({ type: 'success', message: '领料申请成功' });
|
|
|
+ selectedIds.value = [];
|
|
|
+ selectedRows.value = [];
|
|
|
+ // 显示完成确认弹窗
|
|
|
+ gateController('SHOTOPEN');
|
|
|
+ router.push('/');
|
|
|
+ } else {
|
|
|
+ showNotify({ type: 'danger', message: res.errorMessage });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 处理完成确认
|
|
|
+const handleCompleteConfirm = () => {
|
|
|
+ // 调用开门操作
|
|
|
+ gateController('SHOTOPEN');
|
|
|
+ completeModalVisible.value = false;
|
|
|
+ router.push('/');
|
|
|
+ // showToast('领料申请已完成,配送任务已创建!');
|
|
|
+};
|
|
|
+
|
|
|
+// 处理完成取消
|
|
|
+const handleCompleteCancel = () => {
|
|
|
+ completeModalVisible.value = false;
|
|
|
+ // 留在当前页面,刷新列表
|
|
|
+ getDatas();
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 查询领料车中的数量
|
|
|
+ */
|
|
|
+const queryPickingCarCount = async () => {
|
|
|
+ try {
|
|
|
+ const res = await queryPickingCarNumber();
|
|
|
+ if (res.errorCode == 0) {
|
|
|
+ if (res.data) {
|
|
|
+ count.value = res.data;
|
|
|
+ } else {
|
|
|
+ count.value = 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ showNotify({ type: 'danger', message: res.errorMessage });
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('查询领料车数量失败:', error);
|
|
|
+ showNotify({ type: 'danger', message: '查询领料车数量失败' });
|
|
|
+ }
|
|
|
+};
|
|
|
+onMounted(() => {
|
|
|
+ getDatas();
|
|
|
+ getWarehouses();
|
|
|
+ queryPickingCarCount();
|
|
|
+ queryFeedArea();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* ========== 基础样式 - 所有屏幕固定布局 ========== */
|
|
|
+.stock-requisition-page {
|
|
|
+ width: 100%;
|
|
|
+ height: 100vh;
|
|
|
+ max-height: 100vh;
|
|
|
+ position: relative;
|
|
|
+ font-family: 'Microsoft YaHei', sans-serif;
|
|
|
+ color: #fff;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+/* 背景层 */
|
|
|
+.bg-layer {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-color: #041c3d;
|
|
|
+ background-size: cover;
|
|
|
+ background-position: center;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ z-index: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 主内容区域 ========== */
|
|
|
+.main-content {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 0 30px;
|
|
|
+ position: relative;
|
|
|
+ z-index: 10;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 筛选区域 ========== */
|
|
|
+.filter-section {
|
|
|
+ background: rgba(9, 61, 140, 0.5);
|
|
|
+ border: 1px solid #049FD8;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 20px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-row {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-label {
|
|
|
+ font-size: 16px;
|
|
|
+ color: #7ec8ff;
|
|
|
+ white-space: nowrap;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-select {
|
|
|
+ width: 200px;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-input.dark-input {
|
|
|
+ width: 200px;
|
|
|
+ padding: 10px 15px;
|
|
|
+ background: rgba(13, 58, 106, 0.8);
|
|
|
+ border: 1px solid #2a7fff;
|
|
|
+ border-radius: 6px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 14px;
|
|
|
+ outline: none;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-input.dark-input::placeholder {
|
|
|
+ color: #7ec8ff;
|
|
|
+ opacity: 0.7;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-input.dark-input:focus {
|
|
|
+ border-color: #00bfff;
|
|
|
+ box-shadow: 0 0 10px rgba(0, 191, 255, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.filter-buttons {
|
|
|
+ display: flex;
|
|
|
+ gap: 15px;
|
|
|
+ margin-left: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.search-btn,
|
|
|
+.reset-btn {
|
|
|
+ padding: 10px 30px;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+.search-btn {
|
|
|
+ background: linear-gradient(90deg, #1e90ff 0%, #00bfff 100%);
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.search-btn:hover {
|
|
|
+ box-shadow: 0 0 15px rgba(30, 144, 255, 0.6);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+.reset-btn {
|
|
|
+ background: rgba(13, 58, 106, 0.8);
|
|
|
+ border: 1px solid #2a7fff;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.reset-btn:hover {
|
|
|
+ background: rgba(26, 74, 122, 0.8);
|
|
|
+ box-shadow: 0 0 10px rgba(30, 144, 255, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 全选控制栏 ========== */
|
|
|
+.select-all-bar {
|
|
|
+ background: rgba(9, 61, 140, 0.3);
|
|
|
+ border: 1px solid #049FD8;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 10px 20px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.select-all-text {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #7ec8ff;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.select-all-bar .van-checkbox__label) {
|
|
|
+ color: #7ec8ff;
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 卡片网格区域 ========== */
|
|
|
+.card-grid-wrapper {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ min-height: 0;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ padding-bottom: 10px;
|
|
|
+ /* 隐藏滚动条 */
|
|
|
+ scrollbar-width: none;
|
|
|
+ -ms-overflow-style: none;
|
|
|
+}
|
|
|
+
|
|
|
+.card-grid-wrapper::-webkit-scrollbar {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+
|
|
|
+/* 加载更多样式 */
|
|
|
+.loading-more {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 20px 0;
|
|
|
+ color: #7ec8ff;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-more-spinner {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border: 2px solid rgba(30, 144, 255, 0.3);
|
|
|
+ border-top-color: #00bfff;
|
|
|
+ border-radius: 50%;
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+.no-more-data {
|
|
|
+ text-align: center;
|
|
|
+ padding: 15px 0;
|
|
|
+ color: #5a8abf;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 空状态 */
|
|
|
+.empty-state {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #7ec8ff;
|
|
|
+ padding: 60px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-icon {
|
|
|
+ font-size: 80px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ opacity: 0.5;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-state p {
|
|
|
+ font-size: 18px;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 卡片网格 - 4列布局 */
|
|
|
+.card-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 设备卡片样式 ========== */
|
|
|
+.inventory-card {
|
|
|
+ background: rgba(5, 30, 60, 0.8);
|
|
|
+ border: 2px solid #0d4a8a;
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.inventory-card:hover {
|
|
|
+ border-color: #1e90ff;
|
|
|
+ box-shadow: 0 0 20px rgba(30, 144, 255, 0.3);
|
|
|
+ transform: translateY(-3px);
|
|
|
+}
|
|
|
+
|
|
|
+.inventory-card.selected {
|
|
|
+ border-color: #00bfff;
|
|
|
+ box-shadow: 0 0 25px rgba(0, 191, 255, 0.5);
|
|
|
+ background: rgba(10, 50, 100, 0.9);
|
|
|
+}
|
|
|
+
|
|
|
+/* 卡片序号 - 左上角 */
|
|
|
+.card-index {
|
|
|
+ position: absolute;
|
|
|
+ top: 10px;
|
|
|
+ left: 10px;
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ background: linear-gradient(135deg, #1e90ff 0%, #00bfff 100%);
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #fff;
|
|
|
+ z-index: 2;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 191, 255, 0.4);
|
|
|
+}
|
|
|
+
|
|
|
+/* 图片区域 */
|
|
|
+.card-image {
|
|
|
+ width: 100%;
|
|
|
+ aspect-ratio: 3 / 4;
|
|
|
+ background: #fff;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ overflow: hidden;
|
|
|
+ margin: 15px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ border-radius: 8px;
|
|
|
+ width: calc(100% - 30px);
|
|
|
+}
|
|
|
+
|
|
|
+.card-image img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+.image-placeholder {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(135deg, #f0f4f8 0%, #e8ecf0 100%);
|
|
|
+ color: #94a3b8;
|
|
|
+ font-size: 48px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 信息区域 */
|
|
|
+.card-info {
|
|
|
+ padding: 10px 15px 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.info-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.info-row:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.info-label {
|
|
|
+ color: #7ec8ff;
|
|
|
+ white-space: nowrap;
|
|
|
+ margin-right: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.info-value {
|
|
|
+ color: #fff;
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+/* 位置行样式 */
|
|
|
+.location-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 4px;
|
|
|
+ padding-top: 4px;
|
|
|
+ border-top: 1px solid rgba(30, 144, 255, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.location-icon {
|
|
|
+ color: #00bfff;
|
|
|
+ margin-right: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.separator {
|
|
|
+ color: #7ec8ff;
|
|
|
+ margin: 0 6px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 选中状态指示 - 右上角 */
|
|
|
+.selected-indicator {
|
|
|
+ position: absolute;
|
|
|
+ top: 10px;
|
|
|
+ right: 10px;
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ background: linear-gradient(135deg, #00ff88 0%, #00cc6a 100%);
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #fff;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 255, 136, 0.4);
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 底部操作按钮 ========== */
|
|
|
+.bottom-actions {
|
|
|
+ width: 100%;
|
|
|
+ padding: 15px 30px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ background: rgba(4, 28, 61, 0.95);
|
|
|
+ z-index: 20;
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+.pick-btn,
|
|
|
+.submit-btn {
|
|
|
+ flex: 1;
|
|
|
+ padding: 18px 0;
|
|
|
+ border: none;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #fff;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ letter-spacing: 8px;
|
|
|
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.pick-btn {
|
|
|
+ background: linear-gradient(90deg, #26dc8a 0%, #44efa2 100%);
|
|
|
+}
|
|
|
+
|
|
|
+.pick-btn:hover:not(:disabled) {
|
|
|
+ box-shadow: 0 0 30px rgba(42, 220, 138, 0.6);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+.submit-btn {
|
|
|
+ background: #4a99e2;
|
|
|
+}
|
|
|
+
|
|
|
+.submit-btn:hover:not(:disabled) {
|
|
|
+ box-shadow: 0 0 30px rgba(74, 153, 226, 0.6);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+.pick-btn:disabled,
|
|
|
+.submit-btn:disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+ background: linear-gradient(90deg, #6b7280 0%, #9ca3af 100%);
|
|
|
+}
|
|
|
+
|
|
|
+ /* ========== 配送方式选择区域(固定高度) ========== */
|
|
|
+ .card-delivery-section {
|
|
|
+ padding: 10px 15px;
|
|
|
+ border-top: 1px solid rgba(30, 144, 255, 0.3);
|
|
|
+ background: rgba(9, 61, 140, 0.3);
|
|
|
+ min-height: 94px;
|
|
|
+ /* 固定最小高度 */
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 未选中时的占位样式 */
|
|
|
+ .delivery-placeholder {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
+ color: #5a8abf;
|
|
|
+ font-size: 12px;
|
|
|
+ opacity: 0.7;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-row:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-location-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ height: 30px;
|
|
|
+ margin-top: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-label {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #7ec8ff;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 配送方式按钮 */
|
|
|
+ .delivery-method-buttons {
|
|
|
+ display: flex;
|
|
|
+ gap: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-btn {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 4px;
|
|
|
+ padding: 5px 10px;
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 500;
|
|
|
+ border: 1px solid #2a7fff;
|
|
|
+ background: rgba(13, 58, 106, 0.8);
|
|
|
+ color: #7ec8ff;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-btn i {
|
|
|
+ font-size: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-btn.active {
|
|
|
+ background: linear-gradient(90deg, #1e90ff 0%, #00bfff 100%);
|
|
|
+ color: #fff;
|
|
|
+ border-color: #00bfff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-btn.disabled {
|
|
|
+ opacity: 0.4;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-btn:hover:not(.disabled):not(.active) {
|
|
|
+ background: rgba(30, 144, 255, 0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 配送类型徽章 */
|
|
|
+ .delivery-type-badge {
|
|
|
+ padding: 3px 10px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 10px;
|
|
|
+ font-weight: 500;
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-type-badge.badge-manual {
|
|
|
+ background: rgba(107, 114, 128, 0.4);
|
|
|
+ border: 1px solid #6b7280;
|
|
|
+ color: #d1d5db;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-type-badge.badge-agv-force {
|
|
|
+ background: rgba(234, 88, 12, 0.4);
|
|
|
+ border: 1px solid #ea580c;
|
|
|
+ color: #fdba74;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delivery-type-badge.badge-agv-optional {
|
|
|
+ background: rgba(16, 185, 129, 0.4);
|
|
|
+ border: 1px solid #10b981;
|
|
|
+ color: #6ee7b7;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 配送位置选择器 */
|
|
|
+ .location-select {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+/* ========== 响应式 - 1920x1080 横屏 ========== */
|
|
|
+@media screen and (orientation: landscape) {
|
|
|
+
|
|
|
+ /* 横屏固定布局,不出现页面滚动条 */
|
|
|
+ .stock-requisition-page {
|
|
|
+ height: 100vh;
|
|
|
+ min-height: 100vh;
|
|
|
+ max-height: 100vh;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-section {
|
|
|
+ padding: 10px 20px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-title {
|
|
|
+ font-size: 22px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .logout-btn {
|
|
|
+ padding: 6px 14px;
|
|
|
+ font-size: 13px;
|
|
|
+ left: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .logout-btn i {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-actions {
|
|
|
+ right: 20px;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .action-btn {
|
|
|
+ padding: 6px 14px;
|
|
|
+ font-size: 13px;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .action-btn i {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .main-content {
|
|
|
+ padding: 0 20px;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-section {
|
|
|
+ padding: 10px 15px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-row {
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-label {
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-select {
|
|
|
+ width: 150px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-input.dark-input {
|
|
|
+ width: 150px;
|
|
|
+ padding: 6px 10px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.dark-select .vs__dropdown-toggle) {
|
|
|
+ min-height: 32px;
|
|
|
+ padding: 4px 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-btn,
|
|
|
+ .reset-btn {
|
|
|
+ padding: 6px 20px;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 卡片区域可滚动,隐藏滚动条 */
|
|
|
+ .card-grid-wrapper {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ padding-bottom: 10px;
|
|
|
+ /* 隐藏滚动条 */
|
|
|
+ scrollbar-width: none;
|
|
|
+ -ms-overflow-style: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-grid-wrapper::-webkit-scrollbar {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 横屏5列布局,显示20个卡片 */
|
|
|
+ .card-grid {
|
|
|
+ grid-template-columns: repeat(5, 1fr);
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .inventory-card {
|
|
|
+ border-radius: 8px;
|
|
|
+ border-width: 1px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-image {
|
|
|
+ margin: 8px;
|
|
|
+ width: calc(100% - 16px);
|
|
|
+ aspect-ratio: 4 / 3;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-index {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ font-size: 12px;
|
|
|
+ top: 6px;
|
|
|
+ right: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-info {
|
|
|
+ padding: 6px 8px 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-row {
|
|
|
+ font-size: 14px;
|
|
|
+ margin-bottom: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .location-row {
|
|
|
+ margin-top: 2px;
|
|
|
+ padding-top: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .location-icon {
|
|
|
+ font-size: 10px;
|
|
|
+ margin-right: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .separator {
|
|
|
+ margin: 0 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-placeholder {
|
|
|
+ font-size: 28px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .selected-indicator {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ font-size: 10px;
|
|
|
+ top: 6px;
|
|
|
+ right: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-index {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ font-size: 12px;
|
|
|
+ top: 6px;
|
|
|
+ left: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-more {
|
|
|
+ padding: 15px 0;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-more-spinner {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .no-more-data {
|
|
|
+ padding: 10px 0;
|
|
|
+ font-size: 11px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 底部按钮固定 */
|
|
|
+ .bottom-actions {
|
|
|
+ padding: 10px 20px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ background: rgba(4, 28, 61, 0.95);
|
|
|
+ }
|
|
|
+
|
|
|
+ .submit-btn {
|
|
|
+ padding: 12px 0;
|
|
|
+ font-size: 18px;
|
|
|
+ letter-spacing: 6px;
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 响应式 - 中等屏幕(横屏但宽度较小) ========== */
|
|
|
+@media screen and (orientation: landscape) and (max-width: 1400px) {
|
|
|
+ .card-grid {
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-select {
|
|
|
+ width: 130px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-input.dark-input {
|
|
|
+ width: 130px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 响应式 - 中等屏幕(竖屏) ========== */
|
|
|
+@media screen and (orientation: portrait) and (max-width: 900px) {
|
|
|
+ .stock-requisition-page {
|
|
|
+ height: 100vh;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-grid {
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ gap: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-grid-wrapper {
|
|
|
+ scrollbar-width: none;
|
|
|
+ -ms-overflow-style: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-grid-wrapper::-webkit-scrollbar {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-row {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: stretch;
|
|
|
+ gap: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-item {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-select,
|
|
|
+ .filter-input.dark-input {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-buttons {
|
|
|
+ margin-left: 0;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-actions {
|
|
|
+ position: static;
|
|
|
+ transform: none;
|
|
|
+ margin-top: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-section {
|
|
|
+ flex-wrap: wrap;
|
|
|
+ justify-content: space-between;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 响应式 - 小屏幕 ========== */
|
|
|
+@media screen and (max-width: 600px) {
|
|
|
+ .stock-requisition-page {
|
|
|
+ height: 100vh;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-section {
|
|
|
+ padding: 15px 20px;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .logout-btn {
|
|
|
+ position: static;
|
|
|
+ transform: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-actions {
|
|
|
+ position: static;
|
|
|
+ transform: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-title {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .main-content {
|
|
|
+ padding: 0 15px;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-grid-wrapper {
|
|
|
+ scrollbar-width: none;
|
|
|
+ -ms-overflow-style: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-grid-wrapper::-webkit-scrollbar {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-grid {
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-image {
|
|
|
+ margin: 8px;
|
|
|
+ width: calc(100% - 16px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-index {
|
|
|
+ width: 26px;
|
|
|
+ height: 26px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-row {
|
|
|
+ font-size: 11px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-placeholder {
|
|
|
+ font-size: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .bottom-actions {
|
|
|
+ padding: 15px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ background: rgba(4, 28, 61, 0.95);
|
|
|
+ }
|
|
|
+
|
|
|
+ .submit-btn {
|
|
|
+ padding: 14px 0;
|
|
|
+ font-size: 18px;
|
|
|
+ letter-spacing: 4px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 响应式 - 大屏竖屏 (1080x1920) ========== */
|
|
|
+@media screen and (orientation: portrait) and (min-width: 900px) {
|
|
|
+ .header-section {
|
|
|
+ padding: 30px 40px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-title {
|
|
|
+ font-size: 36px;
|
|
|
+ letter-spacing: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .logout-btn {
|
|
|
+ padding: 14px 28px;
|
|
|
+ font-size: 20px;
|
|
|
+ border-radius: 10px;
|
|
|
+ left: 40px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .logout-btn i {
|
|
|
+ font-size: 22px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-actions {
|
|
|
+ right: 40px;
|
|
|
+ gap: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .action-btn {
|
|
|
+ padding: 14px 24px;
|
|
|
+ font-size: 18px;
|
|
|
+ gap: 10px;
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .action-btn i {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .main-content {
|
|
|
+ padding: 0 40px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-section {
|
|
|
+ padding: 25px 30px;
|
|
|
+ margin-bottom: 25px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-label {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-input.dark-input {
|
|
|
+ width: 240px;
|
|
|
+ padding: 14px 18px;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-btn,
|
|
|
+ .reset-btn {
|
|
|
+ padding: 14px 40px;
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-grid {
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ gap: 25px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-index {
|
|
|
+ width: 42px;
|
|
|
+ height: 42px;
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-image {
|
|
|
+ margin: 18px;
|
|
|
+ width: calc(100% - 36px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-info {
|
|
|
+ padding: 12px 18px 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-row {
|
|
|
+ font-size: 16px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .location-row {
|
|
|
+ margin-top: 6px;
|
|
|
+ padding-top: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .location-icon {
|
|
|
+ font-size: 14px;
|
|
|
+ margin-right: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .separator {
|
|
|
+ margin: 0 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-placeholder {
|
|
|
+ font-size: 56px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .selected-indicator {
|
|
|
+ width: 34px;
|
|
|
+ height: 34px;
|
|
|
+ font-size: 16px;
|
|
|
+ right: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-index {
|
|
|
+ width: 42px;
|
|
|
+ height: 42px;
|
|
|
+ font-size: 18px;
|
|
|
+ left: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .bottom-actions {
|
|
|
+ padding: 25px 40px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .submit-btn {
|
|
|
+ padding: 22px 0;
|
|
|
+ font-size: 28px;
|
|
|
+ letter-spacing: 10px;
|
|
|
+ border-radius: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.dark-select .vs__dropdown-toggle) {
|
|
|
+ min-height: 48px;
|
|
|
+ padding: 8px 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.dark-select .vs__dropdown-option) {
|
|
|
+ padding: 12px 18px;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-select {
|
|
|
+ width: 240px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-more {
|
|
|
+ padding: 30px 0;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-more-spinner {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .no-more-data {
|
|
|
+ padding: 20px 0;
|
|
|
+ font-size: 15px;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|