feat: crm customer detail
parent
dba14c5e45
commit
c18d70b0bc
|
@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,44 @@
|
|||
<script lang="ts" setup></script>
|
||||
<script lang="ts" setup>
|
||||
import type { CrmCustomerApi } from '#/api/crm/customer';
|
||||
|
||||
import { Divider } from 'ant-design-vue';
|
||||
|
||||
import { useDescription } from '#/components/description';
|
||||
import { useFollowUpDetailSchema } from '#/views/crm/followup/data';
|
||||
|
||||
import { useDetailBaseSchema } from '../data';
|
||||
|
||||
defineOptions({ name: 'CrmCustomerDetailsInfo' });
|
||||
|
||||
defineProps<{
|
||||
customer: CrmCustomerApi.Customer; // 客户信息
|
||||
}>();
|
||||
|
||||
const [BaseDescription] = useDescription({
|
||||
componentProps: {
|
||||
title: '基本信息',
|
||||
bordered: false,
|
||||
column: 4,
|
||||
class: 'mx-4',
|
||||
},
|
||||
schema: useDetailBaseSchema(),
|
||||
});
|
||||
|
||||
const [SystemDescription] = useDescription({
|
||||
componentProps: {
|
||||
title: '系统信息',
|
||||
bordered: false,
|
||||
column: 3,
|
||||
class: 'mx-4',
|
||||
},
|
||||
schema: useFollowUpDetailSchema(),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>detail-info</div>
|
||||
<div class="p-4">
|
||||
<BaseDescription :data="customer" />
|
||||
<Divider />
|
||||
<SystemDescription :data="customer" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -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<CrmCustomerApi.Customer>({} as CrmCustomerApi.Customer);
|
||||
const permissionListRef = ref(); // 团队成员列表 Ref
|
||||
const customerLogList = ref<SystemOperateLogApi.OperateLog[]>([]);
|
||||
const permissionListRef = ref<InstanceType<typeof PermissionList>>(); // 团队成员列表 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<boolean | undefined> {
|
||||
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<SystemOperateLogApi.OperateLog[]>([]); // 操作日志列表
|
||||
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();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height :title="customer?.name" :loading="loading">
|
||||
<FormModal @success="loadCustomerDetail" />
|
||||
<TransferModal @success="loadCustomerDetail" />
|
||||
<template #extra>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
|
@ -179,17 +217,23 @@ onMounted(async () => {
|
|||
</Card>
|
||||
<Card class="mt-4">
|
||||
<Tabs>
|
||||
<Tabs.TabPane tab="跟进记录" key="1">
|
||||
<div>跟进记录</div>
|
||||
<Tabs.TabPane tab="基本信息" key="1">
|
||||
<CustomerDetailsInfo :customer="customer" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="基本信息" key="2">
|
||||
<CustomerDetailsInfo />
|
||||
<Tabs.TabPane tab="跟进记录" key="2">
|
||||
<FollowUp :biz-id="customerId" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="联系人" key="3">
|
||||
<div>联系人</div>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="团队成员" key="4">
|
||||
<div>团队成员</div>
|
||||
<PermissionList
|
||||
ref="permissionListRef"
|
||||
:biz-id="customerId"
|
||||
:biz-type="BizTypeEnum.CRM_CUSTOMER"
|
||||
:show-action="true"
|
||||
@quit-team="handleBack"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="商机" key="5">
|
||||
<div>商机</div>
|
||||
|
@ -201,7 +245,7 @@ onMounted(async () => {
|
|||
<div>回款</div>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="操作日志" key="8">
|
||||
<OperateLog :log-list="logList" />
|
||||
<OperateLog :log-list="customerLogList" />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import type { DescriptionItemSchema } from '#/components/description';
|
||||
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
/** 详情页的系统字段 */
|
||||
export function useFollowUpDetailSchema(): 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,
|
||||
},
|
||||
];
|
||||
}
|
Loading…
Reference in New Issue