gaoluyang
2026-07-01 7d5f9791941d0ef9cd6d2daae80b5ae391e18b43
银川
1.联调产品维护页面
2.添加采购申请页面、联调
已添加3个文件
已修改12个文件
已删除1个文件
1058 ■■■■■ 文件已修改
mdm_frontend_change.md 523 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pnpm-lock.yaml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/erp/purchase/request/index.ts 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/packages/icons/package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/packages/icons/src/offline-icons.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/preferences.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/mdm/index.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/data.ts 102 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/index.vue 98 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/modules/form.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/modules/item-form.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/modules/order-form.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/modules/process-select-modal.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/supplier/data.ts 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/supplier/modules/form.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/menu/data.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
mdm_frontend_change.md
ÎļþÒÑɾ³ý
pnpm-lock.yaml
@@ -1620,6 +1620,9 @@
  src/packages/icons:
    dependencies:
      '@iconify/json':
        specifier: ^2.2.483
        version: 2.2.489
      '@vben-core/icons':
        specifier: workspace:*
        version: link:../@core/base/icons
src/api/erp/purchase/request/index.ts
@@ -7,15 +7,23 @@
  export interface PurchaseRequest {
    id?: number; // ç¼–号
    no?: string; // ç”³è¯·ç¼–号
    status?: number; // çŠ¶æ€ï¼š0未提交 10审批中 20审核通过 30审核不通过 40已取消
    requestUserId?: number; // ç”³è¯·äººç¼–号
    requestUserName?: string; // ç”³è¯·äººåç§°
    requestDeptId?: number; // ç”³è¯·éƒ¨é—¨ç¼–号
    requestDeptName?: string; // ç”³è¯·éƒ¨é—¨åç§°
    requestTime: string; // ç”³è¯·æ—¶é—´
    requestReason?: string; // ç”³è¯·ç†ç”±
    supplierId?: number; // ä¾›åº”商编号
    supplierName?: string; // ä¾›åº”商名称
    requestTime: string; // ç”³è¯·æ—¶é—´
    requesterId?: number; // ç”³è¯·äººç¼–号
    requesterName?: string; // ç”³è¯·äººåç§°
    reason?: string; // ç”³è¯·ç†ç”±
    estimatedAmount?: number; // é¢„估金额
    status?: number; // çŠ¶æ€
    discountPercent?: number; // ä¼˜æƒ çއ
    fileUrl?: string; // é™„件地址
    remark?: string; // å¤‡æ³¨
    totalCount?: number; // æ€»æ•°é‡
    totalPrice?: number; // æ€»é‡‘额
    productNames?: string; // äº§å“åç§°åˆ—表
    orderId?: number; // ç”Ÿæˆçš„采购订单编号
    orderNo?: string; // ç”Ÿæˆçš„采购订单号
    items?: PurchaseRequestItem[]; // ç”³è¯·æ˜Žç»†
    createTime?: string; // åˆ›å»ºæ—¶é—´
  }
