| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814 |
- <!-- 还料入库 - 智能仓储风格 -->
- <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 refresh-btn" @click="resetView">
- <i class="fas fa-sync-alt" />
- <span>重新校验</span>
- </button>
- </div>
- </div>
- <!-- 主内容区域 -->
- <main class="main-content">
- <!-- 统计信息 -->
- <div v-if="materialList.length > 0" class="stats-section">
- <span class="stats-text">共 {{ materialList.length }} 条数据,不识别 {{ unrecognizedCount }} 条</span>
- </div>
- <!-- 卡片网格区域 -->
- <div class="card-grid-wrapper">
- <!-- 空状态 -->
- <div v-if="materialList.length === 0 && !loading" class="empty-state">
- <i class="fas fa-inbox empty-icon" />
- <p>暂无数据</p>
- </div>
- <!-- 卡片网格 -->
- <div v-else class="card-grid">
- <template v-for="(item, index) in materialList" :key="item.id || index">
- <div
- v-if="item.inventoryId != null"
- class="inventory-card"
- :class="getCardClass(item.remarks)"
- @click="handleSelect(item)"
- >
- <!-- 卡片序号 -->
- <div
- v-if="item.selected" class="card-index"
- style="background: linear-gradient(135deg, #a8f63b 0%, #60faee 100%);"
- :class="getCardClass(item.remarks)"
- />
- <!-- 图片区域 -->
- <div class="card-image">
- <div class="image-placeholder" :class="getCardClass(item.remarks)">
- <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.positionName || '-' }} / {{ item.warehouseName || '-' }}</span>
- </div>
- </div>
- <!-- 状态区域(固定高度) -->
- <div class="card-status-section">
- <div class="status-badge" :class="getCardClass(item.remarks)">
- <i class="fas fa-exclamation-circle" />
- {{ item.remarks || '-' }}
- </div>
- </div>
- </div>
- </template>
- </div>
- </div>
- </main>
- <!-- 底部操作按钮 -->
- <div class="bottom-actions">
- <button class="submit-btn" @click="handleComplete">
- 确认入库
- <span v-if="validStockCount != 0">{{ validStockCount }}</span>
- </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>
- <!-- 直接进入仓库提示 -->
- <div v-if="enterConfirmVisible" class="tech-modal-overlay" @click.self="enterConfirmVisible = false">
- <div class="tech-modal">
- <div class="modal-content-row">
- <div class="modal-text">
- <h3>请确认您要直接进入仓库</h3>
- <p>系统检测到您未携带物料,或者未勾选任何物料,是否确认直接进入仓库?</p>
- </div>
- <div class="modal-icon">
- <div class="icon-box">
- <i class="fas fa-clipboard-check" />
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <button class="modal-btn cancel-btn" @click="enterConfirm">直接进入仓库</button>
- <button class="modal-btn confirm-btn" @click="enterConfirmVisible = false">取消</button>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { useRouter } from 'vue-router';
- import { showNotify } from 'vant';
- import { ref,reactive, onMounted, computed, onUnmounted } from 'vue';
- import { cfStockIn, createStockIn } from '../api/stockIn.js';
- import { areArraysEqual } from '../common/utils.js';
- import { gateController } from '../hardware/GateOperate.js';
- // 图片资源
- import bgImg from '../assets/images/bj.png';
- const router = useRouter();
- // 返回主页
- const goHome = () => {
- router.push('/home');
- };
- // 表格加载状态
- const loading = ref(false);
- // 物料列表数据
- const materialList = ref([]);
- const epcs = ref([]);
- // 分页配置
- const pagination = reactive({
- start: 1,
- lang: 10,
- total: 0,
- });
- // 计算不在库的数量
- const validStockCount = computed(() => {
- return materialList.value.filter(item => item.remarks === '不在库' && item.selected === true).length;
- });
- // 根据 remarks 返回卡片样式类
- const getCardClass = remarks => {
- if (remarks === '不在库') {
- return 'not-in-stock-card';
- } else if (remarks === '在库') {
- return 'in-stock-card';
- }
- return '';
- };
- // 获取设备类型图标
- const getInventoryIcon = type => {
- const iconMap = {
- '工装': 'fas fa-cube',
- '设备': 'fas fa-cogs',
- '成品': 'fas fa-box',
- };
- return iconMap[type] || 'fas fa-cube';
- };
- // 获取状态颜色
- // const getStatusColor = remarks => {
- // if (remarks === '不在库') {
- // return '#3b82f6'; // 蓝色 - 需要入库
- // } else if (remarks === '在库') {
- // return '#10b981'; // 绿色 - 已在库
- // }
- // return '#6b7280'; // 灰色 - 默认
- // };
- // 还料入库
- const handleComplete = async () => {
- // 只处理不在库的数据
- const notInStockItems = materialList.value.filter(item => item.selected === true);
- if (notInStockItems.length === 0) {
- enterConfirmVisible.value = true;
- return;
- }
- const params = [];
- materialList.value.forEach(item => {
- if (item.selected === true) {
- params.push({
- inventoryId: item.inventoryId,
- });
- }
- });
- console.log('提交参数:', params);
- await generateCFStockIn(params);
- };
- // 校验
- const resetView = () => {
- epcs.value = [];
- materialList.value = [];
- };
- // 获取物料列表(外侧校验)
- const getList = async () => {
- // loading.value = true;
- const params = {
- epcList: [],
- };
- const tempMaterialList = [];
- for(let i = 0; i < materialList.value.length; i++) {
- if(materialList.value[i].queryStatus === 0) {
- params.epcList.push(materialList.value[i].epc);
- materialList.value[i].queryStatus = 1;
- tempMaterialList.push(materialList.value[i]);
- }
- }
-
- if(params.epcList.length == 0){
- return;
- }
-
- try {
- const res = await cfStockIn(params);
- if (res.errorCode === 0) {
- if (res.datas && res.datas.length > 0) {
- res.datas.forEach(i => {
- materialList.value.forEach(j => {
- if (j.epc === i.epc) {
- j.inventoryName = i.inventoryName;
- j.inventoryNo = i.inventoryNo;
- j.inventoryType = i.inventoryType;
- j.warehouseName = i.warehouseName;
- j.positionName = i.positionName;
- j.inventoryId = i.inventoryId;
- j.queryStatus = 2;
- j.selected = false;
- j.remarks = i.remarks;
- }
- });
- });
- pagination.total = materialList.value.length;
- } else {
- materialList.value = [];
- }
- } else {
- materialList.value = [];
- showNotify({ type: 'danger', message: res.errorMessage });
- }
- } catch (error) {
- console.error('获取物料列表API调用失败:', error);
- showNotify({ type: 'danger', message: '获取物料列表API调用失败' });
- // 处理查询失败的物料
- tempMaterialList.forEach(item => {
- item.queryStatus = 0;
- });
- } finally {
- loading.value = false;
- }
- };
- // 生成CF入库单
- const generateCFStockIn = async params => {
- loading.value = true;
- try {
- const res = await createStockIn(params);
- if (res.errorCode === 0) {
- // 调用开门操作
- gateController('SHOTOPEN');
-
- showNotify({ type: 'success', message: '入库申请已完成,还料任务已创建!' });
- // 跳转回主页
- router.push('/');
- } else {
- showNotify({ type: 'danger', message: res.errorMessage });
- }
- } catch (error) {
- console.error('生成CF入库单API调用失败:', error);
- showNotify({ type: 'danger', message: '生成CF入库单API调用失败' });
- } finally {
- loading.value = false;
- }
- };
- let timer = null; // 用于保存当前定时器引用
- const addEpc = data => {
- const newEpcs = data.map(item => item.epc);
-
- // 将新epc添加到临时数组中(使用Set去重)
- const uniqueEpcs = new Set([...newEpcs]);
- if(newEpcs != null && newEpcs.length > 0) {
- newEpcs.forEach(item => {
- let exist = false;
- for(let i =0; i < materialList.value.length; i++) {
- if(materialList.value[i].epc === item) {
- exist = true;
- break;
- }
- }
- if(exist == false) {
- materialList.value.push({
- epc: item,
- queryStatus: 0, // 0: 未查询, 1: 查询中, 2: 已查询, 3: 未查询到
- });
- }
- });
- }
- };
- onMounted(() => {
- // getList();
- plugin.gateConfig.sendEpc = function(data){
- if (typeof(GATE_CONFIG) == 'undefined') {
- console.log('设备不支持读写器功能。');
- } else {
- addEpc(data);
- }
- };
-
- // 创建全局唯一定时器,每2秒执行一次getList
- timer = setInterval(() => {
- // 只有当临时数组有数据时才执行getList
- // 将临时数组的数据赋值给epcs
- // 执行获取列表
- //addEpc([{
- // 'epc': 'FFF000000000000000000001',
- // 'epc': 'AAA000000000000000000002',
- //}]);
- getList();
- }, 1000);
- });
- onUnmounted(() => {
- plugin.gateConfig.sendEpc = null;
- // 清除定时器
- if (timer) {
- clearInterval(timer);
- timer = null;
- }
- });
- // 不识别的数据统计
- const unrecognizedCount = computed(() => {
- return materialList.value.filter(item => item.inventoryId == null).length;
- });
- const handleSelect = item => {
- if(item.remarks === '不在库'){
- item.selected = !item.selected;
- }
- };
- const enterConfirmVisible = ref(false);
- const enterConfirm = () => {
- enterConfirmVisible.value = false;
-
- // 调用开门操作
- gateController('SHOTOPEN');
-
- // 跳转回主页
- router.push('/');
- };
- </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;
- }
- /* 统计信息 */
- .stats-section {
- background: rgba(9, 61, 140, 0.5);
- border: 1px solid #049FD8;
- border-radius: 12px;
- padding: 15px 20px;
- margin-bottom: 15px;
- flex-shrink: 0;
- }
- .stats-text {
- color: #7ec8ff;
- font-size: 16px;
- }
- /* ========== 卡片网格区域 ========== */
- .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;
- }
- /* 空状态 */
- .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;
- 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.not-in-stock-card {
- border-color: #3b82f6;
- }
- /* 在库卡片 - 红色警告 */
- .inventory-card.in-stock-card {
- border-color: #ef4444;
- }
- /* 卡片序号 - 左上角 */
- .card-index {
- position: absolute;
- top: 10px;
- left: 10px;
- width: 36px;
- height: 36px;
- /* background: linear-gradient(135deg, #1e90ff 0%, #00bfff 100%); */
- background: linear-gradient(135deg, rgb(86, 236, 17) 0%, #28ea0a 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-index.not-in-stock-card {
- background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
- box-shadow: 0 2px 8px rgba(59, 130, 246, 0.4);
- }
- .card-index.in-stock-card {
- background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
- box-shadow: 0 2px 8px rgba(239, 68, 68, 0.4);
- }
- /* 图片区域 */
- .card-image {
- width: 100%;
- aspect-ratio: 3 / 4;
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: hidden;
- margin: 15px;
- margin-bottom: 10px;
- border-radius: 8px;
- width: calc(100% - 30px);
- }
- .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;
- }
- .image-placeholder.not-in-stock-card {
- background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
- color: #fff;
- }
- .image-placeholder.in-stock-card {
- background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
- color: #fff;
- }
- /* 信息区域 */
- .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;
- font-size: 12px;
- margin-right: 6px;
- }
- /* ========== 状态区域(固定高度) ========== */
- .card-status-section {
- padding: 12px 15px;
- border-top: 1px solid rgba(30, 144, 255, 0.3);
- background: rgba(9, 61, 140, 0.3);
- min-height: 50px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- /* 状态标签 */
- .status-badge {
- padding: 8px 20px;
- border-radius: 20px;
- font-size: 13px;
- font-weight: 500;
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .status-badge.not-in-stock-card {
- background: rgba(59, 130, 246, 0.3);
- border: 1px solid #3b82f6;
- color: #93c5fd;
- }
- .status-badge.in-stock-card {
- background: rgba(239, 68, 68, 0.3);
- border: 1px solid #ef4444;
- color: #fca5a5;
- }
- /* ========== 底部操作按钮 ========== */
- .bottom-actions {
- width: 100%;
- padding: 15px 30px;
- box-sizing: border-box;
- background: rgba(4, 28, 61, 0.95);
- z-index: 20;
- flex-shrink: 0;
- }
- .submit-btn {
- width: 100%;
- padding: 18px 0;
- background: #4a99e2;
- 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);
- }
- .submit-btn:hover:not(:disabled) {
- box-shadow: 0 0 30px rgba(16, 185, 129, 0.6);
- transform: translateY(-2px);
- }
- .submit-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- background: linear-gradient(90deg, #6b7280 0%, #9ca3af 100%);
- }
- /* ========== 响应式 - 横屏 ========== */
- @media screen and (orientation: landscape) {
- .header-section {
- padding: 10px 20px;
- }
- .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;
- }
- .action-btn {
- padding: 6px 14px;
- font-size: 13px;
- }
- .action-btn i {
- font-size: 14px;
- }
- .main-content {
- padding: 0 20px;
- }
- .stats-section {
- padding: 10px 15px;
- margin-bottom: 10px;
- }
- .stats-text {
- font-size: 14px;
- }
- /* 卡片网格响应式 */
- .card-grid {
- grid-template-columns: repeat(6, 1fr);
- gap: 15px;
- }
- .card-index {
- width: 24px;
- height: 24px;
- font-size: 11px;
- top: 6px;
- left: 6px;
- }
- .card-image {
- margin: 10px;
- margin-bottom: 8px;
- width: calc(100% - 20px);
- }
- .image-placeholder {
- font-size: 36px;
- }
- .card-info {
- padding: 6px 10px 8px;
- }
- .info-row {
- font-size: 11px;
- margin-bottom: 3px;
- }
- .location-icon {
- font-size: 10px;
- }
- .card-status-section {
- padding: 8px 10px;
- min-height: 40px;
- }
- .status-badge {
- padding: 5px 12px;
- font-size: 11px;
- gap: 4px;
- }
- .bottom-actions {
- padding: 10px 20px;
- }
- .submit-btn {
- padding: 12px 0;
- font-size: 18px;
- letter-spacing: 6px;
- border-radius: 8px;
- }
- }
- </style>
|