2026-08-03 dcde4b33fa739b09486c39faade9ed19b29a6d7a
feat(srm): 优化供应商选择组件功能

- 在供应商选择组件中新增 excludeApproved 参数配置
- 扩展供应商表单字段,增加银行信息和备注字段映射
- 实现物料选择防重复添加机制,过滤已选物料选项
- 添加报价金额校验逻辑,确保与投标金额一致性
- 统一供应商类型枚举值为字符串类型以对齐后端数据结构
- 完善供应商选择对话框的参数传递和筛选功能
已修改6个文件
93 ■■■■ 文件已修改
src/api/srm/supplier/index.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/enums.ts 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/supplier/components/select-dialog.vue 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/supplier/components/select.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/supplierApply/data.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/tender/detail.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/srm/supplier/index.ts
@@ -7,7 +7,7 @@
    id?: number;
    code: string;
    name: string;
    type: number;
    type: string;
    status: number;
    contactName?: string;
    contactMobile?: string;
src/views/srm/enums.ts
@@ -36,29 +36,30 @@
  labels: { 0: '开启', 1: '关闭' },
};
/** 供应商类型 — 对齐 SupplierTypeEnum (1=生产型 2=贸易型 3=服务型 4=其他) */
/** 供应商类型 — 对齐 SupplierTypeEnum (1=生产型 2=贸易型 3=服务型 4=其他)
 *  注意:后端 DO/RespVO 中 type 为 String 类型,此处选项值统一用字符串与后端对齐 */
export const SUPPLIER_TYPE = {
  PRODUCTION: 1,
  TRADING: 2,
  SERVICE: 3,
  OTHER: 4,
  PRODUCTION: '1',
  TRADING: '2',
  SERVICE: '3',
  OTHER: '4',
} as const;
export const SUPPLIER_TYPE_OPTIONS: EnumOption[] = [
  { label: '生产型', value: 1 },
  { label: '贸易型', value: 2 },
  { label: '服务型', value: 3 },
  { label: '其他', value: 4 },
  { label: '生产型', value: '1' },
  { label: '贸易型', value: '2' },
  { label: '服务型', value: '3' },
  { label: '其他', value: '4' },
];
export const SUPPLIER_TYPE_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = {
export const SUPPLIER_TYPE_CELLTAG: { data: CellTagItem[]; labels: Record<string, string> } = {
  data: [
    { value: 1, color: 'blue' },
    { value: 2, color: 'green' },
    { value: 3, color: 'orange' },
    { value: 4, color: 'default' },
    { value: '1', color: 'blue' },
    { value: '2', color: 'green' },
    { value: '3', color: 'orange' },
    { value: '4', color: 'default' },
  ],
  labels: { 1: '生产型', 2: '贸易型', 3: '服务型', 4: '其他' },
  labels: { '1': '生产型', '2': '贸易型', '3': '服务型', '4': '其他' },
};
// ========== 准入申请 — 对齐 SupplierApplyStatusEnum (0/10/20/30/40/50) ==========
src/views/srm/supplier/components/select-dialog.vue
@@ -20,6 +20,7 @@
const open = ref(false);
const multiple = ref(true);
const approvalFilter = ref(true);
const excludeApproved = ref(false);
const selectedRows = ref<SrmSupplierApi.SupplierVO[]>([]);
const preSelectedIds = ref<number[]>([]);
@@ -178,6 +179,9 @@
          if (approvalFilter.value) {
            params.approved = true;
          }
          if (excludeApproved.value) {
            params.excludeApproved = true;
          }
          return await getSupplierPage(params);
        },
      },
