From 90845519117abbd21955d589366280d77c9b6fc1 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 02 七月 2026 16:57:21 +0800
Subject: [PATCH] 银川 1.销售订单页面开发、联调 2.合同管理页面开发联调 3.项目代码相关配置修改

---
 vite.config.ts                                 |    3 
 src/api/erp/sale/order/index.ts                |   14 +++
 src/views/erp/sale/order/index.vue             |   96 +++++++++++++++++++++--
 src/views/erp/sale/order/modules/item-form.vue |    2 
 src/components/table-action/table-action.vue   |    8 ++
 src/components/table-action/typing.ts          |    2 
 src/views/crm/contract/detail/data.ts          |    5 +
 src/views/crm/contract/detail/modules/info.vue |   27 ++++++
 src/views/crm/contract/index.vue               |   14 +++
 src/views/erp/sale/order/data.ts               |   17 ++--
 src/views/crm/contract/data.ts                 |   27 ++++++
 11 files changed, 194 insertions(+), 21 deletions(-)

diff --git a/src/api/erp/sale/order/index.ts b/src/api/erp/sale/order/index.ts
index 077ca9a..acc7a2c 100644
--- a/src/api/erp/sale/order/index.ts
+++ b/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 },
+  });
+}
diff --git a/src/components/table-action/table-action.vue b/src/components/table-action/table-action.vue
index a265463..cdf81a0 100644
--- a/src/components/table-action/table-action.vue
+++ b/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;
diff --git a/src/components/table-action/typing.ts b/src/components/table-action/typing.ts
index 1e44fb2..91814c5 100644
--- a/src/components/table-action/typing.ts
+++ b/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 {
diff --git a/src/views/crm/contract/data.ts b/src/views/crm/contract/data.ts
index e9ac060..0f99e0a 100644
--- a/src/views/crm/contract/data.ts
+++ b/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: '鍚堝悓鍚嶇О',
diff --git a/src/views/crm/contract/detail/data.ts b/src/views/crm/contract/detail/data.ts
index 1602256..f0602ca 100644
--- a/src/views/crm/contract/detail/data.ts
+++ b/src/views/crm/contract/detail/data.ts
@@ -84,6 +84,11 @@
       label: '鍏徃绛剧害浜�',
     },
     {
+      field: 'orderNo',
+      label: '閿�鍞鍗�',
+      slot: 'orderNo', // 浣跨敤 slot 鑷畾涔夋覆鏌�
+    },
+    {
       field: 'remark',
       label: '澶囨敞',
     },
diff --git a/src/views/crm/contract/detail/modules/info.vue b/src/views/crm/contract/detail/modules/info.vue
index da23e23..df757d3 100644
--- a/src/views/crm/contract/detail/modules/info.vue
+++ b/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>
diff --git a/src/views/crm/contract/index.vue b/src/views/crm/contract/index.vue
index 94b2b2a..99e19fa 100644
--- a/src/views/crm/contract/index.vue
+++ b/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)">
diff --git a/src/views/erp/sale/order/data.ts b/src/views/erp/sale/order/data.ts
index 3d654e2..24cd0b3 100644
--- a/src/views/erp/sale/order/data.ts
+++ b/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: '鎿嶄綔',
diff --git a/src/views/erp/sale/order/index.vue b/src/views/erp/sale/order/index.vue
index d72d704..abccc6d 100644
--- a/src/views/erp/sale/order/index.vue
+++ b/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: '姝e湪浣滃簾璇ヨ鍗�...',
+    duration: 0,
+  });
+  try {
+    await cancelSaleOrder(row.id!);
+    message.success('浣滃簾鎴愬姛');
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍚敤閿�鍞鍗曪紙鎭㈠宸蹭綔搴熻鍗曪級 */
+async function handleEnable(row: ErpSaleOrderApi.SaleOrder) {
+  const hideLoading = message.loading({
+    content: '姝e湪鍚敤璇ヨ鍗�...',
+    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!]),
diff --git a/src/views/erp/sale/order/modules/item-form.vue b/src/views/erp/sale/order/modules/item-form.vue
index 20389e6..8dc45b3 100644
--- a/src/views/erp/sale/order/modules/item-form.vue
+++ b/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
diff --git a/vite.config.ts b/vite.config.ts
index 44d7717..fc97bd3 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -9,6 +9,9 @@
       },
       server: {
         allowedHosts: true,
+        watch: {
+          ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**'],
+        },
         proxy: {
           '/admin-api': {
             changeOrigin: true,

--
Gitblit v1.9.3