From b369a75195a37e4d67aa9cecdd8dbe4ad93689ba Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 07 九月 2026 16:05:46 +0800
Subject: [PATCH] feat(purchase): 优化采购计划表单并添加库存预警建议功能

---
 src/views/erp/purchase/plan/modules/form.vue |  220 +++++++++++++++++++++++++++++++++---------------------
 src/views/erp/purchase/plan/data.ts          |    1 
 src/api/erp/purchase/plan/index.ts           |    7 +
 3 files changed, 142 insertions(+), 86 deletions(-)

diff --git a/src/api/erp/purchase/plan/index.ts b/src/api/erp/purchase/plan/index.ts
index 10f399c..1e6f166 100644
--- a/src/api/erp/purchase/plan/index.ts
+++ b/src/api/erp/purchase/plan/index.ts
@@ -64,6 +64,13 @@
   });
 }
 
+/** 鑾峰彇搴撳瓨棰勮寤鸿閲囪喘鏄庣粏锛堜笉钀藉簱锛屼緵鏂板寮圭獥鍥炲~鏍稿锛� */
+export function getStockAlertSuggestItems() {
+  return requestClient.get<ErpPurchasePlanApi.PurchasePlanItem[]>(
+    '/purchase/plan/get-stock-alert-suggest-items',
+  );
+}
+
 /** 鐢熸垚閲囪喘鐢宠 */
 export function generatePurchaseRequests(id: number) {
   return requestClient.post<number[]>('/purchase/plan/generate-requests', null, {
diff --git a/src/views/erp/purchase/plan/data.ts b/src/views/erp/purchase/plan/data.ts
index 9a279a5..71c888b 100644
--- a/src/views/erp/purchase/plan/data.ts
+++ b/src/views/erp/purchase/plan/data.ts
@@ -102,7 +102,6 @@
         autoSize: { minRows: 1, maxRows: 1 },
         disabled: isReadonly,
       },
-      formItemClass: 'col-span-2',
     },
     {
       fieldName: 'items',
diff --git a/src/views/erp/purchase/plan/modules/form.vue b/src/views/erp/purchase/plan/modules/form.vue
index 8ad6dc6..bfc354a 100644
--- a/src/views/erp/purchase/plan/modules/form.vue
+++ b/src/views/erp/purchase/plan/modules/form.vue
@@ -3,26 +3,31 @@
 
 import { computed, ref } from 'vue';
 
+import dayjs from 'dayjs';
+
 import { useVbenModal } from '@vben/common-ui';
 
-import { Button, DatePicker, Input, InputNumber, message } from 'ant-design-vue';
+import type { TableColumnsType } from 'ant-design-vue';
+import { Button, DatePicker, Input, InputNumber, message, Table } from 'ant-design-vue';
 
 import { useVbenForm } from '#/adapter/form';
 import {
   createPurchasePlan,
   getPurchasePlan,
+  getStockAlertSuggestItems,
   updatePurchasePlan,
 } from '#/api/erp/purchase/plan';
 import { MdmItemSelect } from '#/views/basicData/mdm/components';
 import { $t } from '#/locales';
 
 import type { FormType } from '../data';
-import { useFormSchema } from '../data';
+import { DEMAND_SOURCE, useFormSchema } from '../data';
 
 const emit = defineEmits(['success']);
 const formData = ref<ErpPurchasePlanApi.PurchasePlan>();
 const formType = ref<FormType>('create');
 const items = ref<ErpPurchasePlanApi.PurchasePlanItem[]>([]);
+const generating = ref(false); // 姝e湪鎸夊簱瀛橀璀︾敓鎴愬缓璁槑缁�
 
 const getTitle = computed(() => {
   if (formType.value === 'create') {
@@ -32,6 +37,31 @@
   } else {
     return '閲囪喘璁″垝璇︽儏';
   }
+});
+
+const totalCount = computed(() =>
+  items.value.reduce((sum, item) => sum + (item.count || 0), 0),
+);
+
+const itemColumns = computed<TableColumnsType>(() => {
+  const columns: TableColumnsType = [
+    { title: '搴忓彿', dataIndex: 'index', width: 50, align: 'center' },
+    { title: '浜у搧', dataIndex: 'productId', minWidth: 200 },
+    { title: '鍗曚綅', dataIndex: 'productUnitName', width: 90, align: 'center' },
+    { title: '鏁伴噺', dataIndex: 'count', width: 130, align: 'right' },
+    { title: '闇�姹傛棩鏈�', dataIndex: 'demandTime', width: 160 },
+    { title: '澶囨敞', dataIndex: 'remark', minWidth: 160 },
+  ];
+  if (formType.value !== 'detail') {
+    columns.push({
+      title: '鎿嶄綔',
+      dataIndex: 'action',
+      width: 70,
+      align: 'center',
+      fixed: 'right',
+    });
+  }
+  return columns;
 });
 
 const [Form, formApi] = useVbenForm({
@@ -54,7 +84,7 @@
     productUnitId: undefined as unknown as number,
     productUnitName: '',
     count: 1,
-    demandTime: undefined,
+    demandTime: dayjs().format('YYYY-MM-DD'),
     remark: '',
   });
 }
@@ -72,6 +102,26 @@
   row.productName = item?.name;
   row.productUnitId = item?.unitMeasureId;
   row.productUnitName = item?.unitMeasureName;
+}
+
+/** 鎸夊簱瀛橀璀︾敓鎴愬缓璁槑缁嗗苟鍥炲~ */
+async function handleGenerateSuggest() {
+  if (generating.value) {
+    return;
+  }
+  generating.value = true;
+  try {
+    const suggestItems = await getStockAlertSuggestItems();
+    if (!suggestItems || suggestItems.length === 0) {
+      message.warning('褰撳墠娌℃湁闇�瑕佽ˉ璐х殑鐗╂枡锛屽缓璁噰璐噺涓虹┖');
+      return;
+    }
+    items.value = suggestItems.map((item) => ({ ...item }));
+    await formApi.setFieldValue('demandSource', DEMAND_SOURCE.STOCK_ALERT);
+    message.success(`宸茬敓鎴� ${suggestItems.length} 鏉″缓璁槑缁嗭紝璇锋牳瀵瑰悗淇濆瓨`);
+  } finally {
+    generating.value = false;
+  }
 }
 
 /** 鍒涘缓鎴栨洿鏂伴噰璐鍒� */
