| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <uni-popup |
| | | ref="popupRef" |
| | | type="center" |
| | | :round="20" |
| | | @close="handleClose" |
| | | @open="handleOpen" |
| | | > |
| | | <view class="scan-list-modal"> |
| | | <!-- å¤´é¨ --> |
| | | <view class="modal-header"> |
| | | <text class="modal-title">æ«ç å表 ({{ scanList.length }})</text> |
| | | <view class="close-btn" @click="handleClose"> |
| | | <up-icon name="close" size="20" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- æä½æé® --> |
| | | <view class="modal-actions"> |
| | | <up-button |
| | | type="primary" |
| | | size="small" |
| | | @click="startScan" |
| | | :loading="isScanning" |
| | | > |
| | | {{ isScanning ? 'æ«ç ä¸...' : 'ç»§ç»æ«ç ' }} |
| | | </up-button> |
| | | <up-button |
| | | type="success" |
| | | size="small" |
| | | @click="exportToCSV" |
| | | > |
| | | 导åºè¡¨æ ¼ |
| | | </up-button> |
| | | <up-button |
| | | type="error" |
| | | size="small" |
| | | @click="clearList" |
| | | :disabled="scanList.length === 0" |
| | | > |
| | | æ¸
空å表 |
| | | </up-button> |
| | | </view> |
| | | |
| | | <!-- å表å
容 --> |
| | | <view class="modal-content"> |
| | | <view v-if="scanList.length === 0" class="empty-state"> |
| | | <text class="empty-text">ææ æ«ç è®°å½</text> |
| | | <text class="empty-hint">ç¹å»"ç»§ç»æ«ç "å¼å§æ«ç </text> |
| | | </view> |
| | | <scroll-view v-else scroll-y class="list-container"> |
| | | <view |
| | | v-for="(item, index) in scanList" |
| | | :key="index" |
| | | class="list-item" |
| | | > |
| | | <view class="item-header"> |
| | | <text class="item-index">#{{ index + 1 }}</text> |
| | | <text class="item-time">{{ item.scanTime }}</text> |
| | | <view class="item-delete" @click="deleteItem(index)"> |
| | | <up-icon name="trash" size="16" color="#f56c6c"></up-icon> |
| | | </view> |
| | | </view> |
| | | <view class="item-content"> |
| | | <!-- 产åå¾ç --> |
| | | <view v-if="item.type == 2 && item.url" class="item-image-row"> |
| | | <text class="item-label">产åå¾çï¼</text> |
| | | <image :src="baseUrl + item.url" class="product-image" mode="aspectFit"></image> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="item-label">产ååç§°ï¼</text> |
| | | <text class="item-value">{{ item.productCategory || '-' }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="item-label">产åé«åº¦ï¼</text> |
| | | <text class="item-value">{{ item.specificationModelUnit || '-' }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="item-label">åä»·ï¼ç¾å
ï¼/ä»¶ï¼</text> |
| | | <text class="item-value">{{ item.taxInclusiveUnitPrice || '-' }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="item-label">å
¥åºæ°é/ä»¶ï¼</text> |
| | | <text class="item-value">{{ item.inboundNum || '-' }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="item-label">æ¯ä»¶æ°é/æ¯ï¼</text> |
| | | <text class="item-value">{{ item.boxNum || '-' }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="item-label">çº¸ç®±è§æ ¼ï¼</text> |
| | | <text class="item-value">{{ item.cartonSpecifications || '-' }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="item-label">å
¥åºäººï¼</text> |
| | | <text class="item-value">{{ item.createBy || '-' }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="item-label">å
¥åºæ¶é´ï¼</text> |
| | | <text class="item-value">{{ item.inboundDate || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </view> |
| | | </uni-popup> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue'; |
| | | import { stockinDetail, detailManagementByCustom, exportScanList } from '@/api/inventoryManagement/receiptManagement' |
| | | import config from '@/config' |
| | | import { getToken } from '@/utils/auth' |
| | | import request from '@/utils/request' |
| | | |
| | | const baseUrl = config.imgUrl |
| | | const emit = defineEmits(['scan', 'close']); |
| | | |
| | | // å¼¹çªæ¾ç¤ºç¶æ |
| | | const popupRef = ref(); |
| | | const isScanning = ref(false); |
| | | |
| | | // æ«ç åè¡¨æ°æ® |
| | | const scanList = reactive([]); |
| | | |
| | | // æå¼å¼¹çª |
| | | const open = async () => { |
| | | popupRef.value.open(); |
| | | // æå¼åèªå¨å¼å§æ«ç |
| | | startScan(); |
| | | }; |
| | | |
| | | // å¼å§æ«ç |
| | | const startScan = () => { |
| | | isScanning.value = true; |
| | | uni.scanCode({ |
| | | onlyFromCamera: true, |
| | | scanType: ['barCode', 'qrCode'], |
| | | success: async (res) => { |
| | | isScanning.value = false; |
| | | console.log('æ«ç ç»æ:', res); |
| | | await handleScanResult(res.result || ''); |
| | | }, |
| | | fail: (res) => { |
| | | isScanning.value = false; |
| | | if (res.errMsg && !res.errMsg.includes('cancel')) { |
| | | uni.showToast({ |
| | | title: 'æ«ç 失败', |
| | | icon: 'none', |
| | | duration: 1500 |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // å¤çæ«ç ç»æ |
| | | const handleScanResult = async (barcode) => { |
| | | if (!barcode || barcode.indexOf(',') === -1) { |
| | | uni.showToast({ |
| | | title: "è¯·æ«ææ£ç¡®çäºç»´ç ", |
| | | icon: 'none', |
| | | duration: 1500 |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | let barcodeList = barcode.split(","); |
| | | let barcodeId = barcodeList[0]; |
| | | let type = barcodeList[1]; |
| | | let detailApi = null; |
| | | |
| | | if (type == 1) { |
| | | detailApi = stockinDetail; |
| | | } else if (type == 2) { |
| | | detailApi = detailManagementByCustom; |
| | | } |
| | | |
| | | if (!detailApi) { |
| | | uni.showToast({ |
| | | title: "è¯·æ«ææ£ç¡®çäºç»´ç ", |
| | | icon: 'none', |
| | | duration: 1500 |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // æ£æ¥æ¯å¦å·²åå¨ |
| | | const existingIndex = scanList.findIndex(item => item.barcodeId === barcodeId && item.type === type); |
| | | if (existingIndex !== -1) { |
| | | uni.showToast({ |
| | | title: "该产åå·²æ«ç ï¼è¯·å¿éå¤", |
| | | icon: 'none', |
| | | duration: 1500 |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | uni.showLoading({ |
| | | title: 'è·å产åä¿¡æ¯ä¸...', |
| | | mask: true |
| | | }); |
| | | |
| | | const resp = await detailApi({ id: barcodeId }); |
| | | |
| | | uni.hideLoading(); |
| | | |
| | | if (resp.code != 200) { |
| | | uni.showToast({ |
| | | title: resp.msg, |
| | | icon: "none", |
| | | duration: 1500 |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | if (!resp.data) { |
| | | uni.showToast({ |
| | | title: 'ååä¸åå¨', |
| | | icon: "none", |
| | | duration: 1500 |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // æ·»å å°å表 |
| | | const scanTime = new Date().toLocaleString('zh-CN', { |
| | | year: 'numeric', |
| | | month: '2-digit', |
| | | day: '2-digit', |
| | | hour: '2-digit', |
| | | minute: '2-digit', |
| | | second: '2-digit' |
| | | }); |
| | | |
| | | scanList.push({ |
| | | barcodeId: barcodeId, |
| | | type: type, |
| | | scanTime: scanTime, |
| | | ...resp.data, |
| | | specificationModelUnit: (resp.data.specificationModel || '') + (resp.data.unit || '') |
| | | }); |
| | | |
| | | uni.showToast({ |
| | | title: 'æ«ç æå', |
| | | icon: 'success', |
| | | duration: 1500 |
| | | }); |
| | | |
| | | // è§¦åæ«ç äºä»¶ |
| | | emit('scan', resp.data); |
| | | |
| | | } catch (error) { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: "è·åæ°æ®å¤±è´¥", |
| | | icon: 'none', |
| | | duration: 1500 |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // å é¤å表项 |
| | | const deleteItem = (index) => { |
| | | uni.showModal({ |
| | | title: 'æç¤º', |
| | | content: 'ç¡®å®è¦å é¤è¿æ¡è®°å½åï¼', |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | scanList.splice(index, 1); |
| | | uni.showToast({ |
| | | title: 'å·²å é¤', |
| | | icon: 'success', |
| | | duration: 1000 |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æ¸
空å表 |
| | | const clearList = () => { |
| | | uni.showModal({ |
| | | title: 'æç¤º', |
| | | content: 'ç¡®å®è¦æ¸
空ææè®°å½åï¼', |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | scanList.splice(0, scanList.length); |
| | | uni.showToast({ |
| | | title: 'å·²æ¸
空', |
| | | icon: 'success', |
| | | duration: 1000 |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // 导åºExcelï¼ä½¿ç¨ uni.downloadFile + uni.saveFileï¼ |
| | | const exportToCSV = async () => { |
| | | try { |
| | | if (scanList.length === 0) { |
| | | uni.showToast({ |
| | | title: 'åè¡¨ä¸ºç©ºï¼æ æ³å¯¼åº', |
| | | icon: 'none', |
| | | duration: 1500 |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | uni.showLoading({ |
| | | title: '导åºä¸...', |
| | | mask: true |
| | | }); |
| | | |
| | | // åå¤å¯¼åºåæ° - ä¼ é宿´çæ«ç æ°æ®æ°ç» |
| | | const exportData = scanList.map(item => ({ |
| | | barcodeId: item.barcodeId, |
| | | type: item.type, |
| | | scanTime: item.scanTime, |
| | | productCategory: item.productCategory, |
| | | specificationModel: item.specificationModel, |
| | | unit: item.unit, |
| | | specificationModelUnit: item.specificationModelUnit, |
| | | taxInclusiveUnitPrice: item.taxInclusiveUnitPrice, |
| | | inboundNum: item.inboundNum, |
| | | boxNum: item.boxNum, |
| | | cartonSpecifications: item.cartonSpecifications, |
| | | createBy: item.createBy, |
| | | inboundDate: item.inboundDate, |
| | | url: item.url |
| | | })); |
| | | |
| | | const fileName = `äº§åæ«ç è®°å½_${new Date().toISOString().slice(0, 10).replace(/-/g, '')}_${Date.now()}.xlsx`; |
| | | |
| | | // 1. è°ç¨åç«¯å¯¼åºæ¥å£ï¼ä½¿ç¨å°è£
好ç requestï¼æ¿å° msgï¼æä»¶ç¸å¯¹è·¯å¾ææä»¶åï¼ |
| | | // 注æï¼è¿éå设å端è¿å { code: 200, msg: 'xxx.xlsx' } ç»æ |
| | | // â
è¿éå¿
须走ä¸å¡å端 baseUrlï¼ä¸è¦ç¨æä»¶æå¡å¨ imgUrlï¼å¦åä¼è¢« nginx æç»ï¼405ï¼ |
| | | const apiBaseUrl = config.baseUrl.replace(/\/+$/, ''); |
| | | const resp = await request({ |
| | | url: '/stockin/exportTwoSave', |
| | | method: 'POST', |
| | | data: exportData, |
| | | baseUrl: apiBaseUrl |
| | | }); |
| | | |
| | | const code = resp.code; |
| | | const msg = resp.msg; |
| | | console.log('exportTwoSave è¿å:', resp); |
| | | |
| | | if (code !== 200) { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: msg || '导åºå¤±è´¥', |
| | | icon: 'none', |
| | | duration: 2000 |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // 2. ç¨æä»¶æå¡åç¼ + msg æ¼æå®æ´ä¸è½½å°å |
| | | // å端说æï¼éè¦ç¨ http://114.132.189.42:9044/javaWork/product-inventory-management/file/prod/ å msg æ¼èµ·æ¥ |
| | | const fileServerBase = 'http://114.132.189.42:9044/javaWork/product-inventory-management/file/prod/'; |
| | | if (!msg) { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '导åºå¤±è´¥ï¼è¿åçæä»¶å为空', |
| | | icon: 'none', |
| | | duration: 2000 |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | const downloadUrl = encodeURI(fileServerBase.replace(/\/+$/, '/') + msg); |
| | | console.log('æç»ä¸è½½å°å:', downloadUrl); |
| | | |
| | | // 3. ä½¿ç¨ downloadFile + saveFile ä¸è½½å¹¶æä¹
åï¼åæå¼ |
| | | uni.downloadFile({ |
| | | url: downloadUrl, |
| | | success: (downloadRes) => { |
| | | console.log('downloadFile statusCode:', downloadRes.statusCode); |
| | | if (downloadRes.statusCode !== 200) { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: `ä¸è½½å¤±è´¥ï¼ç¶æç : ${downloadRes.statusCode}`, |
| | | icon: 'none', |
| | | duration: 2000 |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | uni.saveFile({ |
| | | tempFilePath: downloadRes.tempFilePath, |
| | | success: (saveRes) => { |
| | | uni.hideLoading(); |
| | | const savedPath = saveRes.savedFilePath; |
| | | |
| | | uni.showToast({ |
| | | title: 'Excelå·²ä¸è½½', |
| | | icon: 'success', |
| | | duration: 3000 |
| | | }); |
| | | |
| | | setTimeout(() => { |
| | | uni.openDocument({ |
| | | filePath: savedPath, |
| | | success: () => console.log('æä»¶æå¼æå'), |
| | | fail: (err) => console.log('æä»¶å·²ä¿åï¼è·¯å¾ï¼', savedPath, err) |
| | | }); |
| | | }, 300); |
| | | }, |
| | | fail: (err) => { |
| | | uni.hideLoading(); |
| | | console.error('ä¿åæä»¶å¤±è´¥:', err); |
| | | uni.showToast({ |
| | | title: `ä¿åæä»¶å¤±è´¥: ${err.errMsg || 'æªç¥é误'}`, |
| | | icon: 'none', |
| | | duration: 3000 |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | fail: (err) => { |
| | | uni.hideLoading(); |
| | | console.error('ä¸è½½å¤±è´¥:', err); |
| | | uni.showToast({ |
| | | title: `ä¸è½½å¤±è´¥: ${err.errMsg || 'æªç¥é误'}`, |
| | | icon: 'none', |
| | | duration: 3000 |
| | | }); |
| | | } |
| | | }); |
| | | } catch (error) { |
| | | uni.hideLoading(); |
| | | console.error('导åºå¤±è´¥:', error); |
| | | uni.showToast({ |
| | | title: error.message || '导åºå¤±è´¥ï¼è¯·éè¯', |
| | | icon: 'none', |
| | | duration: 2000 |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // å¼¹çªæå¼äºä»¶ |
| | | const handleOpen = () => { |
| | | // å¼¹çªæå¼æ¶çå¤ç |
| | | }; |
| | | |
| | | // å
³éå¼¹çª |
| | | const handleClose = () => { |
| | | popupRef.value.close(); |
| | | emit('close'); |
| | | }; |
| | | |
| | | // æ´é²æ¹æ³ |
| | | defineExpose({ |
| | | open, |
| | | scanList |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .scan-list-modal { |
| | | background: #ffffff; |
| | | border-radius: 20px; |
| | | max-height: 80vh; |
| | | width: 90vw; |
| | | max-width: 600px; |
| | | overflow: hidden; |
| | | display: flex; |
| | | flex-direction: column; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .modal-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 20px 20px 16px 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .modal-title { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .close-btn { |
| | | padding: 4px; |
| | | |
| | | &:active { |
| | | opacity: 0.7; |
| | | } |
| | | } |
| | | |
| | | .modal-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .modal-content { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .empty-state { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 60px 20px; |
| | | color: #999; |
| | | } |
| | | |
| | | .empty-text { |
| | | margin-top: 16px; |
| | | font-size: 16px; |
| | | color: #999; |
| | | } |
| | | |
| | | .empty-hint { |
| | | margin-top: 8px; |
| | | font-size: 14px; |
| | | color: #ccc; |
| | | } |
| | | |
| | | .list-container { |
| | | flex: 1; |
| | | padding: 12px 20px; |
| | | max-height: 50vh; |
| | | } |
| | | |
| | | .list-item { |
| | | background: #f8f9fa; |
| | | border-radius: 12px; |
| | | padding: 16px; |
| | | margin-bottom: 12px; |
| | | border: 1px solid #e9ecef; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | |
| | | .item-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 12px; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px solid #e9ecef; |
| | | } |
| | | |
| | | .item-index { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #2979ff; |
| | | } |
| | | |
| | | .item-time { |
| | | font-size: 12px; |
| | | color: #999; |
| | | flex: 1; |
| | | margin-left: 12px; |
| | | } |
| | | |
| | | .item-delete { |
| | | padding: 4px; |
| | | cursor: pointer; |
| | | |
| | | &:active { |
| | | opacity: 0.7; |
| | | } |
| | | } |
| | | |
| | | .item-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .item-row { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .item-label { |
| | | color: #666; |
| | | min-width: 100px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .item-value { |
| | | color: #333; |
| | | flex: 1; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .item-image-row { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | margin-bottom: 8px; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .product-image { |
| | | width: 200rpx; |
| | | height: 200rpx; |
| | | border-radius: 8rpx; |
| | | background-color: #f5f5f5; |
| | | margin-top: 8px; |
| | | border: 1px solid #e9ecef; |
| | | } |
| | | </style> |
| | | |