@@ -25,11 +33,13 @@
    id?: number; // ç¼–号
    productId: number; // äº§å“ç¼–号
    productName?: string; // äº§å“åç§°
    productUnitName?: string; // äº§å“å•位
    productUnitId?: number; // äº§å“å•位编号
    productUnitName?: string; // äº§å“å•位名称
    productBarCode?: string; // äº§å“æ¡ç 
    requestCount: number; // ç”³è¯·æ•°é‡
    estimatedPrice?: number; // é¢„估单价
    requiredDate?: string; // éœ€æ±‚日期
    productPrice?: number; // äº§å“å•价(参考价)
    count: number; // æ•°é‡
    taxPercent?: number; // ç¨Žçއ
    demandTime?: string; // éœ€æ±‚日期
    remark?: string; // å¤‡æ³¨
  }
}
@@ -44,18 +54,14 @@
  return requestClient.put('/erp/purchase-request/update', data);
}
/** å®¡æ‰¹é‡‡è´­ç”³è¯· */
export function approvePurchaseRequest(params: {
  id: number;
  status: number;
  remark?: string;
}) {
  return requestClient.put('/erp/purchase-request/approve', null, { params });
/** æäº¤é‡‡è´­ç”³è¯·å®¡æ‰¹ */
export function submitPurchaseRequest(id: number, processDefinitionKey: string) {
  return requestClient.put('/erp/purchase-request/submit', null, { params: { id, processDefinitionKey } });
}
/** è½¬ä¸ºé‡‡è´­è®¢å• */
export function convertToPurchaseOrder(id: number) {
  return requestClient.put('/erp/purchase-request/convert', null, { params: { id } });
/** ç”Ÿæˆé‡‡è´­è®¢å• */
export function generatePurchaseOrder(id: number, supplierId: number) {
  return requestClient.post<number>('/erp/purchase-request/generate-order', null, { params: { id, supplierId } });
}
/** åˆ é™¤é‡‡è´­ç”³è¯· */
@@ -82,3 +88,8 @@
export function exportPurchaseRequest(params: any) {
  return requestClient.download('/erp/purchase-request/export-excel', { params });
}
/** èŽ·å–å®¡æ‰¹æµç¨‹åˆ—è¡¨ */
export function getApproveProcessList(id: number) {
  return requestClient.get(`/erp/purchase-request/approve-process-list`, { params: { id } });
}
src/packages/icons/package.json
@@ -17,6 +17,7 @@
    }
  },
  "dependencies": {
    "@iconify/json": "^2.2.483",
    "@vben-core/icons": "workspace:*"
  }
}
src/packages/icons/src/offline-icons.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
/**
 * ç¦»çº¿å›¾æ ‡é¢„加载配置
 * æš‚时禁用,使用在线加载方式
 */
import { addCollection } from '@iconify/vue';
/**
 * åˆå§‹åŒ–离线图标
 * ç›®å‰ä½¿ç”¨åœ¨çº¿åŠ è½½ï¼Œæ­¤å‡½æ•°ä¸ºç©º
 */
export function initOfflineIcons() {
  // æš‚时不预加载,使用 Iconify åœ¨çº¿ API
  // å¦‚果需要离线加载,需要将 JSON æ–‡ä»¶å¤åˆ¶åˆ°é¡¹ç›®ä¸­
}
src/preferences.ts
@@ -32,6 +32,10 @@
    companyName: import.meta.env.VITE_APP_TITLE,
    companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben',
  },
  tabbar: {
    /** åˆ·æ–°æµè§ˆå™¨åŽåªä¿ç•™å½“前页面,不持久化所有标签页 */
    persist: false,
  },
});
export const preferencesExtension =
src/views/basicData/mdm/index.vue
@@ -13,7 +13,7 @@
import {
  deleteItem,
  deleteItemList,
  exportItem,
  // exportItem,
  getItemPage,
  updateItemStatus,
} from '#/api/mdm/item';
@@ -35,10 +35,10 @@
}
/** å¯¼å‡ºè¡¨æ ¼ */
async function handleExport() {
  const data = await exportItem(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '物料.xls', source: data });
}
// async function handleExport() {
//   const data = await exportItem(await gridApi.formApi.getValues());
//   downloadFileFromBlobPart({ fileName: '物料.xls', source: data });
// }
/** åˆ›å»ºç‰©æ–™ */
function handleCreate() {
@@ -156,13 +156,13 @@
              auth: ['mdm:item:create'],
              onClick: handleCreate,
            },
            {
              label: $t('ui.actionTitle.export'),
              type: 'primary',
              icon: ACTION_ICON.DOWNLOAD,
              auth: ['mdm:item:export'],
              onClick: handleExport,
            },
            // {
            //   label: $t('ui.actionTitle.export'),
            //   type: 'primary',
            //   icon: ACTION_ICON.DOWNLOAD,
            //   auth: ['mdm:item:export'],
            //   onClick: handleExport,
            // },
            {
              label: $t('ui.actionTitle.deleteBatch'),
              type: 'primary',
src/views/erp/purchase/request/data.ts
@@ -14,10 +14,11 @@
/** çŠ¶æ€å¸¸é‡ */
export const PURCHASE_REQUEST_STATUS = {
  PENDING: 0, // å¾…审批
  APPROVED: 1, // å·²å®¡æ‰¹
  REJECTED: 2, // å·²æ‹’绝
  CONVERTED: 3, // å·²è½¬è®¢å•
  DRAFT: 0, // æœªæäº¤ï¼ˆè‰ç¨¿ï¼‰
  PROCESSING: 10, // å®¡æ‰¹ä¸­
  APPROVED: 20, // å®¡æ ¸é€šè¿‡
  REJECTED: 30, // å®¡æ ¸ä¸é€šè¿‡
  CANCELLED: 40, // å·²å–消
};
/** è¡¨å•的配置项 */
@@ -54,7 +55,7 @@
      rules: 'required',
    },
    {
      label: '供应商',
      label: '供应商(可选)',
      fieldName: 'supplierId',
      component: 'ApiSelect',
      componentProps: {
@@ -68,12 +69,37 @@
      },
    },
    {
      fieldName: 'reason',
      fieldName: 'discountPercent',
      label: '优惠率(%)',
      component: 'InputNumber',
      componentProps: {
        placeholder: '请输入优惠率',
        min: 0,
        max: 100,
        precision: 2,
        disabled: formType === 'detail',
      },
    },
    {
      fieldName: 'requestReason',
      label: '申请理由',
      component: 'Textarea',
      componentProps: {
        placeholder: '请输入申请理由',
        autoSize: { minRows: 1, maxRows: 1 },
        disabled: formType === 'detail',
      },
      formItemClass: 'col-span-2',
    },
    {
      fieldName: 'fileUrl',
      label: '附件',
      component: 'FileUpload',
      componentProps: {
        placeholder: '请上传附件',
        maxNumber: 5,
        maxSize: 20,
        accept: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'png'],
        disabled: formType === 'detail',
      },
      formItemClass: 'col-span-2',
