From 3682ad63b5bdb47228325dea1efe2bb9069254a5 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期一, 11 五月 2026 15:53:18 +0800
Subject: [PATCH] 合格出库扫销售二维码进行发货
---
src/pages/sales/salesAccount/goOut.vue | 570 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 567 insertions(+), 3 deletions(-)
diff --git a/src/pages/sales/salesAccount/goOut.vue b/src/pages/sales/salesAccount/goOut.vue
index 35e2a3f..05e3f90 100644
--- a/src/pages/sales/salesAccount/goOut.vue
+++ b/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("瑙f瀽鍝嶅簲澶辫触:", e);
+ handleShipUploadError("鍝嶅簲鏁版嵁瑙f瀽澶辫触");
+ }
+ },
+ 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("鍥剧墖姝e湪涓婁紶锛岃绋嶅��");
+ 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;
--
Gitblit v1.9.3