From c7043051efed6648a4ae3d9aceaa70fc34171a58 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期二, 30 六月 2026 14:48:08 +0800
Subject: [PATCH] 生产计划下发

---
 src/pages/productionManagement/mainProductionPlan/issue.vue          |  142 ++++++
 src/pages.json                                                       |   14 
 src/pages/inventoryManagement/stockManagement/selectProductModel.vue |    6 
 src/pages/productionManagement/mainProductionPlan/edit.vue           |  275 +++++++++++++
 src/pages/productionManagement/mainProductionPlan/index.vue          |  770 +++++++++++++++++++++++-------------
 src/api/productionManagement/productionPlan.js                       |   45 ++
 6 files changed, 976 insertions(+), 276 deletions(-)

diff --git a/src/api/productionManagement/productionPlan.js b/src/api/productionManagement/productionPlan.js
index ec9a1ae..4ec0a3c 100644
--- a/src/api/productionManagement/productionPlan.js
+++ b/src/api/productionManagement/productionPlan.js
@@ -27,3 +27,48 @@
     params: query,
   });
 }
+
+// 鐢熶骇璁″垝-鏂板
+export function productionPlanAdd(data) {
+  return request({
+    url: "/productionPlan/addProductionPlan",
+    method: "post",
+    data,
+  });
+}
+
+// 鐢熶骇璁″垝-淇敼
+export function productionPlanUpdate(data) {
+  return request({
+    url: "/productionPlan/updateProductionPlan",
+    method: "put",
+    data,
+  });
+}
+
+// 鐢熶骇璁″垝-鍒犻櫎
+export function productionPlanDelete(data) {
+  return request({
+    url: "/productionPlan/deleteProductionPlan",
+    method: "delete",
+    data,
+  });
+}
+
+// 鍚堝苟涓嬪彂
+export function productionPlanCombine(data) {
+  return request({
+    url: "/productionPlan/combine",
+    method: "post",
+    data,
+  });
+}
+
+// 杩借釜杩涘害
+export function trackProgressByNo(query) {
+  return request({
+    url: "/track/trackProgressByNo",
+    method: "get",
+    params: query,
+  });
+}
diff --git a/src/pages.json b/src/pages.json
index 71f446c..7f0820a 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -880,6 +880,20 @@
       }
     },
     {
+      "path": "pages/productionManagement/mainProductionPlan/edit",
+      "style": {
+        "navigationBarTitleText": "鐢熶骇璁″垝",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/productionManagement/mainProductionPlan/issue",
+      "style": {
+        "navigationBarTitleText": "涓嬪彂",
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/productionManagement/productionScheduling/index",
       "style": {
         "navigationBarTitleText": "鐢熶骇鎺掍骇",
diff --git a/src/pages/inventoryManagement/stockManagement/selectProductModel.vue b/src/pages/inventoryManagement/stockManagement/selectProductModel.vue
index 50892cb..92c738b 100644
--- a/src/pages/inventoryManagement/stockManagement/selectProductModel.vue
+++ b/src/pages/inventoryManagement/stockManagement/selectProductModel.vue
@@ -93,7 +93,11 @@
       .then(res => {
         const records = res?.records || res?.data?.records || res?.data || [];
         const rows = Array.isArray(records) ? records : [];
-        list.value = page.current === 1 ? rows : [...list.value, ...rows];
+        const normalized = rows.map(r => {
+          const modelId = r?.id ?? r?.productModelId ?? r?.materialModelId;
+          return modelId == null ? r : { ...r, id: modelId };
+        });
+        list.value = page.current === 1 ? normalized : [...list.value, ...normalized];
         total.value = Number(res?.total ?? res?.data?.total ?? list.value.length);
         loadStatus.value = list.value.length >= total.value ? "nomore" : "loadmore";
       })
diff --git a/src/pages/productionManagement/mainProductionPlan/edit.vue b/src/pages/productionManagement/mainProductionPlan/edit.vue
new file mode 100644
index 0000000..3c7a396
--- /dev/null
+++ b/src/pages/productionManagement/mainProductionPlan/edit.vue
@@ -0,0 +1,275 @@
+<template>
+  <view class="account-detail">
+    <PageHeader :title="pageTitle"
+                @back="goBack" />
+    <up-form ref="formRef"
+             :model="form"
+             :rules="rules"
+             label-width="110">
+      <up-form-item label="涓荤敓浜ц鍒掑彿"
+                    prop="mpsNo">
+        <up-input v-model="form.mpsNo"
+                  placeholder="鏂板鍚庤嚜鍔ㄧ敓鎴�"
+                  disabled />
+      </up-form-item>
+      <up-form-item label="浜у搧"
+                    prop="productModelId"
+                    required>
+        <up-input v-model="productText"
+                  placeholder="璇烽�夋嫨"
+                  readonly
+                  @click="goSelectProductModel" />
+        <template #right>
+          <up-icon name="arrow-right"
+                   @click="goSelectProductModel"></up-icon>
+        </template>
+      </up-form-item>
+      <up-form-item label="鎵�闇�鏁伴噺"
+                    prop="qtyRequired"
+                    required>
+        <up-input v-model="form.qtyRequired"
+                  type="number"
+                  placeholder="璇疯緭鍏�"
+                  clearable />
+      </up-form-item>
+      <up-form-item label="鍗曚綅"
+                    prop="unit">
+        <up-input v-model="form.unit"
+                  placeholder="鑷姩濉厖"
+                  disabled />
+      </up-form-item>
+      <up-form-item label="闇�姹傛棩鏈�"
+                    prop="requiredDate"
+                    required>
+        <up-input v-model="form.requiredDate"
+                  placeholder="璇烽�夋嫨"
+                  readonly
+                  @click="showRequiredDatePicker = true" />
+        <template #right>
+          <up-icon name="arrow-right"
+                   @click="showRequiredDatePicker = true"></up-icon>
+        </template>
+      </up-form-item>
+      <up-form-item label="鎵胯鏃ユ湡"
+                    prop="promisedDeliveryDate">
+        <up-input v-model="form.promisedDeliveryDate"
+                  placeholder="璇烽�夋嫨"
+                  readonly
+                  @click="showPromisedDatePicker = true" />
+        <template #right>
+          <up-icon name="arrow-right"
+                   @click="showPromisedDatePicker = true"></up-icon>
+        </template>
+      </up-form-item>
+      <up-form-item label="澶囨敞"
+                    prop="remark">
+        <up-textarea v-model="form.remark"
+                     placeholder="璇疯緭鍏�"
+                     auto-height />
+      </up-form-item>
+    </up-form>
+    <FooterButtons :loading="loading"
+                   confirmText="淇濆瓨"
+                   @cancel="goBack"
+                   @confirm="handleSubmit" />
+    <up-datetime-picker :show="showRequiredDatePicker"
+                        v-model="requiredDateValue"
+                        mode="date"
+                        @confirm="onRequiredDateConfirm"
+                        @cancel="showRequiredDatePicker = false" />
+    <up-datetime-picker :show="showPromisedDatePicker"
+                        v-model="promisedDateValue"
+                        mode="date"
+                        @confirm="onPromisedDateConfirm"
+                        @cancel="showPromisedDatePicker = false" />
+  </view>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from "vue";
+  import { onLoad } from "@dcloudio/uni-app";
+  import FooterButtons from "@/components/FooterButtons.vue";
+  import PageHeader from "@/components/PageHeader.vue";
+  import { formatDateToYMD } from "@/utils/ruoyi";
+  import {
+    productionPlanAdd,
+    productionPlanUpdate,
+  } from "@/api/productionManagement/productionPlan";
+
+  const formRef = ref();
+  const loading = ref(false);
+  const editId = ref(undefined);
+  const readonly = ref(false);
+  const selectedProductRow = ref(null);
+
+  const form = ref({
+    id: undefined,
+    mpsNo: "",
+    productId: undefined,
+    productModelId: undefined,
+    productName: "",
+    model: "",
+    qtyRequired: "",
+    unit: "",
+    requiredDate: "",
+    promisedDeliveryDate: "",
+    remark: "",
+  });
+
+  const rules = {
+    qtyRequired: [{ required: true, message: "璇疯緭鍏ユ暟閲�", trigger: "blur" }],
+    requiredDate: [
+      { required: true, message: "璇烽�夋嫨闇�姹傛棩鏈�", trigger: "change" },
+    ],
+  };
+
+  const pageTitle = computed(() =>
+    editId.value ? "缂栬緫鐢熶骇璁″垝" : "鏂板鐢熶骇璁″垝"
+  );
+
+  const productText = computed(() => {
+    const name = form.value.productName || "";
+    const model = form.value.model || "";
+    if (!name && !model) return "";
+    return model ? `${name} / ${model}` : name;
+  });
+
+  const showRequiredDatePicker = ref(false);
+  const showPromisedDatePicker = ref(false);
+  const requiredDateValue = ref(Date.now());
+  const promisedDateValue = ref(Date.now());
+
+  const goBack = () => {
+    uni.navigateBack();
+  };
+
+  const goSelectProductModel = () => {
+    if (readonly.value) return;
+    const onSelected = row => {
+      selectedProductRow.value = row || null;
+      const productId = row?.productId ?? row?.productMainId ?? row?.productID;
+      const productModelId =
+        row?.id ??
+        row?.productModelId ??
+        row?.productModelID ??
+        row?.materialModelId ??
+        row?.materialModelID ??
+        row?.modelId ??
+        row?.modelID;
+      form.value.productId = productId ?? undefined;
+      form.value.productModelId = productModelId ?? undefined;
+      form.value.productName = row.productName || "";
+      form.value.model = row.model || "";
+      form.value.unit = row.unit || "鏂�";
+    };
+    uni.$once("stockManagement:selectedProductModel", onSelected);
+    uni.navigateTo({
+      url: "/pages/inventoryManagement/stockManagement/selectProductModel",
+      events: {
+        selected: onSelected,
+      },
+    });
+  };
+
+  const onRequiredDateConfirm = e => {
+    const value = e?.value ?? requiredDateValue.value;
+    form.value.requiredDate = formatDateToYMD(value);
+    showRequiredDatePicker.value = false;
+  };
+
+  const onPromisedDateConfirm = e => {
+    const value = e?.value ?? promisedDateValue.value;
+    form.value.promisedDeliveryDate = formatDateToYMD(value);
+    showPromisedDatePicker.value = false;
+  };
+
+  const handleSubmit = async () => {
+    if (readonly.value) return;
+    if (!form.value.productModelId && selectedProductRow.value) {
+      const row = selectedProductRow.value;
+      const fallback =
+        row?.id ??
+        row?.productModelId ??
+        row?.productModelID ??
+        row?.materialModelId ??
+        row?.materialModelID ??
+        row?.modelId ??
+        row?.modelID;
+      if (fallback != null) form.value.productModelId = fallback;
+    }
+    if (!form.value.productModelId) {
+      uni.showToast({ title: "璇烽�夋嫨浜у搧", icon: "none" });
+      return;
+    }
+    const valid = await formRef.value.validate().catch(() => false);
+    if (!valid) return;
+
+    const qty = Number(form.value.qtyRequired);
+    if (!qty || qty <= 0) {
+      uni.showToast({ title: "鏁伴噺蹇呴』澶т簬0", icon: "none" });
+      return;
+    }
+
+    loading.value = true;
+    const payload = { ...form.value, qtyRequired: qty };
+    const action = editId.value ? productionPlanUpdate : productionPlanAdd;
+    if (!editId.value) payload.id = null;
+    action(payload)
+      .then(() => {
+        uni.showToast({ title: "淇濆瓨鎴愬姛", icon: "success" });
+        uni.$emit("mainProductionPlan:refresh");
+        goBack();
+      })
+      .catch(err => {
+        const msg =
+          err?.msg || err?.message || err?.data?.msg || err?.data?.message;
+        uni.showToast({ title: msg ? String(msg) : "淇濆瓨澶辫触", icon: "none" });
+      })
+      .finally(() => {
+        loading.value = false;
+      });
+  };
+
+  const initFromRow = row => {
+    if (!row || typeof row !== "object") return;
+    editId.value = row.id;
+    readonly.value = Boolean(row.source === "閿�鍞�" || String(row.status) !== "0");
+    form.value = {
+      ...form.value,
+      id: row.id,
+      mpsNo: row.mpsNo || "",
+      productId: row.productId ?? undefined,
+      productModelId: row.productModelId ?? undefined,
+      productName: row.productName || "",
+      model: row.model || "",
+      qtyRequired: String(row.qtyRequired ?? ""),
+      unit: row.unit || "鏂�",
+      requiredDate: row.requiredDate ? formatDateToYMD(row.requiredDate) : "",
+      promisedDeliveryDate: row.promisedDeliveryDate
+        ? formatDateToYMD(row.promisedDeliveryDate)
+        : "",
+      remark: row.remark || "",
+    };
+  };
+
+  onLoad(options => {
+    if (options?.data) {
+      try {
+        const row = JSON.parse(decodeURIComponent(options.data));
+        initFromRow(row);
+      } catch {
+        uni.showToast({ title: "鏁版嵁鍔犺浇澶辫触", icon: "none" });
+      }
+    }
+  });
+
+  onMounted(() => {
+    if (!editId.value) {
+      form.value.requiredDate = formatDateToYMD(Date.now());
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @import "@/static/scss/form-common.scss";
+</style>
diff --git a/src/pages/productionManagement/mainProductionPlan/index.vue b/src/pages/productionManagement/mainProductionPlan/index.vue
index 5b4eec1..95bd5fd 100644
--- a/src/pages/productionManagement/mainProductionPlan/index.vue
+++ b/src/pages/productionManagement/mainProductionPlan/index.vue
@@ -1,300 +1,520 @@
 <template>
-	<view class="main-production-plan">
-		<!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
-		<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.keyword"
-						@change="handleQuery"
-						clearable
-					/>
-				</view>
-				<view class="filter-button" @click="handleQuery">
-					<up-icon name="search" size="24" color="#999"></up-icon>
-				</view>
-			</view>
-		</view>
-		
-		<!-- 鍒楄〃鍖哄煙 -->
-		<scroll-view scroll-y class="list-container" v-if="tableData.length > 0" @scrolltolower="loadMore">
-			<view v-for="(item, index) in tableData" :key="item.id || index" @click="goDetail(item)">
-				<view 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.mpsNo }}</text>
-						</view>
-						<view class="item-right">
-							<up-tag :text="getStatusText(item.status)" :type="getStatusType(item.status)" size="mini" />
-						</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 highlight">{{ item.qtyRequired || 0 }} {{ item.unit || '鏂�' }}</text>
-						</view>
-						<view class="detail-row">
-							<text class="detail-label">闇�姹傛棩鏈�</text>
-							<text class="detail-value">{{ formatDate(item.requiredDate) }}</text>
-						</view>
-						<view class="detail-row">
-							<text class="detail-label">鏉ユ簮</text>
-							<text class="detail-value">{{ item.source === '閿�鍞�' ? '閿�鍞�' : '鍐呴儴' }}</text>
-						</view>
-					</view>
-					<view class="item-footer">
-						<text class="more-detail">鏌ョ湅璇︽儏</text>
-						<up-icon name="arrow-right" size="14" color="#999"></up-icon>
-					</view>
-				</view>
-			</view>
-			<up-loadmore :status="loadStatus" v-if="tableData.length >= page.size" />
-		</scroll-view>
-		
-		<view v-else class="no-data">
-			<up-empty mode="data" text="鏆傛棤涓荤敓浜ц鍒掓暟鎹�"></up-empty>
-		</view>
-	</view>
+  <view class="main-production-plan">
+    <!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
+    <PageHeader title="涓荤敓浜ц鍒�"
+                @back="goBack">
+      <!-- <template #right>
+        <view class="header-right">
+          <u-button v-if="!selectMode"
+                    size="mini"
+                    @click="enterSelectMode">鍚堝苟涓嬪彂</u-button>
+          <u-button v-else
+                    size="mini"
+                    @click="exitSelectMode">鍙栨秷</u-button>
+        </view>
+      </template> -->
+    </PageHeader>
+    <!-- 鎼滅储鍖哄煙 -->
+    <view class="search-section">
+      <view class="search-bar">
+        <view class="search-input">
+          <up-input class="search-text"
+                    placeholder="璇疯緭鍏ヨ鍒掑彿鎴栦骇鍝佸悕绉�"
+                    v-model="searchForm.keyword"
+                    @change="handleQuery"
+                    clearable />
+        </view>
+        <view class="filter-button"
+              @click="handleQuery">
+          <up-icon name="search"
+                   size="24"
+                   color="#999"></up-icon>
+        </view>
+      </view>
+    </view>
+    <!-- 鍒楄〃鍖哄煙 -->
+    <scroll-view scroll-y
+                 class="list-container"
+                 v-if="tableData.length > 0">
+      <up-checkbox-group v-if="selectMode"
+                         v-model="selectedIds"
+                         placement="column">
+        <view v-for="(item, index) in tableData"
+              :key="item.id || index"
+              @click="toggleSelect(item)">
+          <view 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.mpsNo }}</text>
+              </view>
+              <view class="item-right">
+                <up-checkbox :name="String(item.id)"
+                             :label="''"
+                             @click.stop></up-checkbox>
+              </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 highlight">{{ getRemainingQty(item) }} {{ item.unit || '鏂�' }}</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </up-checkbox-group>
+      <view v-else>
+        <view v-for="(item, index) in tableData"
+              :key="item.id || index"
+              @click="goDetail(item)">
+          <view 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.mpsNo }}</text>
+              </view>
+              <view class="item-right">
+                <up-tag :text="getStatusText(item.status)"
+                        :type="getStatusType(item.status)"
+                        size="mini" />
+              </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 highlight">{{ item.qtyRequired || 0 }} {{ item.unit || '鏂�' }}</text>
+              </view>
+              <view class="detail-row">
+                <text class="detail-label">宸蹭笅鍙戞暟閲�</text>
+                <text class="detail-value">{{ item.quantityIssued || 0 }} {{ item.unit || '鏂�' }}</text>
+              </view>
+              <view class="detail-row">
+                <text class="detail-label">闇�姹傛棩鏈�</text>
+                <text class="detail-value">{{ formatDate(item.requiredDate) }}</text>
+              </view>
+              <view class="detail-row">
+                <text class="detail-label">鏉ユ簮</text>
+                <text class="detail-value">{{ item.source === '閿�鍞�' ? '閿�鍞�' : '鍐呴儴' }}</text>
+              </view>
+            </view>
+            <view class="action-buttons">
+              <u-button v-if="canEdit(item)"
+                        size="small"
+                        class="action-btn"
+                        @click.stop="goEdit(item)">缂栬緫</u-button>
+              <u-button v-if="canIssue(item)"
+                        size="small"
+                        class="action-btn"
+                        type="primary"
+                        @click.stop="goIssue([item])">涓嬪彂</u-button>
+              <u-button v-if="canDelete(item)"
+                        size="small"
+                        class="action-btn"
+                        type="error"
+                        @click.stop="handleDelete(item)">鍒犻櫎</u-button>
+            </view>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+    <view v-else
+          class="no-data">
+      <up-empty mode="data"
+                text="鏆傛棤涓荤敓浜ц鍒掓暟鎹�"></up-empty>
+    </view>
+    <FooterButtons v-if="selectMode"
+                   cancelText="鍙栨秷"
+                   confirmText="涓嬪彂"
+                   :confirmDisabled="selectedIds.length === 0"
+                   @cancel="exitSelectMode"
+                   @confirm="goIssueSelected" />
+    <view class="fab-button"
+          @click="goAdd">
+      <up-icon name="plus"
+               size="24"
+               color="#ffffff"></up-icon>
+    </view>
+  </view>
 </template>
 
 <script setup>
-import { ref, reactive, toRefs, getCurrentInstance } from "vue";
-import { onShow } from '@dcloudio/uni-app';
-import dayjs from "dayjs";
-import { productionPlanListPage } from "@/api/productionManagement/productionPlan.js";
-import PageHeader from "@/components/PageHeader.vue";
+  import { ref, reactive, toRefs } from "vue";
+  import { onShow } from "@dcloudio/uni-app";
+  import dayjs from "dayjs";
+  import {
+    productionPlanDelete,
+    productionPlanListPage,
+  } from "@/api/productionManagement/productionPlan.js";
+  import PageHeader from "@/components/PageHeader.vue";
+  import FooterButtons from "@/components/FooterButtons.vue";
 
-const { proxy } = getCurrentInstance();
+  // 鍔犺浇鐘舵��
+  const loading = ref(false);
+  // 鍒楄〃鏁版嵁
+  const tableData = ref([]);
 
-// 鍔犺浇鐘舵��
-const loading = ref(false);
-const loadStatus = ref('loadmore');
-// 鍒楄〃鏁版嵁
-const tableData = ref([]);
+  const page = reactive({
+    current: -1,
+    size: -1,
+  });
 
-// 鍒嗛〉閰嶇疆
-const page = reactive({
-	current: 1,
-	size: 10,
-	total: 0,
-});
+  // 鎼滅储琛ㄥ崟鏁版嵁
+  const data = reactive({
+    searchForm: {
+      keyword: "",
+      mpsNo: "",
+      productName: "",
+    },
+  });
+  const { searchForm } = toRefs(data);
 
-// 鎼滅储琛ㄥ崟鏁版嵁
-const data = reactive({
-	searchForm: {
-		keyword: "",
-		mpsNo: "",
-		productName: ""
-	},
-});
-const { searchForm } = toRefs(data);
+  const selectMode = ref(false);
+  const selectedIds = ref([]);
 
-// 杩斿洖涓婁竴椤�
-const goBack = () => {
-	uni.navigateBack();
-};
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.navigateBack();
+  };
 
