| | |
| | | <template> |
| | | <view class="invoice-add"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="ç产æ¥å·¥" |
| | | @back="goBack" /> |
| | | <!-- 表åå
容 --> |
| | | <u-form @submit="submitForm" |
| | | ref="formRef" |
| | | label-width="110" |
| | | input-align="right" |
| | | error-message-align="right"> |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <view class="form-section"> |
| | | <u-form-item label="æºå°" |
| | | prop="deviceName" |
| | | required> |
| | | <u-input v-model="form.deviceName" |
| | | disabled |
| | | placeholder="请è¾å
¥æºå°" /> |
| | | </u-form-item> |
| | | <u-form-item label="计åå¼å§æ¶é´" |
| | | prop="planStartTime" |
| | | required> |
| | | <u-input v-model="form.planStartTime" |
| | | placeholder="è¯·éæ©è®¡åå¼å§æ¶é´" |
| | | readonly |
| | | @click="showStartTimePicker = true" |
| | | suffix-icon="calendar" /> |
| | | </u-form-item> |
| | | <u-form-item label="计åç»ææ¶é´" |
| | | prop="planEndTime" |
| | | required> |
| | | <u-input v-model="form.planEndTime" |
| | | placeholder="è¯·éæ©è®¡åç»ææ¶é´" |
| | | readonly |
| | | @click="showEndTimePicker = true" |
| | | suffix-icon="calendar" /> |
| | | </u-form-item> |
| | | <u-form-item label="å¾
ç产æ°é" |
| | | prop="planQuantity" |
| | | required> |
| | | <u-input v-model="form.planQuantity" |
| | | placeholder="èªå¨å¡«å
" |
| | | disabled /> |
| | | </u-form-item> |
| | | <u-form-item label="æ¬æ¬¡ç产æ°é" |
| | | prop="quantity" |
| | | required> |
| | | <u-input v-model="form.quantity" |
| | | placeholder="请è¾å
¥" |
| | | type="number" /> |
| | | </u-form-item> |
| | | <u-form-item label="æ¥åºæ°é" |
| | | prop="scrapQty"> |
| | | <u-input v-model="form.scrapQty" |
| | | placeholder="请è¾å
¥" |
| | | type="number" /> |
| | | </u-form-item> |
| | | <!-- çç»ä¿¡æ¯åå®¡æ ¸äºº --> |
| | | <u-form-item v-for="(item, key) in pickerFields" |
| | | :key="key" |
| | | :label="item.label" |
| | | :prop="key + '.name'" |
| | | required> |
| | | <u-input v-model="form[key].name" |
| | | :placeholder="form[key].name ? '已鿩: ' + form[key].name : 'è¯·éæ©' + item.label" |
| | | readonly |
| | | @click="openProducerPicker(key)" |
| | | suffix-icon="arrow-down" /> |
| | | </u-form-item> |
| | | <view class="work-order"> |
| | | <!-- éç¨é¡µé¢å¤´é¨ --> |
| | | <PageHeader title="ç产工å" @back="goBack" /> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | placeholder="请è¾å
¥å·¥åç¼å·æç´¢" |
| | | v-model="searchForm.workOrderNo" |
| | | @confirm="handleQuery" |
| | | clearable |
| | | /> |
| | | </view> |
| | | |
| | | <view class="filter-button" @click="handleQuery"> |
| | | <up-icon name="search" size="24" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <!-- 使ç¨FooterButtonsç»ä»¶ --> |
| | | <FooterButtons @cancel="goBack" |
| | | @confirm="submitForm" |
| | | :loading="submitting" /> |
| | | <!-- 为åºé¨æé®çåºç©ºé´ --> |
| | | <view style="height: 80px;"></view> |
| | | </u-form> |
| | | <!-- çäº§äººéæ©å¨ --> |
| | | <up-action-sheet :show="showProducerPicker" |
| | | :actions="producerList" |
| | | title="éæ©ç产人" |
| | | @select="onProducerConfirm" |
| | | @close="showProducerPicker = false" /> |
| | | </view> |
| | | |
| | | <!-- å·¥åå表 --> |
| | | <scroll-view |
| | | scroll-y |
| | | class="ledger-list" |
| | | v-if="tableData.length > 0" |
| | | lower-threshold="80" |
| | | @scrolltolower="loadMore" |
| | | > |
| | | <view v-for="(item, index) in tableData" :key="item.id || index" class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | | <up-icon name="file-text" size="16" color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.workOrderNo }}</text> |
| | | </view> |
| | | <view class="item-right"> |
| | | <text class="item-tag tag-type">{{ item.workOrderType }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <up-divider></up-divider> |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产ååç§°</text> |
| | | <text class="detail-value">{{ item.productName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.model }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æºå°åç§°</text> |
| | | <text class="detail-value">{{ item.deviceName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥åºåç§° / 计éåä½</text> |
| | | <text class="detail-value">{{ item.processName }} / {{ item.unit }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">éæ±/宿æ°é</text> |
| | | <text class="detail-value">{{ item.planQuantity }} / {{ item.completeQuantity }}</text> |
| | | </view> |
| | | |
| | | <view class="progress-section"> |
| | | <text class="detail-label">宿è¿åº¦</text> |
| | | <view class="progress-bar"> |
| | | <up-line-progress |
| | | :percentage="toProgressPercentage(item.completionStatus)" |
| | | activeColor="#2979ff" |
| | | :showText="true" |
| | | ></up-line-progress> |
| | | </view> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å®é
å¼å§</text> |
| | | <text class="detail-value">{{ item.startProductTime || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å®é
ç»æ</text> |
| | | <text class="detail-value">{{ item.endProductTime || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å¼å§æ¶é´éæ©å¨ --> |
| | | <up-datetime-picker :show="showStartTimePicker" |
| | | v-model="startTimeValue" |
| | | mode="datetime" |
| | | @confirm="onStartTimeConfirm" |
| | | @cancel="showStartTimePicker = false" /> |
| | | <view class="item-actions" v-if="!item.endProductTime"> |
| | | <up-button |
| | | text="å¼å§æ¥å·¥" |
| | | size="mini" |
| | | type="primary" |
| | | :disabled="!canStartProduction(item) || startSubmittingId === item.id" |
| | | @click="handleStartProduction(item)" |
| | | /> |
| | | <up-button |
| | | text="ç»ææ¥å·¥" |
| | | size="mini" |
| | | type="success" |
| | | :disabled="!canEndProduction(item)" |
| | | @click="openEndReport(item)" |
| | | /> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="loadStatus" /> |
| | | </scroll-view> |
| | | |
| | | <view v-else-if="!loading" class="no-data"> |
| | | <up-empty mode="data" text="ææ å·¥åæ°æ®"></up-empty> |
| | | </view> |
| | | |
| | | <!-- ç»ææ¶é´éæ©å¨ --> |
| | | <up-datetime-picker :show="showEndTimePicker" |
| | | v-model="endTimeValue" |
| | | mode="datetime" |
| | | @confirm="onEndTimeConfirm" |
| | | @cancel="showEndTimePicker = false" /> |
| | | <!-- æµè½¬å¡å¼¹çª --> |
| | | <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" |
| | | /> |
| | | </view> |
| | | <text class="qr-info" v-if="transferCardRowData">{{ transferCardRowData.workOrderNo }}</text> |
| | | <up-button text="å
³é" @click="transferCardVisible = false" style="margin-top: 20px;"></up-button> |
| | | </view> |
| | | </up-popup> |
| | | |
| | | <!-- ç»ææ¥å·¥å¼¹çª --> |
| | | <up-popup |
| | | v-model:show="endReportVisible" |
| | | mode="bottom" |
| | | :round="20" |
| | | :safeAreaInsetBottom="true" |
| | | @close="closeEndReport" |
| | | > |
| | | <view class="report-modal"> |
| | | <view class="modal-header"> |
| | | <view class="modal-header-left"> |
| | | <text class="modal-title">ç»ææ¥å·¥</text> |
| | | <text class="modal-subtitle" v-if="endReportRow"> |
| | | {{ endReportRow.workOrderNo || '-' }} · {{ endReportRow.deviceName || '-' }} |
| | | </text> |
| | | </view> |
| | | <view class="close-btn" @click="closeEndReport"> |
| | | <up-icon name="close" size="20" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | |
| | | <scroll-view class="modal-content" scroll-y> |
| | | <view class="report-summary" v-if="endReportRow"> |
| | | <view class="summary-left"> |
| | | <text class="summary-title">{{ endReportRow.productName || '-' }}</text> |
| | | <text class="summary-sub">{{ endReportRow.processName || '-' }} · {{ endReportRow.model || '-' }}</text> |
| | | </view> |
| | | <view class="summary-right"> |
| | | <text class="summary-num">{{ endReportForm.planQuantity || '0' }}</text> |
| | | <text class="summary-label">å¾
ç产</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <up-form :model="endReportForm" labelWidth="120"> |
| | | <view class="form-section"> |
| | | <text class="section-title">æ°éä¿¡æ¯</text> |
| | | <up-form-item label="å¾
ç产æ°é"> |
| | | <up-input v-model="endReportForm.planQuantity" disabled /> |
| | | </up-form-item> |
| | | <up-form-item label="æ¬æ¬¡ç产æ°é" required> |
| | | <up-input v-model="endReportForm.quantity" disabled /> |
| | | </up-form-item> |
| | | <up-form-item label="补产æ°é"> |
| | | <up-input v-model="endReportForm.replenishQty" type="number" placeholder="请è¾å
¥" /> |
| | | </up-form-item> |
| | | <up-form-item label="æ¥åºæ°é"> |
| | | <up-input v-model="endReportForm.scrapQty" type="number" placeholder="请è¾å
¥" /> |
| | | </up-form-item> |
| | | </view> |
| | | |
| | | <view class="form-section"> |
| | | <text class="section-title">çç»ä¿¡æ¯</text> |
| | | <up-form-item label="çç»äººå"> |
| | | <up-input |
| | | v-model="teamDisplayText" |
| | | readonly |
| | | placeholder="è¯·éæ©(å¯å¤é)" |
| | | @click="openTeamPicker" |
| | | suffixIcon="arrow-down" |
| | | /> |
| | | </up-form-item> |
| | | </view> |
| | | |
| | | <view class="form-section"> |
| | | <text class="section-title">å®¡æ ¸ä¿¡æ¯</text> |
| | | <up-form-item label="å®¡æ ¸äºº" required> |
| | | <up-input |
| | | v-model="endReportForm.auditUserName" |
| | | readonly |
| | | placeholder="è¯·éæ©" |
| | | @click="openAuditPicker" |
| | | suffixIcon="arrow-down" |
| | | /> |
| | | </up-form-item> |
| | | </view> |
| | | </up-form> |
| | | <view style="height: 24px;"></view> |
| | | </scroll-view> |
| | | |
| | | <view class="modal-footer"> |
| | | <up-button |
| | | text="åæ¶" |
| | | type="info" |
| | | plain |
| | | @click="closeEndReport" |
| | | :customStyle="{ marginRight: '12px', flex: 1 }" |
| | | /> |
| | | <up-button text="æäº¤" type="primary" @click="submitEndReport" :customStyle="{ flex: 1 }" /> |
| | | </view> |
| | | </view> |
| | | </up-popup> |
| | | |
| | | <!-- çç»éæ©å¼¹çª --> |
| | | <up-popup v-model:show="teamPickerVisible" mode="bottom" :round="20" :safeAreaInsetBottom="true"> |
| | | <view class="team-modal"> |
| | | <view class="modal-header"> |
| | | <text class="modal-title">éæ©çç»æå</text> |
| | | <view class="close-btn" @click="teamPickerVisible = false"> |
| | | <up-icon name="close" size="20" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <scroll-view class="team-content" scroll-y> |
| | | <up-checkbox-group v-model="teamCheckedIds" placement="column"> |
| | | <up-checkbox |
| | | v-for="u in userOptions" |
| | | :key="u.value" |
| | | :label="u.name" |
| | | :name="String(u.value)" |
| | | :customStyle="{ marginBottom: '10px' }" |
| | | /> |
| | | </up-checkbox-group> |
| | | </scroll-view> |
| | | <view class="modal-footer"> |
| | | <up-button text="åæ¶" type="info" plain @click="teamPickerVisible = false" /> |
| | | <up-button text="ç¡®å®" type="primary" @click="confirmTeamPicker" /> |
| | | </view> |
| | | </view> |
| | | </up-popup> |
| | | |
| | | <!-- å®¡æ ¸äººéæ©å¨ --> |
| | | <up-action-sheet |
| | | :show="auditPickerVisible" |
| | | :actions="auditActions" |
| | | title="éæ©å®¡æ ¸äºº" |
| | | @select="onAuditSelect" |
| | | @close="auditPickerVisible = false" |
| | | /> |
| | | |
| | | <!-- æ¶é´éæ©å¨ --> |
| | | <up-datetime-picker |
| | | :show="startTimePickerVisible" |
| | | v-model="startTimeValue" |
| | | mode="datetime" |
| | | @confirm="onStartTimeConfirm" |
| | | @cancel="startTimePickerVisible = false" |
| | | /> |
| | | <up-datetime-picker |
| | | :show="endTimePickerVisible" |
| | | v-model="endTimeValue" |
| | | mode="datetime" |
| | | @confirm="onEndTimeConfirm" |
| | | @cancel="endTimePickerVisible = false" |
| | | /> |
| | | |
| | | <!-- éä»¶ç»ä»¶ --> |
| | | <FilesDia ref="workOrderFilesRef" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, nextTick } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import FooterButtons from "@/components/FooterButtons.vue"; |
| | | 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 { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import FilesDia from "./components/filesDia.vue"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | import { addProductMain } from "@/api/productionManagement/productionReporting"; |
| | | import { getInfo } from "@/api/login"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user"; |
| | | const userStore = useUserStore(); |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | const loading = ref(false); |
| | | const tableData = ref([]); |
| | | const loadStatus = ref('loadmore'); |
| | | const transferCardVisible = ref(false); |
| | | const transferCardRowData = ref(null); |
| | | const workOrderFilesRef = ref(null); |
| | | const startSubmittingId = ref(null); |
| | | |
| | | // è¡¨åæ°æ® |
| | | const form = ref({ |
| | | deviceName: "", |
| | | planStartTime: "", |
| | | planEndTime: "", |
| | | planQuantity: "", |
| | | quantity: "", |
| | | scrapQty: "", |
| | | workOrderId: "", |
| | | productProcessRouteItemId: "", |
| | | userId: { value: "", name: "" }, // çç»ä¿¡æ¯ |
| | | auditUserId: { value: "", name: "" }, // å®¡æ ¸äºº |
| | | const endReportVisible = ref(false); |
| | | const endReportRow = ref(null); |
| | | const endReportForm = reactive({ |
| | | planQuantity: "", |
| | | quantity: "", |
| | | replenishQty: "0", |
| | | scrapQty: "0", |
| | | teamList: [], |
| | | startTime: "", |
| | | endTime: "", |
| | | auditUserId: "", |
| | | auditUserName: "", |
| | | userId: "", |
| | | userName: "", |
| | | workOrderId: "", |
| | | reportWork: "", |
| | | productProcessRouteItemId: "", |
| | | productMainId: null, |
| | | }); |
| | | |
| | | const userOptions = ref([]); |
| | | const auditPickerVisible = ref(false); |
| | | const auditActions = computed(() => userOptions.value.map(u => ({ name: u.name, value: u.value }))); |
| | | |
| | | const teamPickerVisible = ref(false); |
| | | const teamCheckedIds = ref([]); |
| | | const teamDisplayText = computed(() => { |
| | | const list = Array.isArray(endReportForm.teamList) ? endReportForm.teamList : []; |
| | | const names = list.map(i => String(i?.userName ?? "")).filter(Boolean); |
| | | if (names.length === 0) return ""; |
| | | if (names.length <= 2) return names.join("ã"); |
| | | return `${names.slice(0, 2).join("ã")}ç${names.length}人`; |
| | | }); |
| | | |
| | | const startTimePickerVisible = ref(false); |
| | | const endTimePickerVisible = ref(false); |
| | | const startTimeValue = ref(Date.now()); |
| | | const endTimeValue = ref(Date.now()); |
| | | |
| | | const routeOrderRow = ref(null); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | workOrderNo: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | tableData.value = []; |
| | | loadStatus.value = "loadmore"; |
| | | getList(); |
| | | }; |
| | | |
| | | const showToast = (message) => { |
| | | uni.showToast({ title: message, icon: "none" }); |
| | | }; |
| | | |
| | | const ensureUserInfo = async () => { |
| | | if (userStore.id) return; |
| | | try { |
| | | await userStore.getInfo(); |
| | | } catch { |
| | | } |
| | | }; |
| | | |
| | | const ensureUserOptions = async () => { |
| | | if (userOptions.value.length > 0) return; |
| | | try { |
| | | const res = await userListNoPageByTenantId(); |
| | | const users = res?.data || []; |
| | | users.unshift({ |
| | | nickName:"ä»»æç¨æ·", |
| | | userId:"-1", |
| | | }) |
| | | userOptions.value = users.map(u => ({ |
| | | name: u?.nickName || "", |
| | | value: String(u?.userId ?? ""), |
| | | })).filter(u => u.value); |
| | | } catch { |
| | | userOptions.value = []; |
| | | } |
| | | }; |
| | | |
| | | const getList = () => { |
| | | console.log(searchForm.value); |
| | | if (loading.value) return; |
| | | loading.value = true; |
| | | |
| | | const params = { ...searchForm.value, ...page }; |
| | | |
| | | productWorkOrderPage(params).then((res) => { |
| | | loading.value = false; |
| | | const records = res.data.records || []; |
| | | tableData.value = page.current === 1 ? records : [...tableData.value, ...records]; |
| | | page.total = res.data.total; |
| | | |
| | | if (tableData.value.length >= page.total) { |
| | | loadStatus.value = 'nomore'; |
| | | } else { |
| | | loadStatus.value = 'loadmore'; |
| | | } |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | loadStatus.value = "loadmore"; |
| | | uni.showToast({ title: 'å 载失败', icon: 'error' }); |
| | | }); |
| | | }; |
| | | |
| | | // è¿éçåæ®µé
ç½®ç¨äºæ¨¡çå¾ªç¯ |
| | | const pickerFields = { |
| | | userId: { label: "çç»ä¿¡æ¯" }, |
| | | auditUserId: { label: "å®¡æ ¸äºº" }, |
| | | }; |
| | | const loadSingleWorkOrder = async (row) => { |
| | | if (!row?.id) { |
| | | handleQuery(); |
| | | return; |
| | | } |
| | | loading.value = true; |
| | | try { |
| | | const res = await getProductWorkOrderById({ id: row.id }); |
| | | const data = res?.data; |
| | | tableData.value = data ? [data] : []; |
| | | page.total = tableData.value.length; |
| | | loadStatus.value = 'nomore'; |
| | | } catch { |
| | | tableData.value = []; |
| | | showToast("å 载工å失败"); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // çäº§äººéæ©å¨ç¶æ |
| | | const showProducerPicker = ref(false); |
| | | // æ¶é´éæ©å¨ç¶æ |
| | | const showStartTimePicker = ref(false); |
| | | const showEndTimePicker = ref(false); |
| | | const startTimeValue = ref(Number(new Date())); |
| | | const endTimeValue = ref(Number(new Date())); |
| | | const loadMore = () => { |
| | | console.log(loadStatus.value); |
| | | if (loadStatus.value === 'nomore' || loading.value) return; |
| | | loadStatus.value = "loading"; |
| | | page.current++; |
| | | getList(); |
| | | }; |
| | | |
| | | const producerList = ref([]); |
| | | const currentField = ref(""); // å½åéæ©çåæ®µ |
| | | onReachBottom(() => { |
| | | loadMore(); |
| | | }); |
| | | |
| | | // æå¼çäº§äººéæ©å¨ |
| | | const openProducerPicker = async (field) => { |
| | | if (producerList.value.length === 0) { |
| | | // 妿å表为空ï¼å
å è½½ç¨æ·å表 |
| | | const toProgressPercentage = (val) => { |
| | | const n = Number(val); |
| | | if (!Number.isFinite(n)) return 0; |
| | | if (n <= 0) return 0; |
| | | if (n >= 100) return 100; |
| | | return Math.round(n); |
| | | }; |
| | | |
| | | const showTransferCard = (row) => { |
| | | transferCardRowData.value = row; |
| | | transferCardVisible.value = true; |
| | | }; |
| | | |
| | | const openWorkOrderFiles = (row) => { |
| | | workOrderFilesRef.value?.openDialog(row); |
| | | }; |
| | | |
| | | const getPendingQty = (row) => { |
| | | const plan = Number(row?.planQuantity) || 0; |
| | | const complete = Number(row?.completeQuantity) || 0; |
| | | return plan - complete; |
| | | }; |
| | | |
| | | const isStarted = (row) => { |
| | | if (row?.startProductTime && !row?.endProductTime) return true; |
| | | if (String(row?.reportWork) === "1" && !row?.endProductTime) return true; |
| | | return false; |
| | | }; |
| | | |
| | | const isEnded = (row) => { |
| | | return Boolean(row?.endProductTime); |
| | | }; |
| | | |
| | | 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; |
| | | }; |
| | | |
| | | // 夿æ¯å¦å¯ä»¥å¼å§æ¥å·¥ |
| | | const canStartProduction = (row) => { |
| | | if (!row?.id) return false; |
| | | 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; |
| | | }; |
| | | |
| | | // æ ¹æ®userIds夿æ¯å¦æç¨æ·å¯ä»¥æ¥å·¥ |
| | | const canStartProductionByUserIds = (userId, row) => { |
| | | const team = row?.userIds || ""; |
| | | return team.includes(userId); |
| | | }; |
| | | |
| | | const handleStartProduction = (row) => { |
| | | console.log(userStore.id) |
| | | if (!canStartProduction(row)) return; |
| | | if (startSubmittingId.value) return; |
| | | |
| | | uni.showModal({ |
| | | title: "æç¤º", |
| | | content: `ç¡®å®å¼å§æ¥å·¥ï¼\nå·¥åï¼${row.workOrderNo || "-"}`, |
| | | success: async (res) => { |
| | | if (!res.confirm) return; |
| | | startSubmittingId.value = row.id; |
| | | await ensureUserInfo(); |
| | | uni.showLoading({ title: "æäº¤ä¸...", mask: true }); |
| | | try { |
| | | const res = await userListNoPageByTenantId(); |
| | | const users = res.data || []; |
| | | // 转æ¢ä¸º action-sheet éè¦çæ ¼å¼ |
| | | producerList.value = users.map(user => ({ |
| | | name: user.nickName || "", |
| | | value: user.userId, |
| | | })); |
| | | } catch (error) { |
| | | console.error("å è½½ç¨æ·å表失败:", error); |
| | | showToast("å è½½ç¨æ·å表失败"); |
| | | return; |
| | | const payload = { id: row.id, userId: userStore.id, userName: userStore.nickName }; |
| | | const apiRes = await startProduction(payload); |
| | | if (apiRes?.code === 200) { |
| | | showToast("å¼å§æ¥å·¥æå"); |
| | | handleQuery(); |
| | | } else { |
| | | showToast(apiRes?.msg || "å¼å§æ¥å·¥å¤±è´¥"); |
| | | } |
| | | } catch { |
| | | showToast("å¼å§æ¥å·¥å¤±è´¥"); |
| | | } finally { |
| | | uni.hideLoading(); |
| | | startSubmittingId.value = null; |
| | | } |
| | | } |
| | | showProducerPicker.value = true; |
| | | currentField.value = field; // ä¿åå½ååæ®µ |
| | | }; |
| | | |
| | | // çäº§äººéæ©ç¡®è®¤ |
| | | const onProducerConfirm = e => { |
| | | if (currentField.value && form.value[currentField.value]) { |
| | | form.value[currentField.value].value = e.value; |
| | | form.value[currentField.value].name = e.name ; |
| | | } |
| | | showProducerPicker.value = false; |
| | | }; |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDateTime = (timestamp) => { |
| | | const date = new Date(timestamp); |
| | | const y = date.getFullYear(); |
| | | const m = (date.getMonth() + 1).toString().padStart(2, '0'); |
| | | const d = date.getDate().toString().padStart(2, '0'); |
| | | const h = date.getHours().toString().padStart(2, '0'); |
| | | const min = date.getMinutes().toString().padStart(2, '0'); |
| | | const s = date.getSeconds().toString().padStart(2, '0'); |
| | | return `${y}-${m}-${d} ${h}:${min}:${s}`; |
| | | }; |
| | | |
| | | // å¼å§æ¶é´ç¡®è®¤ |
| | | const onStartTimeConfirm = (e) => { |
| | | form.value.planStartTime = formatDateTime(e.value); |
| | | showStartTimePicker.value = false; |
| | | }; |
| | | |
| | | // ç»ææ¶é´ç¡®è®¤ |
| | | const onEndTimeConfirm = (e) => { |
| | | form.value.planEndTime = formatDateTime(e.value); |
| | | showEndTimePicker.value = false; |
| | | }; |
| | | |
| | | // æäº¤ç¶æ |
| | | const submitting = ref(false); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | // æäº¤è¡¨å |
| | | const submitForm = async () => { |
| | | submitting.value = true; |
| | | // æ ¡éªè¡¨å |
| | | if (!form.value.deviceName) { |
| | | submitting.value = false; |
| | | showToast("请è¾å
¥æºå°"); |
| | | return; |
| | | } |
| | | if (!form.value.planStartTime) { |
| | | submitting.value = false; |
| | | showToast("è¯·éæ©è®¡åå¼å§æ¶é´"); |
| | | return; |
| | | } |
| | | if (!form.value.planEndTime) { |
| | | submitting.value = false; |
| | | showToast("è¯·éæ©è®¡åç»ææ¶é´"); |
| | | return; |
| | | } |
| | | if (!form.value.quantity) { |
| | | submitting.value = false; |
| | | showToast("请è¾å
¥æ¬æ¬¡ç产æ°é"); |
| | | return; |
| | | } |
| | | if (!form.value.userId.value) { |
| | | submitting.value = false; |
| | | showToast("è¯·éæ©çç»ä¿¡æ¯"); |
| | | return; |
| | | } |
| | | if (!form.value.auditUserId.value) { |
| | | submitting.value = false; |
| | | showToast("è¯·éæ©å®¡æ ¸äºº"); |
| | | return; |
| | | } |
| | | // 转æ¢ä¸ºæ°åè¿è¡æ¯è¾ |
| | | const quantity = Number(form.value.quantity) || 0; |
| | | const scrapQty = Number(form.value.scrapQty) || 0; |
| | | const planQuantity = Number(form.value.planQuantity); |
| | | // éªè¯ç产æ°éåæ¥åºæ°éçåä¸è½è¶
è¿å¾
ç产æ°é |
| | | if (quantity + scrapQty > planQuantity) { |
| | | submitting.value = false; |
| | | showToast("ç产æ°éåæ¥åºæ°éçåä¸è½è¶
è¿å¾
ç产æ°é"); |
| | | return; |
| | | } |
| | | if (quantity > planQuantity) { |
| | | submitting.value = false; |
| | | showToast("æ¬æ¬¡ç产æ°éä¸è½å¤§äºå¾
ç产æ°é"); |
| | | return; |
| | | } |
| | | // åå¤æäº¤æ°æ®ï¼ç¡®ä¿æ°éåæ®µä¸ºæ°åç±»å |
| | | const submitData = { |
| | | ...form.value, |
| | | quantity: Number(form.value.quantity), |
| | | scrapQty: Number(form.value.scrapQty) || 0, |
| | | planQuantity: Number(form.value.planQuantity) || 0, |
| | | userId: form.value.userId.value, |
| | | auditUserId: form.value.auditUserId.value, |
| | | auditUserName: form.value.auditUserId.name, |
| | | schedulingUserId: form.value.userId.value, // å
¼å®¹æ§å段 |
| | | }; |
| | | console.log(submitData, "submitData"); |
| | | |
| | | addProductMain(submitData).then(res => { |
| | | if (res.code === 200) { |
| | | showToast("æ¥å·¥æå"); |
| | | submitting.value = false; |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 1000); |
| | | } else { |
| | | showToast(res.msg || "æ¥å·¥å¤±è´¥"); |
| | | submitting.value = false; |
| | | } |
| | | }).catch(err => { |
| | | submitting.value = false; |
| | | }) |
| | | ; |
| | | }; |
| | | |
| | | // 页é¢å è½½æ¶åå§åæ°æ® |
| | | onLoad(options => { |
| | | try { |
| | | const orderRow = JSON.parse(options.orderRow); |
| | | console.log(orderRow, "orderRow"); |
| | | // ç¡®ä¿ planQuantity 转æ¢ä¸ºå符串ï¼ä»¥ä¾¿å¨ u-input 䏿£ç¡®æ¾ç¤º |
| | | form.value.planQuantity = orderRow.planQuantity != null ? String(orderRow.planQuantity) : ""; |
| | | form.value.productProcessRouteItemId = orderRow.productProcessRouteItemId || ""; |
| | | form.value.workOrderId = orderRow.id || ""; |
| | | form.value.deviceName = orderRow.deviceName || ""; |
| | | form.value.planStartTime = orderRow.planStartTime || ""; |
| | | form.value.planEndTime = orderRow.planEndTime || ""; |
| | | getInfo().then(res => { |
| | | // é»è®¤ä½¿ç¨å½åç»å½ç¨æ·ï¼ä½å
è®¸ç¨æ·ä¿®æ¹ |
| | | form.value.userId.value = res.user.userId; |
| | | form.value.userId.name = res.user.nickName; |
| | | }); |
| | | // ä½¿ç¨ nextTick ç¡®ä¿ DOM æ´æ° |
| | | nextTick(() => { |
| | | console.log("form.value after assignment:", form.value); |
| | | }); |
| | | } catch (error) { |
| | | console.error("订åè§£æå¤±è´¥:", error); |
| | | showToast("订åè§£æå¤±è´¥"); |
| | | goBack(); |
| | | return; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const formatDateTime = (timestamp) => { |
| | | const date = new Date(timestamp); |
| | | const y = date.getFullYear(); |
| | | const m = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const d = String(date.getDate()).padStart(2, "0"); |
| | | const h = String(date.getHours()).padStart(2, "0"); |
| | | const min = String(date.getMinutes()).padStart(2, "0"); |
| | | const s = String(date.getSeconds()).padStart(2, "0"); |
| | | return `${y}-${m}-${d} ${h}:${min}:${s}`; |
| | | }; |
| | | |
| | | const openEndReport = async (row) => { |
| | | if (!canEndProduction(row)) return; |
| | | endReportRow.value = row; |
| | | await ensureUserInfo(); |
| | | await ensureUserOptions(); |
| | | |
| | | endReportForm.planQuantity = String(getPendingQty(row)); |
| | | endReportForm.quantity = String(getPendingQty(row)); |
| | | endReportForm.replenishQty = "0"; |
| | | endReportForm.scrapQty = "0"; |
| | | endReportForm.teamList = []; |
| | | teamCheckedIds.value = []; |
| | | |
| | | endReportForm.userId = userStore.id || ""; |
| | | endReportForm.userName = userStore.nickName || ""; |
| | | endReportForm.workOrderId = row.id; |
| | | endReportForm.reportWork = row.reportWork; |
| | | endReportForm.productProcessRouteItemId = row.productProcessRouteItemId || ""; |
| | | endReportForm.productMainId = row.productMainId ?? null; |
| | | |
| | | endReportForm.auditUserId = ""; |
| | | endReportForm.auditUserName = ""; |
| | | |
| | | endReportForm.startTime = row.startProductTime || ""; |
| | | endReportForm.endTime = ""; |
| | | startTimeValue.value = endReportForm.startTime ? new Date(endReportForm.startTime).getTime() : Date.now(); |
| | | if (!endReportForm.startTime) { |
| | | endReportForm.startTime = formatDateTime(startTimeValue.value); |
| | | } |
| | | endTimeValue.value = Date.now(); |
| | | endReportForm.endTime = formatDateTime(endTimeValue.value); |
| | | |
| | | endReportVisible.value = true; |
| | | }; |
| | | |
| | | const closeEndReport = () => { |
| | | endReportVisible.value = false; |
| | | endReportRow.value = null; |
| | | }; |
| | | |
| | | const openAuditPicker = async () => { |
| | | await ensureUserOptions(); |
| | | auditPickerVisible.value = true; |
| | | }; |
| | | |
| | | const onAuditSelect = (e) => { |
| | | endReportForm.auditUserId = String(e?.value ?? ""); |
| | | endReportForm.auditUserName = String(e?.name ?? ""); |
| | | auditPickerVisible.value = false; |
| | | }; |
| | | |
| | | const openTeamPicker = async () => { |
| | | await ensureUserOptions(); |
| | | teamCheckedIds.value = (endReportForm.teamList || []).map(i => String(i.userId)); |
| | | teamPickerVisible.value = true; |
| | | }; |
| | | |
| | | const confirmTeamPicker = () => { |
| | | const ids = teamCheckedIds.value || []; |
| | | endReportForm.teamList = ids |
| | | .map(id => { |
| | | const u = userOptions.value.find(x => String(x.value) === String(id)); |
| | | return { userId: String(id), userName: u?.name || "" }; |
| | | }) |
| | | .filter(i => i.userId); |
| | | teamPickerVisible.value = false; |
| | | }; |
| | | |
| | | const onStartTimeConfirm = (e) => { |
| | | endReportForm.startTime = formatDateTime(e.value); |
| | | startTimePickerVisible.value = false; |
| | | }; |
| | | |
| | | const onEndTimeConfirm = (e) => { |
| | | endReportForm.endTime = formatDateTime(e.value); |
| | | endTimePickerVisible.value = false; |
| | | }; |
| | | |
| | | const submitEndReport = async () => { |
| | | if (!endReportRow.value) return; |
| | | |
| | | const pendingQty = Number(endReportForm.planQuantity) || 0; |
| | | if (pendingQty <= 0) { |
| | | showToast("å¾
ç产æ°é为0ï¼æ æ³æ¥å·¥"); |
| | | return; |
| | | } |
| | | |
| | | const quantity = Number(endReportForm.quantity); |
| | | if (!Number.isFinite(quantity) || !Number.isInteger(quantity) || quantity < 1) { |
| | | showToast("æ¬æ¬¡ç产æ°éå¿
须为大äºçäº1çæ´æ°"); |
| | | return; |
| | | } |
| | | if (quantity > pendingQty) { |
| | | showToast("æ¬æ¬¡ç产æ°éä¸è½è¶
è¿å¾
ç产æ°é"); |
| | | return; |
| | | } |
| | | |
| | | const replenishQty = endReportForm.replenishQty === "" ? 0 : Number(endReportForm.replenishQty); |
| | | if (!Number.isFinite(replenishQty) || !Number.isInteger(replenishQty) || replenishQty < 0) { |
| | | showToast("补产æ°éå¿
须为大äºçäº0çæ´æ°"); |
| | | return; |
| | | } |
| | | |
| | | const scrapQty = endReportForm.scrapQty === "" ? 0 : Number(endReportForm.scrapQty); |
| | | if (!Number.isFinite(scrapQty) || !Number.isInteger(scrapQty) || scrapQty < 0) { |
| | | showToast("æ¥åºæ°éå¿
须为大äºçäº0çæ´æ°"); |
| | | return; |
| | | } |
| | | |
| | | if (!endReportForm.auditUserId) { |
| | | showToast("è¯·éæ©å®¡æ ¸äºº"); |
| | | return; |
| | | } |
| | | |
| | | await ensureUserInfo(); |
| | | uni.showLoading({ title: "æäº¤ä¸...", mask: true }); |
| | | try { |
| | | const submitData = { |
| | | ...endReportForm, |
| | | planQuantity: pendingQty, |
| | | quantity, |
| | | replenishQty, |
| | | scrapQty, |
| | | userId: endReportForm.userId || userStore.id, |
| | | userName: endReportForm.userName || userStore.nickName, |
| | | auditUserId: endReportForm.auditUserId, |
| | | auditUserName: endReportForm.auditUserName, |
| | | }; |
| | | const res = await addProductMain(submitData); |
| | | if (res?.code === 200) { |
| | | showToast("ç»ææ¥å·¥æå"); |
| | | closeEndReport(); |
| | | handleQuery(); |
| | | } else { |
| | | showToast(res?.msg || "ç»ææ¥å·¥å¤±è´¥"); |
| | | } |
| | | } catch { |
| | | showToast("ç»ææ¥å·¥å¤±è´¥"); |
| | | } finally { |
| | | uni.hideLoading(); |
| | | } |
| | | }; |
| | | |
| | | onLoad((options) => { |
| | | if (!options?.orderRow) return; |
| | | try { |
| | | const raw = decodeURIComponent(options.orderRow); |
| | | routeOrderRow.value = JSON.parse(raw); |
| | | } catch { |
| | | try { |
| | | routeOrderRow.value = JSON.parse(options.orderRow); |
| | | } catch { |
| | | routeOrderRow.value = null; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | onShow(() => { |
| | | if (routeOrderRow.value?.id) { |
| | | loadSingleWorkOrder(routeOrderRow.value); |
| | | } else { |
| | | handleQuery(); |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/static/scss/form-common.scss"; |
| | | @import '@/styles/sales-common.scss'; |
| | | |
| | | .work-order { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .tag-type { |
| | | background-color: #e3f2fd; |
| | | color: #2196f3; |
| | | padding: 2px 8px; |
| | | border-radius: 4px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .progress-section { |
| | | margin: 15px 0; |
| | | .detail-label { |
| | | display: block; |
| | | margin-bottom: 8px; |
| | | font-size: 13px; |
| | | color: #666; |
| | | } |
| | | } |
| | | |
| | | .item-actions { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | padding: 12px 0; |
| | | border-top: 1px solid #f5f5f5; |
| | | |
| | | :deep(.up-button) { |
| | | margin: 0; |
| | | width: auto; |
| | | } |
| | | } |
| | | |
| | | .report-modal { |
| | | background: #fff; |
| | | max-height: 85vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | } |
| | | .modal-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 14px 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | } |
| | | .modal-header-left { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 4px; |
| | | min-width: 0; |
| | | } |
| | | .modal-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | } |
| | | .modal-subtitle { |
| | | font-size: 12px; |
| | | color: #8a8a8a; |
| | | max-width: 260px; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | .close-btn { |
| | | padding: 6px; |
| | | } |
| | | .modal-content { |
| | | flex: 1; |
| | | height: 0; |
| | | padding: 12px 12px 0; |
| | | background: #f7f8fa; |
| | | } |
| | | .modal-footer { |
| | | display: flex; |
| | | gap: 12px; |
| | | padding: 12px 16px; |
| | | border-top: 1px solid #f0f0f0; |
| | | background: #fff; |
| | | } |
| | | .report-summary { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 12px 14px; |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | margin-bottom: 12px; |
| | | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04); |
| | | } |
| | | .summary-left { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 4px; |
| | | min-width: 0; |
| | | } |
| | | .summary-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #222; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | .summary-sub { |
| | | font-size: 12px; |
| | | color: #8a8a8a; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | .summary-right { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-end; |
| | | gap: 2px; |
| | | padding-left: 12px; |
| | | } |
| | | .summary-num { |
| | | font-size: 18px; |
| | | font-weight: 700; |
| | | color: #2979ff; |
| | | } |
| | | .summary-label { |
| | | font-size: 11px; |
| | | color: #8a8a8a; |
| | | } |
| | | .form-section { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 10px 12px; |
| | | margin-bottom: 12px; |
| | | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04); |
| | | } |
| | | .section-title { |
| | | display: block; |
| | | font-size: 13px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin: 2px 0 10px; |
| | | } |
| | | .team-modal { |
| | | background: #fff; |
| | | max-height: 80vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | } |
| | | .team-content { |
| | | flex: 1; |
| | | height: 0; |
| | | padding: 12px 16px; |
| | | } |
| | | |
| | | .qr-popup { |
| | | padding: 30px; |
| | | background-color: #fff; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | |
| | | .qr-title { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .qr-box { |
| | | padding: 20px; |
| | | background-color: #fff; |
| | | box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); |
| | | border-radius: 8px; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .qr-info { |
| | | font-size: 14px; |
| | | color: #666; |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 100px; |
| | | } |
| | | </style> |
| | | |
| | | |