| | |
| | | <template> |
| | | <view class="list_box"> |
| | | <CardTitle title="出库" :hideAction="false"> |
| | | <template #action> |
| | | <wd-button type="icon" icon="scan" color="#0D867F" @click="openScan"></wd-button> |
| | | </template> |
| | | </CardTitle> |
| | | <view class="list_content"> |
| | | <view v-if="outboundList.length === 0" class="empty_tip"> |
| | | <view class="empty_text">暂无出库数据</view> |
| | | </view> |
| | | <view v-for="(item, index) in outboundList" :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.batchNo || "-" }}</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 class="outbound-page"> |
| | | <view class="search-section"> |
| | | <wd-row> |
| | | <wd-col :span="21"> |
| | | <wd-search |
| | | v-model="searchKeyword" |
| | | placeholder="请输入车牌号" |
| | | placeholder-left |
| | | hide-cancel |
| | | @search="handleSearch" |
| | | @clear="handleClear" |
| | | ></wd-search> |
| | | </wd-col> |
| | | <wd-col :span="3"> |
| | | <view class="scan_box" @click="handleSearch"> |
| | | <wd-icon name="search" size="24px" color="#0D867F"></wd-icon> |
| | | </view> |
| | | </view> |
| | | <view class="outbound_item_action"> |
| | | <wd-button |
| | | type="icon" |
| | | icon="delete" |
| | | size="small" |
| | | custom-class="delete-btn" |
| | | @click.stop="removeOutboundItem(index)" |
| | | ></wd-button> |
| | | </view> |
| | | </view> |
| | | </wd-col> |
| | | </wd-row> |
| | | </view> |
| | | <view v-if="outboundList.length > 0" class="outbound_footer"> |
| | | <wd-button block @click="handleOutbound"> |
| | | <text class="text-[#fff]">出库</text> |
| | | </wd-button> |
| | | |
| | | <view class="list-section"> |
| | | <z-paging |
| | | ref="pagingRef" |
| | | v-model="shippingList" |
| | | :fixed="false" |
| | | :auto-show-back-to-top="true" |
| | | @query="getList" |
| | | > |
| | | <wd-card |
| | | v-for="(item, index) in shippingList" |
| | | :key="item.deliveryid || index" |
| | | custom-class="card_bg" |
| | | @click="toMaterialDetail(item)" |
| | | > |
| | | <template #title> |
| | | <text class="font-medium text-[#252525]">发货单号: {{ item.vbillcode || "-" }}</text> |
| | | </template> |
| | | <wd-row class="my-2"> |
| | | <wd-col :span="24"> |
| | | <view class="flex"> |
| | | <view class="icon_box"> |
| | | <wd-icon name="folder" color="#0D867F"></wd-icon> |
| | | </view> |
| | | <text class="text-[#646874] mx-2"> |
| | | 发货单日期: |
| | | <text class="text-[#252525]">{{ item.dbilldate || "-" }}</text> |
| | | </text> |
| | | </view> |
| | | </wd-col> |
| | | </wd-row> |
| | | <wd-row class="my-2"> |
| | | <wd-col :span="24"> |
| | | <view class="flex"> |
| | | <view class="icon_box"> |
| | | <wd-icon name="folder" color="#0D867F"></wd-icon> |
| | | </view> |
| | | <text class="text-[#646874] mx-2"> |
| | | 车牌号: |
| | | <text class="text-[#252525]">{{ item.carno || "-" }}</text> |
| | | </text> |
| | | </view> |
| | | </wd-col> |
| | | </wd-row> |
| | | <wd-row class="my-2" v-if="item.vmemo"> |
| | | <wd-col :span="24"> |
| | | <view class="flex"> |
| | | <view class="icon_box"> |
| | | <wd-icon name="folder" color="#0D867F"></wd-icon> |
| | | </view> |
| | | <text class="text-[#646874] mx-2"> |
| | | 发货单备注: |
| | | <text class="text-[#252525]">{{ item.vmemo || "-" }}</text> |
| | | </text> |
| | | </view> |
| | | </wd-col> |
| | | </wd-row> |
| | | </wd-card> |
| | | </z-paging> |
| | | </view> |
| | | <Scan ref="scanRef" emitName="scanOutbound" /> |
| | | |
| | | <wd-toast /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import CardTitle from "@/components/card-title/index.vue"; |
| | | import Scan from "@/components/scan/index.vue"; |
| | | import { ref } from "vue"; |
| | | import { useToast } from "wot-design-uni"; |
| | | import { dayjs } from "wot-design-uni"; |
| | | import zPaging from "@/components/z-paging/z-paging.vue"; |
| | | import OutboundApi from "@/api/product/outbound"; |
| | | |
| | | const scanRef = ref(); |
| | | const toast = useToast(); |
| | | const outboundList = ref<any[]>([]); |
| | | const projectId = ref<string | number>(""); |
| | | const pagingRef = ref(); |
| | | const searchKeyword = ref(""); |
| | | const shippingList = ref<any[]>([]); |
| | | |
| | | // 格式化时间 |
| | | const formatTime = (date: Date) => { |
| | | return dayjs(date).format("YYYY-MM-DD HH:mm:ss"); |
| | | }; |
| | | // 获取列表数据 |
| | | const getList = async (pageNo: number, pageSize: number) => { |
| | | const params: any = { |
| | | contractNo: "", |
| | | carNo: searchKeyword.value || "", |
| | | current: pageNo, |
| | | size: pageSize, |
| | | }; |
| | | |
| | | // 扫码回调 |
| | | 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; |
| | | const { code, data, msg } = await OutboundApi.queryErpOutboundOrder(params); |
| | | if (code === 200 && data) { |
| | | const records = data.records || []; |
| | | if (!records.length) { |
| | | pagingRef.value.complete(true); |
| | | } else { |
| | | pagingRef.value.complete(records); |
| | | } |
| | | } else { |
| | | toast.error(msg || "获取出库单列表失败"); |
| | | pagingRef.value.complete(true); |
| | | } |
| | | |
| | | // 如果 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 = outboundList.value.some((item) => { |
| | | const itemId = item.id; |
| | | return itemId && itemId === outPutId && itemId !== "-"; |
| | | }); |
| | | |
| | | if (exists) { |
| | | toast.error("该条码已存在,请勿重复扫码"); |
| | | return; |
| | | } |
| | | |
| | | // 调用接口获取绞线详细信息 |
| | | const { data: tagData } = await OutboundApi.getTagByIdJx({ |
| | | outPutId: outPutId, |
| | | }); |
| | | |
| | | // 提取数据字段(根据接口返回的数据结构) |
| | | const parsedData = { |
| | | id: tagData?.id || outPutId, |
| | | contractNo: tagData?.contractno || tagData?.contractNo || "-", |
| | | batchNo: tagData?.systemno || tagData?.systemNo || tagData?.batchNo || "-", |
| | | model: tagData?.model || "-", |
| | | weight: tagData?.actuallyweight || tagData?.actuallyWeight || tagData?.weight || "-", |
| | | clienteleName: tagData?.clientelename || tagData?.clienteleName || "-", |
| | | actuallyLength: tagData?.actuallylength || tagData?.actuallyLength || "-", |
| | | productionDate: tagData?.producttime || tagData?.productionDate || "-", |
| | | projectId: tagData?.projectid || tagData?.projectId || projectId.value || "", |
| | | // 保留原始数据 |
| | | rawData: tagData, |
| | | scanCode: scanCode, |
| | | }; |
| | | |
| | | // 如果接口返回的数据中有 projectId,保存它 |
| | | if (parsedData.projectId && !projectId.value) { |
| | | projectId.value = parsedData.projectId; |
| | | } |
| | | |
| | | // 添加到列表 |
| | | const newItem = { |
| | | ...parsedData, |
| | | scanTime: formatTime(new Date()), |
| | | }; |
| | | |
| | | outboundList.value.push(newItem); |
| | | toast.success("扫码成功"); |
| | | } catch (error: any) { |
| | | console.error("扫码处理失败:", error); |
| | | toast.error(error.msg || "二维码异常,请更换二维码!"); |
| | | } catch (error) { |
| | | console.error("获取出库单列表失败:", error); |
| | | toast.error("获取出库单列表失败"); |
| | | pagingRef.value.complete(true); |
| | | } |
| | | }; |
| | | |
| | | // 触发扫码 |
| | | const openScan = () => { |
| | | scanRef.value.triggerScan(); |
| | | // 搜索处理 |
| | | const handleSearch = () => { |
| | | // 重新加载数据 |
| | | pagingRef.value.reload(); |
| | | }; |
| | | |
| | | // 删除项 |
| | | const removeOutboundItem = (index: number) => { |
| | | const item = outboundList.value[index]; |
| | | const itemName = item.contractNo || item.batchNo || `第${index + 1}项`; |
| | | // 清空搜索 |
| | | const handleClear = () => { |
| | | searchKeyword.value = ""; |
| | | handleSearch(); |
| | | }; |
| | | |
| | | uni.showModal({ |
| | | title: "确认删除", |
| | | content: `确定要删除"${itemName}"吗?`, |
| | | confirmText: "删除", |
| | | cancelText: "取消", |
| | | confirmColor: "#ff4444", |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | outboundList.value.splice(index, 1); |
| | | toast.success("删除成功"); |
| | | } |
| | | }, |
| | | // 跳转到物料详情页 |
| | | const toMaterialDetail = (item: any) => { |
| | | uni.navigateTo({ |
| | | url: `/pages/outbound/material?id=${item.deliveryid}&vbillcode=${item.vbillcode}`, |
| | | }); |
| | | }; |
| | | |
| | | // 处理出库 |
| | | const handleOutbound = async () => { |
| | | if (outboundList.value.length === 0) { |
| | | toast.error("暂无出库数据"); |
| | | return; |
| | | } |
| | | |
| | | // 构建请求数据 |
| | | const requestData = outboundList.value.map((item) => ({ |
| | | outPutId: item.id, |
| | | projectId: item.projectId, |
| | | })); |
| | | |
| | | try { |
| | | uni.showLoading({ |
| | | title: "出库中...", |
| | | mask: true, |
| | | }); |
| | | console.log("requestData", requestData); |
| | | const { code, msg } = await OutboundApi.finishedOutbound(requestData); |
| | | |
| | | uni.hideLoading(); |
| | | |
| | | if (code === 200) { |
| | | toast.success("出库成功"); |
| | | // 清空列表 |
| | | outboundList.value = []; |
| | | } else { |
| | | toast.error(msg || "出库失败"); |
| | | } |
| | | } catch (error: any) { |
| | | uni.hideLoading(); |
| | | console.error("出库失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 确保先移除再添加监听 |
| | | const setupScanListener = () => { |
| | | uni.$off("scanOutbound", getScanCode); // 先移除旧的 |
| | | uni.$on("scanOutbound", getScanCode); // 再添加新的 |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | setupScanListener(); |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | uni.$off("scanOutbound", getScanCode); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .list_box { |
| | | height: calc(100vh - 100px); |
| | | .outbound-page { |
| | | min-height: 100vh; |
| | | background: #f3f9f8; |
| | | display: flex; |
| | | flex-direction: column; |
| | | padding: 10rpx; |
| | | } |
| | | |
| | | .list_content { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | padding-bottom: 120rpx; |
| | | .search-section { |
| | | background: #fff; |
| | | padding: 10rpx; |
| | | margin-bottom: 10rpx; |
| | | } |
| | | |
| | | .scan_box { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 38px; |
| | | height: 38px; |
| | | padding: 6px; |
| | | background: #fff; |
| | | } |
| | | |
| | | .list-section { |
| | | padding: 0 4px; |
| | | height: calc(100vh - 150rpx); |
| | | } |
| | | |
| | | .card_bg { |
| | | box-shadow: 0px 0px 12px 0px rgba(0, 0, 0, 0.05); |
| | | padding-bottom: 10px; |
| | | margin-bottom: 10px; |
| | | margin-left: 6px; |
| | | margin-right: 6px; |
| | | } |
| | | |
| | | .shipping-card { |
| | | margin: 0 5px; |
| | | } |
| | | |
| | | .icon_box { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 20px; |
| | | height: 20px; |
| | | background: #e7f4ec99; |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | .empty_tip { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 100rpx 0; |
| | | color: #999; |
| | | |
| | | .empty_text { |
| | | font-size: 32rpx; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | } |
| | | |
| | | .outbound_item { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 30rpx; |
| | | margin: 20rpx; |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | .outbound_item_left { |
| | | display: flex; |
| | | align-items: center; |
| | | flex: 1; |
| | | } |
| | | |
| | | .outbound_item_content { |
| | | flex: 1; |
| | | } |
| | | |
| | | .outbound_item_row { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 12rpx; |
| | | font-size: 28rpx; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | |
| | | .outbound_item_label { |
| | | color: #666; |
| | | min-width: 140rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .outbound_item_value { |
| | | color: #333; |
| | | flex: 1; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .outbound_item_action { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-left: 20rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | padding: 40px 0; |
| | | } |
| | | |
| | | :deep(.delete-btn) { |
| | | .wd-button__content { |
| | | color: #ff4444 !important; |
| | | } |
| | | |
| | | &:active { |
| | | opacity: 0.7; |
| | | } |
| | | .empty_text { |
| | | color: #999; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | :deep(.wd-button__content) { |
| | | color: #0d867f; |
| | | } |
| | | |
| | | .outbound_footer { |
| | | position: fixed; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | padding: 20rpx 30rpx; |
| | | padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); |
| | | z-index: 100; |
| | | :deep(.wd-search__block) { |
| | | border-radius: unset; |
| | | } |
| | | </style> |