-// 鏍煎紡鍖栨棩鏈�
-const formatDate = (date) => {
-	return date ? dayjs(date).format('YYYY-MM-DD') : '-';
-};
+  // 鏍煎紡鍖栨棩鏈�
+  const formatDate = date => {
+    return date ? dayjs(date).format("YYYY-MM-DD") : "-";
+  };
 
-// 鑾峰彇鐘舵�佹枃鏈�
-const getStatusText = (status) => {
-	const statusMap = {
-		0: "寰呬笅鍙�",
-		1: "閮ㄥ垎涓嬪彂",
-		2: "宸蹭笅鍙�",
-	};
-	return statusMap[status] || "鏈煡";
-};
+  // 鑾峰彇鐘舵�佹枃鏈�
+  const getStatusText = status => {
+    const statusMap = {
+      0: "寰呬笅鍙�",
+      1: "閮ㄥ垎涓嬪彂",
+      2: "宸蹭笅鍙�",
+    };
+    return statusMap[status] || "鏈煡";
+  };
 
-// 鑾峰彇鐘舵�佺被鍨� (uView tag type)
-const getStatusType = (status) => {
-	const typeMap = {
-		0: "warning",
-		1: "primary",
-		2: "info",
-	};
-	return typeMap[status] || "info";
-};
+  // 鑾峰彇鐘舵�佺被鍨� (uView tag type)
+  const getStatusType = status => {
+    const typeMap = {
+      0: "warning",
+      1: "primary",
+      2: "info",
+    };
+    return typeMap[status] || "info";
+  };
 
