| src/pages/inventoryManagement/scanOut/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/inventoryManagement/scanOut/scanOut.fields.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/sales/salesAccount/goOut.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/sales/salesAccount/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/sales/salesAccount/out.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/utils/salesLedgerShip.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/pages/inventoryManagement/scanOut/index.vue
@@ -28,7 +28,7 @@ <text class="module-label">åæ ¼åºåº</text> <text class="module-desc">æ«æåæ ¼åè¿è¡é¢ç¨åºåº</text> <text class="module-desc">æ«æåæ ¼åè¿è¡é¢ç¨åºåºï¼é宿«ç åè´§</text> </view> @@ -225,7 +225,19 @@ @click="cancelForm">è¿å</u-button> <u-button class="footer-confirm-btn" <u-button v-if="showSalesShipButton" class="footer-confirm-btn" :loading="submitLoading" :disabled="salesShipButtonDisabled" @click="handleSalesShipFromScan">åè´§</u-button> <u-button v-else class="footer-confirm-btn" :loading="submitLoading" @@ -247,7 +259,12 @@ import PageHeader from "@/components/PageHeader.vue"; import { productList as salesProductList } from "@/api/salesManagement/salesLedger"; import { productList as salesProductList, getSalesLedgerWithProducts, } from "@/api/salesManagement/salesLedger"; import { canLedgerShip, executeSalesLedgerShip, getLedgerShippingLabel } from "@/utils/salesLedgerShip"; import modal from "@/plugins/modal"; @@ -281,7 +298,24 @@ /** äºç»´ç ä¸çå°è´¦ä¸»é® id */ const scanLedgerId = ref(null); /** åæ ¼åºåº+éå®ç ï¼ç¨äºãåè´§ãæé®ä¸å°è´¦çº§æ ¡éªï¼ä¸éå®å°è´¦ä¸è´ï¼ */ const salesLedgerSnapshotForShip = ref(null); const submitLoading = ref(false); const showSalesShipButton = computed( () => showForm.value && type.value === QUALITY_TYPE.qualified && contractKind.value === CONTRACT_KIND.sales ); const salesShipButtonDisabled = computed(() => { if (!showSalesShipButton.value) return false; const snap = salesLedgerSnapshotForShip.value; if (!snap) return false; return !canLedgerShip(snap); }); const submitConfigByScene = createSubmitConfig(scanLedgerId); @@ -568,6 +602,8 @@ return formatProductStockStatus(item.productStockStatus); if (row.key === "ledgerShippingStatus") return getLedgerShippingLabel(item); if (row.key === "heavyBox") return formatHeavyBox(item.heavyBox); if (row.key === "remainingQuantity") { @@ -633,6 +669,8 @@ contractKind.value = CONTRACT_KIND.sales; scanLedgerId.value = null; salesLedgerSnapshotForShip.value = null; expandedByIndex.value = {}; @@ -724,6 +762,62 @@ modal.closeLoading(); console.error("æ«ç åºåºæäº¤å¤±è´¥", e); } finally { submitLoading.value = false; } }; const handleSalesShipFromScan = async () => { if (scanLedgerId.value == null || scanLedgerId.value === "") { modal.msgError("缺å°è®¢åä¿¡æ¯ï¼è¯·éæ°æ«ç "); return; } if (salesLedgerSnapshotForShip.value && !canLedgerShip(salesLedgerSnapshotForShip.value)) return; submitLoading.value = true; try { modal.loading("å è½½ä¸..."); let ledgerRow = salesLedgerSnapshotForShip.value; if (!ledgerRow?.id) { ledgerRow = await getSalesLedgerWithProducts({ id: scanLedgerId.value, type: 1 }); } modal.closeLoading(); if (!ledgerRow?.id) { modal.msgError("è·åå°è´¦å¤±è´¥"); return; } const { productData: _pd, ...ledgerForShip } = ledgerRow; await executeSalesLedgerShip(ledgerForShip); } catch (e) { modal.closeLoading(); console.error("æ«ç å货失败", e); modal.msgError("è·åå°è´¦å¤±è´¥"); } finally { @@ -900,9 +994,35 @@ showForm.value = true; if (type.value === QUALITY_TYPE.qualified && kind === CONTRACT_KIND.sales) { salesLedgerSnapshotForShip.value = null; getSalesLedgerWithProducts({ id: scanData.id, type: 1 }) .then(res => { salesLedgerSnapshotForShip.value = res; }) .catch(() => { salesLedgerSnapshotForShip.value = null; }); } else { salesLedgerSnapshotForShip.value = null; } } else { scanLedgerId.value = null; salesLedgerSnapshotForShip.value = null; modal.msgError("æªæ¥è¯¢å°æç»æ°æ®"); @@ -914,6 +1034,8 @@ scanLedgerId.value = null; salesLedgerSnapshotForShip.value = null; console.error("å¤çæ«ç ç»æå¤±è´¥", error); modal.msgError("æ«ç å¤ç失败ï¼è¯·éè¯"); src/pages/inventoryManagement/scanOut/scanOut.fields.ts
@@ -28,6 +28,7 @@ { label: "éç®±", key: "heavyBox" }, { label: "产åç¶æ", key: "approveStatus" }, { label: "å ¥åºç¶æ", key: "productStockStatus" }, { label: "åè´§ç¶æ", key: "ledgerShippingStatus" }, { label: "å¿«éå ¬å¸", key: "expressCompany" }, { label: "å¿«éåå·", key: "expressNumber" }, { label: "å货车ç", key: "shippingCarNumber" }, @@ -59,6 +60,7 @@ { label: "æ°é", key: "quantity" }, { label: "产åç¶æ", key: "approveStatus" }, { label: "å ¥åºç¶æ", key: "productStockStatus" }, { label: "åè´§ç¶æ", key: "ledgerShippingStatus" }, ] as const; export const summaryFieldRowsPurchase = [ src/pages/sales/salesAccount/goOut.vue
@@ -20,7 +20,78 @@ @click="showPicker = true"></up-icon> </template> </u-form-item> <u-form-item v-if="typeValue === '货车'" prop="shippingCarNumber" label="车çå·" required> <u-input v-model="form.shippingCarNumber" placeholder="请è¾å ¥è½¦çå·" clearable /> </u-form-item> <u-form-item v-if="typeValue === 'å¿«é'" prop="expressNumber" label="å¿«éåå·" required> <u-input v-model="form.expressNumber" placeholder="请è¾å ¥å¿«éåå·" clearable /> </u-form-item> </u-form> <!-- åè´§å¾çï¼ç¸åæç¸æºã/file/uploadãé¢è§ä¸åè¡¨æ ·å¼ --> <view class="ship-images-card"> <view class="ship-images-header"> <text class="ship-images-title">åè´§å¾ç</text> <text class="ship-images-hint">æå¤ {{ uploadConfig.limit }} å¼ </text> </view> <view class="simple-upload-area"> <view class="upload-buttons"> <u-button type="primary" @click="chooseShipImage" :loading="shipUploading" :disabled="shipFiles.length >= uploadConfig.limit" :customStyle="{ width: '100%' }"> <u-icon name="camera" size="18" color="#fff" style="margin-right: 5px;"></u-icon> {{ shipUploading ? 'ä¸ä¼ ä¸...' : 'æ·»å å¾ç' }} </u-button> </view> <view v-if="shipUploading" class="upload-progress"> <u-line-progress :percentage="shipUploadProgress" :showText="true" activeColor="#409eff"></u-line-progress> </view> <view v-if="shipFiles.length > 0" class="file-list"> <view v-for="(file, index) in shipFiles" :key="file.uid || index" class="file-item"> <view class="file-preview-container" @click="previewShipImage(file)"> <image :src="getFileAccessUrl(file)" class="file-preview" mode="aspectFill" /> <view class="delete-btn" @click.stop="removeShipFile(index)"> <u-icon name="close" size="12" color="#fff"></u-icon> </view> </view> <view class="file-info"> <text class="file-name">{{ file.bucketFilename || file.name || 'å¾ç' }}</text> <text class="file-size">{{ formatFileSize(file.size) }}</text> </view> </view> </view> <view v-else class="empty-state"> <text>请ä»ç¸åéæ©ææç §ä¸ä¼ åè´§å¾ç</text> </view> </view> </view> <!-- éæ©å¨å¼¹çª --> <up-action-sheet :show="showPicker" :actions="productOptions" @@ -87,9 +158,11 @@ </template> <script setup> import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; import { ref, computed, onMounted, onUnmounted, reactive, toRefs } from "vue"; import PageHeader from "@/components/PageHeader.vue"; import { addShippingInfo } from "@/api/salesManagement/salesLedger"; import config from "@/config"; import { getToken } from "@/utils/auth"; const showToast = message => { uni.showToast({ title: message, @@ -97,6 +170,8 @@ }); }; import { userListNoPageByTenantId } from "@/api/system/user"; const typeValue = ref("货车"); const data = reactive({ form: { @@ -114,9 +189,43 @@ endDate: "", location: "", price: "", shippingCarNumber: "", expressNumber: "", }, rules: { typeValue: [{ required: false, message: "è¯·éæ©", trigger: "change" }], shippingCarNumber: [ { validator: (rule, value, callback) => { if (typeValue.value !== "货车") { callback(); return; } if (!value || !String(value).trim()) { callback(new Error("请è¾å ¥è½¦çå·")); return; } callback(); }, trigger: ["blur", "change"], }, ], expressNumber: [ { validator: (rule, value, callback) => { if (typeValue.value !== "å¿«é") { callback(); return; } if (!value || !String(value).trim()) { callback(new Error("请è¾å ¥å¿«éåå·")); return; } callback(); }, trigger: ["blur", "change"], }, ], }, }); const { form, rules } = toRefs(data); @@ -138,6 +247,329 @@ const formRef = ref(null); const approveType = ref(0); const goOutData = ref({}); // ä¸è®¾å¤å·¡æ£ inspectionUpload/index.vue ä¸ uploadConfig ä¸è´ const uploadConfig = { action: "/file/upload", limit: 10, fileSize: 50, fileType: ["jpg", "jpeg", "png", "gif", "webp"], }; /** ä¸è®¾å¤å·¡æ£ä¸è´ï¼ç产å type=10 ä¼ ç» /file/upload ç formData.type */ const shipUploadFormType = 10; const uploadFileUrl = computed(() => (config.baseUrl || "").replace(/\/$/, "") + uploadConfig.action); const shipFiles = ref([]); const shipUploading = ref(false); const shipUploadProgress = ref(0); const isImageFile = file => { if (file?.contentType && String(file.contentType).startsWith("image/")) return true; if (file?.type === "image" || file?.mediaType === "image") return true; const name = file?.bucketFilename || file?.originalFilename || file?.name || ""; const ext = name.split(".").pop()?.toLowerCase(); return ["jpg", "jpeg", "png", "gif", "webp"].includes(ext); }; const filePreviewBase = config.fileUrl; const normalizeFileUrl = (rawUrl = "") => { let fileUrl = rawUrl || ""; if (typeof fileUrl === "string") { fileUrl = fileUrl.trim().replace(/^['"]|['"]$/g, ""); } const javaApi = filePreviewBase; const localPrefixes = ["wxfile://", "file://", "content://", "blob:", "data:"]; if (localPrefixes.some(prefix => fileUrl.startsWith(prefix))) { return fileUrl; } if (fileUrl && fileUrl.indexOf("\\") > -1) { const lowerPath = fileUrl.toLowerCase(); const uploadPathIndex = lowerPath.indexOf("uploadpath"); const prodIndex = lowerPath.indexOf("\\prod\\"); if (uploadPathIndex > -1) { fileUrl = fileUrl.substring(uploadPathIndex).replace(/\\/g, "/"); } else if (prodIndex > -1) { fileUrl = fileUrl .substring(prodIndex + "\\prod\\".length) .replace(/\\/g, "/"); } else { fileUrl = fileUrl.replace(/\\/g, "/"); } } const normalizedLower = String(fileUrl).toLowerCase(); const fileProdIdx = normalizedLower.indexOf("/file/prod/"); if (fileProdIdx > -1) { fileUrl = `/profile/prod/${fileUrl.substring(fileProdIdx + "/file/prod/".length)}`; } const fileTempIdx = normalizedLower.indexOf("/file/temp/"); if (fileTempIdx > -1) { fileUrl = `/profile/temp/${fileUrl.substring(fileTempIdx + "/file/temp/".length)}`; } if (/^\/?uploadPath/i.test(fileUrl)) { fileUrl = fileUrl.replace(/^\/?uploadPath/i, "/profile"); } if (fileUrl && !fileUrl.startsWith("http")) { if (!fileUrl.startsWith("/")) fileUrl = "/" + fileUrl; fileUrl = javaApi + fileUrl; } return fileUrl; }; const getFileAccessUrl = (file = {}) => { if (file?.link) { if (String(file.link).startsWith("http")) return file.link; return normalizeFileUrl(file.link); } const remoteUrl = normalizeFileUrl( file?.url || file?.downloadUrl || file?.tempPath || "" ); if (remoteUrl) return remoteUrl; if (file?._localPreviewUrl) return file._localPreviewUrl; return normalizeFileUrl(file?.tempFilePath || file?.path || ""); }; const formatFileSize = size => { if (!size) return ""; if (size < 1024) return size + "B"; if (size < 1024 * 1024) return (size / 1024).toFixed(1) + "KB"; return (size / (1024 * 1024)).toFixed(1) + "MB"; }; const handleShipUploadError = (message = "ä¸ä¼ æä»¶å¤±è´¥") => { shipUploading.value = false; shipUploadProgress.value = 0; uni.showToast({ title: message, icon: "error" }); }; const handleShipUploadSuccess = (res, file) => { const uploadedFile = res.data; if (!uploadedFile) { handleShipUploadError("ä¸ä¼ ååºæ°æ®æ ¼å¼é误"); return; } const fileData = { ...file, id: uploadedFile.id, tempId: uploadedFile.tempId ?? uploadedFile.tempFileId ?? uploadedFile.id, url: uploadedFile.url || uploadedFile.downloadUrl || uploadedFile.tempPath || file.tempFilePath || file.path, tempPath: uploadedFile.tempPath || file.tempPath || "", _localPreviewUrl: file.tempFilePath || file.path || "", bucketFilename: uploadedFile.bucketFilename || uploadedFile.originalFilename || uploadedFile.originalName || file.name, downloadUrl: uploadedFile.downloadUrl || uploadedFile.url, size: uploadedFile.size || uploadedFile.byteSize || file.size, createTime: uploadedFile.createTime || new Date().getTime(), mediaType: file.type || uploadedFile.mediaType, }; shipFiles.value.push(fileData); uni.showToast({ title: "ä¸ä¼ æå", icon: "success" }); }; const uploadShipWithUniUploadFile = (file, filePath, token) => { if (!filePath) { handleShipUploadError("æä»¶è·¯å¾ä¸åå¨"); return; } shipUploading.value = true; shipUploadProgress.value = 0; const uploadTask = uni.uploadFile({ url: uploadFileUrl.value, filePath, name: "file", formData: { type: shipUploadFormType, }, header: { Authorization: `Bearer ${token}`, }, success: res => { try { if (res.statusCode === 200) { const response = JSON.parse(res.data || "{}"); if (response.code === 200) { handleShipUploadSuccess(response, file); } else { handleShipUploadError(response.msg || "æå¡å¨è¿åé误"); } } else { handleShipUploadError(`æå¡å¨é误ï¼ç¶æç : ${res.statusCode}`); } } catch (e) { console.error("è§£æååºå¤±è´¥:", e); handleShipUploadError("ååºæ°æ®è§£æå¤±è´¥"); } }, fail: err => { let errorMessage = "ä¸ä¼ 失败"; if (err.errMsg) { if (err.errMsg.includes("statusCode: null")) { errorMessage = "ç½ç»è¿æ¥å¤±è´¥ï¼è¯·æ£æ¥ç½ç»è®¾ç½®"; } else if (err.errMsg.includes("timeout")) { errorMessage = "ä¸ä¼ è¶ æ¶ï¼è¯·éè¯"; } else if (err.errMsg.includes("fail")) { errorMessage = "ä¸ä¼ 失败ï¼è¯·æ£æ¥ç½ç»è¿æ¥"; } else { errorMessage = err.errMsg; } } handleShipUploadError(errorMessage); }, complete: () => { shipUploading.value = false; shipUploadProgress.value = 0; }, }); if (uploadTask && uploadTask.onProgressUpdate) { uploadTask.onProgressUpdate(r => { shipUploadProgress.value = r.progress; }); } }; const uploadShipFile = file => { const token = getToken(); if (!token) { handleShipUploadError("ç¨æ·æªç»å½"); return; } uploadShipWithUniUploadFile(file, file.tempFilePath || file.path || "", token); }; const handleBeforeShipUpload = async file => { if (uploadConfig.fileType?.length) { const fileName = file.name || ""; const fileExtension = fileName ? fileName.split(".").pop().toLowerCase() : ""; const expectedTypes = ["jpg", "jpeg", "png", "gif", "webp"]; if (fileExtension && expectedTypes.length > 0) { const isAllowed = expectedTypes.some( t => uploadConfig.fileType.includes(t) && t === fileExtension ); if (!isAllowed) { uni.showToast({ title: `æä»¶æ ¼å¼ä¸æ¯æï¼è¯·éæ© ${expectedTypes.join("/")} æ ¼å¼çå¾ç`, icon: "none", }); return false; } } } uploadShipFile(file); return true; }; const chooseShipImage = () => { if (shipFiles.value.length >= uploadConfig.limit) { uni.showToast({ title: `æå¤åªè½ä¸ä¼ ${uploadConfig.limit}å¼ å¾ç`, icon: "none", }); return; } const remaining = uploadConfig.limit - shipFiles.value.length; if (typeof uni.chooseMedia === "function") { uni.chooseMedia({ count: Math.min(remaining, 1), mediaType: ["image"], sizeType: ["compressed", "original"], sourceType: ["album", "camera"], success: res => { try { const files = res?.tempFiles || []; if (!files.length) throw new Error("æªè·åå°æä»¶"); files.forEach((tf, idx) => { const filePath = tf.tempFilePath || tf.path || ""; const file = { tempFilePath: filePath, path: filePath, type: "image", name: `image_${Date.now()}_${idx}.jpg`, size: tf.size || 0, createTime: Date.now(), uid: Date.now() + Math.random() + idx, }; handleBeforeShipUpload(file); }); } catch (e) { console.error("å¤çå¾çéæ©ç»æå¤±è´¥:", e); uni.showToast({ title: "å¤çæä»¶å¤±è´¥", icon: "error" }); } }, fail: () => uni.showToast({ title: "éæ©å¾ç失败", icon: "error" }), }); return; } uni.chooseImage({ count: 1, sizeType: ["compressed", "original"], sourceType: ["album", "camera"], success: res => { const tempFilePath = res?.tempFilePaths?.[0]; const tempFile = res?.tempFiles?.[0] || {}; if (!tempFilePath) return; handleBeforeShipUpload({ tempFilePath, path: tempFilePath, type: "image", name: `photo_${Date.now()}.jpg`, size: tempFile.size || 0, createTime: Date.now(), uid: Date.now() + Math.random(), }); }, fail: () => uni.showToast({ title: "éæ©å¾ç失败", icon: "none" }), }); }; const previewShipImage = file => { if (!file || !isImageFile(file)) return; const imageUrls = shipFiles.value .filter(f => isImageFile(f)) .map(f => getFileAccessUrl(f)) .filter(Boolean); const current = getFileAccessUrl(file); if (!imageUrls.length || !current) return; uni.previewImage({ urls: imageUrls, current }); }; const removeShipFile = index => { uni.showModal({ title: "确认å é¤", content: "ç¡®å®è¦å é¤è¿ä¸ªæä»¶åï¼", success: res => { if (res.confirm) { shipFiles.value.splice(index, 1); uni.showToast({ title: "å 餿å", icon: "success" }); } }, }); }; const getShipTempFileIds = () => shipFiles.value .map(it => it.tempId ?? it.tempFileId ?? it.id) .filter(v => v !== undefined && v !== null && v !== ""); onMounted(async () => { try { userListNoPageByTenantId().then(res => { @@ -161,11 +593,14 @@ // ç§»é¤äºä»¶çå¬ uni.$off("selectContact", handleSelectContact); }); const typeValue = ref("货车"); const onConfirm = item => { // 设置éä¸çé¨é¨ typeValue.value = item.name; showPicker.value = false; if (item.name === "货车") { form.value.expressNumber = ""; } else { form.value.shippingCarNumber = ""; } }; const goBack = () => { @@ -183,6 +618,10 @@ showToast("请为æ¯ä¸ªå®¡æ¹æ¥éª¤éæ©å®¡æ¹äºº"); return; } if (shipUploading.value) { showToast("å¾çæ£å¨ä¸ä¼ ï¼è¯·ç¨å"); return; } formRef.value .validate() .then(valid => { @@ -198,6 +637,11 @@ salesLedgerProductId: goOutData.value.id, type: typeValue.value, approveUserIds, shippingCarNumber: typeValue.value === "货车" ? String(form.value.shippingCarNumber || "").trim() : "", expressNumber: typeValue.value === "å¿«é" ? String(form.value.expressNumber || "").trim() : "", tempFileIds: getShipTempFileIds(), }; console.log(params, "params"); @@ -273,6 +717,126 @@ <style scoped lang="scss"> @import "@/static/scss/form-common.scss"; .ship-images-card { background: #fff; margin: 16px; border-radius: 16px; padding: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); } .ship-images-header { margin-bottom: 12px; } .ship-images-title { font-size: 16px; font-weight: 600; color: #333; display: block; margin-bottom: 4px; } .ship-images-hint { font-size: 12px; color: #999; } /* 以ä¸ä¸ inspectionUpload/index.vue ç®åä¸ä¼ åºãæä»¶å表ãè§é¢å¼¹çªä¸è´ */ .simple-upload-area { padding: 0; } .upload-buttons { display: flex; gap: 10px; margin-bottom: 15px; } .upload-progress { margin-bottom: 12px; } .file-list { margin-top: 15px; display: flex; flex-wrap: wrap; gap: 12px; } .file-item { display: flex; flex-direction: column; align-items: center; background: #fff; border-radius: 12px; padding: 8px; border: 1px solid #e9ecef; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); width: calc(50% - 6px); min-width: 120px; } .file-preview-container { position: relative; margin-bottom: 8px; } .file-preview { width: 80px; height: 80px; border-radius: 8px; object-fit: cover; border: 2px solid #f0f0f0; } .delete-btn { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; background: #ff4757; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 4px rgba(255, 71, 87, 0.3); } .file-info { text-align: center; width: 100%; } .file-name { font-size: 12px; color: #333; font-weight: 500; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100px; } .file-size { font-size: 10px; color: #999; margin-top: 2px; display: block; } .empty-state { text-align: center; padding: 24px 16px; color: #999; font-size: 14px; background: #f8f9fa; border-radius: 8px; border: 2px dashed #ddd; } .approval-process { background: #fff; margin: 16px; src/pages/sales/salesAccount/index.vue
@@ -137,11 +137,14 @@ <script setup> import { ref } from "vue"; import { onShow } from "@dcloudio/uni-app"; import { ledgerListPage, delLedger, productList } from "@/api/salesManagement/salesLedger"; import { ledgerListPage, delLedger, productList, } from "@/api/salesManagement/salesLedger"; hasShippedProducts, getLedgerShippingLabel, getLedgerShippingTagType, canLedgerShip, executeSalesLedgerShip, } from "@/utils/salesLedgerShip"; import useUserStore from "@/store/modules/user"; import PageHeader from "@/components/PageHeader.vue"; const userStore = useUserStore(); @@ -161,173 +164,7 @@ // éå®å°è´¦æ°æ® const ledgerList = ref([]); // 夿æ¯å¦åå¨å·²åè´§/åè´§å®æç产å const hasShippedProducts = products => { if (!products || products.length === 0) return false; return products.some(p => { const statusCode = normalizeShippingStatusToCode(p.deliveryStatus ?? p.shippingStatus); const statusStr = (p.shippingStatus ?? "").toString().trim(); // æåè´§æ¥æ/车çå·ï¼æç¶ææç¡®ä¸ºâå·²åè´§/åè´§å®æâè§ä¸ºå·²åè´§ return ( statusCode === 5 || statusStr === "å·²åè´§" || statusStr === "åè´§å®æ" || statusStr === "已宿åè´§" || !!p.shippingDate || !!p.shippingCarNumber ); }); }; // å°è´¦åè´§ç¶æï¼1-æªåè´§ï¼2-审æ¹ä¸ï¼3-审æ¹ä¸éè¿ï¼4-审æ¹éè¿ï¼5-å·²åè´§ï¼ä¸å端æä¸¾å¯¹é½ï¼å ¼å®¹å¤ç§å段åï¼ const LEDGER_SHIPPING_LABELS = { 1: "æªåè´§", 2: "审æ¹ä¸", 3: "审æ¹ä¸éè¿", 4: "审æ¹éè¿", 5: "å·²åè´§", }; const normalizeShippingStatusToCode = v => { if (v === null || v === undefined || v === "") return 1; const n = Number(v); if (!Number.isNaN(n) && n >= 1 && n <= 5) return n; const s = String(v).trim(); const textMap = { æªåè´§: 1, å¾ åè´§: 1, 审æ¹ä¸: 2, å®¡æ ¸ä¸: 2, å¾ å®¡æ ¸: 2, 审æ¹ä¸éè¿: 3, å®¡æ ¸æç»: 3, 审æ¹éè¿: 4, å®¡æ ¸éè¿: 4, å·²åè´§: 5, åè´§å®æ: 5, 已宿åè´§: 5, }; return textMap[s] ?? 1; }; const getLedgerShippingStatusCode = item => { if (!item) return 1; const raw = item.deliveryStatus ?? item.shippingApprovalStatus ?? item.shipmentApproveStatus ?? item.ledgerShippingStatus; if (raw !== null && raw !== undefined && raw !== "") { return normalizeShippingStatusToCode(raw); } if (item.shippingStatus !== null && item.shippingStatus !== undefined && item.shippingStatus !== "") { return normalizeShippingStatusToCode(item.shippingStatus); } return 1; }; const getLedgerShippingLabel = item => LEDGER_SHIPPING_LABELS[getLedgerShippingStatusCode(item)] ?? "æªåè´§"; const getLedgerShippingTagType = item => { const t = { 1: "info", 2: "warning", 3: "error", 4: "primary", 5: "success" }; return t[getLedgerShippingStatusCode(item)] ?? "info"; }; const canLedgerShip = item => { const c = getLedgerShippingStatusCode(item); return c === 1 || c === 3; }; /** * 夿æ¯å¦å¯ä»¥åè´§ * åªæå¨äº§åç¶ææ¯å è¶³ï¼åè´§ç¶ææ¯å¾ åè´§åå®¡æ ¸æç»çæ¶åæå¯ä»¥åè´§ * @param row è¡æ°æ® */ const canShip = row => { if (!row) return false; // 产åç¶æå¿ é¡»æ¯å è¶³ï¼approveStatus === 1ï¼ if (row.approveStatus !== 1) return false; // 妿已åè´§ï¼æåè´§æ¥ææè½¦çå·ï¼ï¼ä¸è½å次åè´§ if (row.shippingDate || row.shippingCarNumber) return false; // 妿å端è¿åäºåè´§ç¶æï¼deliveryStatusï¼ï¼å·²åè´§åç¦æ¢å次åè´§ const deliveryStatus = row.deliveryStatus; if (deliveryStatus !== null && deliveryStatus !== undefined && String(deliveryStatus).trim() !== "") { const code = normalizeShippingStatusToCode(deliveryStatus); if (code === 5) return false; } // åè´§ç¶æå¿ é¡»æ¯"å¾ åè´§"æ"å®¡æ ¸æç»" const statusStr = row.shippingStatus ? String(row.shippingStatus).trim() : ""; return statusStr === "å¾ åè´§" || statusStr === "å®¡æ ¸æç»"; }; const productLabel = row => { if (!row) return "产å"; const parts = [row.productCategory, row.floorCode, row.specificationModel].filter(Boolean); return parts.length ? parts.join(" / ") : (row.productName || row.goodsName || "产å"); }; const handleShip = async item => { if (!canLedgerShip(item)) { uni.showToast({ title: "ä» æªåè´§æå®¡æ¹ä¸éè¿æ¶å¯åè´§", icon: "none", }); return; } if (!item?.id) return; showLoadingToast("å è½½ä¸..."); try { const res = await productList({ salesLedgerId: item.id, type: 1 }); const products = res.data || res.records || []; closeToast(); if (!products.length) { uni.showToast({ title: "没æäº§åæ°æ®", icon: "none", }); return; } // å æ£æ¥æ¯å¦åå¨âä¸è¶³âç产åï¼æä¸ä¸ªä¸è¶³å°±ç¦æ¢åè´§å¹¶æç¤º const insufficient = products.filter(p => p?.approveStatus !== 1); if (insufficient.length) { const names = insufficient.slice(0, 3).map(productLabel).join("ã"); uni.showToast({ title: `åå¨åºåä¸è¶³äº§åï¼${names}${insufficient.length > 3 ? "â¦" : ""}`, icon: "none", duration: 2500, }); return; } // å ¨é¨å è¶³åï¼åçéå¯å货产åï¼ä» å¾ åè´§/å®¡æ ¸æç»ï¼ const row = products.find(p => canShip(p)); if (!row) { uni.showToast({ title: "没æå¯åè´§ç产åï¼ä» å¾ åè´§/å®¡æ ¸æç»å¯åè´§ï¼", icon: "none", duration: 2500, }); return; } uni.setStorageSync("goOutData", JSON.stringify(row)); uni.navigateTo({ url: "/pages/sales/salesAccount/goOut", }); } catch (e) { closeToast(); uni.showToast({ title: "å 载产å失败", icon: "none", }); } }; const handleShip = item => executeSalesLedgerShip(item); // è¿åä¸ä¸é¡µ const goBack = () => { @@ -374,7 +211,7 @@ if (hasShippedProducts(products)) { uni.showToast({ title: "å·²åè´§/åè´§å®æçéå®è®¢åä¸è½å é¤", title: "å·²åè´§ãé¨ååè´§æå·²æåè´§è®°å½çéå®è®¢åä¸è½å é¤", icon: "none", }); return; src/pages/sales/salesAccount/out.vue
@@ -113,6 +113,10 @@ class="detail-value danger">ä¸è¶³</text> </view> <view class="detail-row"> <text class="detail-label">åè´§ç¶æ</text> <text class="detail-value">{{ getLedgerShippingLabel(item) }}</text> </view> <view class="detail-row"> <text class="detail-label">å¿«éå ¬å¸</text> <text class="detail-value">{{ dv(item.expressCompany) }}</text> </view> @@ -166,6 +170,7 @@ <script setup> import { ref, onMounted } from "vue"; import { productList } from "@/api/salesManagement/salesLedger"; import { getLedgerShippingLabel } from "@/utils/salesLedgerShip"; // 客æ·ä¿¡æ¯ const supplierId = ref(""); src/utils/salesLedgerShip.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,209 @@ import { productList } from "@/api/salesManagement/salesLedger"; /** å°è´¦/产ååè´§ç¶æï¼1-æªåè´§ ⦠5-å·²åè´§ 6-é¨ååè´§ï¼ä¸å端æä¸¾å¯¹é½ï¼å ¼å®¹å¤ç§å段åï¼ */ export const LEDGER_SHIPPING_LABELS = { 1: "æªåè´§", 2: "审æ¹ä¸", 3: "审æ¹ä¸éè¿", 4: "审æ¹éè¿", 5: "å·²åè´§", 6: "é¨ååè´§", }; export const normalizeShippingStatusToCode = v => { if (v === null || v === undefined || v === "") return 1; const n = Number(v); if (!Number.isNaN(n) && n >= 1 && n <= 6) return n; const s = String(v).trim(); const textMap = { æªåè´§: 1, å¾ åè´§: 1, 审æ¹ä¸: 2, å®¡æ ¸ä¸: 2, å¾ å®¡æ ¸: 2, 审æ¹ä¸éè¿: 3, å®¡æ ¸æç»: 3, 审æ¹éè¿: 4, å®¡æ ¸éè¿: 4, å·²åè´§: 5, åè´§å®æ: 5, 已宿åè´§: 5, é¨ååè´§: 6, é¨åå·²åè´§: 6, }; return textMap[s] ?? 1; }; export const getLedgerShippingStatusCode = item => { if (!item) return 1; const raw = item.deliveryStatus ?? item.shippingApprovalStatus ?? item.shipmentApproveStatus ?? item.ledgerShippingStatus; if (raw !== null && raw !== undefined && raw !== "") { return normalizeShippingStatusToCode(raw); } if (item.shippingStatus !== null && item.shippingStatus !== undefined && item.shippingStatus !== "") { return normalizeShippingStatusToCode(item.shippingStatus); } return 1; }; export const getLedgerShippingLabel = item => LEDGER_SHIPPING_LABELS[getLedgerShippingStatusCode(item)] ?? "æªåè´§"; export const getLedgerShippingTagType = item => { const t = { 1: "info", 2: "warning", 3: "error", 4: "primary", 5: "success", 6: "warning", }; return t[getLedgerShippingStatusCode(item)] ?? "info"; }; /** å°è´¦çº§æ¯å¦å 许åèµ·/ç»§ç»åè´§ï¼å«é¨ååè´§åç»§ç»åå©ä½ï¼ */ export const canLedgerShip = item => { const c = getLedgerShippingStatusCode(item); return c === 1 || c === 3 || c === 6; }; /** productStockStatusï¼0 æªåºåº â ä¸å 许åè´§ï¼ä¸ä¸å¡ç«¯å±ç¤ºä¸è´ï¼ */ export const isProductStockStatusUnOutbound = row => { if (!row) return false; const v = row.productStockStatus; return v === 0 || v === "0"; }; /** * åè¡äº§åæ¯å¦å 许è¿å ¥å货页ï¼å è¶³ + éæªåºåºï¼å¾ åè´§/å®¡æ ¸æç»/é¨ååè´§å¯ç»§ç»ï¼ * é¨ååè´§è¡å¯è½å·²æ shippingDate/车çï¼ä»å è®¸åæ¬¡åè´§ */ export const canShipProductRow = row => { if (!row) return false; if (isProductStockStatusUnOutbound(row)) return false; if (row.approveStatus !== 1) return false; const statusStr = row.shippingStatus ? String(row.shippingStatus).trim() : ""; let rowCode = 1; if (row.deliveryStatus !== null && row.deliveryStatus !== undefined && String(row.deliveryStatus).trim() !== "") { rowCode = normalizeShippingStatusToCode(row.deliveryStatus); } else if (statusStr) { rowCode = normalizeShippingStatusToCode(statusStr); } if (rowCode === 5) return false; const isPartialRow = rowCode === 6 || statusStr === "é¨ååè´§" || statusStr === "é¨åå·²åè´§"; if ((row.shippingDate || row.shippingCarNumber) && !isPartialRow) return false; if (row.deliveryStatus !== null && row.deliveryStatus !== undefined && String(row.deliveryStatus).trim() !== "") { const code = normalizeShippingStatusToCode(row.deliveryStatus); if (code === 5) return false; } return ( statusStr === "å¾ åè´§" || statusStr === "å®¡æ ¸æç»" || statusStr === "é¨ååè´§" || statusStr === "é¨åå·²åè´§" || rowCode === 6 ); }; export const productLabelForShip = row => { if (!row) return "产å"; const parts = [row.productCategory, row.floorCode, row.specificationModel].filter(Boolean); return parts.length ? parts.join(" / ") : row.productName || row.goodsName || "产å"; }; /** 夿æ¯å¦åå¨å·²åè´§ãé¨ååè´§æå·²æåè´§ç迹ç产åï¼å é¤å°è´¦çåºæ¯ï¼ */ export const hasShippedProducts = products => { if (!products || products.length === 0) return false; return products.some(p => { const statusCode = normalizeShippingStatusToCode(p.deliveryStatus ?? p.shippingStatus); const statusStr = (p.shippingStatus ?? "").toString().trim(); return ( statusCode === 5 || statusCode === 6 || statusStr === "å·²åè´§" || statusStr === "åè´§å®æ" || statusStr === "已宿åè´§" || statusStr === "é¨ååè´§" || statusStr === "é¨åå·²åè´§" || !!p.shippingDate || !!p.shippingCarNumber ); }); }; const showLoadingToast = message => { uni.showLoading({ title: message, mask: true }); }; const closeToast = () => { uni.hideLoading(); }; /** * ä¸éå®å°è´¦ãåè´§ãæé®ä¸è´ï¼æ ¡éªå°è´¦ç¶æ â æäº§å â æ ¡éªåºåä¸å¯åè´§è¡ â 跳转 goOut * @param {Record<string, any>} ledgerItem è³å°å« idï¼å«å货审æ¹å段æ¶å å canLedgerShip æ ¡éª */ export async function executeSalesLedgerShip(ledgerItem) { if (!canLedgerShip(ledgerItem)) { uni.showToast({ title: "ä» æªåè´§ã审æ¹ä¸éè¿æé¨ååè´§æ¶å¯ç»§ç»åè´§", icon: "none", }); return; } if (!ledgerItem?.id) return; showLoadingToast("å è½½ä¸..."); try { const res = await productList({ salesLedgerId: ledgerItem.id, type: 1 }); const products = res.data || res.records || []; closeToast(); if (!products.length) { uni.showToast({ title: "没æäº§åæ°æ®", icon: "none" }); return; } const insufficient = products.filter(p => p?.approveStatus !== 1); if (insufficient.length) { const names = insufficient.slice(0, 3).map(productLabelForShip).join("ã"); uni.showToast({ title: `åå¨åºåä¸è¶³äº§åï¼${names}${insufficient.length > 3 ? "â¦" : ""}`, icon: "none", duration: 2500, }); return; } const unOutbound = products.filter(p => isProductStockStatusUnOutbound(p)); if (unOutbound.length) { const names = unOutbound.slice(0, 3).map(productLabelForShip).join("ã"); uni.showToast({ title: `å卿ªåºåºäº§åï¼ä¸è½åè´§ï¼${names}${unOutbound.length > 3 ? "â¦" : ""}`, icon: "none", duration: 2500, }); return; } const row = products.find(p => canShipProductRow(p)); if (!row) { uni.showToast({ title: "没æå¯åè´§ç产åï¼å¾ åè´§ãå®¡æ ¸æç»æé¨ååè´§å¯ç»§ç»ï¼", icon: "none", duration: 2500, }); return; } uni.setStorageSync("goOutData", JSON.stringify(row)); uni.navigateTo({ url: "/pages/sales/salesAccount/goOut", }); } catch (e) { closeToast(); uni.showToast({ title: "å 载产å失败", icon: "none" }); } }