4 天以前 b369a75195a37e4d67aa9cecdd8dbe4ad93689ba
feat(purchase): 优化采购计划表单并添加库存预警建议功能

- 移除表单项的 col-span-2 样式类
- 集成 dayjs 用于日期处理
- 添加 Table 组件支持以实现更好的表格展示
- 新增库存预警建议采购明细获取接口
- 实现按库存预警生成建议明细的功能
- 添加总计数量计算功能
- 重构明细表格为 Ant Design Table 组件
- 增加生成建议明细按钮和相关加载状态
- 更新模态框宽度为固定像素值
- 优化表格列配置和数据展示逻辑
已修改3个文件
228 ■■■■■ 文件已修改
src/api/erp/purchase/plan/index.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/plan/data.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/plan/modules/form.vue 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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, {
src/views/erp/purchase/plan/data.ts
@@ -102,7 +102,6 @@
        autoSize: { minRows: 1, maxRows: 1 },
        disabled: isReadonly,
      },
      formItemClass: 'col-span-2',
    },
    {
      fieldName: 'items',
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); // 正在按库存预警生成建议明细
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>