-// 鏌ヨ鍒楄〃
-const handleQuery = () => {
-	page.current = 1;
-	tableData.value = [];
-	getList();
-};
+  const getRemainingQty = row => {
+    const required = Number(row?.qtyRequired || 0);
+    const issued = Number(row?.quantityIssued || 0);
+    const remaining = required - issued;
+    return Number((remaining > 0 ? remaining : 0).toFixed(4));
+  };
 
-// 鍔犺浇鏇村
-const loadMore = () => {
-	if (loadStatus.value === 'nomore' || loading.value) return;
-	page.current++;
-	getList();
-};
+  const canEdit = row => {
+    return String(row?.status) === "0" && row?.source !== "閿�鍞�";
+  };
 
-// 鑾峰彇鍒楄〃鏁版嵁
-const getList = () => {
-	loading.value = true;
-	loadStatus.value = 'loading';
-	
-	// 鏋勯�犺姹傚弬鏁�
-	// PC绔帴鍙f敮鎸� mpsNo, productName 绛夛紝杩欓噷绠�鍗曞鐞嗭紝濡傛灉 keyword 瀛樺湪锛屽垯灏濊瘯鍖归厤
-	const params = { 
-		current: page.current,
-		size: page.size,
-		mpsNo: searchForm.value.keyword, // 绠�鍗曞鐞嗭細鎼滅储鍙�
-		productName: searchForm.value.keyword // 绠�鍗曞鐞嗭細鎼滅储鍚嶇О
-	};
-	
-	productionPlanListPage(params).then((res) => {
-		loading.value = false;
-		const records = res.data.records || [];
-		if (page.current === 1) {
-			tableData.value = records;
-		} else {
-			tableData.value = [...tableData.value, ...records];
-		}
-		
-		if (records.length < page.size) {
-			loadStatus.value = 'nomore';
-		} else {
-			loadStatus.value = 'loadmore';
-		}
-		page.total = res.data.total || 0;
-	}).catch(() => {
-		loading.value = false;
-		loadStatus.value = 'loadmore';
-		uni.showToast({
-			title: '鍔犺浇澶辫触',
-			icon: 'error'
-		});
-	});
-};
+  const canDelete = row => {
+    return String(row?.status) === "0";
+  };
 
