银川
1.销售订单页面开发、联调
2.合同管理页面开发联调
3.项目代码相关配置修改
已修改11个文件
215 ■■■■■ 文件已修改
src/api/erp/sale/order/index.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/table-action/table-action.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/table-action/typing.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/contract/data.ts 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/contract/detail/data.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/contract/detail/modules/info.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/contract/index.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/data.ts 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/index.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/modules/item-form.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/erp/sale/order/index.ts
@@ -100,3 +100,17 @@
export function exportSaleOrder(params: any) {
  return requestClient.download('/erp/sale-order/export-excel', { params });
}
/** 作废销售订单 */
export function cancelSaleOrder(id: number) {
  return requestClient.put('/erp/sale-order/cancel', null, {
    params: { id },
  });
}
/** 启用销售订单(恢复已作废订单) */
export function enableSaleOrder(id: number) {
  return requestClient.put('/erp/sale-order/enable', null, {
    params: { id },
  });
}
src/components/table-action/table-action.vue
@@ -97,6 +97,8 @@
    attrs.onCancel = popConfirm.cancel;
  }
  // 不设置 getPopupContainer,让它默认挂载到 body,避免被遮挡
  return attrs;
}
@@ -269,6 +271,12 @@
}
.ant-popconfirm {
  width: 210px;
  //.ant-popconfirm-message {
  //  white-space: nowrap;
  //}
  .ant-popconfirm-buttons {
    .ant-btn {
      margin-inline-start: 4px !important;
src/components/table-action/typing.ts
@@ -12,6 +12,8 @@
  cancel?: () => void;
  icon?: string;
  disabled?: boolean;
  placement?: string;
  getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
}
export interface ActionItem extends ButtonProps {
src/views/crm/contract/data.ts
@@ -277,6 +277,32 @@
        allowClear: true,
      },
    },
    {
      fieldName: 'auditStatus',
      label: '合同状态',
      component: 'Select',
      componentProps: {
        options: [
          { label: '未提交', value: 0 },
          { label: '审批中', value: 10 },
          { label: '审核通过', value: 20 },
          { label: '审核不通过', value: 30 },
          { label: '已取消', value: 40 },
          { label: '已作废', value: 50 },
        ],
        placeholder: '请选择合同状态',
        allowClear: true,
      },
    },
    {
      fieldName: 'orderNo',
      label: '关联订单',
      component: 'Input',
      componentProps: {
        placeholder: '请输入订单号',
        allowClear: true,
      },
    },
  ];
}
@@ -287,6 +313,7 @@
      field: 'no',
      minWidth: 180,
      fixed: 'left',
      slots: { default: 'no' },
    },
    {
      title: '合同名称',
src/views/crm/contract/detail/data.ts
@@ -84,6 +84,11 @@
      label: '公司签约人',
    },
    {
      field: 'orderNo',
      label: '销售订单',
      slot: 'orderNo', // 使用 slot 自定义渲染
    },
    {
      field: 'remark',
      label: '备注',
    },
