<template>
|
<view class="material-page">
|
<view class="material-header">
|
<CardTitle title="物料出库" :hideAction="false">
|
<template #action>
|
<wd-button
|
type="icon"
|
icon="scan"
|
color="#0d867f"
|
@click="openScan"
|
style="color: #0d867f"
|
></wd-button>
|
</template>
|
</CardTitle>
|
|
<!-- 当前物料信息 -->
|
<view class="material-info" v-if="currentMaterial">
|
<wd-card custom-class="info-card">
|
<view class="info-compact">
|
<view class="icon_box">
|
<wd-icon name="folder" color="#0D867F"></wd-icon>
|
</view>
|
<view class="info-text">
|
<view class="info-line">
|
<text class="label">物料名称:</text>
|
<text class="value">{{ currentMaterial.materialname || "-" }}</text>
|
</view>
|
<view class="info-line">
|
<text class="label">物料规格:</text>
|
<text class="value">{{ currentMaterial.materialspec || "-" }}</text>
|
</view>
|
<view class="info-line">
|
<text class="label">待发货数量:</text>
|
<text class="value">{{ currentMaterial.shippedQuantity || 0 }} 吨</text>
|
</view>
|
</view>
|
</view>
|
</wd-card>
|
</view>
|
</view>
|
|
<!-- 货物列表 -->
|
<view class="list_content">
|
<view v-if="goodsList.length === 0" class="empty_tip">
|
<view class="empty_text">暂无货物数据</view>
|
</view>
|
<view v-for="(item, index) in goodsList" :key="index" class="outbound_item">
|
<view class="outbound_item_left">
|
<view class="outbound_item_content">
|
<view class="outbound_item_row">
|
<text class="outbound_item_label">合同号:</text>
|
<text class="outbound_item_value">{{ item.contractNo || "-" }}</text>
|
</view>
|
<view class="outbound_item_row">
|
<text class="outbound_item_label">生产批次号:</text>
|
<text class="outbound_item_value">{{ item.monofilamentNumber || "-" }}</text>
|
</view>
|
<view class="outbound_item_row">
|
<text class="outbound_item_label">规格型号:</text>
|
<text class="outbound_item_value">{{ item.model || "-" }}</text>
|
</view>
|
<view class="outbound_item_row">
|
<text class="outbound_item_label">重量:</text>
|
<text class="outbound_item_value">{{ item.weight || "-" }} kg</text>
|
</view>
|
<view class="outbound_item_row">
|
<text class="outbound_item_label">厂家:</text>
|
<text class="outbound_item_value">{{ item.clienteleName || "-" }}</text>
|
</view>
|
<view class="outbound_item_row">
|
<text class="outbound_item_label">段长:</text>
|
<text class="outbound_item_value">{{ item.actuallyLength || "-" }} M</text>
|
</view>
|
<view class="outbound_item_row">
|
<text class="outbound_item_label">生产日期:</text>
|
<text class="outbound_item_value">{{ item.productionDate || "-" }}</text>
|
</view>
|
</view>
|
</view>
|
<view class="outbound_item_action">
|
<wd-button
|
type="icon"
|
icon="delete"
|
size="small"
|
custom-class="delete-btn"
|
@click.stop="removeGoodsItem(index)"
|
></wd-button>
|
</view>
|
</view>
|
</view>
|
|
<!-- 底部按钮 -->
|
<view v-if="goodsList.length > 0" class="outbound_footer">
|
<wd-button block @click="handleOutbound" style="background: #0d867f">
|
<text class="text-[#fff]">出库</text>
|
</wd-button>
|
</view>
|
|
<Scan ref="scanRef" emitName="scanMaterial" />
|
<wd-toast />
|
</view>
|
</template>
|
|
<script setup lang="ts">
|
import { ref, onMounted, onUnmounted } from "vue";
|
import { onLoad } from "@dcloudio/uni-app";
|
// @ts-ignore
|
import CardTitle from "@/components/card-title/index.vue";
|
// @ts-ignore
|
import Scan from "@/components/scan/index.vue";
|
import { useToast, dayjs } from "wot-design-uni";
|
import OutboundApi from "@/api/product/outbound";
|
|
const toast = useToast();
|
const scanRef = ref();
|
const vsrccode = ref<string>("");
|
|
// 当前物料信息
|
const currentMaterial = ref<any | null>(null);
|
|
// 货物列表
|
const goodsList = ref<any[]>([]);
|
|
// 格式化时间
|
const formatTime = (date: Date) => {
|
return dayjs(date).format("YYYY-MM-DD HH:mm:ss");
|
};
|
|
// 直接扫码
|
const openScan = () => {
|
scanRef.value?.triggerScan();
|
};
|
|
// 扫码回调
|
const getScanCode = async (code: any) => {
|
try {
|
// 如果 code 是对象且有 code 字段,使用 code.code;否则直接使用 code
|
let scanCode = code.code || code;
|
|
// 如果 scanCode 是对象,尝试获取其 code 字段
|
if (typeof scanCode === "object" && scanCode.code) {
|
scanCode = scanCode.code;
|
}
|
|
// 如果 scanCode 是字符串,直接使用;如果是对象,转为字符串
|
if (typeof scanCode !== "string") {
|
scanCode = JSON.stringify(scanCode);
|
}
|
|
if (!scanCode) {
|
toast.error("扫码内容为空");
|
return;
|
}
|
|
// 解析扫码数据,现在二维码只包含id
|
let scanData;
|
try {
|
scanData = JSON.parse(scanCode);
|
} catch (e) {
|
toast.error("二维码格式错误");
|
return;
|
}
|
|
const outPutId = scanData.id;
|
|
if (!outPutId) {
|
toast.error("二维码格式错误,缺少id信息");
|
return;
|
}
|
|
// 检查是否已存在(根据id判断)
|
const exists = goodsList.value.some((item) => {
|
const itemId = item.id;
|
return itemId && itemId === outPutId && itemId !== "-";
|
});
|
|
if (exists) {
|
toast.error("该条码已存在,请勿重复扫码");
|
return;
|
}
|
|
// 调用接口获取绞线/拉丝详细信息(含出库状态)
|
const { data } = await OutboundApi.getTagByIdAll({
|
outPutId: outPutId,
|
});
|
|
const list = data || [];
|
if (!list.length) {
|
toast.error("未查询到条码信息");
|
return;
|
}
|
|
const tagData = list[0];
|
|
// 已出库校验
|
if (tagData?.state === "已出库") {
|
toast.error("该条码已出库,无法重复出库");
|
return;
|
}
|
|
// 合同号校验:检查扫码的合同号是否等于发货单的vsrccode
|
const scannedContractNo = tagData?.contractno || "";
|
if (scannedContractNo && vsrccode.value && scannedContractNo !== vsrccode.value) {
|
toast.error(`合同号"${scannedContractNo}"与当前发货单为"${vsrccode.value}"不匹配`);
|
return;
|
}
|
|
// 提取数据字段(根据接口返回的数据结构)
|
const parsedData = {
|
id: tagData?.id || outPutId,
|
contractNo: tagData?.contractno || tagData?.contractNo || "-",
|
monofilamentNumber:
|
tagData?.monofilamentnumber ||
|
tagData?.monofilamentNumber ||
|
tagData?.systemno ||
|
tagData?.systemNo ||
|
"-",
|
model: tagData?.model || "-",
|
weight: tagData?.actuallylength || tagData?.actuallyLength || tagData?.weight || "-",
|
clienteleName: tagData?.clientelename || tagData?.clienteleName || "-",
|
actuallyLength: tagData?.actuallylength || tagData?.actuallyLength || "-",
|
productionDate: tagData?.producttime || tagData?.productionDate || "-",
|
type: tagData?.type || "",
|
devicemodel: tagData?.devicemodel || "",
|
state: tagData?.state || "",
|
projectId: tagData?.projectid || tagData?.projectId || "",
|
productuser: tagData?.productuser || "",
|
// 保留原始数据
|
rawData: tagData,
|
scanCode: scanCode,
|
};
|
|
// 添加到列表
|
const newItem = {
|
...parsedData,
|
scanTime: formatTime(new Date()),
|
};
|
|
goodsList.value.push(newItem);
|
toast.success("扫码成功");
|
} catch (error: any) {
|
console.error("扫码处理失败:", error);
|
toast.error(error.msg || "二维码异常,请更换二维码!");
|
}
|
};
|
|
// 删除货物项
|
const removeGoodsItem = (index: number) => {
|
const item = goodsList.value[index];
|
const itemName = item.contractNo || item.monofilamentNumber || `第${index + 1}项`;
|
|
uni.showModal({
|
title: "确认删除",
|
content: `确定要删除"${itemName}"吗?`,
|
confirmText: "删除",
|
cancelText: "取消",
|
confirmColor: "#ff4444",
|
success: (res) => {
|
if (res.confirm) {
|
goodsList.value.splice(index, 1);
|
toast.success("删除成功");
|
}
|
},
|
});
|
};
|
|
// 统计物料数量和类型规格
|
const getMaterialStatistics = () => {
|
const statistics: Record<string, { type: string; model: string; count: number }> = {};
|
|
goodsList.value.forEach((item) => {
|
const type = item.type || "未知类型";
|
const model = item.model || "未知规格";
|
const key = `${type}_${model}`;
|
|
if (!statistics[key]) {
|
statistics[key] = {
|
type,
|
model,
|
count: 0,
|
};
|
}
|
statistics[key].count++;
|
});
|
|
return Object.values(statistics);
|
};
|
|
// 处理出库
|
const handleOutbound = async () => {
|
if (goodsList.value.length === 0) {
|
toast.error("暂无货物数据");
|
return;
|
}
|
|
// 统计物料数量和类型规格
|
const statistics = getMaterialStatistics();
|
|
// 构建统计信息文本
|
let statisticsText = "出库物料统计:\n\n";
|
statistics.forEach((item, index) => {
|
statisticsText += `${index + 1}. ${item.type} - ${item.model}:${item.count}件\n`;
|
});
|
statisticsText += `\n总计:${goodsList.value.length}件\n\n确认出库吗?`;
|
|
// 显示确认弹框
|
uni.showModal({
|
title: "确认出库",
|
content: statisticsText,
|
confirmText: "确认",
|
cancelText: "取消",
|
confirmColor: "#0d867f",
|
success: async (res) => {
|
if (res.confirm) {
|
// 构建请求数据数组
|
const requestData = goodsList.value.map((item) => ({
|
outPutId: item.id,
|
projectId: item.projectId || "",
|
}));
|
|
// 按出库单明细构建请求体
|
const payload = {
|
finishedOutDtos: requestData,
|
cdeliveryid: currentMaterial.value?.cdeliveryid || "",
|
cdeliverybid: currentMaterial.value?.cdeliverybid || "",
|
materialcode: currentMaterial.value?.materialcode || "",
|
};
|
|
try {
|
uni.showLoading({
|
title: "出库中...",
|
mask: true,
|
});
|
|
console.log("request payload", payload);
|
const { code, msg } = await OutboundApi.finishedOutboundByOutboundId(payload);
|
|
uni.hideLoading();
|
|
if (code === 200) {
|
toast.success("出库成功");
|
// 清空列表
|
goodsList.value = [];
|
// 返回上一页
|
setTimeout(() => {
|
uni.navigateBack();
|
}, 1500);
|
} else {
|
toast.error(msg || "出库失败");
|
}
|
} catch (error: any) {
|
uni.hideLoading();
|
console.error("出库失败:", error);
|
toast.error(error.msg || "出库失败");
|
}
|
}
|
},
|
});
|
};
|
|
// 设置扫码监听
|
const setupScanListener = () => {
|
uni.$off("scanMaterial", getScanCode);
|
uni.$on("scanMaterial", getScanCode);
|
};
|
|
onLoad((options: any) => {
|
// 从路由参数中获取物料基本信息、出库单信息和 vsrccode
|
currentMaterial.value = {
|
materialname: options.materialname || "-",
|
materialspec: options.materialspec || "-",
|
shippedQuantity: Number(options.shippedQuantity || 0),
|
cdeliveryid: options.cdeliveryid || "",
|
cdeliverybid: options.cdeliverybid || "",
|
materialcode: options.materialcode || "",
|
};
|
vsrccode.value = options.vsrccode || "";
|
});
|
|
onMounted(() => {
|
setupScanListener();
|
});
|
|
onUnmounted(() => {
|
uni.$off("scanMaterial", getScanCode);
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.material-page {
|
min-height: 100vh;
|
background: #f3f9f8;
|
padding-bottom: 200rpx;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.material-header {
|
position: sticky;
|
top: 0;
|
z-index: 10;
|
background: #f3f9f8;
|
/* 让头部整体更紧凑 */
|
padding-bottom: 8rpx;
|
}
|
|
.material-info {
|
/* 减少上下留白,让卡片更贴近导航栏 */
|
padding: 4rpx 16rpx 0 16rpx;
|
}
|
|
.info-card {
|
border-radius: 16rpx;
|
background: linear-gradient(135deg, #ffffff, #f2fbfa);
|
/* 减弱阴影,整体更扁平紧凑 */
|
box-shadow: 0 4rpx 10rpx rgba(3, 112, 102, 0.05);
|
overflow: hidden;
|
}
|
|
.info-row {
|
/* 收紧每行高度 */
|
padding: 6rpx 0;
|
}
|
|
.info-compact {
|
display: flex;
|
align-items: center;
|
padding: 8rpx 12rpx;
|
}
|
|
.info-text {
|
flex: 1;
|
display: flex;
|
flex-wrap: wrap;
|
row-gap: 4rpx;
|
}
|
|
.info-line {
|
display: flex;
|
font-size: 24rpx;
|
margin-right: 24rpx;
|
}
|
|
.info-line .label {
|
color: #646874;
|
}
|
|
.info-line .value {
|
color: #252525;
|
max-width: 260rpx;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.icon_box {
|
/* 缩小图标容器尺寸 */
|
width: 40rpx;
|
height: 40rpx;
|
border-radius: 10rpx;
|
background: rgba(13, 134, 127, 0.08);
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-right: 8rpx;
|
}
|
|
.list_content {
|
flex: 1;
|
overflow-y: auto;
|
padding: 24rpx;
|
padding-bottom: 220rpx;
|
}
|
|
.empty_tip {
|
margin-top: 120rpx;
|
display: flex;
|
justify-content: center;
|
}
|
|
.empty_text {
|
color: #999;
|
font-size: 28rpx;
|
}
|
|
.outbound_item {
|
display: flex;
|
justify-content: space-between;
|
padding: 20rpx 24rpx;
|
background: #fff;
|
border-radius: 16rpx;
|
margin-bottom: 20rpx;
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
|
}
|
|
.outbound_item_left {
|
flex: 1;
|
}
|
|
.outbound_item_content {
|
display: flex;
|
flex-direction: column;
|
gap: 6rpx;
|
}
|
|
.outbound_item_row {
|
display: flex;
|
font-size: 26rpx;
|
line-height: 1.6;
|
}
|
|
.outbound_item_label {
|
color: #646874;
|
min-width: 160rpx;
|
}
|
|
.outbound_item_value {
|
color: #252525;
|
}
|
|
.outbound_item_action {
|
margin-left: 16rpx;
|
display: flex;
|
align-items: flex-start;
|
}
|
|
.delete-btn {
|
--wd-button-default-border-color: #f5f5f5;
|
}
|
|
.outbound_footer {
|
position: fixed;
|
bottom: 20rpx;
|
left: 0;
|
right: 0;
|
padding: 0 30rpx;
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
z-index: 100;
|
background: transparent;
|
}
|
</style>
|