-// 璺宠浆璇︽儏
-const goDetail = (item) => {
-	uni.navigateTo({
-		url: `/pages/productionManagement/mainProductionPlan/detail?data=${encodeURIComponent(JSON.stringify(item))}`
-	});
-};
+  const canIssue = row => {
+    return String(row?.status) !== "2" && getRemainingQty(row) > 0;
+  };
 
-// 椤甸潰鏄剧ず鏃跺姞杞芥暟鎹�
-onShow(() => {
-	handleQuery();
-});
+  const enterSelectMode = () => {
+    selectMode.value = true;
+    selectedIds.value = [];
+  };
+
+  const exitSelectMode = () => {
+    selectMode.value = false;
+    selectedIds.value = [];
+  };
+
+  const toggleSelect = item => {
+    if (!item?.id) return;
+    const id = String(item.id);
+    const idx = selectedIds.value.indexOf(id);
+    if (idx >= 0) selectedIds.value.splice(idx, 1);
+    else selectedIds.value.push(id);
+  };
+
+  // 鏌ヨ鍒楄〃
+  const handleQuery = () => {
+    getList();
+  };
+
+  // 鑾峰彇鍒楄〃鏁版嵁
+  const getList = () => {
+    loading.value = true;
+    exitSelectMode();
+
+    const params = {
+      current: page.current,
+      size: page.size,
+      mpsNo: searchForm.value.keyword, // 绠�鍗曞鐞嗭細鎼滅储鍙�
+      productName: searchForm.value.keyword, // 绠�鍗曞鐞嗭細鎼滅储鍚嶇О
+    };
+
+    productionPlanListPage(params)
+      .then(res => {
+        loading.value = false;
+        const payload = res?.data;
+        if (Array.isArray(payload)) {
+          tableData.value = payload;
+        } else if (payload && typeof payload === "object") {
+          tableData.value = payload.records || payload.rows || payload.data || [];
+        } else {
+          tableData.value = [];
+        }
+      })
+      .catch(() => {
+        loading.value = false;
+        uni.showToast({
+          title: "鍔犺浇澶辫触",
+          icon: "error",
+        });
+      });
+  };
+
+  // 璺宠浆璇︽儏
+  const goDetail = item => {
+    if (selectMode.value) return;
+    uni.navigateTo({
+      url: `/pages/productionManagement/mainProductionPlan/detail?data=${encodeURIComponent(
+        JSON.stringify(item)
+      )}`,
+    });
+  };
+
+  const goAdd = () => {
+    uni.navigateTo({
+      url: "/pages/productionManagement/mainProductionPlan/edit",
+    });
+  };
+
+  const goEdit = item => {
+    uni.navigateTo({
+      url: `/pages/productionManagement/mainProductionPlan/edit?data=${encodeURIComponent(
+        JSON.stringify(item)
+      )}`,
+    });
+  };
+
+  const goIssue = rows => {
+    const list = Array.isArray(rows) ? rows : [];
+    if (list.length === 0) return;
+    const first = list[0];
+    const sumRemaining = Number(
+      list.reduce((sum, r) => sum + getRemainingQty(r), 0).toFixed(4)
+    );
+    const payload = {
+      productName: first.productName || "",
+      model: first.model || "",
+      productId: first.productId ?? undefined,
+      ids: list.map(r => r.id),
+      planCompleteTime:
+        formatDate(first.requiredDate) === "-"
+          ? ""
+          : formatDate(first.requiredDate),
+      totalAssignedQuantity: sumRemaining,
+      maxQuantity: sumRemaining,
+    };
+    uni.navigateTo({
+      url: `/pages/productionManagement/mainProductionPlan/issue?data=${encodeURIComponent(
+        JSON.stringify(payload)
+      )}`,
+    });
+  };
+
+  const goIssueSelected = () => {
+    const rows = tableData.value.filter(r =>
+      selectedIds.value.includes(String(r.id))
+    );
+    if (rows.length === 0) {
+      uni.showToast({ title: "璇烽�夋嫨瑕佷笅鍙戠殑璁″垝", icon: "none" });
+      return;
+    }
+    const first = rows[0];
+    const modelId = first.productModelId;
+    const invalid = rows.some(r => r.productModelId !== modelId || !canIssue(r));
+    if (invalid) {
+      uni.showToast({
+        title: "鍙兘鍚堝苟涓嬪彂鐩稿悓瑙勬牸涓斿彲涓嬪彂鐨勮鍒�",
+        icon: "none",
+      });
+      return;
+    }
+    goIssue(rows);
+  };
+
+  const handleDelete = item => {
+    if (!item?.id) return;
+    uni.showModal({
+      title: "鍒犻櫎鎻愮ず",
+      content: "纭鍒犻櫎璇ョ敓浜ц鍒掞紵鍒犻櫎鍚庢棤娉曟仮澶�",
+      success: res => {
+        if (!res.confirm) return;
+        uni.showLoading({ title: "鍒犻櫎涓�...", mask: true });
+        productionPlanDelete([item.id])
+          .then(() => {
+            uni.showToast({ title: "鍒犻櫎鎴愬姛", icon: "success" });
+            getList();
+          })
+          .catch(() => {
+            uni.showToast({ title: "鍒犻櫎澶辫触", icon: "error" });
+          })
+          .finally(() => {
+            uni.hideLoading();
+          });
+      },
+    });
+  };
+
+  // 椤甸潰鏄剧ず鏃跺姞杞芥暟鎹�
+  onShow(() => {
+    handleQuery();
+  });
 </script>
 
 <style scoped lang="scss">