@@ -140,97 +190,97 @@
 <template>
   <Modal
     :title="getTitle"
-    class="w-3/4"
+    class="w-[1100px]"
     :show-confirm-button="formType !== 'detail'"
   >
     <Form class="mx-3">
       <template #items>
         <div class="mt-2">
-          <div v-if="formType !== 'detail'" class="mb-2">
-            <Button type="dashed" @click="handleAddItem">+ 娣诲姞鏄庣粏</Button>
+          <div class="mb-2 flex items-center justify-between">
+            <span class="text-sm font-medium text-muted-foreground">
+              璁″垝鏄庣粏锛堝叡 {{ items.length }} 鏉★紝鍚堣鏁伴噺 {{ totalCount }}锛�
+            </span>
+            <div v-if="formType !== 'detail'" class="flex space-x-2">
+              <Button
+                type="primary"
+                ghost
+                :loading="generating"
+                @click="handleGenerateSuggest"
+              >
+                鎸夊簱瀛橀璀︾敓鎴愬缓璁槑缁�
+              </Button>
+              <Button type="dashed" @click="handleAddItem">+ 娣诲姞鏄庣粏</Button>
+            </div>
           </div>
-          <table class="w-full border-collapse border border-gray-200 text-sm">
-            <thead>
-              <tr class="bg-gray-100">
-                <th class="border border-gray-200 px-2 py-1 w-12">搴忓彿</th>
-                <th class="border border-gray-200 px-2 py-1">浜у搧</th>
-                <th class="border border-gray-200 px-2 py-1 w-20">鍗曚綅</th>
-                <th class="border border-gray-200 px-2 py-1 w-32">鏁伴噺</th>
-                <th class="border border-gray-200 px-2 py-1 w-40">闇�姹傛棩鏈�</th>
-                <th class="border border-gray-200 px-2 py-1">澶囨敞</th>
-                <th
+          <Table
+            :row-key="(record, index) => record.id ?? index ?? 0"
+            :columns="itemColumns"
+            :data-source="items"
+            :pagination="false"
+            :locale="{ emptyText: '鏆傛棤鏄庣粏锛岃娣诲姞' }"
+            :scroll="{ x: 800, y: 320 }"
+            size="small"
+            bordered
+          >
+            <template #bodyCell="{ column, record, index }">
+              <!-- 搴忓彿 -->
+              <template v-if="column.dataIndex === 'index'">
+                <span class="block text-center">{{ index + 1 }}</span>
+              </template>
+              <!-- 浜у搧 -->
+              <template v-else-if="column.dataIndex === 'productId'">
+                <MdmItemSelect
                   v-if="formType !== 'detail'"
