mirror of
https://gitee.com/yudaocode/yudao-ui-admin-vben.git
synced 2025-12-30 10:32:25 +00:00
feat:【ele】【crm】customer 的迁移初始化
This commit is contained in:
@@ -20,15 +20,15 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
component: () => import('#/views/crm/clue/detail/index.vue'),
|
||||
},
|
||||
// {
|
||||
// path: 'customer/detail/:id',
|
||||
// name: 'CrmCustomerDetail',
|
||||
// meta: {
|
||||
// title: '客户详情',
|
||||
// activePath: '/crm/customer',
|
||||
// },
|
||||
// component: () => import('#/views/crm/customer/detail/index.vue'),
|
||||
// },
|
||||
{
|
||||
path: 'customer/detail/:id',
|
||||
name: 'CrmCustomerDetail',
|
||||
meta: {
|
||||
title: '客户详情',
|
||||
activePath: '/crm/customer',
|
||||
},
|
||||
component: () => import('#/views/crm/customer/detail/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'business/detail/:id',
|
||||
name: 'CrmBusinessDetail',
|
||||
|
||||
395
apps/web-ele/src/views/crm/customer/data.ts
Normal file
395
apps/web-ele/src/views/crm/customer/data.ts
Normal file
@@ -0,0 +1,395 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { z } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
const userStore = useUserStore();
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
triggerFields: [''],
|
||||
show: () => false,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '客户名称',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入客户名称',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'source',
|
||||
label: '客户来源',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE, 'number'),
|
||||
placeholder: '请选择客户来源',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'mobile',
|
||||
label: '手机',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'ownerUserId',
|
||||
label: '负责人',
|
||||
component: 'ApiSelect',
|
||||
dependencies: {
|
||||
triggerFields: ['id'],
|
||||
disabled: (values) => values.id,
|
||||
},
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择负责人',
|
||||
allowClear: true,
|
||||
},
|
||||
defaultValue: userStore.userInfo?.id,
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'telephone',
|
||||
label: '电话',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入电话',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'email',
|
||||
label: '邮箱',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入邮箱',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'wechat',
|
||||
label: '微信',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入微信',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'qq',
|
||||
label: 'QQ',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入QQ',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'industryId',
|
||||
label: '客户行业',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, 'number'),
|
||||
placeholder: '请选择客户行业',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'level',
|
||||
label: '客户级别',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL, 'number'),
|
||||
placeholder: '请选择客户级别',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'areaId',
|
||||
label: '地址',
|
||||
component: 'ApiTreeSelect',
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
placeholder: '请选择地址',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'detailAddress',
|
||||
label: '详细地址',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入详细地址',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'contactNextTime',
|
||||
label: '下次联系时间',
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
showTime: true,
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'x',
|
||||
placeholder: '请选择下次联系时间',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Textarea',
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '客户名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入客户名称',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'mobile',
|
||||
label: '手机号',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机号',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'telephone',
|
||||
label: '电话',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入电话',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'createTime',
|
||||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
placeholder: ['开始日期', '结束日期'],
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 导入客户的表单 */
|
||||
export function useImportFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'ownerUserId',
|
||||
label: '负责人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择负责人',
|
||||
allowClear: true,
|
||||
class: 'w-full',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['id'],
|
||||
disabled: (values) => values.id,
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'file',
|
||||
label: '客户数据',
|
||||
component: 'Upload',
|
||||
rules: 'required',
|
||||
help: '仅允许导入 xls、xlsx 格式文件',
|
||||
},
|
||||
{
|
||||
fieldName: 'updateSupport',
|
||||
label: '是否覆盖',
|
||||
component: 'Switch',
|
||||
componentProps: {
|
||||
activeValue: true,
|
||||
inactiveValue: false,
|
||||
},
|
||||
rules: z.boolean().default(false),
|
||||
help: '是否更新已经存在的客户数据',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'name',
|
||||
title: '客户名称',
|
||||
fixed: 'left',
|
||||
minWidth: 160,
|
||||
slots: { default: 'name' },
|
||||
},
|
||||
{
|
||||
field: 'source',
|
||||
title: '客户来源',
|
||||
minWidth: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.CRM_CUSTOMER_SOURCE },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
title: '手机',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'telephone',
|
||||
title: '电话',
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
title: '邮箱',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'level',
|
||||
title: '客户级别',
|
||||
minWidth: 135,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.CRM_CUSTOMER_LEVEL },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'industryId',
|
||||
title: '客户行业',
|
||||
minWidth: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'contactNextTime',
|
||||
title: '下次联系时间',
|
||||
formatter: 'formatDateTime',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
field: 'lockStatus',
|
||||
title: '锁定状态',
|
||||
minWidth: 120,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'dealStatus',
|
||||
title: '成交状态',
|
||||
minWidth: 120,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'contactLastTime',
|
||||
title: '最后跟进时间',
|
||||
formatter: 'formatDateTime',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'contactLastContent',
|
||||
title: '最后跟进记录',
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
field: 'detailAddress',
|
||||
title: '地址',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'poolDay',
|
||||
title: '距离进入公海天数',
|
||||
minWidth: 140,
|
||||
formatter: ({ cellValue }) =>
|
||||
cellValue === null ? '-' : `${cellValue} 天`,
|
||||
},
|
||||
{
|
||||
field: 'ownerUserName',
|
||||
title: '负责人',
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'ownerUserDeptName',
|
||||
title: '所属部门',
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'updateTime',
|
||||
title: '更新时间',
|
||||
formatter: 'formatDateTime',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '创建时间',
|
||||
formatter: 'formatDateTime',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'creatorName',
|
||||
title: '创建人',
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 130,
|
||||
fixed: 'right',
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
130
apps/web-ele/src/views/crm/customer/detail/data.ts
Normal file
130
apps/web-ele/src/views/crm/customer/detail/data.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { DescriptionItemSchema } from '#/components/description';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
|
||||
/** 分配客户表单 */
|
||||
export function useDistributeFormSchema(): VbenFormSchema[] {
|
||||
const userStore = useUserStore();
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'id',
|
||||
dependencies: {
|
||||
triggerFields: [''],
|
||||
show: () => false,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'ownerUserId',
|
||||
label: '负责人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
defaultValue: userStore.userInfo?.id,
|
||||
rules: 'required',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 详情页的字段 */
|
||||
export function useDetailSchema(): DescriptionItemSchema[] {
|
||||
return [
|
||||
{
|
||||
field: 'level',
|
||||
label: '客户级别',
|
||||
render: (val) =>
|
||||
h(DictTag, { type: DICT_TYPE.CRM_CUSTOMER_LEVEL, value: val }),
|
||||
},
|
||||
{
|
||||
field: 'dealStatus',
|
||||
label: '成交状态',
|
||||
render: (val) => (val ? '已成交' : '未成交'),
|
||||
},
|
||||
{
|
||||
field: 'ownerUserName',
|
||||
label: '负责人',
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
label: '创建时间',
|
||||
render: (val) => formatDateTime(val) as string,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 详情页的基础字段 */
|
||||
export function useDetailBaseSchema(): DescriptionItemSchema[] {
|
||||
return [
|
||||
{
|
||||
field: 'name',
|
||||
label: '客户名称',
|
||||
},
|
||||
{
|
||||
field: 'source',
|
||||
label: '客户来源',
|
||||
render: (val) =>
|
||||
h(DictTag, {
|
||||
type: DICT_TYPE.CRM_CUSTOMER_SOURCE,
|
||||
value: val,
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
label: '手机',
|
||||
},
|
||||
{
|
||||
field: 'telephone',
|
||||
label: '电话',
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
label: '邮箱',
|
||||
},
|
||||
{
|
||||
field: 'areaName',
|
||||
label: '地址',
|
||||
render: (val, data) => {
|
||||
const areaName = val ?? '';
|
||||
const detailAddress = data?.detailAddress ?? '';
|
||||
return [areaName, detailAddress].filter(Boolean).join(' ');
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'qq',
|
||||
label: 'QQ',
|
||||
},
|
||||
{
|
||||
field: 'wechat',
|
||||
label: '微信',
|
||||
},
|
||||
{
|
||||
field: 'industryId',
|
||||
label: '客户行业',
|
||||
render: (val) =>
|
||||
h(DictTag, {
|
||||
type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY,
|
||||
value: val,
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: 'contactNextTime',
|
||||
label: '下次联系时间',
|
||||
render: (val) => formatDateTime(val) as string,
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
label: '备注',
|
||||
},
|
||||
];
|
||||
}
|
||||
312
apps/web-ele/src/views/crm/customer/detail/index.vue
Normal file
312
apps/web-ele/src/views/crm/customer/detail/index.vue
Normal file
@@ -0,0 +1,312 @@
|
||||
<script setup lang="ts">
|
||||
import type { CrmCustomerApi } from '#/api/crm/customer';
|
||||
import type { SystemOperateLogApi } from '#/api/system/operate-log';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
||||
import { useTabs } from '@vben/hooks';
|
||||
|
||||
import { ElCard, ElMessage, ElTabPane, ElTabs } from 'element-plus';
|
||||
|
||||
import {
|
||||
getCustomer,
|
||||
lockCustomer,
|
||||
putCustomerPool,
|
||||
receiveCustomer,
|
||||
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 { ACTION_ICON, TableAction } from '#/components/table-action';
|
||||
import { $t } from '#/locales';
|
||||
import { BusinessDetailsList } from '#/views/crm/business/components';
|
||||
import { ContactDetailsList } from '#/views/crm/contact/components';
|
||||
import { ContractDetailsList } from '#/views/crm/contract/components';
|
||||
import { FollowUp } from '#/views/crm/followup';
|
||||
import { PermissionList, TransferForm } from '#/views/crm/permission';
|
||||
import { ReceivableDetailsList } from '#/views/crm/receivable/components';
|
||||
import { ReceivablePlanDetailsList } from '#/views/crm/receivable/plan/components';
|
||||
|
||||
import Form from '../modules/form.vue';
|
||||
import { useDetailSchema } from './data';
|
||||
import DistributeForm from './modules/distribute-form.vue';
|
||||
import Info from './modules/info.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const tabs = useTabs();
|
||||
|
||||
const loading = ref(false); // 加载中
|
||||
const customerId = ref(0); // 客户编号
|
||||
const customer = ref<CrmCustomerApi.Customer>({} as CrmCustomerApi.Customer); // 客户详情
|
||||
const activeTabName = ref('1'); // 选中 Tab 名
|
||||
const logList = ref<SystemOperateLogApi.OperateLog[]>([]); // 操作日志
|
||||
const permissionListRef = ref<InstanceType<typeof PermissionList>>(); // 团队成员列表 Ref
|
||||
|
||||
const [Descriptions] = useDescription({
|
||||
border: false,
|
||||
column: 4,
|
||||
class: 'mx-4',
|
||||
schema: useDetailSchema(),
|
||||
});
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const [TransferModal, transferModalApi] = useVbenModal({
|
||||
connectedComponent: TransferForm,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const [DistributeModal, distributeModalApi] = useVbenModal({
|
||||
connectedComponent: DistributeForm,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
/** 加载客户详情 */
|
||||
async function loadCustomerDetail() {
|
||||
loading.value = true;
|
||||
try {
|
||||
customer.value = await getCustomer(customerId.value);
|
||||
// 操作日志
|
||||
const res = await getOperateLogPage({
|
||||
bizType: BizTypeEnum.CRM_CUSTOMER,
|
||||
bizId: customerId.value,
|
||||
});
|
||||
logList.value = res.list;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 返回列表页 */
|
||||
function handleBack() {
|
||||
tabs.closeCurrentTab();
|
||||
router.push({ name: 'CrmCustomer' });
|
||||
}
|
||||
|
||||
/** 编辑客户 */
|
||||
function handleEdit() {
|
||||
formModalApi.setData({ id: customerId.value }).open();
|
||||
}
|
||||
|
||||
/** 转移线索 */
|
||||
function handleTransfer() {
|
||||
transferModalApi.setData({ id: customerId.value }).open();
|
||||
}
|
||||
|
||||
/** 锁定客户 */
|
||||
function handleLock(lockStatus: boolean): Promise<boolean | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
confirm({
|
||||
content: `确定锁定客户【${customer.value.name}】吗?`,
|
||||
})
|
||||
.then(async () => {
|
||||
// 锁定客户
|
||||
await lockCustomer(customerId.value, lockStatus);
|
||||
// 提示并返回成功
|
||||
ElMessage.success(lockStatus ? '锁定客户成功' : '解锁客户成功');
|
||||
resolve(true);
|
||||
})
|
||||
.catch(() => {
|
||||
reject(new Error('取消操作'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 领取客户 */
|
||||
function handleReceive(): Promise<boolean | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
confirm({
|
||||
content: `确定领取客户【${customer.value.name}】吗?`,
|
||||
})
|
||||
.then(async () => {
|
||||
// 领取客户
|
||||
await receiveCustomer([customerId.value]);
|
||||
// 提示并返回成功
|
||||
ElMessage.success('领取客户成功');
|
||||
resolve(true);
|
||||
})
|
||||
.catch(() => {
|
||||
reject(new Error('取消操作'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 分配客户 */
|
||||
function handleDistributeForm() {
|
||||
distributeModalApi.setData({ id: customerId.value }).open();
|
||||
}
|
||||
|
||||
/** 客户放入公海 */
|
||||
function handlePutPool(): Promise<boolean | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
confirm({
|
||||
content: `确定将客户【${customer.value.name}】放入公海吗?`,
|
||||
})
|
||||
.then(async () => {
|
||||
// 放入公海
|
||||
await putCustomerPool(customerId.value);
|
||||
// 提示并返回成功
|
||||
ElMessage.success('放入公海成功');
|
||||
resolve(true);
|
||||
})
|
||||
.catch(() => {
|
||||
reject(new Error('取消操作'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新成交状态操作 */
|
||||
async function handleUpdateDealStatus(): Promise<boolean | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dealStatus = !customer.value.dealStatus;
|
||||
confirm({
|
||||
content: `确定更新成交状态为【${dealStatus ? '已成交' : '未成交'}】吗?`,
|
||||
})
|
||||
.then(async () => {
|
||||
// 更新成交状态
|
||||
await updateCustomerDealStatus(customerId.value, dealStatus);
|
||||
// 提示并返回成功
|
||||
ElMessage.success('更新成交状态成功');
|
||||
resolve(true);
|
||||
})
|
||||
.catch(() => {
|
||||
reject(new Error('取消操作'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 加载数据 */
|
||||
onMounted(() => {
|
||||
customerId.value = Number(route.params.id);
|
||||
loadCustomerDetail();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height :title="customer?.name" :loading="loading">
|
||||
<FormModal @success="loadCustomerDetail" />
|
||||
<TransferModal @success="loadCustomerDetail" />
|
||||
<DistributeModal @success="loadCustomerDetail" />
|
||||
<template #extra>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '返回',
|
||||
type: 'default',
|
||||
icon: 'lucide:arrow-left',
|
||||
onClick: handleBack,
|
||||
},
|
||||
{
|
||||
label: $t('ui.actionTitle.edit'),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.EDIT,
|
||||
auth: ['crm:customer:update'],
|
||||
ifShow: permissionListRef?.validateWrite,
|
||||
onClick: handleEdit,
|
||||
},
|
||||
{
|
||||
label: '转移',
|
||||
type: 'primary',
|
||||
ifShow: permissionListRef?.validateOwnerUser,
|
||||
onClick: handleTransfer,
|
||||
},
|
||||
{
|
||||
label: '更改成交状态',
|
||||
type: 'default',
|
||||
ifShow: permissionListRef?.validateWrite,
|
||||
onClick: handleUpdateDealStatus,
|
||||
},
|
||||
{
|
||||
label: '锁定',
|
||||
type: 'default',
|
||||
ifShow:
|
||||
!customer.lockStatus && permissionListRef?.validateOwnerUser,
|
||||
onClick: handleLock.bind(null, true),
|
||||
},
|
||||
{
|
||||
label: '解锁',
|
||||
type: 'default',
|
||||
ifShow: customer.lockStatus && permissionListRef?.validateOwnerUser,
|
||||
onClick: handleLock.bind(null, false),
|
||||
},
|
||||
{
|
||||
label: '领取',
|
||||
type: 'primary',
|
||||
ifShow: !customer.ownerUserId,
|
||||
onClick: handleReceive,
|
||||
},
|
||||
{
|
||||
label: '分配',
|
||||
type: 'default',
|
||||
ifShow: !customer.ownerUserId,
|
||||
onClick: handleDistributeForm,
|
||||
},
|
||||
{
|
||||
label: '放入公海',
|
||||
type: 'default',
|
||||
ifShow:
|
||||
!!customer.ownerUserId && permissionListRef?.validateOwnerUser,
|
||||
onClick: handlePutPool,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<ElCard class="min-h-[10%]">
|
||||
<Descriptions :data="customer" />
|
||||
</ElCard>
|
||||
<ElCard class="mt-4 min-h-[60%]">
|
||||
<ElTabs v-model:model-value="activeTabName">
|
||||
<ElTabPane label="跟进记录" name="1">
|
||||
<FollowUp :biz-id="customerId" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
|
||||
</ElTabPane>
|
||||
<ElTabPane label="基本信息" name="2">
|
||||
<Info :customer="customer" />
|
||||
</ElTabPane>
|
||||
<ElTabPane label="联系人" name="3">
|
||||
<ContactDetailsList
|
||||
:biz-id="customerId"
|
||||
:biz-type="BizTypeEnum.CRM_CUSTOMER"
|
||||
:customer-id="customerId"
|
||||
/>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="团队成员" name="4">
|
||||
<PermissionList
|
||||
ref="permissionListRef"
|
||||
:biz-id="customerId"
|
||||
:biz-type="BizTypeEnum.CRM_CUSTOMER"
|
||||
:show-action="true"
|
||||
@quit-team="handleBack"
|
||||
/>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="商机" name="5">
|
||||
<BusinessDetailsList
|
||||
:biz-id="customerId"
|
||||
:biz-type="BizTypeEnum.CRM_CUSTOMER"
|
||||
:customer-id="customerId"
|
||||
/>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="合同" name="6">
|
||||
<ContractDetailsList
|
||||
:biz-id="customerId"
|
||||
:biz-type="BizTypeEnum.CRM_CUSTOMER"
|
||||
/>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="回款" name="7">
|
||||
<ReceivablePlanDetailsList :customer-id="customerId" />
|
||||
<ReceivableDetailsList :customer-id="customerId" />
|
||||
</ElTabPane>
|
||||
<ElTabPane label="操作日志" name="8">
|
||||
<OperateLog :log-list="logList" />
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
</ElCard>
|
||||
</Page>
|
||||
</template>
|
||||
@@ -0,0 +1,69 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { distributeCustomer } from '#/api/crm/customer';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useDistributeFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 120,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: useDistributeFormSchema(),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
// 提交表单
|
||||
const data = await formApi.getValues();
|
||||
try {
|
||||
await distributeCustomer([data.id], data.ownerUserId);
|
||||
// 关闭并提示
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
// 加载数据
|
||||
const data = modalApi.getData();
|
||||
if (!data || !data.id) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
await formApi.setValues(data);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal title="分配客户" class="w-2/5">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
38
apps/web-ele/src/views/crm/customer/detail/modules/info.vue
Normal file
38
apps/web-ele/src/views/crm/customer/detail/modules/info.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CrmCustomerApi } from '#/api/crm/customer';
|
||||
|
||||
import { ElDivider } from 'element-plus';
|
||||
|
||||
import { useDescription } from '#/components/description';
|
||||
import { useFollowUpDetailSchema } from '#/views/crm/followup/data';
|
||||
|
||||
import { useDetailBaseSchema } from '../data';
|
||||
|
||||
defineProps<{
|
||||
customer: CrmCustomerApi.Customer; // 客户信息
|
||||
}>();
|
||||
|
||||
const [BaseDescriptions] = useDescription({
|
||||
title: '基本信息',
|
||||
border: false,
|
||||
column: 4,
|
||||
class: 'mx-4',
|
||||
schema: useDetailBaseSchema(),
|
||||
});
|
||||
|
||||
const [SystemDescriptions] = useDescription({
|
||||
title: '系统信息',
|
||||
border: false,
|
||||
column: 3,
|
||||
class: 'mx-4',
|
||||
schema: useFollowUpDetailSchema(),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<BaseDescriptions :data="customer" />
|
||||
<ElDivider />
|
||||
<SystemDescriptions :data="customer" />
|
||||
</div>
|
||||
</template>
|
||||
207
apps/web-ele/src/views/crm/customer/index.vue
Normal file
207
apps/web-ele/src/views/crm/customer/index.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { CrmCustomerApi } from '#/api/crm/customer';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
import { ElButton, ElLoading, ElMessage, ElTabPane, ElTabs } from 'element-plus';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
deleteCustomer,
|
||||
exportCustomer,
|
||||
getCustomerPage,
|
||||
} from '#/api/crm/customer';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
import ImportForm from './modules/import-form.vue';
|
||||
|
||||
const { push } = useRouter();
|
||||
const sceneType = ref('1');
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const [ImportModal, importModalApi] = useVbenModal({
|
||||
connectedComponent: ImportForm,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 处理场景类型的切换 */
|
||||
function handleChangeSceneType(key: number | string) {
|
||||
sceneType.value = key.toString();
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 导入客户 */
|
||||
function handleImport() {
|
||||
importModalApi.open();
|
||||
}
|
||||
|
||||
/** 导出表格 */
|
||||
async function handleExport() {
|
||||
const formValues = await gridApi.formApi.getValues();
|
||||
const data = await exportCustomer({
|
||||
sceneType: sceneType.value,
|
||||
...formValues,
|
||||
});
|
||||
downloadFileFromBlobPart({ fileName: '客户.xls', source: data });
|
||||
}
|
||||
|
||||
/** 创建客户 */
|
||||
function handleCreate() {
|
||||
formModalApi.setData(null).open();
|
||||
}
|
||||
|
||||
/** 编辑客户 */
|
||||
function handleEdit(row: CrmCustomerApi.Customer) {
|
||||
formModalApi.setData(row).open();
|
||||
}
|
||||
|
||||
/** 删除客户 */
|
||||
async function handleDelete(row: CrmCustomerApi.Customer) {
|
||||
const loadingInstance = ElLoading.service({
|
||||
text: $t('ui.actionMessage.deleting', [row.name]),
|
||||
});
|
||||
try {
|
||||
await deleteCustomer(row.id!);
|
||||
ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
loadingInstance.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** 查看客户详情 */
|
||||
function handleDetail(row: CrmCustomerApi.Customer) {
|
||||
push({ name: 'CrmCustomerDetail', params: { id: row.id } });
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getCustomerPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
sceneType: sceneType.value,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<CrmCustomerApi.Customer>,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<template #doc>
|
||||
<DocAlert
|
||||
title="【客户】客户管理"
|
||||
url="https://doc.iocoder.cn/crm/customer/"
|
||||
/>
|
||||
<DocAlert
|
||||
title="【通用】数据权限"
|
||||
url="https://doc.iocoder.cn/crm/permission/"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<FormModal @success="handleRefresh" />
|
||||
<ImportModal @success="handleRefresh" />
|
||||
<Grid>
|
||||
<template #toolbar-actions>
|
||||
<ElTabs class="w-full" v-model:model-value="sceneType" @tab-change="handleChangeSceneType">
|
||||
<ElTabPane label="我负责的" name="1" />
|
||||
<ElTabPane label="我参与的" name="2" />
|
||||
<ElTabPane label="下属负责的" name="3" />
|
||||
</ElTabs>
|
||||
</template>
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('ui.actionTitle.create', ['客户']),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.ADD,
|
||||
auth: ['crm:customer:create'],
|
||||
onClick: handleCreate,
|
||||
},
|
||||
{
|
||||
label: $t('ui.actionTitle.import'),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.UPLOAD,
|
||||
auth: ['crm:customer:import'],
|
||||
onClick: handleImport,
|
||||
},
|
||||
{
|
||||
label: $t('ui.actionTitle.export'),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.DOWNLOAD,
|
||||
auth: ['crm:customer:export'],
|
||||
onClick: handleExport,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #name="{ row }">
|
||||
<ElButton type="primary" link @click="handleDetail(row)">
|
||||
{{ row.name }}
|
||||
</ElButton>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'primary',
|
||||
link: true,
|
||||
icon: ACTION_ICON.EDIT,
|
||||
auth: ['crm:customer:update'],
|
||||
onClick: handleEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'danger',
|
||||
link: true,
|
||||
icon: ACTION_ICON.DELETE,
|
||||
auth: ['crm:customer:delete'],
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||
confirm: handleDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
154
apps/web-ele/src/views/crm/customer/limitConfig/data.ts
Normal file
154
apps/web-ele/src/views/crm/customer/limitConfig/data.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
import { LimitConfType } from '#/api/crm/customer/limitConfig';
|
||||
import { getSimpleDeptList } from '#/api/system/dept';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(confType: LimitConfType): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
triggerFields: [''],
|
||||
show: () => false,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'type',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
triggerFields: [''],
|
||||
show: () => false,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'userIds',
|
||||
label: '规则适用人群',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
allowClear: true,
|
||||
placeholder: '请选择规则适用人群',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'deptIds',
|
||||
label: '规则适用部门',
|
||||
component: 'ApiTreeSelect',
|
||||
componentProps: {
|
||||
api: async () => {
|
||||
const data = await getSimpleDeptList();
|
||||
return handleTree(data);
|
||||
},
|
||||
multiple: true,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
placeholder: '请选择规则适用部门',
|
||||
treeDefaultExpandAll: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'maxCount',
|
||||
label:
|
||||
confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT
|
||||
? '拥有客户数上限'
|
||||
: '锁定客户数上限',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: `请输入${
|
||||
LimitConfType.CUSTOMER_QUANTITY_LIMIT === confType
|
||||
? '拥有客户数上限'
|
||||
: '锁定客户数上限'
|
||||
}`,
|
||||
controlsPosition: 'right',
|
||||
class: '!w-full',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'dealCountEnabled',
|
||||
label: '成交客户是否占用拥有客户数',
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: [''],
|
||||
show: () => confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT,
|
||||
},
|
||||
defaultValue: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns(
|
||||
confType: LimitConfType,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
title: '编号',
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
field: 'users',
|
||||
title: '规则适用人群',
|
||||
formatter: ({ cellValue }) => {
|
||||
return cellValue
|
||||
.map((user: any) => {
|
||||
return user.nickname;
|
||||
})
|
||||
.join(',');
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'depts',
|
||||
title: '规则适用部门',
|
||||
formatter: ({ cellValue }) => {
|
||||
return cellValue
|
||||
.map((dept: any) => {
|
||||
return dept.name;
|
||||
})
|
||||
.join(',');
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'maxCount',
|
||||
title:
|
||||
confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT
|
||||
? '拥有客户数上限'
|
||||
: '锁定客户数上限',
|
||||
},
|
||||
{
|
||||
field: 'dealCountEnabled',
|
||||
title: '成交客户是否占用拥有客户数',
|
||||
visible: confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '创建时间',
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
168
apps/web-ele/src/views/crm/customer/limitConfig/index.vue
Normal file
168
apps/web-ele/src/views/crm/customer/limitConfig/index.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { CrmCustomerLimitConfigApi } from '#/api/crm/customer/limitConfig';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { ElLoading, ElMessage, ElTabPane, ElTabs } from 'element-plus';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
deleteCustomerLimitConfig,
|
||||
getCustomerLimitConfigPage,
|
||||
LimitConfType,
|
||||
} from '#/api/crm/customer/limitConfig';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
const configType = ref(LimitConfType.CUSTOMER_QUANTITY_LIMIT);
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 处理配置类型的切换 */
|
||||
function handleChangeConfigType(key: number | string) {
|
||||
configType.value = key as LimitConfType;
|
||||
gridApi.setGridOptions({
|
||||
columns: useGridColumns(configType.value),
|
||||
});
|
||||
handleRefresh();
|
||||
}
|
||||
|
||||
/** 创建规则 */
|
||||
function handleCreate(type: LimitConfType) {
|
||||
formModalApi.setData({ type }).open();
|
||||
}
|
||||
|
||||
/** 编辑规则 */
|
||||
function handleEdit(
|
||||
row: CrmCustomerLimitConfigApi.CustomerLimitConfig,
|
||||
type: LimitConfType,
|
||||
) {
|
||||
formModalApi.setData({ id: row.id, type }).open();
|
||||
}
|
||||
|
||||
/** 删除规则 */
|
||||
async function handleDelete(
|
||||
row: CrmCustomerLimitConfigApi.CustomerLimitConfig,
|
||||
) {
|
||||
const loadingInstance = ElLoading.service({
|
||||
text: $t('ui.actionMessage.deleting', [row.id]),
|
||||
});
|
||||
try {
|
||||
await deleteCustomerLimitConfig(row.id!);
|
||||
ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.id]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
loadingInstance.close();
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
gridOptions: {
|
||||
columns: useGridColumns(configType.value),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getCustomerLimitConfigPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
type: configType.value,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<CrmCustomerLimitConfigApi.CustomerLimitConfig>,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<template #doc>
|
||||
<DocAlert
|
||||
title="【客户】客户管理、公海客户"
|
||||
url="https://doc.iocoder.cn/crm/customer/"
|
||||
/>
|
||||
<DocAlert
|
||||
title="【通用】数据权限"
|
||||
url="https://doc.iocoder.cn/crm/permission/"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<FormModal @success="handleRefresh" />
|
||||
<Grid>
|
||||
<template #toolbar-actions>
|
||||
<ElTabs class="w-full" v-model:model-value="configType" @tab-change="handleChangeConfigType">
|
||||
<ElTabPane
|
||||
label="拥有客户数限制"
|
||||
:name="LimitConfType.CUSTOMER_QUANTITY_LIMIT"
|
||||
/>
|
||||
<ElTabPane
|
||||
label="锁定客户数限制"
|
||||
:name="LimitConfType.CUSTOMER_LOCK_LIMIT"
|
||||
/>
|
||||
</ElTabs>
|
||||
</template>
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('ui.actionTitle.create', ['规则']),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.ADD,
|
||||
auth: ['crm:customer-limit-config:create'],
|
||||
onClick: handleCreate.bind(null, configType),
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'primary',
|
||||
link: true,
|
||||
icon: ACTION_ICON.EDIT,
|
||||
auth: ['crm:customer-limit-config:update'],
|
||||
onClick: handleEdit.bind(null, row, configType),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'danger',
|
||||
link: true,
|
||||
icon: ACTION_ICON.DELETE,
|
||||
auth: ['crm:customer-limit-config:delete'],
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
|
||||
confirm: handleDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
100
apps/web-ele/src/views/crm/customer/limitConfig/modules/form.vue
Normal file
100
apps/web-ele/src/views/crm/customer/limitConfig/modules/form.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CrmCustomerLimitConfigApi } from '#/api/crm/customer/limitConfig';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
createCustomerLimitConfig,
|
||||
getCustomerLimitConfig,
|
||||
LimitConfType,
|
||||
updateCustomerLimitConfig,
|
||||
} from '#/api/crm/customer/limitConfig';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<CrmCustomerLimitConfigApi.CustomerLimitConfig>();
|
||||
const getTitle = computed(() => {
|
||||
return formData.value?.id
|
||||
? $t('ui.actionTitle.edit', ['规则'])
|
||||
: $t('ui.actionTitle.create', ['规则']);
|
||||
});
|
||||
|
||||
const confType = ref<LimitConfType>(LimitConfType.CUSTOMER_LOCK_LIMIT);
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 200,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: useFormSchema(confType.value),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
// 提交表单
|
||||
const data =
|
||||
(await formApi.getValues()) as CrmCustomerLimitConfigApi.CustomerLimitConfig;
|
||||
try {
|
||||
await (formData.value?.id
|
||||
? updateCustomerLimitConfig(data)
|
||||
: createCustomerLimitConfig(data));
|
||||
// 关闭并提示
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
// 加载数据
|
||||
let data =
|
||||
modalApi.getData<CrmCustomerLimitConfigApi.CustomerLimitConfig>();
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
if (data.type) {
|
||||
confType.value = data.type as LimitConfType;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(confType.value) });
|
||||
modalApi.lock();
|
||||
try {
|
||||
if (data.id) {
|
||||
data = await getCustomerLimitConfig(data.id);
|
||||
}
|
||||
formData.value = data;
|
||||
// 设置到 values
|
||||
await formApi.setValues(data);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="getTitle" class="w-2/5">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
85
apps/web-ele/src/views/crm/customer/modules/form.vue
Normal file
85
apps/web-ele/src/views/crm/customer/modules/form.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CrmCustomerApi } from '#/api/crm/customer';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenForm, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import {
|
||||
createCustomer,
|
||||
getCustomer,
|
||||
updateCustomer,
|
||||
} from '#/api/crm/customer';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<CrmCustomerApi.Customer>();
|
||||
const getTitle = computed(() => {
|
||||
return formData.value?.id
|
||||
? $t('ui.actionTitle.edit', ['客户'])
|
||||
: $t('ui.actionTitle.create', ['客户']);
|
||||
});
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
labelWidth: 100,
|
||||
},
|
||||
wrapperClass: 'grid-cols-2',
|
||||
layout: 'horizontal',
|
||||
schema: useFormSchema(),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
// 提交表单
|
||||
const data = (await formApi.getValues()) as CrmCustomerApi.Customer;
|
||||
try {
|
||||
await (formData.value?.id ? updateCustomer(data) : createCustomer(data));
|
||||
// 关闭并提示
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
// 加载数据
|
||||
const data = modalApi.getData<CrmCustomerApi.Customer>();
|
||||
if (!data || !data.id) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
formData.value = await getCustomer(data.id);
|
||||
// 设置到 values
|
||||
await formApi.setValues(formData.value);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="getTitle" class="w-2/5">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
87
apps/web-ele/src/views/crm/customer/modules/import-form.vue
Normal file
87
apps/web-ele/src/views/crm/customer/modules/import-form.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script lang="ts" setup>
|
||||
import type { UploadRawFile } from 'element-plus';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
import { ElButton, ElMessage, ElUpload } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { importCustomer, importCustomerTemplate } from '#/api/crm/customer';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useImportFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 120,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: useImportFormSchema(),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
// 提交表单
|
||||
const data = await formApi.getValues();
|
||||
try {
|
||||
await importCustomer({
|
||||
ownerUserId: data.ownerUserId,
|
||||
file: data.file,
|
||||
updateSupport: data.updateSupport,
|
||||
});
|
||||
// 关闭并提示
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/** 上传前 */
|
||||
function beforeUpload(file: UploadRawFile) {
|
||||
formApi.setFieldValue('file', file);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 下载模版 */
|
||||
async function handleDownload() {
|
||||
const data = await importCustomerTemplate();
|
||||
downloadFileFromBlobPart({ fileName: '客户导入模板.xls', source: data });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal title="客户导入" class="w-1/3">
|
||||
<Form class="mx-4">
|
||||
<template #file>
|
||||
<div class="w-full">
|
||||
<ElUpload
|
||||
:limit="1"
|
||||
accept=".xls,.xlsx"
|
||||
:before-upload="beforeUpload"
|
||||
:auto-upload="false"
|
||||
>
|
||||
<ElButton type="primary"> 选择 Excel 文件 </ElButton>
|
||||
</ElUpload>
|
||||
</div>
|
||||
</template>
|
||||
</Form>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center">
|
||||
<ElButton @click="handleDownload"> 下载导入模板 </ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
161
apps/web-ele/src/views/crm/customer/pool/data.ts
Normal file
161
apps/web-ele/src/views/crm/customer/pool/data.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '客户名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入客户名称',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'mobile',
|
||||
label: '手机',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'industryId',
|
||||
label: '所属行业',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, 'number'),
|
||||
placeholder: '请选择所属行业',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'level',
|
||||
label: '客户级别',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL, 'number'),
|
||||
placeholder: '请选择客户级别',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'source',
|
||||
label: '客户来源',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE, 'number'),
|
||||
placeholder: '请选择客户来源',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
title: '客户名称',
|
||||
field: 'name',
|
||||
minWidth: 160,
|
||||
fixed: 'left',
|
||||
slots: { default: 'name' },
|
||||
},
|
||||
{
|
||||
title: '客户来源',
|
||||
field: 'source',
|
||||
minWidth: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.CRM_CUSTOMER_SOURCE },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '手机',
|
||||
field: 'mobile',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
title: '电话',
|
||||
field: 'telephone',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
field: 'email',
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
title: '客户级别',
|
||||
field: 'level',
|
||||
minWidth: 135,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.CRM_CUSTOMER_LEVEL },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '客户行业',
|
||||
field: 'industryId',
|
||||
minWidth: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '下次联系时间',
|
||||
field: 'contactNextTime',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
field: 'remark',
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
title: '成交状态',
|
||||
field: 'dealStatus',
|
||||
minWidth: 80,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '最后跟进时间',
|
||||
field: 'contactLastTime',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
title: '最后跟进记录',
|
||||
field: 'contactLastContent',
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
field: 'updateTime',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
field: 'createTime',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
field: 'creatorName',
|
||||
minWidth: 100,
|
||||
},
|
||||
];
|
||||
}
|
||||
97
apps/web-ele/src/views/crm/customer/pool/index.vue
Normal file
97
apps/web-ele/src/views/crm/customer/pool/index.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { CrmCustomerApi } from '#/api/crm/customer';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { DocAlert, Page } from '@vben/common-ui';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
import { ElButton } from 'element-plus';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { exportCustomer, getCustomerPage } from '#/api/crm/customer';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
const { push } = useRouter();
|
||||
|
||||
/** 导出表格 */
|
||||
async function handleExport() {
|
||||
const data = await exportCustomer(await gridApi.formApi.getValues());
|
||||
downloadFileFromBlobPart({ fileName: '客户公海.xls', source: data });
|
||||
}
|
||||
|
||||
/** 查看客户详情 */
|
||||
function handleDetail(row: CrmCustomerApi.Customer) {
|
||||
push({ name: 'CrmCustomerDetail', params: { id: row.id } });
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getCustomerPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
pool: true,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<CrmCustomerApi.Customer>,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<template #doc>
|
||||
<DocAlert
|
||||
title="【客户】客户管理、公海客户"
|
||||
url="https://doc.iocoder.cn/crm/customer/"
|
||||
/>
|
||||
<DocAlert
|
||||
title="【通用】数据权限"
|
||||
url="https://doc.iocoder.cn/crm/permission/"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<Grid>
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('ui.actionTitle.export'),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.DOWNLOAD,
|
||||
auth: ['crm:customer:export'],
|
||||
onClick: handleExport,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #name="{ row }">
|
||||
<ElButton type="primary" link @click="handleDetail(row)">
|
||||
{{ row.name }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
84
apps/web-ele/src/views/crm/customer/poolConfig/data.ts
Normal file
84
apps/web-ele/src/views/crm/customer/poolConfig/data.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
|
||||
export const schema: VbenFormSchema[] = [
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
fieldName: 'enabled',
|
||||
label: '客户公海规则设置',
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '开启', value: true },
|
||||
{ label: '关闭', value: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'contactExpireDays',
|
||||
componentProps: {
|
||||
min: 0,
|
||||
precision: 0,
|
||||
controlsPosition: 'right',
|
||||
class: '!w-full',
|
||||
},
|
||||
renderComponentContent: () => ({
|
||||
addonAfter: () => '天不跟进或',
|
||||
}),
|
||||
dependencies: {
|
||||
triggerFields: ['enabled'],
|
||||
show: (value) => value.enabled,
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'dealExpireDays',
|
||||
renderComponentContent: () => ({
|
||||
addonBefore: () => '或',
|
||||
addonAfter: () => '天未成交',
|
||||
}),
|
||||
componentProps: {
|
||||
min: 0,
|
||||
precision: 0,
|
||||
controlsPosition: 'right',
|
||||
class: '!w-full',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['enabled'],
|
||||
show: (value) => value.enabled,
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
fieldName: 'notifyEnabled',
|
||||
label: '提前提醒设置',
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '开启', value: true },
|
||||
{ label: '关闭', value: false },
|
||||
],
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['enabled'],
|
||||
show: (value) => value.enabled,
|
||||
},
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'notifyDays',
|
||||
componentProps: {
|
||||
min: 0,
|
||||
precision: 0,
|
||||
controlsPosition: 'right',
|
||||
class: '!w-full',
|
||||
},
|
||||
renderComponentContent: () => ({
|
||||
addonBefore: () => '提前',
|
||||
addonAfter: () => '天提醒',
|
||||
}),
|
||||
dependencies: {
|
||||
triggerFields: ['notifyEnabled'],
|
||||
show: (value) => value.enabled && value.notifyEnabled,
|
||||
},
|
||||
},
|
||||
];
|
||||
69
apps/web-ele/src/views/crm/customer/poolConfig/index.vue
Normal file
69
apps/web-ele/src/views/crm/customer/poolConfig/index.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CrmCustomerPoolConfigApi } from '#/api/crm/customer/poolConfig';
|
||||
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { ElCard, ElMessage } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
getCustomerPoolConfig,
|
||||
saveCustomerPoolConfig,
|
||||
} from '#/api/crm/customer/poolConfig';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { schema } from './data';
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
labelClass: 'w-100',
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema,
|
||||
handleSubmit,
|
||||
});
|
||||
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// 提交表单
|
||||
const data =
|
||||
(await formApi.getValues()) as CrmCustomerPoolConfigApi.CustomerPoolConfig;
|
||||
if (!data.enabled) {
|
||||
data.contactExpireDays = undefined;
|
||||
data.dealExpireDays = undefined;
|
||||
data.notifyEnabled = false;
|
||||
}
|
||||
if (!data.notifyEnabled) {
|
||||
data.notifyDays = undefined;
|
||||
}
|
||||
await saveCustomerPoolConfig(data);
|
||||
// 关闭并提示
|
||||
await formApi.setValues(data);
|
||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||
}
|
||||
|
||||
/** 获取配置 */
|
||||
async function getConfigInfo() {
|
||||
const res = await getCustomerPoolConfig();
|
||||
await formApi.setValues(res);
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getConfigInfo();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<ElCard title="客户公海规则设置">
|
||||
<Form class="w-1/4" />
|
||||
</ElCard>
|
||||
</Page>
|
||||
</template>
|
||||
Reference in New Issue
Block a user