-@import '@/styles/sales-common.scss';
+  @import "@/styles/sales-common.scss";
 
-.main-production-plan {
-	min-height: 100vh;
-	background: #f8f9fa;
-	display: flex;
-	flex-direction: column;
-}
+  .main-production-plan {
+    min-height: 100vh;
+    background: #f8f9fa;
+    display: flex;
+    flex-direction: column;
+  }
 
-.list-container {
-	flex: 1;
-	height: 0;
-}
+  .list-container {
+    flex: 1;
+    height: 0;
+    padding-bottom: 140rpx;
+  }
 
-.ledger-item {
-	background: #fff;
-	margin: 20rpx;
-	padding: 20rpx;
-	border-radius: 12rpx;
-	box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
-	
-	.item-header {
-		display: flex;
-		justify-content: space-between;
-		align-items: center;
-		padding-bottom: 10rpx;
-		
-		.item-left {
-			display: flex;
-			align-items: center;
-			
-			.document-icon {
-				width: 40rpx;
-				height: 40rpx;
-				background: #3c9cff;
-				border-radius: 8rpx;
-				display: flex;
-				justify-content: center;
-				align-items: center;
-				margin-right: 16rpx;
-			}
-			
-			.item-id {
-				font-size: 28rpx;
-				font-weight: bold;
-				color: #333;
-			}
-		}
-	}
-	
-	.item-details {
-		padding: 10rpx 0;
-		
-		.detail-row {
-			display: flex;
-			justify-content: space-between;
-			margin-bottom: 12rpx;
-			
-			.detail-label {
-				font-size: 26rpx;
-				color: #999;
-			}
-			
-			.detail-value {
-				font-size: 26rpx;
-				color: #333;
-				
-				&.highlight {
-					color: #f56c6c;
-					font-weight: bold;
-				}
-			}
-		}
-	}
-	
-	.item-footer {
-		display: flex;
-		justify-content: flex-end;
-		align-items: center;
-		padding-top: 16rpx;
-		border-top: 1rpx solid #f0f0f0;
-		
-		.more-detail {
-			font-size: 24rpx;
-			color: #999;
-			margin-right: 8rpx;
-		}
-	}
-}
+  .header-right {
+    display: flex;
+    align-items: center;
+    padding-right: 12rpx;
+  }
 
