feat: crm business status

pull/158/head
xingyu4j 2025-06-25 15:17:37 +08:00
parent 53e049241a
commit faa303526d
5 changed files with 148 additions and 30 deletions

View File

@ -5,20 +5,19 @@ import { requestClient } from '#/api/request';
export namespace CrmBusinessStatusApi { export namespace CrmBusinessStatusApi {
/** 商机状态信息 */ /** 商机状态信息 */
export interface BusinessStatusType { export interface BusinessStatusType {
id: number; id?: number;
name: string; name: string;
percent: number; percent: number;
sort: number;
} }
/** 商机状态组信息 */ /** 商机状态组信息 */
export interface BusinessStatus { export interface BusinessStatus {
id: number; id?: number;
name: string; name: string;
deptIds: number[]; deptIds?: number[];
deptNames: string[]; deptNames?: string[];
creator: string; creator?: string;
createTime: Date; createTime?: Date;
statuses?: BusinessStatusType[]; statuses?: BusinessStatusType[];
} }
} }

View File

@ -3,9 +3,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { handleTree } from '@vben/utils'; import { handleTree } from '@vben/utils';
import { z } from '#/adapter/form';
import { getDeptList } from '#/api/system/dept'; import { getDeptList } from '#/api/system/dept';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
@ -38,17 +36,13 @@ export function useFormSchema(): VbenFormSchema[] {
placeholder: '请选择应用部门', placeholder: '请选择应用部门',
treeDefaultExpandAll: true, treeDefaultExpandAll: true,
}, },
help: '不选择部门时,默认全公司生效',
}, },
{ {
fieldName: 'status', fieldName: 'statuses',
label: '状态', label: '阶段设置',
component: 'RadioGroup', component: 'Input',
componentProps: { rules: 'required',
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
buttonStyle: 'solid',
optionType: 'button',
},
rules: z.number().default(CommonStatusEnum.ENABLE),
}, },
]; ];
} }

View File

@ -5,11 +5,13 @@ import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue'; import { Input, InputNumber, message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form'; import { useVbenForm } from '#/adapter/form';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
createBusinessStatus, createBusinessStatus,
DEFAULT_STATUSES,
getBusinessStatus, getBusinessStatus,
updateBusinessStatus, updateBusinessStatus,
} from '#/api/crm/business/status'; } from '#/api/crm/business/status';
@ -62,30 +64,153 @@ const [Modal, modalApi] = useVbenModal({
}, },
async onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (!isOpen) { if (!isOpen) {
formData.value = undefined;
return; return;
} }
// //
const data = modalApi.getData<CrmBusinessStatusApi.BusinessStatus>(); const data = modalApi.getData<CrmBusinessStatusApi.BusinessStatus>();
if (!data || !data.id) {
return;
}
modalApi.lock(); modalApi.lock();
try { try {
if (!data || !data.id) {
formData.value = {
id: undefined,
name: '',
deptIds: [],
statuses: [],
};
addStatus();
} else {
formData.value = await getBusinessStatus(data.id as number); formData.value = await getBusinessStatus(data.id as number);
// values if (
if (formData.value) { !formData.value?.statuses?.length ||
await formApi.setValues(formData.value); formData.value?.statuses?.length === 0
) {
addStatus();
} }
}
// values
await formApi.setValues(formData.value as any);
formData.value!.statuses =
formData.value?.statuses?.concat(DEFAULT_STATUSES);
gridApi.grid.reloadData(formData.value!.statuses as any);
} finally { } finally {
modalApi.unlock(); modalApi.unlock();
} }
}, },
}); });
/** 添加状态 */
function addStatus() {
formData.value!.statuses!.push({
name: '',
percent: undefined,
} as any);
}
/** 删除状态 */
function deleteStatusArea(row: any) {
formData.value!.statuses!.splice(row.index, 1);
}
/** 表格配置 */
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
editConfig: {
trigger: 'click',
mode: 'cell',
},
columns: [
{
field: 'defaultStatus',
title: '阶段',
minWidth: 100,
slots: { default: 'defaultStatus' },
},
{
field: 'name',
title: '阶段名称',
minWidth: 100,
slots: { default: 'name' },
},
{
field: 'percent',
title: '赢单率(%',
minWidth: 100,
slots: { default: 'percent' },
},
{
title: '操作',
width: 130,
fixed: 'right',
slots: { default: 'actions' },
},
],
data: formData.value?.statuses?.concat(DEFAULT_STATUSES),
border: true,
showOverflow: true,
autoResize: true,
keepSource: true,
rowConfig: {
keyField: 'row_id',
},
pagerConfig: {
enabled: false,
},
toolbarConfig: {
enabled: false,
},
},
});
</script> </script>
<template> <template>
<Modal :title="getTitle" class="w-1/2"> <Modal :title="getTitle" class="w-1/2">
<Form class="mx-4" /> <Form class="mx-4">
<template #statuses>
<Grid class="w-full">
<template #defaultStatus="{ row, rowIndex }">
<span>
{{ row.defaultStatus ? '结束' : `阶段${rowIndex + 1}` }}
</span>
</template>
<template #name="{ row }">
<Input v-if="!row.endStatus" v-model:value="row.name" />
<span v-else>{{ row.name }}</span>
</template>
<template #percent="{ row }">
<InputNumber
v-if="!row.endStatus"
v-model:value="row.percent"
:min="0"
:max="100"
:precision="2"
/>
<span v-else>{{ row.percent }}</span>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create'),
type: 'link',
ifShow: () => !row.endStatus,
onClick: addStatus,
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
ifShow: () => !row.endStatus,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: deleteStatusArea.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</template>
</Form>
</Modal> </Modal>
</template> </template>

View File

@ -43,7 +43,7 @@ export function useFormSchema(): VbenFormSchema[] {
componentProps: { componentProps: {
api: () => getCustomerSimpleList(), api: () => getCustomerSimpleList(),
fieldNames: { fieldNames: {
label: 'nickname', label: 'name',
value: 'id', value: 'id',
}, },
}, },

View File

@ -112,7 +112,7 @@ watch(
item.sellingPrice = item.contractPrice; item.sellingPrice = item.contractPrice;
}); });
} }
gridApi.grid?.loadData(tableData.value); gridApi.grid.reloadData(tableData.value);
}, },
{ {
immediate: true, immediate: true,