feat: crm detail

pull/130/head^2
xingyu4j 2025-06-05 21:28:17 +08:00
parent 181367791f
commit d576f7b18a
10 changed files with 168 additions and 21 deletions

View File

@ -33,6 +33,10 @@ const FollowUp = defineAsyncComponent(
() => import('#/views/crm/followup/index.vue'),
);
const ProductDetailsList = defineAsyncComponent(
() => import('#/views/crm/product/modules/detail-list.vue'),
);
const PermissionList = defineAsyncComponent(
() => import('#/views/crm/permission/modules/permission-list.vue'),
);
@ -124,9 +128,9 @@ async function handleUpdateStatus() {
}
//
onMounted(async () => {
onMounted(() => {
businessId.value = Number(route.params.id);
await loadBusinessDetail();
loadBusinessDetail();
});
</script>
@ -173,12 +177,15 @@ onMounted(async () => {
<ContactDetailsList
:biz-id="businessId"
:biz-type="BizTypeEnum.CRM_BUSINESS"
:business-id="business.id"
:business-id="businessId"
:customer-id="business.customerId"
/>
</Tabs.TabPane>
<Tabs.TabPane tab="产品" key="4" :force-render="true">
<div>产品</div>
<ProductDetailsList
:biz-type="BizTypeEnum.CRM_BUSINESS"
:business="business"
/>
</Tabs.TabPane>
<Tabs.TabPane tab="合同" key="5" :force-render="true">
<ContractDetailsList

View File

@ -127,9 +127,9 @@ async function handleTransform(): Promise<boolean | undefined> {
}
//
onMounted(async () => {
onMounted(() => {
clueId.value = Number(route.params.id);
await loadClueDetail();
loadClueDetail();
});
</script>

View File

@ -107,9 +107,9 @@ function handleTransfer() {
}
//
onMounted(async () => {
onMounted(() => {
contactId.value = Number(route.params.id);
await loadContactDetail();
loadContactDetail();
});
</script>

View File

@ -23,6 +23,10 @@ const FollowUp = defineAsyncComponent(
() => import('#/views/crm/followup/index.vue'),
);
const ProductDetailsList = defineAsyncComponent(
() => import('#/views/crm/product/modules/detail-list.vue'),
);
const PermissionList = defineAsyncComponent(
() => import('#/views/crm/permission/modules/permission-list.vue'),
);
@ -39,6 +43,14 @@ const ContractDetailsInfo = defineAsyncComponent(
() => import('./detail-info.vue'),
);
const ReceivableDetailsList = defineAsyncComponent(
() => import('#/views/crm/receivable/modules/detail-list.vue'),
);
const ReceivablePlanDetailsList = defineAsyncComponent(
() => import('#/views/crm/receivable/plan/modules/detail-list.vue'),
);
const loading = ref(false);
const route = useRoute();
@ -107,9 +119,9 @@ function handleTransfer() {
}
//
onMounted(async () => {
onMounted(() => {
contractId.value = Number(route.params.id);
await loadContractDetail();
loadContractDetail();
});
</script>
@ -147,8 +159,22 @@ onMounted(async () => {
<Tabs.TabPane tab="合同跟进" key="2" :force-render="true">
<FollowUp :biz-id="contractId" :biz-type="BizTypeEnum.CRM_CONTRACT" />
</Tabs.TabPane>
<Tabs.TabPane tab="产品" key="3" :force-render="true" />
<Tabs.TabPane tab="回款" key="4" :force-render="true" />
<Tabs.TabPane tab="产品" key="3" :force-render="true">
<ProductDetailsList
:biz-type="BizTypeEnum.CRM_CONTRACT"
:contract="contract"
/>
</Tabs.TabPane>
<Tabs.TabPane tab="回款" key="4" :force-render="true">
<ReceivablePlanDetailsList
:contract-id="contractId"
:customer-id="contract.customerId"
/>
<ReceivableDetailsList
:contract-id="contractId"
:customer-id="contract.customerId"
/>
</Tabs.TabPane>
<Tabs.TabPane tab="团队成员" key="5" :force-render="true">
<PermissionList
ref="permissionListRef"

View File

@ -57,6 +57,14 @@ const OperateLog = defineAsyncComponent(
() => import('#/components/operate-log'),
);
const ReceivableDetailsList = defineAsyncComponent(
() => import('#/views/crm/receivable/modules/detail-list.vue'),
);
const ReceivablePlanDetailsList = defineAsyncComponent(
() => import('#/views/crm/receivable/plan/modules/detail-list.vue'),
);
const loading = ref(false);
const route = useRoute();
@ -174,9 +182,9 @@ async function handleUpdateDealStatus(): Promise<boolean | undefined> {
}
//
onMounted(async () => {
onMounted(() => {
customerId.value = Number(route.params.id);
await loadCustomerDetail();
loadCustomerDetail();
});
</script>
@ -283,7 +291,8 @@ onMounted(async () => {
/>
</Tabs.TabPane>
<Tabs.TabPane tab="回款" key="7" :force-render="true">
<div>回款</div>
<ReceivablePlanDetailsList :customer-id="customerId" />
<ReceivableDetailsList :customer-id="customerId" />
</Tabs.TabPane>
<Tabs.TabPane tab="操作日志" key="8" :force-render="true">
<OperateLog :log-list="customerLogList" />

View File

@ -247,3 +247,48 @@ export function useDetailBaseSchema(): DescriptionItemSchema[] {
},
];
}
/** 详情列表的字段 */
export function useDetailListColumns(
showBussinePrice: boolean,
): VxeTableGridOptions['columns'] {
return [
{
field: 'productName',
title: '产品名称',
},
{
field: 'productNo',
title: '产品条码',
},
{
field: 'productUnit',
title: '产品单位',
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.CRM_PRODUCT_UNIT },
},
},
{
field: 'productPrice',
title: '产品价格(元)',
formatter: 'formatNumber',
},
{
field: 'businessPrice',
title: '商机价格(元)',
formatter: 'formatNumber',
visible: showBussinePrice,
},
{
field: 'count',
title: '数量',
formatter: 'formatNumber',
},
{
field: 'totalPrice',
title: '合计金额(元)',
formatter: 'formatNumber',
},
];
}

View File

@ -0,0 +1,59 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { CrmBusinessApi } from '#/api/crm/business';
import type { CrmContractApi } from '#/api/crm/contract';
import type { CrmProductApi } from '#/api/crm/product';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { BizTypeEnum } from '#/api/crm/permission';
import { erpPriceInputFormatter } from '#/utils';
import { useDetailListColumns } from '../data';
const props = defineProps<{
bizType: BizTypeEnum;
business?: CrmBusinessApi.Business; //
contract?: CrmContractApi.Contract; //
}>();
const [Grid] = useVbenVxeGrid({
gridOptions: {
columns: useDetailListColumns(props.bizType === BizTypeEnum.CRM_BUSINESS),
data:
props.bizType === BizTypeEnum.CRM_BUSINESS
? props.business?.products
: props.contract?.products,
height: 600,
pagerConfig: {
enabled: false,
},
keepSource: true,
rowConfig: {
keyField: 'id',
},
} as VxeTableGridOptions<CrmProductApi.Product>,
});
</script>
<template>
<div>
<Grid />
<div class="flex justify-end">
<span class="font-bold text-red-500">
整单折扣{{
erpPriceInputFormatter(
business ? business.discountPercent : contract?.discountPercent,
)
}}%
</span>
<span class="ml-4 font-bold text-red-500">
产品总金额{{
erpPriceInputFormatter(
business ? business.totalProductPrice : contract?.totalProductPrice,
)
}}
</span>
</div>
</div>
</template>

View File

@ -63,10 +63,11 @@ function handleBack() {
tabs.closeCurrentTab();
router.push('/crm/product');
}
//
onMounted(async () => {
onMounted(() => {
productId.value = Number(route.params.id);
await loadProductDetail();
loadProductDetail();
});
</script>

View File

@ -88,9 +88,9 @@ function handleEdit() {
}
//
onMounted(async () => {
onMounted(() => {
receivableId.value = Number(route.params.id);
await loadReceivableDetail();
loadReceivableDetail();
});
</script>

View File

@ -88,9 +88,9 @@ function handleEdit() {
}
//
onMounted(async () => {
onMounted(() => {
receivablePlanId.value = Number(route.params.id);
await loadreceivablePlanDetail();
loadreceivablePlanDetail();
});
</script>