-.no-data {
-	padding-top: 200rpx;
-}
+  .ledger-item {
+    background: #fff;
+    margin: 20rpx;
+    padding: 20rpx;
+    border-radius: 12rpx;
+    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+
+    .item-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding-bottom: 10rpx;
+
+      .item-left {
+        display: flex;
+        align-items: center;
+
+        .document-icon {
+          width: 40rpx;
+          height: 40rpx;
+          background: #3c9cff;
+          border-radius: 8rpx;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          margin-right: 16rpx;
+        }
+
+        .item-id {
+          font-size: 28rpx;
+          font-weight: bold;
+          color: #333;
+        }
+      }
+    }
+
+    .action-buttons {
+      display: flex;
+      justify-content: flex-end;
+      align-items: center;
+      gap: 16rpx;
+      padding-top: 16rpx;
+      border-top: 1rpx solid #f0f0f0;
+    }
+
+    .item-details {
+      padding: 10rpx 0;
+
+      .detail-row {
+        display: flex;
+        justify-content: space-between;
+        margin-bottom: 12rpx;
+
+        .detail-label {
+          font-size: 26rpx;
+          color: #999;
+        }
+
+        .detail-value {
+          font-size: 26rpx;
+          color: #333;
+
+          &.highlight {
+            color: #f56c6c;
+            font-weight: bold;
+          }
+        }
+      }
+    }
+
+    .item-right :deep(.up-checkbox__label) {
+      display: none;
+    }
+  }
+
+  .fab-button {
+    position: fixed;
+    right: 30rpx;
+    bottom: 140rpx;
+    width: 100rpx;
+    height: 100rpx;
+    background: #3c9cff;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-shadow: 0 8rpx 24rpx rgba(60, 156, 255, 0.3);
+    z-index: 1001;
+  }
+
+  .no-data {
+    padding-top: 200rpx;
+  }
 </style>
diff --git a/src/pages/productionManagement/mainProductionPlan/issue.vue b/src/pages/productionManagement/mainProductionPlan/issue.vue
new file mode 100644
index 0000000..f22c8e8
--- /dev/null
+++ b/src/pages/productionManagement/mainProductionPlan/issue.vue
@@ -0,0 +1,142 @@
+<template>
+  <view class="account-detail">
+    <PageHeader title="涓嬪彂" @back="goBack" />
+    <up-form ref="formRef" :model="form" :rules="rules" label-width="110">
+      <up-form-item label="浜у搧鍚嶇О">
+        <up-input v-model="form.productName" disabled placeholder="鑷姩濉厖" />
+      </up-form-item>
+      <up-form-item label="瑙勬牸鍨嬪彿">
+        <up-input v-model="form.model" disabled placeholder="鑷姩濉厖" />
+      </up-form-item>
+      <up-form-item label="璁″垝瀹屾垚鏃堕棿" prop="planCompleteTime" required>
+        <up-input v-model="form.planCompleteTime" placeholder="璇烽�夋嫨" readonly @click="showPlanDatePicker = true" />
+        <template #right>
+          <up-icon name="arrow-right" @click="showPlanDatePicker = true"></up-icon>
+        </template>
+      </up-form-item>
+      <up-form-item label="鐢熶骇鏁伴噺" prop="totalAssignedQuantity" required>
+        <up-input v-model="form.totalAssignedQuantity" type="number" placeholder="璇疯緭鍏�" clearable />
+      </up-form-item>
+      <up-form-item label="鍙笅鍙戞暟閲�">
+        <up-input :model-value="String(maxQuantity)" disabled placeholder="鑷姩璁$畻" />
+      </up-form-item>
+    </up-form>
+
+    <FooterButtons :loading="loading" confirmText="纭畾涓嬪彂" @cancel="goBack" @confirm="handleSubmit" />
+
+    <up-datetime-picker
+      :show="showPlanDatePicker"
+      v-model="planDateValue"
+      mode="date"
+      @confirm="onPlanDateConfirm"
+      @cancel="showPlanDatePicker = false"
+    />
+  </view>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+  import { onLoad } from "@dcloudio/uni-app";
+  import FooterButtons from "@/components/FooterButtons.vue";
+  import PageHeader from "@/components/PageHeader.vue";
+  import { formatDateToYMD } from "@/utils/ruoyi";
+  import { productionPlanCombine } from "@/api/productionManagement/productionPlan";
+
+  const formRef = ref();
+  const loading = ref(false);
+
+  const maxQuantity = ref(0);
+  const showPlanDatePicker = ref(false);
+  const planDateValue = ref(Date.now());
+
+  const form = ref({
+    productName: "",
+    model: "",
+    totalAssignedQuantity: "",
+    planCompleteTime: "",
+    productId: undefined,
+    ids: [],
+  });
+
+  const rules = {
+    planCompleteTime: [{ required: true, message: "璇烽�夋嫨璁″垝瀹屾垚鏃堕棿", trigger: "change" }],
+    totalAssignedQuantity: [{ required: true, message: "璇疯緭鍏ョ敓浜ф暟閲�", trigger: "blur" }],
+  };
+
+  const goBack = () => {
+    uni.navigateBack();
+  };
+
+  const onPlanDateConfirm = e => {
+    const value = e?.value ?? planDateValue.value;
+    form.value.planCompleteTime = formatDateToYMD(value);
+    showPlanDatePicker.value = false;
+  };
+
+  const handleSubmit = async () => {
+    const valid = await formRef.value.validate().catch(() => false);
+    if (!valid) return;
+
+    const qty = Number(form.value.totalAssignedQuantity);
+    if (!qty || qty <= 0) {
+      uni.showToast({ title: "鐢熶骇鏁伴噺蹇呴』澶т簬0", icon: "none" });
+      return;
+    }
+    if (qty > Number(maxQuantity.value || 0)) {
+      uni.showToast({ title: "鐢熶骇鏁伴噺涓嶈兘澶т簬鍙笅鍙戞暟閲�", icon: "none" });
+      return;
+    }
+    if (!Array.isArray(form.value.ids) || form.value.ids.length === 0) {
+      uni.showToast({ title: "璇烽�夋嫨瑕佷笅鍙戠殑璁″垝", icon: "none" });
+      return;
+    }
+
+    loading.value = true;
+    const payload = {
+      ...form.value,
+      totalAssignedQuantity: Number(qty.toFixed(4)),
+    };
+    productionPlanCombine(payload)
+      .then(res => {
+        if (res?.code && Number(res.code) !== 200) {
+          const msg = res?.msg || res?.message || "涓嬪彂澶辫触";
+          uni.showToast({ title: String(msg), icon: "none" });
+          return;
+        }
+        uni.showToast({ title: "涓嬪彂鎴愬姛", icon: "success" });
+        uni.$emit("mainProductionPlan:refresh");
+        goBack();
+      })
+      .catch(err => {
+        const msg = err?.msg || err?.message || err?.data?.msg || err?.data?.message;
+        uni.showToast({ title: msg ? String(msg) : "涓嬪彂澶辫触", icon: "none" });
+      })
+      .finally(() => {
+        loading.value = false;
+      });
+  };
+
+  onLoad(options => {
+    if (!options?.data) return;
+    try {
+      const payload = JSON.parse(decodeURIComponent(options.data));
+      form.value.productName = payload.productName || "";
+      form.value.model = payload.model || "";
+      form.value.productId = payload.productId ?? undefined;
+      form.value.ids = Array.isArray(payload.ids) ? payload.ids : [];
+      form.value.planCompleteTime = payload.planCompleteTime || "";
+      form.value.totalAssignedQuantity = String(payload.totalAssignedQuantity ?? "");
+      maxQuantity.value = Number(payload.maxQuantity ?? 0);
+      if (!form.value.planCompleteTime) {
+        form.value.planCompleteTime = formatDateToYMD(Date.now());
+      }
+    } catch {
+      uni.showToast({ title: "鏁版嵁鍔犺浇澶辫触", icon: "none" });
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @import "@/static/scss/form-common.scss";
+</style>
+

--
Gitblit v1.9.3