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