| | |
| | | lower-threshold="80" |
| | | @scrolltolower="loadMore" |
| | | > |
| | | <view v-for="(item, index) in tableData" :key="item.id || index" class="ledger-item"> |
| | | <view v-for="(item, index) in tableData" :key="item.id || index" class="ledger-item" @click="showTransferCard(item)"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="item-actions" v-if="!item.endProductTime"> |
| | | <view class="item-actions" v-if="!isEnded(item)"> |
| | | <up-button |
| | | text="开始报工" |
| | | size="mini" |
| | | type="primary" |
| | | :disabled="!canStartProduction(item) || startSubmittingId === item.id" |
| | | @click="handleStartProduction(item)" |
| | | @click.stop="handleStartProduction(item)" |
| | | /> |
| | | <up-button |
| | | text="结束报工" |
| | | size="mini" |
| | | type="success" |
| | | :disabled="!canEndProduction(item)" |
| | | @click="openEndReport(item)" |
| | | @click.stop="openEndReport(item)" |
| | | /> |
| | | </view> |
| | | </view> |
| | |
| | | <!-- 流转卡弹窗 --> |
| | | <up-popup :show="transferCardVisible" mode="center" @close="transferCardVisible = false" round="10"> |
| | | <view class="qr-popup"> |
| | | <text class="qr-title">工单流转卡二维码</text> |
| | | <view class="qr-box"> |
| | | <geek-qrcode |
| | | v-if="transferCardRowData" |
| | | :val="String(transferCardRowData.id)" |
| | | :size="200" |
| | | /> |
| | | <text class="qr-info" v-if="transferCardRowData">{{ transferCardRowData.workOrderNo || '-' }}</text> |
| | | <view class="transfer-records"> |
| | | <view class="transfer-records-header"> |
| | | <text class="transfer-records-title">报工审核记录</text> |
| | | <text class="transfer-records-count">共 {{ transferCardRecords.length }} 条</text> |
| | | </view> |
| | | <text class="qr-info" v-if="transferCardRowData">{{ transferCardRowData.workOrderNo }}</text> |
| | | <view v-if="transferCardLoading" class="transfer-records-loading">加载中...</view> |
| | | <view v-else-if="transferCardRecords.length > 0" class="transfer-records-list"> |
| | | <view |
| | | v-for="(record, index) in transferCardRecords" |
| | | :key="record.id || index" |
| | | class="transfer-record-item" |
| | | > |
| | | <view class="transfer-record-top"> |
| | | <view class="transfer-record-top-main"> |
| | | <text class="transfer-record-no">{{ record.productNo || `报工记录${index + 1}` }}</text> |
| | | <text class="transfer-record-sub"> |
| | | {{ record.process || '-' }} / {{ record.deviceName || '-' }} |
| | | </text> |
| | | </view> |
| | | <text class="audit-status-tag" :class="`audit-status-${getAuditStatusType(record.auditStatus)}`"> |
| | | {{ getAuditStatusLabel(record.auditStatus) }} |
| | | </text> |
| | | </view> |
| | | <view class="transfer-record-grid"> |
| | | <view |
| | | v-for="field in transferRecordFields" |
| | | :key="field.prop" |
| | | class="transfer-record-cell" |
| | | :class="{ 'is-wide': field.wide }" |
| | | > |
| | | <text class="transfer-record-label">{{ field.label }}</text> |
| | | <view v-if="field.prop === 'teamNames'" class="transfer-record-content"> |
| | | <view v-if="getTeamNameList(record.teamNames).length > 0" class="transfer-tag-list"> |
| | | <text |
| | | v-for="name in getTeamNameList(record.teamNames)" |
| | | :key="name" |
| | | class="transfer-name-tag" |
| | | > |
| | | {{ name }} |
| | | </text> |
| | | </view> |
| | | <text v-else class="transfer-record-value">-</text> |
| | | </view> |
| | | <view v-else-if="field.prop === 'auditStatus'" class="transfer-record-content"> |
| | | <text class="audit-status-tag" :class="`audit-status-${getAuditStatusType(record.auditStatus)}`"> |
| | | {{ getAuditStatusLabel(record.auditStatus) }} |
| | | </text> |
| | | </view> |
| | | <text v-else class="transfer-record-value"> |
| | | {{ formatTransferRecordValue(record, field.prop) }} |
| | | </text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else class="transfer-records-empty">暂无报工审核记录</view> |
| | | </view> |
| | | <up-button text="关闭" @click="transferCardVisible = false" style="margin-top: 20px;"></up-button> |
| | | </view> |
| | | </up-popup> |
| | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, computed, getCurrentInstance } from "vue"; |
| | | import { onShow, onLoad, onReachBottom } from "@dcloudio/uni-app"; |
| | | import { productWorkOrderPage, addProductMain, startProduction, getProductWorkOrderById } from "@/api/productionManagement/productionReporting.js"; |
| | | import { productWorkOrderPage, addProductMain, startProduction, getProductWorkOrderById,getProductionProductMain } from "@/api/productionManagement/productionReporting.js"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import FilesDia from "./components/filesDia.vue"; |
| | |
| | | const loadStatus = ref('loadmore'); |
| | | const transferCardVisible = ref(false); |
| | | const transferCardRowData = ref(null); |
| | | const transferCardLoading = ref(false); |
| | | const transferCardRecords = ref([]); |
| | | const auditStatusOptions = ref([ |
| | | { label: "未审核", value: 0 }, |
| | | { label: "通过", value: 1 }, |
| | | { label: "不通过", value: 2 }, |
| | | ]); |
| | | const transferRecordFields = [ |
| | | { label: "报工人员", prop: "teamNames", wide: true }, |
| | | { label: "审核人", prop: "auditUserName" }, |
| | | { label: "最终审核人", prop: "sureAuditUserName" }, |
| | | { label: "工单编号", prop: "workOrderNo" }, |
| | | { label: "订单编号", prop: "salesContractNo" }, |
| | | { label: "产品名称", prop: "productName" }, |
| | | { label: "产品规格型号", prop: "productModelName" }, |
| | | { label: "产出数量", prop: "quantity" }, |
| | | { label: "报废数量", prop: "scrapQty" }, |
| | | { label: "单位", prop: "unit" }, |
| | | { label: "审核状态", prop: "auditStatus" }, |
| | | { label: "备注信息", prop: "auditOpinion", wide: true }, |
| | | { label: "创建时间", prop: "createTime", wide: true }, |
| | | ]; |
| | | const workOrderFilesRef = ref(null); |
| | | const startSubmittingId = ref(null); |
| | | |
| | |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | workOrderNo: "", |
| | | workOrderId: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | |
| | | return Math.round(n); |
| | | }; |
| | | |
| | | const showTransferCard = (row) => { |
| | | transferCardRowData.value = row; |
| | | transferCardVisible.value = true; |
| | | const getAuditStatusLabel = (val) => { |
| | | const target = auditStatusOptions.value.find(item => Number(item.value) === Number(val)); |
| | | return target?.label || "未知"; |
| | | }; |
| | | |
| | | const openWorkOrderFiles = (row) => { |
| | | workOrderFilesRef.value?.openDialog(row); |
| | | const getAuditStatusType = (val) => { |
| | | const typeMap = { 0: "info", 1: "success", 2: "danger" }; |
| | | return typeMap[Number(val)] || "default"; |
| | | }; |
| | | |
| | | const getTeamNameList = (val) => { |
| | | if (!val) return []; |
| | | return String(val) |
| | | .split(",") |
| | | .map(item => item.trim()) |
| | | .filter(Boolean); |
| | | }; |
| | | |
| | | const formatTransferRecordValue = (record, prop) => { |
| | | if (prop === "auditStatus") { |
| | | return getAuditStatusLabel(record?.[prop]); |
| | | } |
| | | |
| | | const value = record?.[prop]; |
| | | if (value === null || value === undefined || value === "") { |
| | | return "-"; |
| | | } |
| | | |
| | | return String(value); |
| | | }; |
| | | |
| | | const getTransferCardParams = (row) => { |
| | | const params = { |
| | | current: 1, |
| | | size: 100, |
| | | }; |
| | | |
| | | if (row?.id != null) { |
| | | params.workOrderId = row.id; |
| | | } |
| | | if (row?.workOrderNo) { |
| | | params.workOrderNo = row.workOrderNo; |
| | | } |
| | | if (row?.productMainId != null) { |
| | | params.productMainId = row.productMainId; |
| | | } |
| | | |
| | | return params; |
| | | }; |
| | | |
| | | const showTransferCard = async (row) => { |
| | | transferCardRowData.value = row; |
| | | transferCardVisible.value = true; |
| | | transferCardRecords.value = []; |
| | | transferCardLoading.value = true; |
| | | |
| | | try { |
| | | const res = await getProductionProductMain(getTransferCardParams(row)); |
| | | transferCardRecords.value = res?.data?.records || res?.records || []; |
| | | } catch { |
| | | transferCardRecords.value = []; |
| | | showToast("加载报工审核记录失败"); |
| | | } finally { |
| | | transferCardLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const getPendingQty = (row) => { |
| | | const plan = Number(row?.planQuantity) || 0; |
| | | const complete = Number(row?.completeQuantity) || 0; |
| | | return plan - complete; |
| | | return Math.max(plan - complete, 0); |
| | | }; |
| | | |
| | | const isStarted = (row) => { |
| | | if (row?.startProductTime && !row?.endProductTime) return true; |
| | | if (String(row?.reportWork) === "1" && !row?.endProductTime) return true; |
| | | return false; |
| | | if (!row?.id) return false; |
| | | if (Number(row?.completeQuantity) > 0) return true; |
| | | if (row?.startProductTime) return true; |
| | | return String(row?.reportWork) === "1"; |
| | | }; |
| | | |
| | | const isEnded = (row) => { |
| | | return Boolean(row?.endProductTime); |
| | | return getPendingQty(row) <= 0; |
| | | }; |
| | | |
| | | const canEndProduction = (row) => { |
| | | if (!row?.id) return false; |
| | | if (getPendingQty(row) <= 0) return false; |
| | | if (isEnded(row)) return false; |
| | | if (!isStarted(row)) return false; |
| | | return true; |
| | | return isStarted(row); |
| | | |
| | | }; |
| | | |
| | | // 判断是否可以开始报工 |
| | |
| | | if (getPendingQty(row) <= 0) return false; |
| | | if (isEnded(row)) return false; |
| | | if (isStarted(row)) return false; |
| | | if (!canStartProductionByUserIds(userStore.id, row)) return false; |
| | | return true; |
| | | return canStartProductionByUserIds(userStore.id, row); |
| | | |
| | | }; |
| | | |
| | | // 根据userIds判断是否有用户可以报工 |
| | |
| | | } |
| | | } |
| | | |
| | | .qr-popup { |
| | | width: 620rpx; |
| | | max-width: 88vw; |
| | | max-height: 80vh; |
| | | padding: 32rpx 28rpx; |
| | | background: #fff; |
| | | border-radius: 20rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | box-sizing: border-box; |
| | | } |
| | | .qr-title { |
| | | font-size: 30rpx; |
| | | font-weight: 600; |
| | | color: #222; |
| | | } |
| | | .qr-box { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 100%; |
| | | margin-top: 24rpx; |
| | | } |
| | | .qr-info { |
| | | margin-top: 16rpx; |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | } |
| | | .transfer-records { |
| | | width: 100%; |
| | | margin-top: 28rpx; |
| | | } |
| | | .transfer-records-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 16rpx; |
| | | margin-bottom: 16rpx; |
| | | } |
| | | .transfer-records-title { |
| | | font-size: 26rpx; |
| | | font-weight: 600; |
| | | color: #222; |
| | | } |
| | | .transfer-records-count { |
| | | font-size: 22rpx; |
| | | color: #8a8a8a; |
| | | } |
| | | .transfer-records-loading, |
| | | .transfer-records-empty { |
| | | padding: 28rpx 0; |
| | | font-size: 24rpx; |
| | | color: #8a8a8a; |
| | | text-align: center; |
| | | } |
| | | .transfer-records-list { |
| | | max-height: 420rpx; |
| | | overflow: auto; |
| | | } |
| | | .transfer-record-item { |
| | | padding: 20rpx 22rpx; |
| | | border-radius: 16rpx; |
| | | background: #f7f8fa; |
| | | } |
| | | .transfer-record-item + .transfer-record-item { |
| | | margin-top: 16rpx; |
| | | } |
| | | .transfer-record-top { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | gap: 24rpx; |
| | | } |
| | | .transfer-record-top-main { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8rpx; |
| | | min-width: 0; |
| | | } |
| | | .transfer-record-no { |
| | | font-size: 28rpx; |
| | | font-weight: 600; |
| | | color: #222; |
| | | word-break: break-all; |
| | | } |
| | | .transfer-record-sub { |
| | | font-size: 22rpx; |
| | | color: #8a8a8a; |
| | | word-break: break-all; |
| | | } |
| | | .transfer-record-grid { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 16rpx; |
| | | margin-top: 18rpx; |
| | | } |
| | | .transfer-record-cell { |
| | | width: calc(50% - 8rpx); |
| | | min-width: 0; |
| | | } |
| | | .transfer-record-cell.is-wide { |
| | | width: 100%; |
| | | } |
| | | .transfer-record-label { |
| | | display: block; |
| | | margin-bottom: 8rpx; |
| | | font-size: 22rpx; |
| | | color: #8a8a8a; |
| | | } |
| | | .transfer-record-content { |
| | | min-height: 36rpx; |
| | | } |
| | | .transfer-record-value { |
| | | display: block; |
| | | font-size: 24rpx; |
| | | color: #222; |
| | | word-break: break-all; |
| | | } |
| | | .transfer-tag-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10rpx; |
| | | } |
| | | .transfer-name-tag { |
| | | padding: 6rpx 14rpx; |
| | | border-radius: 999rpx; |
| | | background: #e9f2ff; |
| | | color: #2f6bff; |
| | | font-size: 22rpx; |
| | | } |
| | | .audit-status-tag { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | min-height: 40rpx; |
| | | padding: 0 16rpx; |
| | | border-radius: 999rpx; |
| | | font-size: 22rpx; |
| | | box-sizing: border-box; |
| | | } |
| | | .audit-status-info { |
| | | background: #edf1f5; |
| | | color: #6b7280; |
| | | } |
| | | .audit-status-success { |
| | | background: #e9f9ef; |
| | | color: #16a34a; |
| | | } |
| | | .audit-status-danger { |
| | | background: #feeeee; |
| | | color: #dc2626; |
| | | } |
| | | .audit-status-default { |
| | | background: #f4f4f5; |
| | | color: #606266; |
| | | } |
| | | |
| | | .report-modal { |
| | | background: #fff; |
| | | max-height: 85vh; |