feat: business detail
parent
c18d70b0bc
commit
9f24438d2e
|
|
@ -40,6 +40,7 @@ export namespace CrmBusinessApi {
|
||||||
totalProductPrice: number;
|
totalProductPrice: number;
|
||||||
totalPrice: number;
|
totalPrice: number;
|
||||||
discountPercent: number;
|
discountPercent: number;
|
||||||
|
status?: number;
|
||||||
remark: string;
|
remark: string;
|
||||||
creator: string; // 创建人
|
creator: string; // 创建人
|
||||||
creatorName?: string; // 创建人名称
|
creatorName?: string; // 创建人名称
|
||||||
|
|
@ -47,6 +48,12 @@ export namespace CrmBusinessApi {
|
||||||
updateTime: Date; // 更新时间
|
updateTime: Date; // 更新时间
|
||||||
products?: BusinessProduct[];
|
products?: BusinessProduct[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BusinessStatus {
|
||||||
|
id: number;
|
||||||
|
statusId: number | undefined;
|
||||||
|
endStatus: number | undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询商机列表 */
|
/** 查询商机列表 */
|
||||||
|
|
@ -90,7 +97,7 @@ export function updateBusiness(data: CrmBusinessApi.Business) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改商机状态 */
|
/** 修改商机状态 */
|
||||||
export function updateBusinessStatus(data: CrmBusinessApi.Business) {
|
export function updateBusinessStatus(data: CrmBusinessApi.BusinessStatus) {
|
||||||
return requestClient.put('/crm/business/update-status', data);
|
return requestClient.put('/crm/business/update-status', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,27 @@ import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace CrmBusinessStatusApi {
|
export namespace CrmBusinessStatusApi {
|
||||||
/** 商机状态信息 */
|
/** 商机状态信息 */
|
||||||
export interface BusinessStatus {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
percent: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 商机状态组信息 */
|
|
||||||
export interface BusinessStatusType {
|
export interface BusinessStatusType {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
deptIds: number[];
|
percent: number;
|
||||||
statuses?: BusinessStatus[];
|
sort: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 默认商机状态 */
|
/** 商机状态组信息 */
|
||||||
export const DEFAULT_STATUSES = [
|
export interface BusinessStatus {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
deptIds: number[];
|
||||||
|
deptNames: string[];
|
||||||
|
creator: string;
|
||||||
|
createTime: Date;
|
||||||
|
statuses?: BusinessStatusType[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 默认商机状态 */
|
||||||
|
export const DEFAULT_STATUSES = [
|
||||||
{
|
{
|
||||||
endStatus: 1,
|
endStatus: 1,
|
||||||
key: '结束',
|
key: '结束',
|
||||||
|
|
@ -38,12 +43,11 @@ export namespace CrmBusinessStatusApi {
|
||||||
name: '无效',
|
name: '无效',
|
||||||
percent: 0,
|
percent: 0,
|
||||||
},
|
},
|
||||||
] as const;
|
];
|
||||||
}
|
|
||||||
|
|
||||||
/** 查询商机状态组列表 */
|
/** 查询商机状态组列表 */
|
||||||
export function getBusinessStatusPage(params: PageParam) {
|
export function getBusinessStatusPage(params: PageParam) {
|
||||||
return requestClient.get<PageResult<CrmBusinessStatusApi.BusinessStatusType>>(
|
return requestClient.get<PageResult<CrmBusinessStatusApi.BusinessStatus>>(
|
||||||
'/crm/business-status/page',
|
'/crm/business-status/page',
|
||||||
{ params },
|
{ params },
|
||||||
);
|
);
|
||||||
|
|
@ -51,21 +55,21 @@ export function getBusinessStatusPage(params: PageParam) {
|
||||||
|
|
||||||
/** 新增商机状态组 */
|
/** 新增商机状态组 */
|
||||||
export function createBusinessStatus(
|
export function createBusinessStatus(
|
||||||
data: CrmBusinessStatusApi.BusinessStatusType,
|
data: CrmBusinessStatusApi.BusinessStatus,
|
||||||
) {
|
) {
|
||||||
return requestClient.post('/crm/business-status/create', data);
|
return requestClient.post('/crm/business-status/create', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改商机状态组 */
|
/** 修改商机状态组 */
|
||||||
export function updateBusinessStatus(
|
export function updateBusinessStatus(
|
||||||
data: CrmBusinessStatusApi.BusinessStatusType,
|
data: CrmBusinessStatusApi.BusinessStatus,
|
||||||
) {
|
) {
|
||||||
return requestClient.put('/crm/business-status/update', data);
|
return requestClient.put('/crm/business-status/update', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询商机状态类型详情 */
|
/** 查询商机状态类型详情 */
|
||||||
export function getBusinessStatus(id: number) {
|
export function getBusinessStatus(id: number) {
|
||||||
return requestClient.get<CrmBusinessStatusApi.BusinessStatusType>(
|
return requestClient.get<CrmBusinessStatusApi.BusinessStatus>(
|
||||||
`/crm/business-status/get?id=${id}`,
|
`/crm/business-status/get?id=${id}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -77,14 +81,14 @@ export function deleteBusinessStatus(id: number) {
|
||||||
|
|
||||||
/** 获得商机状态组列表 */
|
/** 获得商机状态组列表 */
|
||||||
export function getBusinessStatusTypeSimpleList() {
|
export function getBusinessStatusTypeSimpleList() {
|
||||||
return requestClient.get<CrmBusinessStatusApi.BusinessStatusType[]>(
|
return requestClient.get<CrmBusinessStatusApi.BusinessStatus[]>(
|
||||||
'/crm/business-status/type-simple-list',
|
'/crm/business-status/type-simple-list',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获得商机阶段列表 */
|
/** 获得商机阶段列表 */
|
||||||
export function getBusinessStatusSimpleList(typeId: number) {
|
export function getBusinessStatusSimpleList(typeId: number) {
|
||||||
return requestClient.get<CrmBusinessStatusApi.BusinessStatus[]>(
|
return requestClient.get<CrmBusinessStatusApi.BusinessStatusType[]>(
|
||||||
'/crm/business-status/status-simple-list',
|
'/crm/business-status/status-simple-list',
|
||||||
{ params: { typeId } },
|
{ params: { typeId } },
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -80,3 +80,107 @@ export function calculateRelativeRate(
|
||||||
((100 * ((value || 0) - reference)) / reference).toFixed(0),
|
((100 * ((value || 0) - reference)) / reference).toFixed(0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== ERP 专属方法 ==========
|
||||||
|
|
||||||
|
const ERP_COUNT_DIGIT = 3;
|
||||||
|
const ERP_PRICE_DIGIT = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【ERP】格式化 Input 数字
|
||||||
|
*
|
||||||
|
* 例如说:库存数量
|
||||||
|
*
|
||||||
|
* @param num 数量
|
||||||
|
* @package
|
||||||
|
* @return 格式化后的数量
|
||||||
|
*/
|
||||||
|
export function erpNumberFormatter(
|
||||||
|
num: number | string | undefined,
|
||||||
|
digit: number,
|
||||||
|
) {
|
||||||
|
if (num === null || num === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (typeof num === 'string') {
|
||||||
|
num = Number.parseFloat(num);
|
||||||
|
}
|
||||||
|
// 如果非 number,则直接返回空串
|
||||||
|
if (Number.isNaN(num)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return num.toFixed(digit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【ERP】格式化数量,保留三位小数
|
||||||
|
*
|
||||||
|
* 例如说:库存数量
|
||||||
|
*
|
||||||
|
* @param num 数量
|
||||||
|
* @return 格式化后的数量
|
||||||
|
*/
|
||||||
|
export function erpCountInputFormatter(num: number | string | undefined) {
|
||||||
|
return erpNumberFormatter(num, ERP_COUNT_DIGIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection JSCommentMatchesSignature
|
||||||
|
/**
|
||||||
|
* 【ERP】格式化数量,保留三位小数
|
||||||
|
*
|
||||||
|
* @param cellValue 数量
|
||||||
|
* @return 格式化后的数量
|
||||||
|
*/
|
||||||
|
export function erpCountTableColumnFormatter(cellValue: any) {
|
||||||
|
return erpNumberFormatter(cellValue, ERP_COUNT_DIGIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【ERP】格式化金额,保留二位小数
|
||||||
|
*
|
||||||
|
* 例如说:库存数量
|
||||||
|
*
|
||||||
|
* @param num 数量
|
||||||
|
* @return 格式化后的数量
|
||||||
|
*/
|
||||||
|
export function erpPriceInputFormatter(num: number | string | undefined) {
|
||||||
|
return erpNumberFormatter(num, ERP_PRICE_DIGIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection JSCommentMatchesSignature
|
||||||
|
/**
|
||||||
|
* 【ERP】格式化金额,保留二位小数
|
||||||
|
*
|
||||||
|
* @param cellValue 数量
|
||||||
|
* @return 格式化后的数量
|
||||||
|
*/
|
||||||
|
export function erpPriceTableColumnFormatter(cellValue: any) {
|
||||||
|
return erpNumberFormatter(cellValue, ERP_PRICE_DIGIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【ERP】价格计算,四舍五入保留两位小数
|
||||||
|
*
|
||||||
|
* @param price 价格
|
||||||
|
* @param count 数量
|
||||||
|
* @return 总价格。如果有任一为空,则返回 undefined
|
||||||
|
*/
|
||||||
|
export function erpPriceMultiply(price: number, count: number) {
|
||||||
|
if (price === null || count === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return Number.parseFloat((price * count).toFixed(ERP_PRICE_DIGIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【ERP】百分比计算,四舍五入保留两位小数
|
||||||
|
*
|
||||||
|
* 如果 total 为 0,则返回 0
|
||||||
|
*
|
||||||
|
* @param value 当前值
|
||||||
|
* @param total 总值
|
||||||
|
*/
|
||||||
|
export function erpCalculatePercentage(value: number, total: number) {
|
||||||
|
if (total === 0) return 0;
|
||||||
|
return ((value / total) * 100).toFixed(2);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { DescriptionItemSchema } from '#/components/description';
|
||||||
|
|
||||||
|
import { formatDateTime } from '@vben/utils';
|
||||||
|
|
||||||
|
import { getBusinessStatusTypeSimpleList } from '#/api/crm/business/status';
|
||||||
|
import { getCustomerSimpleList } from '#/api/crm/customer';
|
||||||
|
import { getSimpleUserList } from '#/api/system/user';
|
||||||
|
import { erpPriceInputFormatter, erpPriceMultiply } from '#/utils';
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
|
|
@ -19,18 +27,49 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'customerId',
|
fieldName: 'ownerUserId',
|
||||||
label: '客户',
|
label: '负责人',
|
||||||
component: 'Input',
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
api: () => getSimpleUserList(),
|
||||||
|
fieldNames: {
|
||||||
|
label: 'nickname',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'totalPrice',
|
fieldName: 'customerId',
|
||||||
label: '商机金额',
|
label: '客户名称',
|
||||||
component: 'InputNumber',
|
component: 'ApiSelect',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
min: 0,
|
api: () => getCustomerSimpleList(),
|
||||||
placeholder: '请输入商机金额',
|
fieldNames: {
|
||||||
|
label: 'name',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['id'],
|
||||||
|
disabled: (values) => !values.customerId,
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'statusTypeId',
|
||||||
|
label: '商机状态组',
|
||||||
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
api: () => getBusinessStatusTypeSimpleList(),
|
||||||
|
fieldNames: {
|
||||||
|
label: 'name',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['id'],
|
||||||
|
disabled: (values) => !values.id,
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
|
|
@ -46,9 +85,43 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'remark',
|
fieldName: 'totalProductPrice',
|
||||||
label: '备注',
|
label: '产品总金额',
|
||||||
component: 'Textarea',
|
component: 'InputNumber',
|
||||||
|
componentProps: {
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'discountPercent',
|
||||||
|
label: '整单折扣(%)',
|
||||||
|
component: 'InputNumber',
|
||||||
|
componentProps: {
|
||||||
|
min: 0,
|
||||||
|
precision: 2,
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'totalPrice',
|
||||||
|
label: '折扣后金额',
|
||||||
|
component: 'InputNumber',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['totalProductPrice', 'discountPercent'],
|
||||||
|
disabled: () => true,
|
||||||
|
trigger(values, form) {
|
||||||
|
const discountPrice =
|
||||||
|
erpPriceMultiply(
|
||||||
|
values.totalProductPrice,
|
||||||
|
values.discountPercent / 100,
|
||||||
|
) ?? 0;
|
||||||
|
form.setFieldValue(
|
||||||
|
'totalPrice',
|
||||||
|
values.totalProductPrice - discountPrice,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -143,3 +216,72 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 详情页的字段 */
|
||||||
|
export function useDetailSchema(): DescriptionItemSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'customerName',
|
||||||
|
label: '客户名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'totalPrice',
|
||||||
|
label: '商机金额(元)',
|
||||||
|
content: (data) => erpPriceInputFormatter(data.totalPrice),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'statusTypeName',
|
||||||
|
label: '商机组',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'ownerUserName',
|
||||||
|
label: '负责人',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'createTime',
|
||||||
|
label: '创建时间',
|
||||||
|
content: (data) => formatDateTime(data?.createTime) as string,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 详情页的基础字段 */
|
||||||
|
export function useDetailBaseSchema(): DescriptionItemSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
label: '商机名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'customerName',
|
||||||
|
label: '客户名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'totalPrice',
|
||||||
|
label: '商机金额(元)',
|
||||||
|
content: (data) => erpPriceInputFormatter(data.totalPrice),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'dealTime',
|
||||||
|
label: '预计成交日期',
|
||||||
|
content: (data) => formatDateTime(data?.dealTime) as string,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'contactNextTime',
|
||||||
|
label: '下次联系时间',
|
||||||
|
content: (data) => formatDateTime(data?.contactNextTime) as string,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'statusTypeName',
|
||||||
|
label: '商机状态组',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'statusName',
|
||||||
|
label: '商机阶段',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,44 @@
|
||||||
<script lang="ts" setup></script>
|
<script lang="ts" setup>
|
||||||
|
import type { CrmBusinessApi } from '#/api/crm/business';
|
||||||
|
|
||||||
|
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<{
|
||||||
|
business: CrmBusinessApi.Business; // 商机信息
|
||||||
|
}>();
|
||||||
|
|
||||||
|
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>
|
<template>
|
||||||
<div>businessInfo</div>
|
<div class="p-4">
|
||||||
|
<BaseDescription :data="business" />
|
||||||
|
<Divider />
|
||||||
|
<SystemDescription :data="business" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,188 @@
|
||||||
<script lang="ts" setup></script>
|
<script setup lang="ts">
|
||||||
|
import type { CrmBusinessApi } from '#/api/crm/business';
|
||||||
|
import type { SystemOperateLogApi } from '#/api/system/operate-log';
|
||||||
|
|
||||||
|
import { defineAsyncComponent, onMounted, ref } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { useTabs } from '@vben/hooks';
|
||||||
|
|
||||||
|
import { Button, Card, Tabs } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getBusiness } from '#/api/crm/business';
|
||||||
|
import { getOperateLogPage } from '#/api/crm/operateLog';
|
||||||
|
import { BizTypeEnum } from '#/api/crm/permission';
|
||||||
|
import { useDescription } from '#/components/description';
|
||||||
|
|
||||||
|
import { useDetailSchema } from '../data';
|
||||||
|
|
||||||
|
const BusinessDetailsInfo = 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 BusinessForm = defineAsyncComponent(
|
||||||
|
() => import('#/views/crm/business/modules/form.vue'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const UpStatusForm = defineAsyncComponent(
|
||||||
|
() => import('#/views/crm/business/modules/up-status-form.vue'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const tabs = useTabs();
|
||||||
|
|
||||||
|
const businessId = ref(0);
|
||||||
|
|
||||||
|
const business = ref<CrmBusinessApi.Business>({} as CrmBusinessApi.Business);
|
||||||
|
const businessLogList = ref<SystemOperateLogApi.OperateLog[]>([]);
|
||||||
|
const permissionListRef = ref<InstanceType<typeof PermissionList>>(); // 团队成员列表 Ref
|
||||||
|
|
||||||
|
const [Description] = useDescription({
|
||||||
|
componentProps: {
|
||||||
|
bordered: false,
|
||||||
|
column: 4,
|
||||||
|
class: 'mx-4',
|
||||||
|
},
|
||||||
|
schema: useDetailSchema(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: BusinessForm,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [TransferModal, transferModalApi] = useVbenModal({
|
||||||
|
connectedComponent: TransferForm,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [UpStatusModal, upStatusModalApi] = useVbenModal({
|
||||||
|
connectedComponent: UpStatusForm,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 加载详情 */
|
||||||
|
async function loadBusinessDetail() {
|
||||||
|
loading.value = true;
|
||||||
|
const data = await getBusiness(businessId.value);
|
||||||
|
const logList = await getOperateLogPage({
|
||||||
|
bizType: BizTypeEnum.CRM_BUSINESS,
|
||||||
|
bizId: businessId.value,
|
||||||
|
});
|
||||||
|
businessLogList.value = logList.list;
|
||||||
|
business.value = data;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 返回列表页 */
|
||||||
|
function handleBack() {
|
||||||
|
tabs.closeCurrentTab();
|
||||||
|
router.push('/crm/business');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 编辑 */
|
||||||
|
function handleEdit() {
|
||||||
|
formModalApi.setData({ id: businessId.value }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 转移线索 */
|
||||||
|
function handleTransfer() {
|
||||||
|
transferModalApi.setData({ bizType: BizTypeEnum.CRM_BUSINESS }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新商机状态操作 */
|
||||||
|
async function handleUpdateStatus() {
|
||||||
|
upStatusModalApi.setData(business.value).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
onMounted(async () => {
|
||||||
|
businessId.value = Number(route.params.id);
|
||||||
|
await loadBusinessDetail();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<Page auto-content-height :title="business?.name" :loading="loading">
|
||||||
<p>待完成</p>
|
<FormModal @success="loadBusinessDetail" />
|
||||||
|
<TransferModal @success="loadBusinessDetail" />
|
||||||
|
<UpStatusModal @success="loadBusinessDetail" />
|
||||||
|
<template #extra>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
v-if="permissionListRef?.validateWrite"
|
||||||
|
type="primary"
|
||||||
|
@click="handleEdit"
|
||||||
|
>
|
||||||
|
{{ $t('ui.actionTitle.edit') }}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="permissionListRef?.validateWrite"
|
||||||
|
@click="handleUpdateStatus"
|
||||||
|
>
|
||||||
|
变更商机状态
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="permissionListRef?.validateOwnerUser"
|
||||||
|
@click="handleTransfer"
|
||||||
|
>
|
||||||
|
转移
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
<Card class="min-h-[10%]">
|
||||||
|
<Description :data="business" />
|
||||||
|
</Card>
|
||||||
|
<Card class="mt-4 min-h-[60%]">
|
||||||
|
<Tabs>
|
||||||
|
<Tabs.TabPane tab="详细资料" key="1" :force-render="true">
|
||||||
|
<BusinessDetailsInfo :business="business" />
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="跟进记录" key="2" :force-render="true">
|
||||||
|
<FollowUp :biz-id="businessId" :biz-type="BizTypeEnum.CRM_BUSINESS" />
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="联系人" key="3" :force-render="true">
|
||||||
|
<div>联系人</div>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="产品" key="4" :force-render="true">
|
||||||
|
<div>产品</div>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="合同" key="5" :force-render="true">
|
||||||
|
<div>合同</div>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="团队成员" key="6" :force-render="true">
|
||||||
|
<PermissionList
|
||||||
|
ref="permissionListRef"
|
||||||
|
:biz-id="businessId"
|
||||||
|
:biz-type="BizTypeEnum.CRM_BUSINESS"
|
||||||
|
:show-action="true"
|
||||||
|
@quit-team="handleBack"
|
||||||
|
/>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="操作日志" key="7" :force-render="true">
|
||||||
|
<OperateLog :log-list="businessLogList" />
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { CrmBusinessApi } from '#/api/crm/business';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { updateBusinessStatus } from '#/api/crm/business';
|
||||||
|
import {
|
||||||
|
DEFAULT_STATUSES,
|
||||||
|
getBusinessStatusSimpleList,
|
||||||
|
} from '#/api/crm/business/status';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
|
const formData = ref<CrmBusinessApi.Business>();
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 120,
|
||||||
|
},
|
||||||
|
layout: 'horizontal',
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
fieldName: 'id',
|
||||||
|
component: 'Input',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: [''],
|
||||||
|
show: () => false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'statusId',
|
||||||
|
label: '商机状态',
|
||||||
|
component: 'Input',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: [''],
|
||||||
|
show: () => false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'endStatus',
|
||||||
|
label: '商机状态',
|
||||||
|
component: 'Input',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: [''],
|
||||||
|
show: () => false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '商机阶段',
|
||||||
|
component: 'Select',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: [''],
|
||||||
|
async componentProps() {
|
||||||
|
const statusList = await getBusinessStatusSimpleList(
|
||||||
|
formData.value?.statusTypeId ?? 0,
|
||||||
|
);
|
||||||
|
const statusOptions = statusList.map((item) => ({
|
||||||
|
label: `${item.name}(赢单率:${item.percent}%)`,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
const options = DEFAULT_STATUSES.map((item) => ({
|
||||||
|
label: `${`${item.name}(赢单率:${item.percent}`}%)`,
|
||||||
|
value: item.endStatus,
|
||||||
|
}));
|
||||||
|
statusOptions.push(...options);
|
||||||
|
return {
|
||||||
|
options: statusOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modalApi.lock();
|
||||||
|
// 提交表单
|
||||||
|
const data = (await formApi.getValues()) as CrmBusinessApi.Business;
|
||||||
|
try {
|
||||||
|
if (!data.status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await updateBusinessStatus({
|
||||||
|
id: data.id,
|
||||||
|
statusId: data.status > 0 ? data.status : undefined,
|
||||||
|
endStatus: data.status < 0 ? -data.status : undefined,
|
||||||
|
});
|
||||||
|
// 关闭并提示
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
message.success($t('ui.actionMessage.operationSuccess'));
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 加载数据
|
||||||
|
const data = modalApi.getData<CrmBusinessApi.Business>();
|
||||||
|
if (!data || !data.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.status = data.endStatus === null ? data.statusId : -data.endStatus;
|
||||||
|
formData.value = data;
|
||||||
|
modalApi.lock();
|
||||||
|
try {
|
||||||
|
// 设置到 values
|
||||||
|
await formApi.setValues(formData.value);
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal title="变更商机状态" class="w-[40%]">
|
||||||
|
<Form class="mx-4" />
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
@ -18,7 +18,7 @@ import { $t } from '#/locales';
|
||||||
import { useFormSchema } from '../data';
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
const formData = ref<CrmBusinessStatusApi.BusinessStatusType>();
|
const formData = ref<CrmBusinessStatusApi.BusinessStatus>();
|
||||||
const getTitle = computed(() => {
|
const getTitle = computed(() => {
|
||||||
return formData.value?.id
|
return formData.value?.id
|
||||||
? $t('ui.actionTitle.edit', ['商机状态'])
|
? $t('ui.actionTitle.edit', ['商机状态'])
|
||||||
|
|
@ -47,7 +47,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock();
|
modalApi.lock();
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const data =
|
const data =
|
||||||
(await formApi.getValues()) as CrmBusinessStatusApi.BusinessStatusType;
|
(await formApi.getValues()) as CrmBusinessStatusApi.BusinessStatus;
|
||||||
try {
|
try {
|
||||||
await (formData.value?.id
|
await (formData.value?.id
|
||||||
? updateBusinessStatus(data)
|
? updateBusinessStatus(data)
|
||||||
|
|
@ -66,7 +66,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const data = modalApi.getData<CrmBusinessStatusApi.BusinessStatusType>();
|
const data = modalApi.getData<CrmBusinessStatusApi.BusinessStatus>();
|
||||||
if (!data || !data.id) {
|
if (!data || !data.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -212,21 +212,21 @@ onMounted(async () => {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<Card>
|
<Card class="min-h-[10%]">
|
||||||
<Description :data="customer" />
|
<Description :data="customer" />
|
||||||
</Card>
|
</Card>
|
||||||
<Card class="mt-4">
|
<Card class="mt-4 min-h-[60%]">
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tabs.TabPane tab="基本信息" key="1">
|
<Tabs.TabPane tab="基本信息" key="1" :force-render="true">
|
||||||
<CustomerDetailsInfo :customer="customer" />
|
<CustomerDetailsInfo :customer="customer" />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="跟进记录" key="2">
|
<Tabs.TabPane tab="跟进记录" key="2" :force-render="true">
|
||||||
<FollowUp :biz-id="customerId" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
|
<FollowUp :biz-id="customerId" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="联系人" key="3">
|
<Tabs.TabPane tab="联系人" key="3" :force-render="true">
|
||||||
<div>联系人</div>
|
<div>联系人</div>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="团队成员" key="4">
|
<Tabs.TabPane tab="团队成员" key="4" :force-render="true">
|
||||||
<PermissionList
|
<PermissionList
|
||||||
ref="permissionListRef"
|
ref="permissionListRef"
|
||||||
:biz-id="customerId"
|
:biz-id="customerId"
|
||||||
|
|
@ -235,16 +235,16 @@ onMounted(async () => {
|
||||||
@quit-team="handleBack"
|
@quit-team="handleBack"
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="商机" key="5">
|
<Tabs.TabPane tab="商机" key="5" :force-render="true">
|
||||||
<div>商机</div>
|
<div>商机</div>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="合同" key="6">
|
<Tabs.TabPane tab="合同" key="6" :force-render="true">
|
||||||
<div>合同</div>
|
<div>合同</div>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="回款" key="7">
|
<Tabs.TabPane tab="回款" key="7" :force-render="true">
|
||||||
<div>回款</div>
|
<div>回款</div>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="操作日志" key="8">
|
<Tabs.TabPane tab="操作日志" key="8" :force-render="true">
|
||||||
<OperateLog :log-list="customerLogList" />
|
<OperateLog :log-list="customerLogList" />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue