From b9d3432b9994a3a050cb913d6137a8a7f0be9f67 Mon Sep 17 00:00:00 2001
From: 张诺 <zhang_12370@163.com>
Date: 星期五, 24 四月 2026 18:50:16 +0800
Subject: [PATCH] feat(production): 重构生产报工为工单列表和上下机流程

---
 src/pages/productionManagement/productionReport/index.vue | 1185 ++++++++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 883 insertions(+), 302 deletions(-)

diff --git a/src/pages/productionManagement/productionReport/index.vue b/src/pages/productionManagement/productionReport/index.vue
index ab29bc3..9ad4115 100644
--- a/src/pages/productionManagement/productionReport/index.vue
+++ b/src/pages/productionManagement/productionReport/index.vue
@@ -1,326 +1,907 @@
 <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("璁㈠崟瑙f瀽澶辫触:", error);
-      showToast("璁㈠崟瑙f瀽澶辫触");
-      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>
-
-

--
Gitblit v1.9.3