-                  class="border border-gray-200 px-2 py-1 w-16"
-                >
-                  鎿嶄綔
-                </th>
-              </tr>
-            </thead>
-            <tbody>
-              <tr v-for="(item, index) in items" :key="index">
-                <td class="border border-gray-200 px-2 py-1 text-center">
-                  {{ index + 1 }}
-                </td>
-                <td class="border border-gray-200 px-2 py-1">
-                  <MdmItemSelect
-                    v-if="formType !== 'detail'"
-                    :model-value="item.productId"
-                    placeholder="璇烽�夋嫨浜у搧"
-                    @change="handleItemSelect(index, $event)"
-                  />
-                  <span v-else>{{ item.productName }}</span>
-                </td>
-                <td class="border border-gray-200 px-2 py-1 text-center">
-                  {{ item.productUnitName || '-' }}
-                </td>
-                <td class="border border-gray-200 px-2 py-1">
-                  <InputNumber
-                    v-if="formType !== 'detail'"
-                    v-model:value="item.count"
-                    class="!w-full"
-                    :min="0.01"
-                    :precision="2"
-                  />
-                  <span v-else>{{ item.count }}</span>
-                </td>
-                <td class="border border-gray-200 px-2 py-1">
-                  <DatePicker
-                    v-if="formType !== 'detail'"
-                    v-model:value="item.demandTime"
-                    class="!w-full"
-                    value-format="YYYY-MM-DD"
-                  />
-                  <span v-else>{{ item.demandTime || '-' }}</span>
-                </td>
-                <td class="border border-gray-200 px-2 py-1">
-                  <Input
-                    v-if="formType !== 'detail'"
-                    v-model:value="item.remark"
-                    placeholder="澶囨敞"
-                  />
-                  <span v-else>{{ item.remark || '-' }}</span>
-                </td>
-                <td
+                  :model-value="record.productId"
+                  placeholder="璇烽�夋嫨浜у搧"
+                  @change="handleItemSelect(index, $event)"
+                />
+                <span v-else>{{ record.productName || '-' }}</span>
+              </template>
+              <!-- 鍗曚綅 -->
+              <template v-else-if="column.dataIndex === 'productUnitName'">
+                <span>{{ record.productUnitName || '-' }}</span>
+              </template>
+              <!-- 鏁伴噺 -->
+              <template v-else-if="column.dataIndex === 'count'">
+                <InputNumber
                   v-if="formType !== 'detail'"
-                  class="border border-gray-200 px-2 py-1 text-center"
-                >
-                  <Button type="link" danger @click="handleRemoveItem(index)">
-                    鍒犻櫎
-                  </Button>
-                </td>
-              </tr>
-              <tr v-if="!items.length">
-                <td
-                  :colspan="formType === 'detail' ? 6 : 7"
-                  class="border border-gray-200 px-2 py-4 text-center text-gray-400"
-                >
-                  鏆傛棤鏄庣粏锛岃娣诲姞
-                </td>
-              </tr>
-            </tbody>
-          </table>
+                  v-model:value="record.count"
+                  class="!w-full"
+                  :min="0.01"
+                  :precision="2"
+                />
+                <span v-else>{{ record.count }}</span>
+              </template>
+              <!-- 闇�姹傛棩鏈� -->
+              <template v-else-if="column.dataIndex === 'demandTime'">
+                <DatePicker
+                  v-if="formType !== 'detail'"
+                  v-model:value="record.demandTime"
+                  class="!w-full"
+                  value-format="YYYY-MM-DD"
+                />
+                <span v-else>{{ record.demandTime || '-' }}</span>
+              </template>
+              <!-- 澶囨敞 -->
+              <template v-else-if="column.dataIndex === 'remark'">
+                <Input
+                  v-if="formType !== 'detail'"
+                  v-model:value="record.remark"
+                  placeholder="澶囨敞"
+                />
+                <span v-else>{{ record.remark || '-' }}</span>
+              </template>
+              <!-- 鎿嶄綔 -->
+              <template v-else-if="column.dataIndex === 'action'">
+                <Button type="link" danger @click="handleRemoveItem(index)">
+                  鍒犻櫎
+                </Button>
+              </template>
+            </template>
+          </Table>
         </div>
       </template>
     </Form>
   </Modal>
-</template>
+</template>
\ No newline at end of file

--
Gitblit v1.9.3