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