src/views/crm/contract/detail/modules/info.vue
@@ -1,16 +1,20 @@
<script lang="ts" setup>
import type { CrmContractApi } from '#/api/crm/contract';
import { Divider } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import { Button, Divider } from 'ant-design-vue';
import { useDescription } from '#/components/description';
import { useFollowUpDetailSchema } from '#/views/crm/followup/data';
import { useDetailBaseSchema } from '../data';
defineProps<{
const props = defineProps<{
  contract: CrmContractApi.Contract; // 合同信息
}>();
const router = useRouter();
const [BaseDescriptions] = useDescription({
  title: '基本信息',
@@ -27,11 +31,28 @@
  class: 'mx-4',
  schema: useFollowUpDetailSchema(),
});
/** 查看销售订单 */
function handleViewOrder() {
  if (props.contract.orderId) {
    router.push({
      path: '/erp/sale/order',
      query: { orderId: props.contract.orderId },
    });
  }
}
</script>
<template>
  <div>
    <BaseDescriptions :data="contract" />
    <BaseDescriptions :data="contract">
      <template #orderNo="{ data }">
        <Button v-if="data.orderId" type="link" @click="handleViewOrder">
          {{ data.orderNo }}
        </Button>
        <span v-else>-</span>
      </template>
    </BaseDescriptions>
    <Divider />
    <SystemDescriptions :data="contract" />
  </div>
src/views/crm/contract/index.vue
@@ -8,7 +8,7 @@
import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
import { Button, message, Modal, Select, Tabs, Tag } from 'ant-design-vue';
import { Button, message, Modal, Select, Tabs, Tag, Tooltip } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -238,6 +238,16 @@
          {{ row.name }}
        </Button>
      </template>
      <template #no="{ row }">
        <Tooltip v-if="row.auditStatus === 50" title="该合同已作废,订单已失效">
          <span class="text-red-500 cursor-pointer" @click="handleDetail(row)">
            {{ row.no }}
          </span>
        </Tooltip>
        <Button v-else type="link" @click="handleDetail(row)">
          {{ row.no }}
        </Button>
      </template>
      <template #customerName="{ row }">
        <Button type="link" @click="handleCustomerDetail(row)">
          {{ row.customerName }}
@@ -258,6 +268,8 @@
        <Tag v-if="row.auditStatus === 10" color="warning">审批中</Tag>
        <Tag v-if="row.auditStatus === 20" color="success">审核通过</Tag>
        <Tag v-if="row.auditStatus === 30" color="error">审核不通过</Tag>
        <Tag v-if="row.auditStatus === 40" color="default">已取消</Tag>
        <Tag v-if="row.auditStatus === 50" color="error">已作废</Tag>
      </template>
      <template #orderNo="{ row }">
        <Button v-if="row.orderId" type="link" @click="handleViewOrder(row)">
src/views/erp/sale/order/data.ts
@@ -1,8 +1,6 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE } from '../../../../packages/constants/src';
import { getDictOptions } from '../../../../packages/effects/hooks/src';
import { erpPriceInputFormatter } from '../../../../packages/utils/src';
import { z } from '#/adapter/form';
@@ -348,7 +346,11 @@
      label: '状态',
      component: 'Select',
      componentProps: {
        options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'),
        options: [
          { label: '未审核', value: 10 },
          { label: '已审核', value: 20 },
          { label: '已作废', value: 30 },
        ],
        placeholder: '请选择状态',
        allowClear: true,
      },
