<template>
|
<view class="shipment-page">
|
<PageHeader title="发货"
|
@back="goBack" />
|
<view class="form-container">
|
<up-form ref="formRef"
|
:model="form"
|
:rules="rules"
|
label-width="100"
|
input-align="right"
|
error-message-align="right">
|
<!-- 基本信息 -->
|
<u-cell-group title="基本信息"
|
class="form-section">
|
<up-form-item label="发货方式"
|
prop="type"
|
required>
|
<up-input v-model="form.type"
|
readonly
|
placeholder="请选择发货方式"
|
@click="showTypePicker = true" />
|
<template #right>
|
<up-icon name="arrow-right"
|
@click="showTypePicker = true"></up-icon>
|
</template>
|
</up-form-item>
|
<block v-if="form.type === '货车'">
|
<up-form-item label="发货车牌"
|
prop="shippingCarNumber"
|
required>
|
<up-input v-model="form.shippingCarNumber"
|
placeholder="请输入发货车牌号"
|
clearable />
|
</up-form-item>
|
</block>
|
<block v-if="form.type === '快递'">
|
<up-form-item label="快递公司"
|
prop="expressCompany"
|
required>
|
<up-input v-model="form.expressCompany"
|
placeholder="请输入快递公司"
|
clearable />
|
</up-form-item>
|
<up-form-item label="快递单号"
|
prop="expressNumber"
|
required>
|
<up-input v-model="form.expressNumber"
|
placeholder="请输入快递单号"
|
clearable />
|
</up-form-item>
|
</block>
|
</u-cell-group>
|
<!-- 批次选择 -->
|
<u-cell-group title="批次选择"
|
class="form-section">
|
<view class="section-header-info">
|
<text class="subtitle">待发货数量: {{ goOutData.noQuantity || 0 }}</text>
|
</view>
|
<view v-if="batchList.length === 0"
|
class="empty-text">
|
<text>暂无可用库存批次</text>
|
</view>
|
<view v-else
|
class="batch-list">
|
<view v-for="(item, index) in batchList"
|
:key="index"
|
class="batch-card">
|
<view class="batch-header">
|
<text class="batch-no">批号: {{ item.batchNo }}</text>
|
<text class="batch-qty">库存: {{ getAvailableQty(item) }}</text>
|
</view>
|
<up-divider></up-divider>
|
<view class="batch-body">
|
<up-form-item label="发货数量">
|
<up-input v-model="item.deliveryQuantity"
|
type="digit"
|
placeholder="0.00"
|
input-align="right"
|
@blur="onBatchQtyChange(item)" />
|
</up-form-item>
|
</view>
|
</view>
|
</view>
|
</u-cell-group>
|
<!-- 发货图片 -->
|
<u-cell-group title="发货图片"
|
class="form-section">
|
<view class="upload-container">
|
<up-upload :fileList="fileList"
|
@afterRead="afterRead"
|
@delete="deleteFile"
|
multiple
|
:maxCount="9"
|
width="160rpx"
|
height="160rpx" />
|
</view>
|
</u-cell-group>
|
</up-form>
|
</view>
|
<!-- 底部按钮 -->
|
<FooterButtons confirmText="确认发货"
|
@cancel="goBack"
|
@confirm="submitForm" />
|
<!-- 发货方式选择器 -->
|
<up-action-sheet :show="showTypePicker"
|
:actions="typeActions"
|
title="发货方式"
|
@select="onTypeSelect"
|
@close="showTypePicker = false" />
|
</view>
|
</template>
|
|
<script setup>
|
import config from "@/config";
|
import { ref, onMounted, reactive } from "vue";
|
import PageHeader from "@/components/PageHeader.vue";
|
import FooterButtons from "@/components/FooterButtons.vue";
|
import { addShippingInfo } from "@/api/salesManagement/salesLedger";
|
import { getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory";
|
import { getToken } from "@/utils/auth";
|
|
const goOutData = ref({});
|
const batchList = ref([]);
|
const fileList = ref([]);
|
const showTypePicker = ref(false);
|
const typeActions = [
|
{ name: "货车", value: "货车" },
|
{ name: "快递", value: "快递" },
|
];
|
|
const form = reactive({
|
type: "货车",
|
shippingCarNumber: "",
|
expressCompany: "",
|
expressNumber: "",
|
});
|
|
const rules = {
|
type: [{ required: true, message: "请选择发货方式", trigger: "change" }],
|
shippingCarNumber: [
|
{
|
required: true,
|
validator: (rule, value, callback) => {
|
if (form.type === "货车" && !value) {
|
return false;
|
}
|
return true;
|
},
|
message: "请输入车牌号",
|
trigger: "blur",
|
},
|
],
|
expressCompany: [
|
{
|
required: true,
|
validator: (rule, value, callback) => {
|
if (form.type === "快递" && !value) {
|
return false;
|
}
|
return true;
|
},
|
message: "请输入快递公司",
|
trigger: "blur",
|
},
|
],
|
expressNumber: [
|
{
|
required: true,
|
validator: (rule, value, callback) => {
|
if (form.type === "快递" && !value) {
|
return false;
|
}
|
return true;
|
},
|
message: "请输入快递单号",
|
trigger: "blur",
|
},
|
],
|
};
|
|
const formRef = ref(null);
|
|
onMounted(async () => {
|
const storedData = uni.getStorageSync("goOutData");
|
goOutData.value = JSON.parse(storedData || "{}");
|
if (goOutData.value.productModelId) {
|
loadBatches(goOutData.value.productModelId);
|
}
|
});
|
|
const loadBatches = async modelId => {
|
if (!modelId) return;
|
try {
|
const res = await getStockInventoryByModelId(modelId);
|
const rawList = Array.isArray(res?.data)
|
? res.data
|
: res?.data?.records || res?.data?.rows || res || [];
|
const seenIds = new Set();
|
batchList.value = rawList
|
.filter(item => {
|
if (!item?.id || !item?.batchNo || seenIds.has(item.id)) {
|
return false;
|
}
|
seenIds.add(item.id);
|
return true;
|
})
|
.map(item => ({
|
...item,
|
deliveryQuantity: "",
|
}));
|
} catch (e) {
|
console.error("加载批次失败", e);
|
}
|
};
|
|
const getAvailableQty = item => {
|
const quantity =
|
item?.qualitity ??
|
item?.quantity ??
|
item?.unLockedQuantity ??
|
item?.qualifiedUnLockedQuantity ??
|
item?.qualifiedQuantity ??
|
item?.stockQuantity;
|
return quantity ?? 0;
|
};
|
|
const onBatchQtyChange = item => {
|
const val = parseFloat(item.deliveryQuantity);
|
if (isNaN(val) || val <= 0) {
|
item.deliveryQuantity = "";
|
return;
|
}
|
|
const available = getAvailableQty(item);
|
if (val > available) {
|
uni.showToast({ title: "不能超过库存数量", icon: "none" });
|
item.deliveryQuantity = available.toString();
|
}
|
|
const totalToShip = Number(goOutData.value.noQuantity || 0);
|
const otherBatchesTotal = batchList.value.reduce((sum, b) => {
|
if (b.id === item.id) return sum;
|
return sum + Number(b.deliveryQuantity || 0);
|
}, 0);
|
|
if (val + otherBatchesTotal > totalToShip) {
|
uni.showToast({ title: "总数不能超过待发货数量", icon: "none" });
|
item.deliveryQuantity = (totalToShip - otherBatchesTotal).toString();
|
}
|
};
|
|
const onTypeSelect = item => {
|
form.type = item.name;
|
showTypePicker.value = false;
|
};
|
|
const afterRead = async event => {
|
const { file } = event;
|
const lists = [].concat(file);
|
const token = getToken();
|
|
for (let i = 0; i < lists.length; i++) {
|
const item = lists[i];
|
const uploadIndex = fileList.value.length;
|
fileList.value.push({
|
...item,
|
status: "uploading",
|
message: "上传中",
|
});
|
|
uni.uploadFile({
|
url: config.baseUrl + "/common/upload",
|
filePath: item.url,
|
name: "files",
|
header: {
|
Authorization: "Bearer " + token,
|
},
|
success: res => {
|
try {
|
const data = JSON.parse(res.data);
|
if (data.code === 200) {
|
const fileData = Array.isArray(data.data)
|
? data.data[0]
|
: data.data || data;
|
fileList.value[uploadIndex].status = "success";
|
fileList.value[uploadIndex].message = "";
|
fileList.value[uploadIndex].url = fileData.url;
|
fileList.value[uploadIndex].storageBlobDTO = fileData;
|
} else {
|
fileList.value[uploadIndex].status = "failed";
|
fileList.value[uploadIndex].message = data.msg || "上传失败";
|
}
|
} catch (e) {
|
fileList.value[uploadIndex].status = "failed";
|
fileList.value[uploadIndex].message = "解析失败";
|
}
|
},
|
fail: () => {
|
fileList.value[uploadIndex].status = "failed";
|
fileList.value[uploadIndex].message = "网络异常";
|
},
|
});
|
}
|
};
|
|
const deleteFile = event => {
|
fileList.value.splice(event.index, 1);
|
};
|
|
const goBack = () => uni.navigateBack();
|
|
const submitForm = async () => {
|
const valid = await formRef.value.validate().catch(() => false);
|
if (!valid) return;
|
|
const selectedBatches = batchList.value.filter(
|
b => parseFloat(b.deliveryQuantity) > 0
|
);
|
if (selectedBatches.length === 0) {
|
uni.showToast({ title: "请至少填写一个批次的发货数量", icon: "none" });
|
return;
|
}
|
|
// Check if any file is still uploading
|
if (fileList.value.some(f => f.status === "uploading")) {
|
uni.showToast({ title: "请等待图片上传完成", icon: "none" });
|
return;
|
}
|
|
const payload = {
|
salesLedgerId: goOutData.value.salesLedgerId,
|
salesLedgerProductId: goOutData.value.id,
|
type: form.type,
|
shippingCarNumber: form.type === "货车" ? form.shippingCarNumber : "",
|
expressCompany: form.type === "快递" ? form.expressCompany : "",
|
expressNumber: form.type === "快递" ? form.expressNumber : "",
|
storageBlobDTOs: fileList.value
|
.filter(f => f.status === "success")
|
.map(f => f.storageBlobDTO),
|
batchNo: selectedBatches.map(b => b.id),
|
batchNoDetailList: selectedBatches.map(b => ({
|
stockInventoryId: b.id,
|
batchNo: b.batchNo,
|
quantity: parseFloat(b.deliveryQuantity),
|
productModelId: goOutData.value.productModelId,
|
})),
|
};
|
|
try {
|
uni.showLoading({ title: "提交中..." });
|
const res = await addShippingInfo(payload);
|
uni.hideLoading();
|
uni.showToast({ title: "发货成功" });
|
setTimeout(() => goBack(), 500);
|
} catch (e) {
|
uni.hideLoading();
|
uni.showToast({ title: "发货失败", icon: "none" });
|
}
|
};
|
</script>
|
|
<style scoped lang="scss">
|
@import "@/static/scss/form-common.scss";
|
.shipment-page {
|
min-height: 100vh;
|
background: #f8f9fa;
|
padding-bottom: 100px;
|
}
|
|
.form-container {
|
padding: 12px 12px 0;
|
}
|
|
.form-section {
|
margin-bottom: 12px;
|
border-radius: 12px;
|
overflow: hidden;
|
box-shadow: 0 2px 10px rgba(15, 35, 95, 0.05);
|
}
|
|
.section-header-info {
|
padding: 10px 18px;
|
background: #f8fbff;
|
display: flex;
|
justify-content: flex-end;
|
|
.subtitle {
|
font-size: 13px;
|
color: #7a8599;
|
}
|
}
|
|
.batch-list {
|
padding: 12px;
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
}
|
|
.batch-card {
|
background: #fff;
|
border-radius: 12px;
|
padding: 0 12px 12px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
border: 1px solid #f0f3f7;
|
}
|
|
.batch-header {
|
padding: 12px 0;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
|
.batch-no {
|
font-size: 14px;
|
font-weight: 600;
|
color: #22324d;
|
}
|
|
.batch-qty {
|
font-size: 13px;
|
color: #2979ff;
|
background: #eef6ff;
|
padding: 2px 8px;
|
border-radius: 4px;
|
}
|
}
|
|
.empty-text {
|
padding: 30px 12px;
|
text-align: center;
|
color: #999;
|
font-size: 14px;
|
}
|
|
.upload-container {
|
padding: 12px 18px;
|
}
|
|
:deep(.u-cell-group__title) {
|
padding: 14px 18px 10px !important;
|
font-size: 15px !important;
|
font-weight: 600 !important;
|
color: #22324d !important;
|
background: #f8fbff !important;
|
}
|
|
:deep(.u-form-item) {
|
padding: 0 18px !important;
|
}
|
</style>
|