From c18d70b0bcf09170841e19b50aecf54921a60939 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Wed, 4 Jun 2025 17:38:28 +0800 Subject: [PATCH] feat: crm customer detail --- apps/web-antd/src/views/crm/clue/data.ts | 33 ----- .../views/crm/clue/modules/detail-info.vue | 5 +- apps/web-antd/src/views/crm/customer/data.ts | 68 ++++----- .../crm/customer/modules/detail-info.vue | 43 +++++- .../src/views/crm/customer/modules/detail.vue | 134 ++++++++++++------ apps/web-antd/src/views/crm/followup/data.ts | 36 +++++ 6 files changed, 197 insertions(+), 122 deletions(-) create mode 100644 apps/web-antd/src/views/crm/followup/data.ts diff --git a/apps/web-antd/src/views/crm/clue/data.ts b/apps/web-antd/src/views/crm/clue/data.ts index 4d3926695..bd2924aad 100644 --- a/apps/web-antd/src/views/crm/clue/data.ts +++ b/apps/web-antd/src/views/crm/clue/data.ts @@ -353,36 +353,3 @@ export function useDetailBaseSchema(): DescriptionItemSchema[] { }, ]; } - -/** 详情系统信息的配置 */ -export function useDetailSystemSchema(): DescriptionItemSchema[] { - return [ - { - field: 'ownerUserName', - label: '负责人', - }, - { - field: 'contactLastContent', - label: '最后跟进记录', - }, - { - field: 'contactLastTime', - label: '最后跟进时间', - content: (data) => formatDateTime(data?.contactLastTime) as string, - }, - { - field: 'creatorName', - label: '创建人', - }, - { - field: 'createTime', - label: '创建时间', - content: (data) => formatDateTime(data?.createTime) as string, - }, - { - field: 'updateTime', - label: '更新时间', - content: (data) => formatDateTime(data?.updateTime) as string, - }, - ]; -} diff --git a/apps/web-antd/src/views/crm/clue/modules/detail-info.vue b/apps/web-antd/src/views/crm/clue/modules/detail-info.vue index ea3cb1fbf..6abc70a99 100644 --- a/apps/web-antd/src/views/crm/clue/modules/detail-info.vue +++ b/apps/web-antd/src/views/crm/clue/modules/detail-info.vue @@ -4,8 +4,9 @@ import type { CrmClueApi } from '#/api/crm/clue'; import { Divider } from 'ant-design-vue'; import { useDescription } from '#/components/description'; +import { useFollowUpDetailSchema } from '#/views/crm/followup/data'; -import { useDetailBaseSchema, useDetailSystemSchema } from '../data'; +import { useDetailBaseSchema } from '../data'; defineOptions({ name: 'CrmClueDetailsInfo' }); @@ -30,7 +31,7 @@ const [SystemDescription] = useDescription({ column: 3, class: 'mx-4', }, - schema: useDetailSystemSchema(), + schema: useFollowUpDetailSchema(), }); diff --git a/apps/web-antd/src/views/crm/customer/data.ts b/apps/web-antd/src/views/crm/customer/data.ts index 551569da0..840d56636 100644 --- a/apps/web-antd/src/views/crm/customer/data.ts +++ b/apps/web-antd/src/views/crm/customer/data.ts @@ -243,7 +243,24 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { /** 详情页的字段 */ export function useDetailSchema(): DescriptionItemSchema[] { - return [...useDetailBaseSchema(), ...useDetailSystemSchema()]; + return [ + { + field: 'level', + label: '客户级别', + content: (data) => + h(DictTag, { type: DICT_TYPE.CRM_CUSTOMER_LEVEL, value: data?.level }), + }, + { + field: 'dealStatus', + label: '成交状态', + content: (data) => (data.dealStatus ? '已成交' : '未成交'), + }, + { + field: 'createTime', + label: '创建时间', + content: (data) => formatDateTime(data?.createTime) as string, + }, + ]; } /** 详情页的基础字段 */ @@ -275,13 +292,21 @@ export function useDetailBaseSchema(): DescriptionItemSchema[] { label: '邮箱', }, { - field: 'wechat', - label: '微信', + field: 'areaName', + label: '地址', + }, + { + field: 'detailAddress', + label: '详细地址', }, { field: 'qq', label: 'QQ', }, + { + field: 'wechat', + label: '微信', + }, { field: 'industryId', label: '客户行业', @@ -297,14 +322,6 @@ export function useDetailBaseSchema(): DescriptionItemSchema[] { content: (data) => h(DictTag, { type: DICT_TYPE.CRM_CUSTOMER_LEVEL, value: data?.level }), }, - { - field: 'areaName', - label: '地址', - }, - { - field: 'detailAddress', - label: '详细地址', - }, { field: 'contactNextTime', label: '下次联系时间', @@ -316,32 +333,3 @@ export function useDetailBaseSchema(): DescriptionItemSchema[] { }, ]; } - -/** 详情页的系统字段 */ -export function useDetailSystemSchema(): DescriptionItemSchema[] { - return [ - { - field: 'ownerUserName', - label: '负责人', - }, - { - field: 'ownerUserDeptName', - label: '所属部门', - }, - { - field: 'contactLastTime', - label: '最后跟进时间', - content: (data) => formatDateTime(data?.contactLastTime) as string, - }, - { - field: 'createTime', - label: '创建时间', - content: (data) => formatDateTime(data?.createTime) as string, - }, - { - field: 'updateTime', - label: '更新时间', - content: (data) => formatDateTime(data?.updateTime) as string, - }, - ]; -} diff --git a/apps/web-antd/src/views/crm/customer/modules/detail-info.vue b/apps/web-antd/src/views/crm/customer/modules/detail-info.vue index c651e23e0..12b2526d2 100644 --- a/apps/web-antd/src/views/crm/customer/modules/detail-info.vue +++ b/apps/web-antd/src/views/crm/customer/modules/detail-info.vue @@ -1,5 +1,44 @@ - + diff --git a/apps/web-antd/src/views/crm/customer/modules/detail.vue b/apps/web-antd/src/views/crm/customer/modules/detail.vue index 19c2b2695..1697075eb 100644 --- a/apps/web-antd/src/views/crm/customer/modules/detail.vue +++ b/apps/web-antd/src/views/crm/customer/modules/detail.vue @@ -5,15 +5,15 @@ import type { SystemOperateLogApi } from '#/api/system/operate-log'; import { defineAsyncComponent, onMounted, ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; -import { Page } from '@vben/common-ui'; +import { confirm, Page, useVbenModal } from '@vben/common-ui'; +import { useTabs } from '@vben/hooks'; -import { Button, Card, Modal, Tabs } from 'ant-design-vue'; +import { Button, Card, message, Tabs } from 'ant-design-vue'; import { getCustomer, updateCustomerDealStatus } from '#/api/crm/customer'; import { getOperateLogPage } from '#/api/crm/operateLog'; import { BizTypeEnum } from '#/api/crm/permission'; import { useDescription } from '#/components/description'; -import { OperateLog } from '#/components/operate-log'; import { useDetailSchema } from '../data'; @@ -21,15 +21,37 @@ const CustomerDetailsInfo = defineAsyncComponent( () => import('./detail-info.vue'), ); +const FollowUp = defineAsyncComponent( + () => import('#/views/crm/followup/index.vue'), +); + +const PermissionList = defineAsyncComponent( + () => import('#/views/crm/permission/modules/permission-list.vue'), +); + +const TransferForm = defineAsyncComponent( + () => import('#/views/crm/permission/modules/transfer-form.vue'), +); + +const OperateLog = defineAsyncComponent( + () => import('#/components/operate-log'), +); + +const CustomerForm = defineAsyncComponent( + () => import('#/views/crm/customer/modules/form.vue'), +); + const loading = ref(false); const route = useRoute(); const router = useRouter(); +const tabs = useTabs(); const customerId = ref(0); const customer = ref({} as CrmCustomerApi.Customer); -const permissionListRef = ref(); // 团队成员列表 Ref +const customerLogList = ref([]); +const permissionListRef = ref>(); // 团队成员列表 Ref const [Description] = useDescription({ componentProps: { @@ -40,91 +62,107 @@ const [Description] = useDescription({ schema: useDetailSchema(), }); +const [FormModal, formModalApi] = useVbenModal({ + connectedComponent: CustomerForm, + destroyOnClose: true, +}); + +const [TransferModal, transferModalApi] = useVbenModal({ + connectedComponent: TransferForm, + destroyOnClose: true, +}); + /** 加载详情 */ async function loadCustomerDetail() { loading.value = true; customerId.value = Number(route.params.id); const data = await getCustomer(customerId.value); - await getOperateLog(); + const logList = await getOperateLogPage({ + bizType: BizTypeEnum.CRM_CUSTOMER, + bizId: customerId.value, + }); + customerLogList.value = logList.list; customer.value = data; loading.value = false; } +/** 返回列表页 */ +function handleBack() { + tabs.closeCurrentTab(); + router.push('/crm/customer'); +} + /** 编辑 */ function handleEdit() { - // formModalApi.setData({ id: clueId }).open(); + formModalApi.setData({ id: customerId.value }).open(); } /** 转移线索 */ function handleTransfer() { - // transferModalApi.setData({ id: clueId }).open(); + transferModalApi.setData({ id: customerId.value }).open(); } /** 锁定客户 */ function handleLock() { - // transferModalApi.setData({ id: clueId }).open(); + transferModalApi.setData({ id: customerId.value }).open(); } /** 解锁客户 */ function handleUnlock() { - // transferModalApi.setData({ id: clueId }).open(); + transferModalApi.setData({ id: customerId.value }).open(); } /** 领取客户 */ function handleReceive() { - // transferModalApi.setData({ id: clueId }).open(); + transferModalApi.setData({ id: customerId.value }).open(); } /** 分配客户 */ function handleDistributeForm() { - // transferModalApi.setData({ id: clueId }).open(); + transferModalApi.setData({ id: customerId.value }).open(); } /** 客户放入公海 */ function handlePutPool() { - // transferModalApi.setData({ id: clueId }).open(); + transferModalApi.setData({ id: customerId.value }).open(); } /** 更新成交状态操作 */ -async function handleUpdateDealStatus() { - const dealStatus = !customer.value.dealStatus; - try { - await Modal.confirm({ - title: '提示', +async function handleUpdateDealStatus(): Promise { + return new Promise((resolve, reject) => { + const dealStatus = !customer.value.dealStatus; + confirm({ content: `确定更新成交状态为【${dealStatus ? '已成交' : '未成交'}】吗?`, - }); - await updateCustomerDealStatus(customerId.value, dealStatus); - Modal.success({ - title: '成功', - content: '更新成交状态成功', - }); - await loadCustomerDetail(); - } catch { - // 用户取消操作 - } -} - -/** 获取操作日志 */ -const logList = ref([]); // 操作日志列表 -async function getOperateLog() { - if (!customerId.value) { - return; - } - const data = await getOperateLogPage({ - bizType: BizTypeEnum.CRM_CUSTOMER, - bizId: customerId.value, + }) + .then(async () => { + const res = await updateCustomerDealStatus( + customerId.value, + dealStatus, + ); + if (res) { + message.success('更新成交状态成功'); + resolve(true); + } else { + reject(new Error('更新成交状态失败')); + } + }) + .catch(() => { + reject(new Error('取消操作')); + }); }); - logList.value = data.list; } // 加载数据 onMounted(async () => { + customerId.value = Number(route.params.id); await loadCustomerDetail(); });