@@ -121,18 +147,18 @@
      minWidth: 80,
    },
    {
      field: 'requestCount',
      title: '申请数量',
      field: 'count',
      title: '数量',
      minWidth: 120,
      fixed: 'right',
      slots: { default: 'requestCount' },
      slots: { default: 'count' },
    },
    {
      field: 'estimatedPrice',
      title: '预估单价',
      field: 'productPrice',
      title: '单价(参考价)',
      minWidth: 120,
      fixed: 'right',
      slots: { default: 'estimatedPrice' },
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalPrice',
@@ -142,11 +168,18 @@
      formatter: 'formatAmount2',
    },
    {
      field: 'requiredDate',
      field: 'taxPercent',
      title: '税率(%)',
      minWidth: 100,
      fixed: 'right',
      slots: { default: 'taxPercent' },
    },
    {
      field: 'demandTime',
      title: '需求日期',
      minWidth: 140,
      fixed: 'right',
      slots: { default: 'requiredDate' },
      slots: { default: 'demandTime' },
    },
    {
      field: 'remark',
@@ -199,7 +232,7 @@
      },
    },
    {
      fieldName: 'requesterId',
      fieldName: 'requestUserId',
      label: '申请人',
      component: 'ApiSelect',
      componentProps: {
@@ -217,10 +250,11 @@
      component: 'Select',
      componentProps: {
        options: [
          { label: '待审批', value: 0 },
          { label: '已审批', value: 1 },
          { label: '已拒绝', value: 2 },
          { label: '已转订单', value: 3 },
          { label: '未提交', value: 0 },
          { label: '审批中', value: 10 },
          { label: '审核通过', value: 20 },
          { label: '审核不通过', value: 30 },
          { label: '已取消', value: 40 },
        ],
        placeholder: '请选择状态',
        allowClear: true,
@@ -255,15 +289,37 @@
      formatter: 'formatDate',
    },
    {
      field: 'requesterName',
      field: 'requestUserName',
      title: '申请人',
      minWidth: 120,
    },
    {
      field: 'estimatedAmount',
      field: 'requestDeptName',
      title: '申请部门',
      minWidth: 120,
    },
    {
      field: 'totalCount',
      title: '申请数量',
      minWidth: 120,
      formatter: 'formatAmount3',
    },
    {
      field: 'totalPrice',
      title: '预估金额',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
    {
      field: 'productNames',
      title: '申请产品',
      minWidth: 150,
      showOverflow: 'tooltip',
    },
    {
      field: 'orderNo',
      title: '采购订单',
      minWidth: 180,
    },
    {
      field: 'status',
@@ -272,7 +328,7 @@
      slots: { default: 'status' },
    },
    {
      field: 'reason',
      field: 'requestReason',
      title: '申请理由',
      minWidth: 150,
      showOverflow: 'tooltip',
@@ -285,7 +341,7 @@
    },
    {
      title: '操作',
      width: 200,
      width: 240,
      fixed: 'right',
      slots: { default: 'actions' },
    },
src/views/erp/purchase/request/index.vue
@@ -4,29 +4,41 @@
import { ref } from 'vue';
import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message, Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
  approvePurchaseRequest,
  convertToPurchaseOrder,
  deletePurchaseRequest,
  exportPurchaseRequest,
  getApproveProcessList,
  getPurchaseRequestPage,
  submitPurchaseRequest,
} from '#/api/erp/purchase/request';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema, PURCHASE_REQUEST_STATUS } from './data';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
import OrderForm from './modules/order-form.vue';
import ProcessSelectModal from './modules/process-select-modal.vue';
/** ERP é‡‡è´­ç”³è¯·åˆ—表 */
defineOptions({ name: 'ErpPurchaseRequest' });
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [OrderFormModal, orderFormModalApi] = useVbenModal({
  connectedComponent: OrderForm,
  destroyOnClose: true,
});
const [ProcessModal, processModalApi] = useVbenModal({
  connectedComponent: ProcessSelectModal,
  destroyOnClose: true,
});
@@ -66,34 +78,29 @@
  }
}
/** å®¡æ‰¹é‡‡è´­ç”³è¯· */
async function handleApprove(row: ErpPurchaseRequestApi.PurchaseRequest, status: number) {
/** æäº¤å®¡æ‰¹ - å…ˆèŽ·å–æµç¨‹åˆ—è¡¨ */
async function handleSubmit(row: ErpPurchaseRequestApi.PurchaseRequest) {
  const hideLoading = message.loading({
    content: `确定${status === 1 ? '审批通过' : '审批拒绝'}该申请吗?`,
    content: '获取审批流程...',
    duration: 0,
  });
  try {
    await approvePurchaseRequest({ id: row.id!, status, remark: '' });
    message.success(`${status === 1 ? '审批通过' : '审批拒绝'}成功`);
    handleRefresh();
  } finally {
    const processList = await getApproveProcessList(row.id!);
    hideLoading();
    if (!processList || processList.length === 0) {
      message.warning('暂无可用审批流程,请先配置 BPM æµç¨‹');
      return;
    }
    // æ‰“开流程选择弹窗
    processModalApi.setData({ id: row.id, processList }).open();
  } catch {
    hideLoading();
  }
}
/** è½¬ä¸ºé‡‡è´­è®¢å• */
async function handleConvert(row: ErpPurchaseRequestApi.PurchaseRequest) {
  const hideLoading = message.loading({
    content: '正在转为采购订单...',
    duration: 0,
  });
  try {
    await convertToPurchaseOrder(row.id!);
    message.success('已成功转为采购订单');
    handleRefresh();
  } finally {
    hideLoading();
  }
/** ç”Ÿæˆé‡‡è´­è®¢å• */
function handleGenerateOrder(row: ErpPurchaseRequestApi.PurchaseRequest) {
  orderFormModalApi.setData({ id: row.id, supplierId: row.supplierId }).open();
}
const checkedIds = ref<number[]>([]);
@@ -148,6 +155,8 @@
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <OrderFormModal @success="handleRefresh" />
    <ProcessModal @success="handleRefresh" />
    <Grid table-title="采购申请列表">
      <template #toolbar-tools>
        <TableAction
@@ -182,10 +191,11 @@
        />
      </template>
      <template #status="{ row }">
        <Tag v-if="row.status === 0" color="warning">待审批</Tag>
        <Tag v-else-if="row.status === 1" color="success">已审批</Tag>
        <Tag v-else-if="row.status === 2" color="error">已拒绝</Tag>
        <Tag v-else-if="row.status === 3" color="default">已转订单</Tag>
        <Tag v-if="row.status === 0" color="default">未提交</Tag>
        <Tag v-else-if="row.status === 10" color="processing">审批中</Tag>
        <Tag v-else-if="row.status === 20" color="success">审核通过</Tag>
        <Tag v-else-if="row.status === 30" color="error">审核不通过</Tag>
        <Tag v-else-if="row.status === 40" color="warning">已取消</Tag>
      </template>
      <template #actions="{ row }">
        <TableAction
@@ -206,35 +216,17 @@
              onClick: handleEdit.bind(null, row),
            },
            {
              label: '审批通过',
              label: '提交',
              type: 'link',
              auth: ['erp:purchase-request:approve'],
              ifShow: () => row.status === 0,
              popConfirm: {
                title: '确认审批通过该申请吗?',
                confirm: handleApprove.bind(null, row, 1),
              },
              ifShow: () => row.status === 0 || row.status === 30,
              onClick: handleSubmit.bind(null, row),
            },
            {
              label: '审批拒绝',
              label: '生成订单',
              type: 'link',
              danger: true,
              auth: ['erp:purchase-request:approve'],
              ifShow: () => row.status === 0,
              popConfirm: {
                title: '确认审批拒绝该申请吗?',
                confirm: handleApprove.bind(null, row, 2),
              },
            },
            {
              label: '转订单',
              type: 'link',
              auth: ['erp:purchase-request:convert'],
              ifShow: () => row.status === 1,
              popConfirm: {
                title: '确认转为采购订单吗?',
                confirm: handleConvert.bind(null, row),
              },
              auth: ['erp:purchase-request:generate-order'],
              ifShow: () => row.status === 20 && !row.orderId,
              onClick: handleGenerateOrder.bind(null, row),
            },
            {
              label: $t('common.delete'),
src/views/erp/purchase/request/modules/form.vue
@@ -7,7 +7,7 @@
import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { message, Tag } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
@@ -17,7 +17,7 @@
} from '#/api/erp/purchase/request';
import { $t } from '#/locales';
import { useFormSchema, PURCHASE_REQUEST_STATUS } from '../data';
import { useFormSchema } from '../data';
import PurchaseRequestItemForm from './item-form.vue';
const emit = defineEmits(['success']);
@@ -78,8 +78,13 @@
    const data =
      (await formApi.getValues()) as ErpPurchaseRequestApi.PurchaseRequest;
    data.items = formData.value?.items?.map((item) => ({
      ...item,
      id: undefined,
      productId: item.productId,
      productUnitId: item.productUnitId,
      productPrice: item.productPrice,
      count: item.count,
      taxPercent: item.taxPercent,
      demandTime: item.demandTime,
      remark: item.remark,
    }));
    try {
      await (formType.value === 'create'
src/views/erp/purchase/request/modules/item-form.vue
@@ -35,10 +35,10 @@
/** èŽ·å–è¡¨æ ¼åˆè®¡æ•°æ® */
const summaries = computed(() => {
  return {
    count: tableData.value.reduce((sum, item) => sum + (item.requestCount || 0), 0),
    count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0),
    totalPrice: tableData.value.reduce(
      (sum, item) => {
        const price = erpPriceMultiply(item.estimatedPrice || 0, item.requestCount || 0) ?? 0;
        const price = erpPriceMultiply(item.productPrice || 0, item.count || 0) ?? 0;
        return sum + price;
      },
      0,
@@ -89,11 +89,13 @@
  const newRow = {
    id: undefined,
    productId: undefined,
    productUnitId: undefined,
    productUnitName: undefined,
    productBarCode: undefined,
    requestCount: 1,
    estimatedPrice: undefined,
    requiredDate: undefined,
    count: 1,
    productPrice: undefined,
    taxPercent: undefined,
    demandTime: undefined,
    remark: undefined,
    totalPrice: 0,
  };
@@ -118,10 +120,11 @@
  }
  row.productId = productId;
  row.productBarCode = product.barCode;
  row.productUnitId = product.unitMeasureId;
  row.productUnitName = product.unitMeasureName;
  row.productName = product.name;
  row.estimatedPrice = product.purchasePrice || 0;
  row.requestCount = row.requestCount || 1;
  row.productPrice = product.purchasePrice || 0;
  row.count = row.count || 1;
  handleRowChange(row);
}
@@ -134,16 +137,16 @@
    tableData.value[index] = row;
  }
  // è®¡ç®—预估金额
  if (row.estimatedPrice && row.requestCount) {
    row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0;
  if (row.productPrice && row.count) {
    row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
  }
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpPurchaseRequestApi.PurchaseRequestItem) {
  if (row.estimatedPrice && row.requestCount) {
    row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0;
  if (row.productPrice && row.count) {
    row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
  }
}
@@ -155,8 +158,8 @@
      if (!item.productId) {
        throw new Error(`第 ${i + 1} è¡Œï¼šäº§å“ä¸èƒ½ä¸ºç©º`);
      }
      if (!item.requestCount || item.requestCount <= 0) {
        throw new Error(`第 ${i + 1} è¡Œï¼šç”³è¯·æ•°é‡ä¸èƒ½ä¸ºç©º`);
      if (!item.count || item.count <= 0) {
        throw new Error(`第 ${i + 1} è¡Œï¼šæ•°é‡ä¸èƒ½ä¸ºç©º`);
      }
    }
  }
@@ -191,35 +194,46 @@
      />
      <span v-else>{{ row.productName || '-' }}</span>
    </template>
    <template #requestCount="{ row }">
    <template #count="{ row }">
      <InputNumber
        v-if="!disabled"
        v-model:value="row.requestCount"
        v-model:value="row.count"
        :min="0"
        :precision="3"
        @change="handleRowChange(row)"
      />
      <span v-else>{{ erpCountInputFormatter(row.requestCount) || '-' }}</span>
      <span v-else>{{ erpCountInputFormatter(row.count) || '-' }}</span>
    </template>
    <template #estimatedPrice="{ row }">
    <template #productPrice="{ row }">
      <InputNumber
        v-if="!disabled"
        v-model:value="row.estimatedPrice"
        v-model:value="row.productPrice"
        :min="0"
        :precision="2"
        @change="handleRowChange(row)"
      />
      <span v-else>{{ erpPriceInputFormatter(row.estimatedPrice) || '-' }}</span>
      <span v-else>{{ erpPriceInputFormatter(row.productPrice) || '-' }}</span>
    </template>
    <template #requiredDate="{ row }">
    <template #taxPercent="{ row }">
      <InputNumber
        v-if="!disabled"
        v-model:value="row.taxPercent"
        :min="0"
        :max="100"
        :precision="2"
        @change="handleRowChange(row)"
      />
      <span v-else>{{ row.taxPercent ? `${row.taxPercent}%` : '-' }}</span>
    </template>
    <template #demandTime="{ row }">
      <DatePicker
        v-if="!disabled"
        v-model:value="row.requiredDate"
        v-model:value="row.demandTime"
        valueFormat="YYYY-MM-DD"
        class="w-full"
        @change="handleRowChange(row)"
      />
      <span v-else>{{ row.requiredDate || '-' }}</span>
      <span v-else>{{ row.demandTime || '-' }}</span>
    </template>
    <template #remark="{ row }">
      <Input v-if="!disabled" v-model:value="row.remark" class="w-full" @change="handleRowChange(row)" />
@@ -246,7 +260,7 @@
        <div class="flex justify-between text-sm text-muted-foreground">
          <span class="font-medium text-foreground">合计:</span>
          <div class="flex space-x-4">
            <span>申请数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>预估金额:{{ erpPriceInputFormatter(summaries.totalPrice) }}</span>
          </div>
        </div>
src/views/erp/purchase/request/modules/order-form.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
<script lang="ts" setup>
import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request';
import { ref, computed } from 'vue';
import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { message, Select } from 'ant-design-vue';
import { generatePurchaseOrder } from '#/api/erp/purchase/request';
import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
const emit = defineEmits(['success']);
const requestId = ref<number>();
const supplierId = ref<number>();
const supplierOptions = ref<{ id: number; name: string }[]>([]);
const supplierSelectOptions = computed(() => {
  return supplierOptions.value.map((s) => ({
    label: s.name,
    value: s.id,
  }));
});
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    if (!supplierId.value) {
      message.warning('请选择供应商');
      return;
    }
    modalApi.lock();
    try {
      const orderId = await generatePurchaseOrder(requestId.value!, supplierId.value!);
      await modalApi.close();
      emit('success');
      message.success(`生成采购订单成功,订单编号:${orderId}`);
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      requestId.value = undefined;
      supplierId.value = undefined;
      return;
    }
    const data = modalApi.getData<{ id: number; supplierId?: number }>();
    requestId.value = data.id;
    supplierId.value = data.supplierId;
    // èŽ·å–ä¾›åº”å•†åˆ—è¡¨
    supplierOptions.value = await getSupplierSimpleList();
  },
});
</script>
<template>
  <Modal title="生成采购订单" class="w-1/3">
    <div class="mb-4">
      <span class="mr-2">选择供应商:</span>
      <Select
        v-model:value="supplierId"
        :options="supplierSelectOptions"
        style="width: 200px"
        placeholder="请选择供应商"
        show-search
        :filter-option="(input: string, option: any) => option.label.toLowerCase().includes(input.toLowerCase())"
      />
    </div>
  </Modal>