@@ -406,6 +408,7 @@
      title: '订单单号',
      width: 200,
      fixed: 'left',
      slots: { default: 'no' },
    },
    {
      field: 'productNames',
@@ -474,11 +477,9 @@
    {
      field: 'status',
      title: '状态',
      minWidth: 120,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.ERP_AUDIT_STATUS },
      },
      minWidth: 100,
      fixed: 'right',
      slots: { default: 'status' },
    },
    {
      title: '操作',
src/views/erp/sale/order/index.vue
@@ -8,11 +8,13 @@
import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
import { message, Tag, Tooltip } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
  cancelSaleOrder,
  deleteSaleOrder,
  enableSaleOrder,
  exportSaleOrder,
  getSaleOrderPage,
  updateSaleOrderStatus,
@@ -80,6 +82,36 @@
  try {
    await updateSaleOrderStatus(row.id!, status);
    message.success(`${status === 20 ? '审批' : '反审批'}成功`);
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** 作废销售订单 */
async function handleCancel(row: ErpSaleOrderApi.SaleOrder) {
  const hideLoading = message.loading({
    content: '正在作废该订单...',
    duration: 0,
  });
  try {
    await cancelSaleOrder(row.id!);
    message.success('作废成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** 启用销售订单(恢复已作废订单) */
async function handleEnable(row: ErpSaleOrderApi.SaleOrder) {
  const hideLoading = message.loading({
    content: '正在启用该订单...',
    duration: 0,
  });
  try {
    await enableSaleOrder(row.id!);
    message.success('启用成功');
    handleRefresh();
  } finally {
    hideLoading();
@@ -180,11 +212,26 @@
          ]"
        />
      </template>
      <template #no="{ row }">
        <Tooltip v-if="row.status === 30" title="该订单已作废,订单已失效">
          <span class="text-red-500 cursor-pointer" @click="handleDetail(row)">
            {{ row.no }}
          </span>
        </Tooltip>
        <a v-else class="text-primary cursor-pointer" @click="handleDetail(row)">
          {{ row.no }}
        </a>
      </template>
      <template #contractNo="{ row }">
        <a v-if="row.contractId" class="text-primary cursor-pointer" @click="handleViewContract(row)">
          {{ row.contractNo }}
        </a>
        <span v-else>-</span>
      </template>
      <template #status="{ row }">
        <Tag v-if="row.status === 10" color="warning">未审核</Tag>
        <Tag v-if="row.status === 20" color="success">已审核</Tag>
        <Tag v-if="row.status === 30" color="error">已作废</Tag>
      </template>
      <template #actions="{ row }">
        <TableAction
@@ -201,21 +248,51 @@
              type: 'link',
              icon: ACTION_ICON.EDIT,
              auth: ['erp:sale-order:update'],
              ifShow: () => row.status !== 20,
              ifShow: () => row.status === 10,
              onClick: handleEdit.bind(null, row),
            },
            {
              label: row.status === 10 ? '审批' : '反审批',
              label: '审批',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['erp:sale-order:update-status'],
              ifShow: () => row.status === 10,
              popConfirm: {
                title: `确认${row.status === 10 ? '审批' : '反审批'}${row.no}吗?`,
                confirm: handleUpdateStatus.bind(
                  null,
                  row,
                  row.status === 10 ? 20 : 10,
                ),
                title: `确认审批${row.no}吗?`,
                confirm: handleUpdateStatus.bind(null, row, 20),
              },
            },
            {
              label: '反审批',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['erp:sale-order:update-status'],
              ifShow: () => row.status === 20,
              popConfirm: {
                title: `确认反审批${row.no}吗?`,
                confirm: handleUpdateStatus.bind(null, row, 10),
              },
            },
            {
              label: '作废',
              type: 'link',
              icon: 'ant-design:stop-outlined',
              auth: ['erp:sale-order:cancel'],
              ifShow: () => row.status === 20,
              popConfirm: {
                title: `确认作废${row.no}吗?作废后不可恢复!`,
                confirm: handleCancel.bind(null, row),
              },
            },
            {
              label: '启用',
              type: 'link',
              icon: 'ant-design:check-circle-outlined',
              auth: ['erp:sale-order:enable'],
              ifShow: () => row.status === 30,
              popConfirm: {
                title: `确认启用${row.no}吗?`,
                confirm: handleEnable.bind(null, row),
              },
            },
            {
@@ -224,6 +301,7 @@
              danger: true,
              icon: ACTION_ICON.DELETE,
              auth: ['erp:sale-order:delete'],
              ifShow: () => row.status === 10,
              popConfirm: {
                title: $t('ui.actionMessage.deleteConfirm', [row.no]),
                confirm: handleDelete.bind(null, [row.id!]),
src/views/erp/sale/order/modules/item-form.vue
@@ -229,6 +229,7 @@
  <Grid class="w-full">
    <template #productId="{ row }">
      <Select
        v-if="!disabled"
        v-model:value="row.productId"
        :options="productOptions"
        :field-names="{ label: 'name', value: 'id' }"
@@ -237,6 +238,7 @@
        show-search
        @change="handleProductChange($event, row)"
      />
      <span v-else>{{ row.productName || '-' }}</span>
    </template>
    <template #count="{ row }">
      <InputNumber
vite.config.ts
@@ -9,6 +9,9 @@
      },
      server: {
        allowedHosts: true,
        watch: {
          ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**'],
        },
        proxy: {
          '/admin-api': {
            changeOrigin: true,