@@ -211,11 +215,12 @@
async function openModal(
  selectedIds?: number[],
  options?: { multiple?: boolean; approvalFilter?: boolean },
  options?: { multiple?: boolean; approvalFilter?: boolean; excludeApproved?: boolean },
) {
  open.value = true;
  multiple.value = options?.multiple ?? true;
  approvalFilter.value = options?.approvalFilter ?? true;
  excludeApproved.value = options?.excludeApproved ?? false;
  preSelectedIds.value = selectedIds || [];
  await nextTick();
  gridApi.setGridOptions({
src/views/srm/supplier/components/select.vue
@@ -16,6 +16,7 @@
    allowClear?: boolean;
    approvalFilter?: boolean;
    disabled?: boolean;
    excludeApproved?: boolean;
    modelValue?: number;
    placeholder?: string;
    supplierName?: string;
@@ -24,6 +25,7 @@
    allowClear: true,
    approvalFilter: true,
    disabled: false,
    excludeApproved: false,
    modelValue: undefined,
    placeholder: '请选择供应商',
    supplierName: undefined,
@@ -66,7 +68,7 @@
  const selectedIds = (
    props.modelValue === null ? [] : [props.modelValue]
  ) as number[];
  dialogRef.value?.open(selectedIds, { multiple: false, approvalFilter: props.approvalFilter });
  dialogRef.value?.open(selectedIds, { multiple: false, approvalFilter: props.approvalFilter, excludeApproved: props.excludeApproved });
}
function handleSelected(rows: SrmSupplierApi.SupplierVO[]) {
src/views/srm/supplierApply/data.ts
@@ -27,6 +27,7 @@
      component: markRaw(SrmSupplierSelect),
      componentProps: {
        approvalFilter: false,
        excludeApproved: true,
        placeholder: '请选择SRM供应商(或直接填写供应商名称)',
        onChange: async (supplier?: SrmSupplierApi.SupplierVO) => {
          if (!supplier || !formApi) return;
@@ -37,6 +38,9 @@
            contactEmail: supplier.contactEmail || '',
            taxNo: supplier.taxNo || '',
            address: supplier.address || '',
            bankName: supplier.bankName || '',
            bankAccount: supplier.bankAccount || '',
            remark: supplier.remark || '',
          });
        },
      },
@@ -44,6 +48,7 @@
        triggerFields: ['supplierName'],
        componentProps: (values) => ({
          approvalFilter: false,
          excludeApproved: true,
          placeholder: '请选择SRM供应商(或直接填写供应商名称)',
          supplierName: values.supplierName as string,
        }),
src/views/srm/tender/detail.vue
@@ -6,7 +6,7 @@
import { Page } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { message, Modal } from 'ant-design-vue';
import { IconifyIcon } from '@vben/icons';
import {
@@ -63,6 +63,25 @@
const materialLoading = ref(false);
const materialFormVisible = ref(false);
const materialForm = ref<SrmTenderApi.TenderMaterialVO>({ tenderProjectId: projectId });
// 已添加的物料产品ID集合,避免重复添加
const addedProductIds = computed(() => {
  const ids = new Set(
    materialList.value
      .filter((m) => m.productId !== undefined)
      .map((m) => m.productId!),
  );
  // 编辑时允许保留当前物料的 productId
  if (materialForm.value.id && materialForm.value.productId) {
    ids.delete(materialForm.value.productId);
  }
  return ids;
});
// 物料下拉可选列表(排除已添加的)
const availableMdmItems = computed(() =>
  mdmItemList.value.filter((item) => !addedProductIds.value.has(item.id!)),
);
async function loadProject() {
  project.value = await getTenderProject(projectId);
@@ -150,6 +169,27 @@
  try { quoteList.value = await getQuoteList(bidId); } finally { quoteLoading.value = false; }
}
async function handleSaveQuote() {
  // 校验:报价金额与投标金额一致性
  const bid = bidList.value.find((b) => b.id === quoteForm.value.bidId);
  if (bid?.bidTotalAmount != null) {
    const otherQuotesTotal = quoteList.value
      .filter((q) => q.id !== quoteForm.value.id)
      .reduce((sum, q) => sum + (q.quotePrice ?? 0), 0);
    const newTotal = otherQuotesTotal + (quoteForm.value.quotePrice ?? 0);
    if (newTotal !== bid.bidTotalAmount) {
      await new Promise<void>((resolve, reject) => {
        Modal.confirm({
          title: '金额不一致提示',
          content: `报价总金额(${newTotal.toLocaleString()})与投标金额(${bid.bidTotalAmount!.toLocaleString()})不一致,是否继续提交?`,
          okText: '继续提交',
          cancelText: '取消',
          onOk: () => { resolve(); },
          onCancel: () => { reject(new Error('cancel')); },
        });
      });
    }
  }
  if (quoteForm.value.id) {
    await updateQuote(quoteForm.value);
  } else {
@@ -685,7 +725,7 @@
        <a-form layout="vertical" class="mt-4">
          <a-form-item label="MDM物料" required>
            <a-select v-model:value="materialForm.productId" show-search placeholder="搜索并选择MDM物料" :filter-option="filterOption" option-label-prop="label" @change="onMaterialSelect">
              <a-select-option v-for="item in mdmItemList" :key="item.id" :value="item.id" :label="`${item.code} ${item.name}`">
              <a-select-option v-for="item in availableMdmItems" :key="item.id" :value="item.id" :label="`${item.code} ${item.name}`">
                <div class="flex flex-col">
                  <span class="font-medium">{{ item.code }}</span>
                  <span class="text-xs text-gray-400">{{ item.name }}{{ item.specification ? ` · ${item.specification}` : '' }}</span>