</template>
src/views/erp/purchase/request/modules/process-select-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
<script lang="ts" setup>
import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request';
import { ref, computed } from 'vue';
import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { message, Select } from 'ant-design-vue';
import { submitPurchaseRequest } from '#/api/erp/purchase/request';
interface ProcessDefinition {
  id: string;
  name: string;
  version: number;
  key: string;
}
const emit = defineEmits(['success']);
const requestId = ref<number>();
const processList = ref<ProcessDefinition[]>([]);
const selectedProcessKey = ref<string>('');
const processOptions = computed(() => {
  return processList.value.map((p) => ({
    label: `${p.name} (V${p.version})`,
    value: p.key,
  }));
});
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    if (!selectedProcessKey.value) {
      message.warning('请选择审批流程');
      return;
    }
    modalApi.lock();
    try {
      await submitPurchaseRequest(requestId.value!, selectedProcessKey.value);
      await modalApi.close();
      emit('success');
      message.success('提交成功');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      requestId.value = undefined;
      selectedProcessKey.value = '';
      processList.value = [];
      return;
    }
    const data = modalApi.getData<{ id: number; processList: ProcessDefinition[] }>();
    requestId.value = data.id;
    processList.value = data.processList || [];
    // é»˜è®¤é€‰æ‹©ç¬¬ä¸€ä¸ªæµç¨‹
    if (processList.value.length > 0) {
      selectedProcessKey.value = processList.value[0].key;
    }
  },
});
</script>
<template>
  <Modal title="选择审批流程" class="w-1/3">
    <div class="mb-4">
      <span class="mr-2">审批流程:</span>
      <Select
        v-model:value="selectedProcessKey"
        :options="processOptions"
        style="width: 200px"
        placeholder="请选择审批流程"
      />
    </div>
  </Modal>
</template>
src/views/erp/purchase/supplier/data.ts
@@ -116,6 +116,17 @@
      rules: z.number().default(0),
    },
    {
      fieldName: 'sort',
      label: '排序',
      component: 'InputNumber',
      componentProps: {
        placeholder: '请输入排序',
        min: 0,
        class: '!w-full',
      },
      rules: z.number().default(0),
    },
    {
      fieldName: 'qualification',
      label: '资质信息',
      component: 'Textarea',
src/views/erp/purchase/supplier/modules/form.vue
@@ -30,7 +30,7 @@
    componentProps: {
      class: 'w-full',
    },
    labelWidth: 100,
    labelWidth: 120,
  },
  wrapperClass: 'grid-cols-2',
  layout: 'horizontal',
src/views/system/menu/data.ts
@@ -101,7 +101,7 @@
      component: 'IconPicker',
      componentProps: {
        placeholder: '请选择菜单图标',
        prefix: 'carbon',
        prefix: 'ep',
      },
      rules: 'required',
      dependencies: {