Merge remote-tracking branch 'yudao/dev' into dev
						commit
						a2832f1546
					
				|  | @ -12,16 +12,14 @@ export namespace CrmCustomerLimitConfigApi { | |||
|     maxCount?: number; | ||||
|     dealCountEnabled?: boolean; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|   /** | ||||
|    * 客户限制配置类型 | ||||
|    */ | ||||
|   export enum LimitConfType { | ||||
| /** 客户限制配置类型 */ | ||||
| export enum LimitConfType { | ||||
|   /** 锁定客户数限制 */ | ||||
|   CUSTOMER_LOCK_LIMIT = 2, | ||||
|   /** 拥有客户数限制 */ | ||||
|   CUSTOMER_QUANTITY_LIMIT = 1, | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询客户限制配置列表 */ | ||||
|  |  | |||
|  | @ -1,5 +1,3 @@ | |||
| import type { PageParam } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmProductCategoryApi { | ||||
|  | @ -38,7 +36,7 @@ export function deleteProductCategory(id: number) { | |||
| } | ||||
| 
 | ||||
| /** 产品分类列表 */ | ||||
| export function getProductCategoryList(params?: PageParam) { | ||||
| export function getProductCategoryList(params?: any) { | ||||
|   return requestClient.get<CrmProductCategoryApi.ProductCategory[]>( | ||||
|     '/crm/product-category/list', | ||||
|     { params }, | ||||
|  |  | |||
|  | @ -1,15 +1,5 @@ | |||
| import type { Ref } from 'vue'; | ||||
| 
 | ||||
| import type { VbenFormSchema } from '#/adapter/form'; | ||||
| import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmReceivableApi } from '#/api/crm/receivable'; | ||||
| 
 | ||||
| import { useAccess } from '@vben/access'; | ||||
| 
 | ||||
| import { DICT_TYPE } from '#/utils'; | ||||
| 
 | ||||
| const { hasAccessByCodes } = useAccess(); | ||||
| 
 | ||||
| export interface LeftSideItem { | ||||
|   name: string; | ||||
|   menu: string; | ||||
|  | @ -109,663 +99,3 @@ export const useLeftSides = ( | |||
|     }, | ||||
|   ]; | ||||
| }; | ||||
| 
 | ||||
| /** 分配给我的线索 列表的搜索表单 */ | ||||
| export function useClueFollowFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'followUpStatus', | ||||
|       label: '状态', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: FOLLOWUP_STATUS, | ||||
|       }, | ||||
|       defaultValue: false, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 分配给我的线索 列表的字段 */ | ||||
| export function useClueFollowColumns(): VxeTableGridOptions['columns'] { | ||||
|   return [ | ||||
|     { | ||||
|       field: 'name', | ||||
|       title: '线索名称', | ||||
|       fixed: 'left', | ||||
|       slots: { default: 'name' }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'source', | ||||
|       title: '线索来源', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_CUSTOMER_SOURCE }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'mobile', | ||||
|       title: '手机', | ||||
|     }, | ||||
|     { | ||||
|       field: 'telephone', | ||||
|       title: '电话', | ||||
|     }, | ||||
|     { | ||||
|       field: 'email', | ||||
|       title: '邮箱', | ||||
|     }, | ||||
|     { | ||||
|       field: 'detailAddress', | ||||
|       title: '地址', | ||||
|     }, | ||||
|     { | ||||
|       field: 'industryId', | ||||
|       title: '客户行业', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'level', | ||||
|       title: '客户级别', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_CUSTOMER_LEVEL }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactNextTime', | ||||
|       title: '下次联系时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'remark', | ||||
|       title: '备注', | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactLastTime', | ||||
|       title: '最后跟进时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactLastContent', | ||||
|       title: '最后跟进记录', | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserName', | ||||
|       title: '负责人', | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserDeptName', | ||||
|       title: '所属部门', | ||||
|     }, | ||||
|     { | ||||
|       field: 'updateTime', | ||||
|       title: '更新时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'creatorName', | ||||
|       title: '创建人', | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 合同审核列表的搜索表单 */ | ||||
| export function useContractAuditFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'auditStatus', | ||||
|       label: '合同状态', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: AUDIT_STATUS, | ||||
|       }, | ||||
|       defaultValue: 10, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 合同提醒列表的搜索表单 */ | ||||
| export function useContractRemindFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'expiryType', | ||||
|       label: '到期状态', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: CONTRACT_EXPIRY_TYPE, | ||||
|       }, | ||||
|       defaultValue: 1, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 合同审核列表的字段 */ | ||||
| export function useContractColumns(): VxeTableGridOptions['columns'] { | ||||
|   return [ | ||||
|     { | ||||
|       field: 'no', | ||||
|       title: '合同编号', | ||||
|       fixed: 'left', | ||||
|     }, | ||||
|     { | ||||
|       field: 'name', | ||||
|       title: '合同名称', | ||||
|       slots: { default: 'name' }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'customerName', | ||||
|       title: '客户名称', | ||||
|       slots: { default: 'customerName' }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'businessName', | ||||
|       title: '商机名称', | ||||
|       slots: { default: 'businessName' }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'price', | ||||
|       title: '合同金额(元)', | ||||
|       formatter: 'formatNumber', | ||||
|     }, | ||||
|     { | ||||
|       field: 'orderDate', | ||||
|       title: '下单时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'startTime', | ||||
|       title: '合同开始时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'endTime', | ||||
|       title: '合同结束时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactName', | ||||
|       title: '客户签约人', | ||||
|       slots: { default: 'contactName' }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'signUserName', | ||||
|       title: '公司签约人', | ||||
|     }, | ||||
|     { | ||||
|       field: 'remark', | ||||
|       title: '备注', | ||||
|     }, | ||||
|     { | ||||
|       field: 'totalReceivablePrice', | ||||
|       title: '已回款金额(元)', | ||||
|       formatter: 'formatNumber', | ||||
|     }, | ||||
|     { | ||||
|       field: 'noReceivablePrice', | ||||
|       title: '未回款金额(元)', | ||||
|       formatter: 'formatNumber', | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactLastTime', | ||||
|       title: '最后跟进时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserName', | ||||
|       title: '负责人', | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserDeptName', | ||||
|       title: '所属部门', | ||||
|     }, | ||||
|     { | ||||
|       field: 'updateTime', | ||||
|       title: '更新时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'creatorName', | ||||
|       title: '创建人', | ||||
|     }, | ||||
|     { | ||||
|       field: 'auditStatus', | ||||
|       title: '合同状态', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       title: '操作', | ||||
|       width: 80, | ||||
|       fixed: 'right', | ||||
|       slots: { default: 'actions' }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 客户跟进列表的搜索表单 */ | ||||
| export function useCustomerFollowFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'followUpStatus', | ||||
|       label: '状态', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: FOLLOWUP_STATUS, | ||||
|       }, | ||||
|       defaultValue: false, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 待进入公海客户列表的搜索表单 */ | ||||
| export function useCustomerPutPoolFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'sceneType', | ||||
|       label: '归属', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: SCENE_TYPES, | ||||
|       }, | ||||
|       defaultValue: 1, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 今日需联系客户列表的搜索表单 */ | ||||
| export function useCustomerTodayContactFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'contactStatus', | ||||
|       label: '状态', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: CONTACT_STATUS, | ||||
|       }, | ||||
|       defaultValue: 1, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'sceneType', | ||||
|       label: '归属', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: SCENE_TYPES, | ||||
|       }, | ||||
|       defaultValue: 1, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 客户列表的字段 */ | ||||
| export function useCustomerColumns(): VxeTableGridOptions['columns'] { | ||||
|   return [ | ||||
|     { | ||||
|       field: 'name', | ||||
|       title: '客户名称', | ||||
|       slots: { default: 'name' }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'source', | ||||
|       title: '客户来源', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_CUSTOMER_SOURCE }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'mobile', | ||||
|       title: '手机', | ||||
|     }, | ||||
|     { | ||||
|       field: 'telephone', | ||||
|       title: '电话', | ||||
|     }, | ||||
|     { | ||||
|       field: 'email', | ||||
|       title: '邮箱', | ||||
|     }, | ||||
|     { | ||||
|       field: 'level', | ||||
|       title: '客户级别', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_CUSTOMER_LEVEL }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'industryId', | ||||
|       title: '客户行业', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactNextTime', | ||||
|       title: '下次联系时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'remark', | ||||
|       title: '备注', | ||||
|     }, | ||||
|     { | ||||
|       field: 'lockStatus', | ||||
|       title: '锁定状态', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'dealStatus', | ||||
|       title: '成交状态', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactLastTime', | ||||
|       title: '最后跟进时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactLastContent', | ||||
|       title: '最后跟进记录', | ||||
|     }, | ||||
|     { | ||||
|       field: 'detailAddress', | ||||
|       title: '地址', | ||||
|     }, | ||||
|     { | ||||
|       field: 'poolDay', | ||||
|       title: '距离进入公海天数', | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserName', | ||||
|       title: '负责人', | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserDeptName', | ||||
|       title: '所属部门', | ||||
|     }, | ||||
|     { | ||||
|       field: 'updateTime', | ||||
|       title: '更新时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'creatorName', | ||||
|       title: '创建人', | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 回款审核列表的搜索表单 */ | ||||
| export function useReceivableAuditFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'auditStatus', | ||||
|       label: '合同状态', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: AUDIT_STATUS, | ||||
|       }, | ||||
|       defaultValue: 10, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 回款审核列表的字段 */ | ||||
| export function useReceivableAuditColumns<T = CrmReceivableApi.Receivable>( | ||||
|   onActionClick: OnActionClickFn<T>, | ||||
| ): VxeTableGridOptions['columns'] { | ||||
|   return [ | ||||
|     { | ||||
|       field: 'no', | ||||
|       title: '回款编号', | ||||
|       fixed: 'left', | ||||
|       slots: { default: 'no' }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'customerName', | ||||
|       title: '客户名称', | ||||
|       slots: { default: 'customerName' }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contractNo', | ||||
|       title: '合同编号', | ||||
|       slots: { default: 'contractNo' }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'returnTime', | ||||
|       title: '回款日期', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'price', | ||||
|       title: '回款金额(元)', | ||||
|       formatter: 'formatNumber', | ||||
|     }, | ||||
|     { | ||||
|       field: 'returnType', | ||||
|       title: '回款方式', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'remark', | ||||
|       title: '备注', | ||||
|     }, | ||||
|     { | ||||
|       field: 'contract.totalPrice', | ||||
|       title: '合同金额(元)', | ||||
|       formatter: 'formatNumber', | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserName', | ||||
|       title: '负责人', | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserDeptName', | ||||
|       title: '所属部门', | ||||
|     }, | ||||
|     { | ||||
|       field: 'updateTime', | ||||
|       title: '更新时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'creatorName', | ||||
|       title: '创建人', | ||||
|     }, | ||||
|     { | ||||
|       field: 'auditStatus', | ||||
|       title: '回款状态', | ||||
|       fixed: 'right', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'operation', | ||||
|       title: '操作', | ||||
|       width: 140, | ||||
|       fixed: 'right', | ||||
|       align: 'center', | ||||
|       cellRender: { | ||||
|         attrs: { | ||||
|           nameField: 'name', | ||||
|           nameTitle: '角色', | ||||
|           onClick: onActionClick, | ||||
|         }, | ||||
|         name: 'CellOperation', | ||||
|         options: [ | ||||
|           { | ||||
|             code: 'processDetail', | ||||
|             text: '查看审批', | ||||
|             show: hasAccessByCodes(['crm:receivable:update']), | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 回款计划提醒列表的搜索表单 */ | ||||
| export function useReceivablePlanRemindFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'remindType', | ||||
|       label: '合同状态', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: RECEIVABLE_REMIND_TYPE, | ||||
|       }, | ||||
|       defaultValue: 1, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 回款计划提醒列表的字段 */ | ||||
| export function useReceivablePlanRemindColumns<T = CrmReceivableApi.Receivable>( | ||||
|   onActionClick: OnActionClickFn<T>, | ||||
| ): VxeTableGridOptions['columns'] { | ||||
|   return [ | ||||
|     { | ||||
|       field: 'customerName', | ||||
|       title: '客户名称', | ||||
|       fixed: 'left', | ||||
|       slots: { default: 'customerName' }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contractNo', | ||||
|       title: '合同编号', | ||||
|     }, | ||||
|     { | ||||
|       field: 'period', | ||||
|       title: '期数', | ||||
|       slots: { default: 'period' }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'price', | ||||
|       title: '计划回款金额(元)', | ||||
|       formatter: 'formatNumber', | ||||
|     }, | ||||
|     { | ||||
|       field: 'returnTime', | ||||
|       title: '计划回款日期', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'remindDays', | ||||
|       title: '提前几天提醒', | ||||
|     }, | ||||
|     { | ||||
|       field: 'remindTime', | ||||
|       title: '提醒日期', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'returnType', | ||||
|       title: '回款方式', | ||||
|       fixed: 'right', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'remark', | ||||
|       title: '备注', | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserName', | ||||
|       title: '负责人', | ||||
|     }, | ||||
|     { | ||||
|       field: 'receivable.price', | ||||
|       title: '实际回款金额(元)', | ||||
|       formatter: 'formatNumber', | ||||
|     }, | ||||
|     { | ||||
|       field: 'receivable.returnTime', | ||||
|       title: '实际回款日期', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'updateTime', | ||||
|       title: '更新时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'creatorName', | ||||
|       title: '创建人', | ||||
|     }, | ||||
|     { | ||||
|       field: 'operation', | ||||
|       title: '操作', | ||||
|       width: 140, | ||||
|       fixed: 'right', | ||||
|       align: 'center', | ||||
|       cellRender: { | ||||
|         attrs: { | ||||
|           nameField: 'customerName', | ||||
|           nameTitle: '客户名称', | ||||
|           onClick: onActionClick, | ||||
|         }, | ||||
|         name: 'CellOperation', | ||||
|         options: [ | ||||
|           { | ||||
|             code: 'receivableForm', | ||||
|             text: '创建回款', | ||||
|             show: hasAccessByCodes(['crm:receivable:create']), | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
|  |  | |||
|  | @ -10,17 +10,16 @@ import * as ContractApi from '#/api/crm/contract'; | |||
| import * as CustomerApi from '#/api/crm/customer'; | ||||
| import * as ReceivableApi from '#/api/crm/receivable'; | ||||
| import * as ReceivablePlanApi from '#/api/crm/receivable/plan'; | ||||
| import { DocAlert } from '#/components/doc-alert'; | ||||
| 
 | ||||
| import { useLeftSides } from './data'; | ||||
| import ClueFollowList from './modules/clue-follow-list.vue'; | ||||
| import ContractAuditList from './modules/contract-audit-list.vue'; | ||||
| import ContractRemindList from './modules/ContractRemindList.vue'; | ||||
| import CustomerFollowList from './modules/CustomerFollowList.vue'; | ||||
| import CustomerPutPoolRemindList from './modules/CustomerPutPoolRemindList.vue'; | ||||
| import CustomerTodayContactList from './modules/CustomerTodayContactList.vue'; | ||||
| import ReceivableAuditList from './modules/ReceivableAuditList.vue'; | ||||
| import ReceivablePlanRemindList from './modules/ReceivablePlanRemindList.vue'; | ||||
| import ContractRemindList from './modules/contract-remind-list.vue'; | ||||
| import CustomerFollowList from './modules/customer-follow-list.vue'; | ||||
| import CustomerPutPoolRemindList from './modules/customer-put-pool-remind-list.vue'; | ||||
| import CustomerTodayContactList from './modules/customer-today-contact-list.vue'; | ||||
| import ReceivableAuditList from './modules/receivable-audit-list.vue'; | ||||
| import ReceivablePlanRemindList from './modules/receivable-plan-remind-list.vue'; | ||||
| 
 | ||||
| defineOptions({ name: 'CrmBacklog' }); | ||||
| 
 | ||||
|  | @ -92,12 +91,6 @@ onMounted(async () => { | |||
| </script> | ||||
| <template> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="【通用】跟进记录、待办事项" | ||||
|         url="https://doc.iocoder.cn/crm/follow-up/" | ||||
|       /> | ||||
|     </template> | ||||
|     <div class="flex h-full w-full"> | ||||
|       <Card class="w-1/5"> | ||||
|         <List item-layout="horizontal" :data-source="leftSides"> | ||||
|  | @ -105,11 +98,17 @@ onMounted(async () => { | |||
|             <List.Item> | ||||
|               <List.Item.Meta> | ||||
|                 <template #title> | ||||
|                   <a @click="sideClick(item)"> {{ item.name }} </a> | ||||
|                   <a @click="sideClick(item)"> | ||||
|                     {{ item.name }} | ||||
|                   </a> | ||||
|                 </template> | ||||
|               </List.Item.Meta> | ||||
|               <template #extra v-if="item.count.value && item.count.value > 0"> | ||||
|                 <Badge :count="item.count.value" /> | ||||
|               <template #extra> | ||||
|                 <Badge | ||||
|                   :color="item.menu === leftMenu ? 'blue' : 'red'" | ||||
|                   :count="item.count.value" | ||||
|                   :show-zero="true" | ||||
|                 /> | ||||
|               </template> | ||||
|             </List.Item> | ||||
|           </template> | ||||
|  |  | |||
|  | @ -1,90 +0,0 @@ | |||
| <!-- 待回款提醒 --> | ||||
| <script lang="ts" setup> | ||||
| import type { OnActionClickParams } from '#/adapter/vxe-table'; | ||||
| import type { CrmReceivableApi } from '#/api/crm/receivable'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getReceivablePage } from '#/api/crm/receivable'; | ||||
| 
 | ||||
| import { | ||||
|   useReceivablePlanRemindColumns, | ||||
|   useReceivablePlanRemindFormSchema, | ||||
| } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 打开回款详情 */ | ||||
| function openDetail(row: CrmReceivableApi.Receivable) { | ||||
|   push({ name: 'CrmReceivableDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| /** 打开客户详情 */ | ||||
| function openCustomerDetail(row: CrmReceivableApi.Receivable) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.customerId } }); | ||||
| } | ||||
| 
 | ||||
| /** 创建回款 */ | ||||
| function openReceivableForm(row: CrmReceivableApi.Receivable) { | ||||
|   // Todo: 打开创建回款 | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.customerId } }); | ||||
| } | ||||
| 
 | ||||
| /** 表格操作按钮的回调函数 */ | ||||
| function onActionClick({ | ||||
|   code, | ||||
|   row, | ||||
| }: OnActionClickParams<CrmReceivableApi.Receivable>) { | ||||
|   switch (code) { | ||||
|     case 'receivableForm': { | ||||
|       openReceivableForm(row); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useReceivablePlanRemindFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useReceivablePlanRemindColumns(onActionClick), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getReceivablePage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="待回款提醒"> | ||||
|     <template #customerName="{ row }"> | ||||
|       <Button type="link" @click="openCustomerDetail(row)"> | ||||
|         {{ row.customerName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #period="{ row }"> | ||||
|       <Button type="link" @click="openDetail(row)">{{ row.period }}</Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -1,5 +1,6 @@ | |||
| <!-- 分配给我的线索 --> | ||||
| <script lang="ts" setup> | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmClueApi } from '#/api/crm/clue'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
|  | @ -8,8 +9,9 @@ import { Button } from 'ant-design-vue'; | |||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getCluePage } from '#/api/crm/clue'; | ||||
| import { useGridColumns } from '#/views/crm/clue/data'; | ||||
| 
 | ||||
| import { useClueFollowColumns, useClueFollowFormSchema } from '../data'; | ||||
| import { FOLLOWUP_STATUS } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
|  | @ -20,10 +22,21 @@ function handleDetail(row: CrmClueApi.Clue) { | |||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useClueFollowFormSchema(), | ||||
|     schema: [ | ||||
|       { | ||||
|         fieldName: 'followUpStatus', | ||||
|         label: '状态', | ||||
|         component: 'Select', | ||||
|         componentProps: { | ||||
|           allowClear: true, | ||||
|           options: FOLLOWUP_STATUS, | ||||
|         }, | ||||
|         defaultValue: false, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useClueFollowColumns(), | ||||
|     columns: useGridColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|  | @ -45,14 +58,17 @@ const [Grid] = useVbenVxeGrid({ | |||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
|   } as VxeTableGridOptions<CrmClueApi.Clue>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="分配给我的线索"> | ||||
|   <Grid> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="handleDetail(row)">{{ row.name }}</Button> | ||||
|     </template> | ||||
|     <template #actions="{ row }"> | ||||
|       <Button type="link" @click="handleDetail(row)">查看详情</Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| <!-- 待审核合同 --> | ||||
| <script lang="ts" setup> | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmContractApi } from '#/api/crm/contract'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
|  | @ -8,8 +9,9 @@ import { Button } from 'ant-design-vue'; | |||
| 
 | ||||
| import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getContractPage } from '#/api/crm/contract'; | ||||
| import { useGridColumns } from '#/views/crm/contract/data'; | ||||
| 
 | ||||
| import { useContractAuditFormSchema, useContractColumns } from '../data'; | ||||
| import { AUDIT_STATUS } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
|  | @ -42,10 +44,21 @@ function handleBusinessDetail(row: CrmContractApi.Contract) { | |||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useContractAuditFormSchema(), | ||||
|     schema: [ | ||||
|       { | ||||
|         fieldName: 'auditStatus', | ||||
|         label: '合同状态', | ||||
|         component: 'Select', | ||||
|         componentProps: { | ||||
|           allowClear: true, | ||||
|           options: AUDIT_STATUS, | ||||
|         }, | ||||
|         defaultValue: 10, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useContractColumns(), | ||||
|     columns: useGridColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|  | @ -67,12 +80,12 @@ const [Grid] = useVbenVxeGrid({ | |||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
|   } as VxeTableGridOptions<CrmContractApi.Contract>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="待审核合同"> | ||||
|   <Grid> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="handleContractDetail(row)"> | ||||
|         {{ row.name }} | ||||
|  |  | |||
|  | @ -1,21 +1,22 @@ | |||
| <!-- 即将到期的合同 --> | ||||
| <script lang="ts" setup> | ||||
| import type { OnActionClickParams } from '#/adapter/vxe-table'; | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmContractApi } from '#/api/crm/contract'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getContractPage } from '#/api/crm/contract'; | ||||
| import { useGridColumns } from '#/views/crm/contract/data'; | ||||
| 
 | ||||
| import { useContractColumns, useContractRemindFormSchema } from '../data'; | ||||
| import { CONTRACT_EXPIRY_TYPE } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 查看审批 */ | ||||
| function openProcessDetail(row: CrmContractApi.Contract) { | ||||
| function handleProcessDetail(row: CrmContractApi.Contract) { | ||||
|   push({ | ||||
|     name: 'BpmProcessInstanceDetail', | ||||
|     query: { id: row.processInstanceId }, | ||||
|  | @ -23,43 +24,41 @@ function openProcessDetail(row: CrmContractApi.Contract) { | |||
| } | ||||
| 
 | ||||
| /** 打开合同详情 */ | ||||
| function openContractDetail(row: CrmContractApi.Contract) { | ||||
| function handleContractDetail(row: CrmContractApi.Contract) { | ||||
|   push({ name: 'CrmContractDetail', params: { id: row.id } }); | ||||
| } | ||||
| /** 打开客户详情 */ | ||||
| function openCustomerDetail(row: CrmContractApi.Contract) { | ||||
| function handleCustomerDetail(row: CrmContractApi.Contract) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| /** 打开联系人详情 */ | ||||
| function openContactDetail(row: CrmContractApi.Contract) { | ||||
| function handleContactDetail(row: CrmContractApi.Contract) { | ||||
|   push({ name: 'CrmContactDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| /** 打开商机详情 */ | ||||
| function openBusinessDetail(row: CrmContractApi.Contract) { | ||||
| function handleBusinessDetail(row: CrmContractApi.Contract) { | ||||
|   push({ name: 'CrmBusinessDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| /** 表格操作按钮的回调函数 */ | ||||
| function onActionClick({ | ||||
|   code, | ||||
|   row, | ||||
| }: OnActionClickParams<CrmContractApi.Contract>) { | ||||
|   switch (code) { | ||||
|     case 'processDetail': { | ||||
|       openProcessDetail(row); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useContractRemindFormSchema(), | ||||
|     schema: [ | ||||
|       { | ||||
|         fieldName: 'expiryType', | ||||
|         label: '到期状态', | ||||
|         component: 'Select', | ||||
|         componentProps: { | ||||
|           allowClear: true, | ||||
|           options: CONTRACT_EXPIRY_TYPE, | ||||
|         }, | ||||
|         defaultValue: 1, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useContractColumns(onActionClick), | ||||
|     columns: useGridColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|  | @ -81,31 +80,43 @@ const [Grid] = useVbenVxeGrid({ | |||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
|   } as VxeTableGridOptions<CrmContractApi.Contract>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="即将到期的合同"> | ||||
|   <Grid> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="openContractDetail(row)"> | ||||
|       <Button type="link" @click="handleContractDetail(row)"> | ||||
|         {{ row.name }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #customerName="{ row }"> | ||||
|       <Button type="link" @click="openCustomerDetail(row)"> | ||||
|       <Button type="link" @click="handleCustomerDetail(row)"> | ||||
|         {{ row.customerName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #businessName="{ row }"> | ||||
|       <Button type="link" @click="openBusinessDetail(row)"> | ||||
|       <Button type="link" @click="handleBusinessDetail(row)"> | ||||
|         {{ row.businessName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #contactName="{ row }"> | ||||
|       <Button type="link" @click="openContactDetail(row)"> | ||||
|         {{ row.contactName }} | ||||
|     <template #signContactName="{ row }"> | ||||
|       <Button type="link" @click="handleContactDetail(row)"> | ||||
|         {{ row.signContactName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #actions="{ row }"> | ||||
|       <TableAction | ||||
|         :actions="[ | ||||
|           { | ||||
|             label: '查看审批', | ||||
|             type: 'link', | ||||
|             auth: ['crm:contract:update'], | ||||
|             onClick: handleProcessDetail.bind(null, row), | ||||
|           }, | ||||
|         ]" | ||||
|       /> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -1,5 +1,6 @@ | |||
| <!-- 分配给我的客户 --> | ||||
| <script lang="ts" setup> | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmCustomerApi } from '#/api/crm/customer'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
|  | @ -8,22 +9,34 @@ import { Button } from 'ant-design-vue'; | |||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getCustomerPage } from '#/api/crm/customer'; | ||||
| import { useGridColumns } from '#/views/crm/customer/data'; | ||||
| 
 | ||||
| import { useCustomerColumns, useCustomerFollowFormSchema } from '../data'; | ||||
| import { FOLLOWUP_STATUS } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 打开客户详情 */ | ||||
| function onDetail(row: CrmCustomerApi.Customer) { | ||||
| function handleDetail(row: CrmCustomerApi.Customer) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useCustomerFollowFormSchema(), | ||||
|     schema: [ | ||||
|       { | ||||
|         fieldName: 'followUpStatus', | ||||
|         label: '状态', | ||||
|         component: 'Select', | ||||
|         componentProps: { | ||||
|           allowClear: true, | ||||
|           options: FOLLOWUP_STATUS, | ||||
|         }, | ||||
|         defaultValue: false, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useCustomerColumns(), | ||||
|     columns: useGridColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|  | @ -45,14 +58,17 @@ const [Grid] = useVbenVxeGrid({ | |||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
|   } as VxeTableGridOptions<CrmCustomerApi.Customer>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="分配给我的客户"> | ||||
|   <Grid> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="onDetail(row)">{{ row.name }}</Button> | ||||
|       <Button type="link" @click="handleDetail(row)">{{ row.name }}</Button> | ||||
|     </template> | ||||
|     <template #actions="{ row }"> | ||||
|       <Button type="link" @click="handleDetail(row)">查看详情</Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -1,5 +1,6 @@ | |||
| <!-- 待进入公海的客户 --> | ||||
| <script lang="ts" setup> | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmCustomerApi } from '#/api/crm/customer'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
|  | @ -8,22 +9,34 @@ import { Button } from 'ant-design-vue'; | |||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getCustomerPage } from '#/api/crm/customer'; | ||||
| import { useGridColumns } from '#/views/crm/customer/data'; | ||||
| 
 | ||||
| import { useCustomerColumns, useCustomerPutPoolFormSchema } from '../data'; | ||||
| import { SCENE_TYPES } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 打开客户详情 */ | ||||
| function onDetail(row: CrmCustomerApi.Customer) { | ||||
| function handleDetail(row: CrmCustomerApi.Customer) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useCustomerPutPoolFormSchema(), | ||||
|     schema: [ | ||||
|       { | ||||
|         fieldName: 'sceneType', | ||||
|         label: '归属', | ||||
|         component: 'Select', | ||||
|         componentProps: { | ||||
|           allowClear: true, | ||||
|           options: SCENE_TYPES, | ||||
|         }, | ||||
|         defaultValue: 1, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useCustomerColumns(), | ||||
|     columns: useGridColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|  | @ -45,14 +58,17 @@ const [Grid] = useVbenVxeGrid({ | |||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
|   } as VxeTableGridOptions<CrmCustomerApi.Customer>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="待进入公海的客户"> | ||||
|   <Grid> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="onDetail(row)">{{ row.name }}</Button> | ||||
|       <Button type="link" @click="handleDetail(row)">{{ row.name }}</Button> | ||||
|     </template> | ||||
|     <template #actions="{ row }"> | ||||
|       <Button type="link" @click="handleDetail(row)">查看详情</Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -1,5 +1,6 @@ | |||
| <!-- 今日需联系客户 --> | ||||
| <script lang="ts" setup> | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmCustomerApi } from '#/api/crm/customer'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
|  | @ -8,22 +9,44 @@ import { Button } from 'ant-design-vue'; | |||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getCustomerPage } from '#/api/crm/customer'; | ||||
| import { useGridColumns } from '#/views/crm/customer/data'; | ||||
| 
 | ||||
| import { useCustomerColumns, useCustomerTodayContactFormSchema } from '../data'; | ||||
| import { CONTACT_STATUS, SCENE_TYPES } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 打开客户详情 */ | ||||
| function onDetail(row: CrmCustomerApi.Customer) { | ||||
| function handleDetail(row: CrmCustomerApi.Customer) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useCustomerTodayContactFormSchema(), | ||||
|     schema: [ | ||||
|       { | ||||
|         fieldName: 'contactStatus', | ||||
|         label: '状态', | ||||
|         component: 'Select', | ||||
|         componentProps: { | ||||
|           allowClear: true, | ||||
|           options: CONTACT_STATUS, | ||||
|         }, | ||||
|         defaultValue: 1, | ||||
|       }, | ||||
|       { | ||||
|         fieldName: 'sceneType', | ||||
|         label: '归属', | ||||
|         component: 'Select', | ||||
|         componentProps: { | ||||
|           allowClear: true, | ||||
|           options: SCENE_TYPES, | ||||
|         }, | ||||
|         defaultValue: 1, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useCustomerColumns(), | ||||
|     columns: useGridColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|  | @ -45,14 +68,17 @@ const [Grid] = useVbenVxeGrid({ | |||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
|   } as VxeTableGridOptions<CrmCustomerApi.Customer>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="今日需联系客户"> | ||||
|   <Grid> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="onDetail(row)">{{ row.name }}</Button> | ||||
|       <Button type="link" @click="handleDetail(row)">{{ row.name }}</Button> | ||||
|     </template> | ||||
|     <template #actions="{ row }"> | ||||
|       <Button type="link" @click="handleDetail(row)">查看详情</Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -1,6 +1,6 @@ | |||
| <!-- 待审核回款 --> | ||||
| <script lang="ts" setup> | ||||
| import type { OnActionClickParams } from '#/adapter/vxe-table'; | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmReceivableApi } from '#/api/crm/receivable'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
|  | @ -9,16 +9,14 @@ import { Button } from 'ant-design-vue'; | |||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getReceivablePage } from '#/api/crm/receivable'; | ||||
| import { useGridColumns } from '#/views/crm/receivable/data'; | ||||
| 
 | ||||
| import { | ||||
|   useReceivableAuditColumns, | ||||
|   useReceivableAuditFormSchema, | ||||
| } from '../data'; | ||||
| import { AUDIT_STATUS } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 查看审批 */ | ||||
| function openProcessDetail(row: CrmReceivableApi.Receivable) { | ||||
| function handleProcessDetail(row: CrmReceivableApi.Receivable) { | ||||
|   push({ | ||||
|     name: 'BpmProcessInstanceDetail', | ||||
|     query: { id: row.processInstanceId }, | ||||
|  | @ -26,39 +24,37 @@ function openProcessDetail(row: CrmReceivableApi.Receivable) { | |||
| } | ||||
| 
 | ||||
| /** 打开回款详情 */ | ||||
| function openDetail(row: CrmReceivableApi.Receivable) { | ||||
| function handleDetail(row: CrmReceivableApi.Receivable) { | ||||
|   push({ name: 'CrmReceivableDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| /** 打开客户详情 */ | ||||
| function openCustomerDetail(row: CrmReceivableApi.Receivable) { | ||||
| function handleCustomerDetail(row: CrmReceivableApi.Receivable) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.customerId } }); | ||||
| } | ||||
| 
 | ||||
| /** 打开合同详情 */ | ||||
| function openContractDetail(row: CrmReceivableApi.Receivable) { | ||||
| function handleContractDetail(row: CrmReceivableApi.Receivable) { | ||||
|   push({ name: 'CrmContractDetail', params: { id: row.contractId } }); | ||||
| } | ||||
| 
 | ||||
| /** 表格操作按钮的回调函数 */ | ||||
| function onActionClick({ | ||||
|   code, | ||||
|   row, | ||||
| }: OnActionClickParams<CrmReceivableApi.Receivable>) { | ||||
|   switch (code) { | ||||
|     case 'processDetail': { | ||||
|       openProcessDetail(row); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useReceivableAuditFormSchema(), | ||||
|     schema: [ | ||||
|       { | ||||
|         fieldName: 'auditStatus', | ||||
|         label: '合同状态', | ||||
|         component: 'Select', | ||||
|         componentProps: { | ||||
|           allowClear: true, | ||||
|           options: AUDIT_STATUS, | ||||
|         }, | ||||
|         defaultValue: 10, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useReceivableAuditColumns(onActionClick), | ||||
|     columns: useGridColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|  | @ -79,26 +75,29 @@ const [Grid] = useVbenVxeGrid({ | |||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
|   } as VxeTableGridOptions<CrmReceivableApi.Receivable>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="待审核回款"> | ||||
|   <Grid> | ||||
|     <template #no="{ row }"> | ||||
|       <Button type="link" @click="openDetail(row)"> | ||||
|       <Button type="link" @click="handleDetail(row)"> | ||||
|         {{ row.no }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #customerName="{ row }"> | ||||
|       <Button type="link" @click="openCustomerDetail(row)"> | ||||
|       <Button type="link" @click="handleCustomerDetail(row)"> | ||||
|         {{ row.customerName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #contractNo="{ row }"> | ||||
|       <Button type="link" @click="openContractDetail(row)"> | ||||
|       <Button type="link" @click="handleContractDetail(row)"> | ||||
|         {{ row.contractNo }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #actions="{ row }"> | ||||
|       <Button type="link" @click="handleProcessDetail(row)">查看审批</Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -0,0 +1,101 @@ | |||
| <!-- 待回款提醒 --> | ||||
| <script lang="ts" setup> | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmReceivablePlanApi } from '#/api/crm/receivable/plan'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { useVbenModal } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getReceivablePlanPage } from '#/api/crm/receivable/plan'; | ||||
| import Form from '#/views/crm/receivable/modules/form.vue'; | ||||
| import { useGridColumns } from '#/views/crm/receivable/plan/data'; | ||||
| 
 | ||||
| import { RECEIVABLE_REMIND_TYPE } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| const [FormModal, formModalApi] = useVbenModal({ | ||||
|   connectedComponent: Form, | ||||
|   destroyOnClose: true, | ||||
| }); | ||||
| 
 | ||||
| /** 打开回款详情 */ | ||||
| function handleDetail(row: CrmReceivablePlanApi.Plan) { | ||||
|   push({ name: 'CrmReceivableDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| /** 打开客户详情 */ | ||||
| function handleCustomerDetail(row: CrmReceivablePlanApi.Plan) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.customerId } }); | ||||
| } | ||||
| 
 | ||||
| /** 创建回款 */ | ||||
| function handleCreateReceivable(row: CrmReceivablePlanApi.Plan) { | ||||
|   formModalApi.setData({ plan: row }).open(); | ||||
| } | ||||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: [ | ||||
|       { | ||||
|         fieldName: 'remindType', | ||||
|         label: '合同状态', | ||||
|         component: 'Select', | ||||
|         componentProps: { | ||||
|           allowClear: true, | ||||
|           options: RECEIVABLE_REMIND_TYPE, | ||||
|         }, | ||||
|         defaultValue: 1, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useGridColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getReceivablePlanPage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   } as VxeTableGridOptions<CrmReceivablePlanApi.Plan>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div> | ||||
|     <FormModal /> | ||||
|     <Grid> | ||||
|       <template #customerName="{ row }"> | ||||
|         <Button type="link" @click="handleCustomerDetail(row)"> | ||||
|           {{ row.customerName }} | ||||
|         </Button> | ||||
|       </template> | ||||
|       <template #period="{ row }"> | ||||
|         <Button type="link" @click="handleDetail(row)">{{ row.period }}</Button> | ||||
|       </template> | ||||
|       <template #actions="{ row }"> | ||||
|         <Button type="link" @click="handleCreateReceivable(row)"> | ||||
|           创建回款 | ||||
|         </Button> | ||||
|       </template> | ||||
|     </Grid> | ||||
|   </div> | ||||
| </template> | ||||
|  | @ -0,0 +1,140 @@ | |||
| import type { VbenFormSchema } from '@vben/common-ui'; | ||||
| 
 | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| 
 | ||||
| import { handleTree } from '@vben/utils'; | ||||
| 
 | ||||
| import { LimitConfType } from '#/api/crm/customer/limitConfig'; | ||||
| import { getSimpleDeptList } from '#/api/system/dept'; | ||||
| import { getSimpleUserList } from '#/api/system/user'; | ||||
| import { DICT_TYPE } from '#/utils'; | ||||
| 
 | ||||
| /** 新增/修改的表单 */ | ||||
| export function useFormSchema(confType: LimitConfType): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'id', | ||||
|       component: 'Input', | ||||
|       dependencies: { | ||||
|         triggerFields: [''], | ||||
|         show: () => false, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'userIds', | ||||
|       label: '规则适用人群', | ||||
|       component: 'ApiSelect', | ||||
|       componentProps: { | ||||
|         api: getSimpleUserList, | ||||
|         fieldNames: { | ||||
|           label: 'nickname', | ||||
|           value: 'id', | ||||
|         }, | ||||
|         multiple: true, | ||||
|         allowClear: true, | ||||
|       }, | ||||
|       rules: 'required', | ||||
|     }, | ||||
|     { | ||||
|       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, | ||||
|       }, | ||||
|       rules: 'required', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'maxCount', | ||||
|       label: | ||||
|         confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT | ||||
|           ? '拥有客户数上限' | ||||
|           : '锁定客户数上限', | ||||
|       component: 'InputNumber', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'dealCountEnabled', | ||||
|       label: '成交客户是否占用拥有客户数', | ||||
|       component: 'RadioGroup', | ||||
|       componentProps: { | ||||
|         options: [ | ||||
|           { label: '是', value: true }, | ||||
|           { label: '否', value: false }, | ||||
|         ], | ||||
|       }, | ||||
|       dependencies: { | ||||
|         triggerFields: [''], | ||||
|         show: () => confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT, | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 列表的字段 */ | ||||
| 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' }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
|  | @ -1,13 +1,109 @@ | |||
| <script lang="ts" setup> | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmCustomerLimitConfigApi } from '#/api/crm/customer/limitConfig'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| import { Page, useVbenModal } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { message, Tabs } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { | ||||
|   deleteCustomerLimitConfig, | ||||
|   getCustomerLimitConfigPage, | ||||
|   LimitConfType, | ||||
| } from '#/api/crm/customer/limitConfig'; | ||||
| import { DocAlert } from '#/components/doc-alert'; | ||||
| import { $t } from '#/locales'; | ||||
| 
 | ||||
| import { useGridColumns } from './data'; | ||||
| import Form from './modules/form.vue'; | ||||
| 
 | ||||
| const configType = ref(LimitConfType.CUSTOMER_QUANTITY_LIMIT); | ||||
| 
 | ||||
| /** 刷新表格 */ | ||||
| function onRefresh() { | ||||
|   gridApi.query(); | ||||
| } | ||||
| 
 | ||||
| const [FormModal, formModalApi] = useVbenModal({ | ||||
|   connectedComponent: Form, | ||||
|   destroyOnClose: true, | ||||
| }); | ||||
| 
 | ||||
| /** 创建规则 */ | ||||
| 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 hideLoading = message.loading({ | ||||
|     content: $t('ui.actionMessage.deleting', [row.id]), | ||||
|     key: 'action_key_msg', | ||||
|   }); | ||||
|   try { | ||||
|     await deleteCustomerLimitConfig(row.id as number); | ||||
|     message.success({ | ||||
|       content: $t('ui.actionMessage.deleteSuccess', [row.id]), | ||||
|       key: 'action_key_msg', | ||||
|     }); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|     hideLoading(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const [Grid, gridApi] = useVbenVxeGrid({ | ||||
|   gridOptions: { | ||||
|     columns: useGridColumns(configType.value), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getCustomerLimitConfigPage({ | ||||
|             page: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             type: configType.value, | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   } as VxeTableGridOptions<CrmCustomerLimitConfigApi.CustomerLimitConfig>, | ||||
| }); | ||||
| 
 | ||||
| function onChangeConfigType(key: number | string) { | ||||
|   configType.value = key as LimitConfType; | ||||
|   gridApi.setGridOptions({ | ||||
|     columns: useGridColumns(configType.value), | ||||
|   }); | ||||
|   onRefresh(); | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="【客户】客户管理、公海客户" | ||||
|         url="https://doc.iocoder.cn/crm/customer/" | ||||
|  | @ -16,23 +112,59 @@ import { DocAlert } from '#/components/doc-alert'; | |||
|         title="【通用】数据权限" | ||||
|         url="https://doc.iocoder.cn/crm/permission/" | ||||
|       /> | ||||
|     <Button | ||||
|       danger | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" | ||||
|     > | ||||
|       该功能支持 Vue3 + element-plus 版本! | ||||
|     </Button> | ||||
|     <br /> | ||||
|     <Button | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/crm/customer/limitConfig/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/crm/customer/limitConfig/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|     </template> | ||||
| 
 | ||||
|     <FormModal /> | ||||
|     <Grid> | ||||
|       <template #top> | ||||
|         <Tabs class="border-none" @change="onChangeConfigType"> | ||||
|           <Tabs.TabPane | ||||
|             tab="拥有客户数限制" | ||||
|             :key="LimitConfType.CUSTOMER_QUANTITY_LIMIT" | ||||
|           /> | ||||
|           <Tabs.TabPane | ||||
|             tab="锁定客户数限制" | ||||
|             :key="LimitConfType.CUSTOMER_LOCK_LIMIT" | ||||
|           /> | ||||
|         </Tabs> | ||||
|       </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: 'link', | ||||
|               icon: ACTION_ICON.EDIT, | ||||
|               auth: ['crm:customer-limit-config:update'], | ||||
|               onClick: handleEdit.bind(null, row, configType), | ||||
|             }, | ||||
|             { | ||||
|               label: $t('common.delete'), | ||||
|               type: 'link', | ||||
|               danger: true, | ||||
|               icon: ACTION_ICON.DELETE, | ||||
|               auth: ['crm:customer-limit-config:delete'], | ||||
|               popConfirm: { | ||||
|                 title: $t('ui.actionMessage.deleteConfirm', [row.name]), | ||||
|                 confirm: handleDelete.bind(null, row), | ||||
|               }, | ||||
|             }, | ||||
|           ]" | ||||
|         /> | ||||
|       </template> | ||||
|     </Grid> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -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 { message } from 'ant-design-vue'; | ||||
| 
 | ||||
| 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: 120, | ||||
|   }, | ||||
|   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'); | ||||
|       message.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 as number); | ||||
|       } | ||||
|       formData.value = data; | ||||
|       // 设置到 values | ||||
|       await formApi.setValues(data); | ||||
|     } finally { | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Modal :title="getTitle" class="w-[40%]"> | ||||
|     <Form class="mx-4" /> | ||||
|   </Modal> | ||||
| </template> | ||||
|  | @ -0,0 +1,91 @@ | |||
| import type { VbenFormSchema } from '#/adapter/form'; | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmProductCategoryApi } from '#/api/crm/product/category'; | ||||
| 
 | ||||
| import { handleTree } from '@vben/utils'; | ||||
| 
 | ||||
| import { getProductCategoryList } from '#/api/crm/product/category'; | ||||
| 
 | ||||
| /** 新增/修改的表单 */ | ||||
| export function useFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       component: 'Input', | ||||
|       fieldName: 'id', | ||||
|       dependencies: { | ||||
|         triggerFields: [''], | ||||
|         show: () => false, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'parentId', | ||||
|       label: '上级分类', | ||||
|       component: 'ApiTreeSelect', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         api: async () => { | ||||
|           const data = await getProductCategoryList(); | ||||
|           data.unshift({ | ||||
|             id: 0, | ||||
|             name: '顶级分类', | ||||
|           } as CrmProductCategoryApi.ProductCategory); | ||||
|           return handleTree(data); | ||||
|         }, | ||||
|         fieldNames: { label: 'name', value: 'id', children: 'children' }, | ||||
|         placeholder: '请选择上级分类', | ||||
|         showSearch: true, | ||||
|         treeDefaultExpandAll: true, | ||||
|       }, | ||||
|       rules: 'selectRequired', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'name', | ||||
|       label: '分类名称', | ||||
|       component: 'Input', | ||||
|       componentProps: { | ||||
|         placeholder: '请输入分类名称', | ||||
|       }, | ||||
|       rules: 'required', | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 列表的搜索表单 */ | ||||
| export function useGridFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'name', | ||||
|       label: '分类名称', | ||||
|       component: 'Input', | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 表格列配置 */ | ||||
| export function useGridColumns(): VxeTableGridOptions<CrmProductCategoryApi.ProductCategory>['columns'] { | ||||
|   return [ | ||||
|     { | ||||
|       field: 'name', | ||||
|       title: '分类名称', | ||||
|       treeNode: true, | ||||
|     }, | ||||
|     { | ||||
|       field: 'id', | ||||
|       title: '分类编号', | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'actions', | ||||
|       title: '操作', | ||||
|       width: 200, | ||||
|       fixed: 'right', | ||||
|       slots: { | ||||
|         default: 'actions', | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
|  | @ -1,34 +1,173 @@ | |||
| <script lang="ts" setup> | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmProductCategoryApi } from '#/api/crm/product/category'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| import { Page, useVbenModal } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { message } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { | ||||
|   deleteProductCategory, | ||||
|   getProductCategoryList, | ||||
| } from '#/api/crm/product/category'; | ||||
| import { DocAlert } from '#/components/doc-alert'; | ||||
| import { $t } from '#/locales'; | ||||
| 
 | ||||
| import { useGridColumns, useGridFormSchema } from './data'; | ||||
| import Form from './modules/form.vue'; | ||||
| 
 | ||||
| const [FormModal, formModalApi] = useVbenModal({ | ||||
|   connectedComponent: Form, | ||||
|   destroyOnClose: true, | ||||
| }); | ||||
| 
 | ||||
| /** 刷新表格 */ | ||||
| function onRefresh() { | ||||
|   gridApi.query(); | ||||
| } | ||||
| 
 | ||||
| /** 创建分类 */ | ||||
| function handleCreate() { | ||||
|   formModalApi.setData({}).open(); | ||||
| } | ||||
| 
 | ||||
| /** 添加下级分类 */ | ||||
| function handleAppend(row: CrmProductCategoryApi.ProductCategory) { | ||||
|   formModalApi.setData({ parentId: row.id }).open(); | ||||
| } | ||||
| 
 | ||||
| /** 编辑分类 */ | ||||
| function handleEdit(row: CrmProductCategoryApi.ProductCategory) { | ||||
|   formModalApi.setData(row).open(); | ||||
| } | ||||
| 
 | ||||
| /** 删除分类 */ | ||||
| async function handleDelete(row: CrmProductCategoryApi.ProductCategory) { | ||||
|   const hideLoading = message.loading({ | ||||
|     content: $t('ui.actionMessage.deleting', [row.name]), | ||||
|     key: 'action_key_msg', | ||||
|   }); | ||||
|   try { | ||||
|     await deleteProductCategory(row.id as number); | ||||
|     message.success({ | ||||
|       content: $t('ui.actionMessage.deleteSuccess', [row.name]), | ||||
|       key: 'action_key_msg', | ||||
|     }); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|     hideLoading(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 切换树形展开/收缩状态 */ | ||||
| const isExpanded = ref(false); | ||||
| function toggleExpand() { | ||||
|   isExpanded.value = !isExpanded.value; | ||||
|   gridApi.grid.setAllTreeExpand(isExpanded.value); | ||||
| } | ||||
| 
 | ||||
| const [Grid, gridApi] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useGridFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useGridColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     pagerConfig: { | ||||
|       enabled: false, | ||||
|     }, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async (_, formValues) => { | ||||
|           return await getProductCategoryList(formValues); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|     }, | ||||
|     treeConfig: { | ||||
|       parentField: 'parentId', | ||||
|       rowField: 'id', | ||||
|       transform: true, | ||||
|       reserve: true, | ||||
|     }, | ||||
|   } as VxeTableGridOptions<CrmProductCategoryApi.ProductCategory>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="【产品】产品管理、产品分类" | ||||
|         url="https://doc.iocoder.cn/crm/product/" | ||||
|       /> | ||||
|     <Button | ||||
|       danger | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" | ||||
|     > | ||||
|       该功能支持 Vue3 + element-plus 版本! | ||||
|     </Button> | ||||
|     <br /> | ||||
|     <Button | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/crm/product/category/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/crm/product/category/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|     </template> | ||||
| 
 | ||||
|     <FormModal @success="onRefresh" /> | ||||
|     <Grid> | ||||
|       <template #toolbar-tools> | ||||
|         <TableAction | ||||
|           :actions="[ | ||||
|             { | ||||
|               label: $t('ui.actionTitle.create', ['分类']), | ||||
|               type: 'primary', | ||||
|               icon: ACTION_ICON.ADD, | ||||
|               auth: ['crm:product-category:create'], | ||||
|               onClick: handleCreate, | ||||
|             }, | ||||
|             { | ||||
|               label: isExpanded ? '收缩' : '展开', | ||||
|               type: 'primary', | ||||
|               onClick: toggleExpand, | ||||
|             }, | ||||
|           ]" | ||||
|         /> | ||||
|       </template> | ||||
|       <template #name="{ row }"> | ||||
|         <div class="flex w-full items-center gap-1"> | ||||
|           <span class="flex-auto">{{ row.name }}</span> | ||||
|         </div> | ||||
|       </template> | ||||
|       <template #actions="{ row }"> | ||||
|         <TableAction | ||||
|           :actions="[ | ||||
|             { | ||||
|               label: '新增下级', | ||||
|               type: 'link', | ||||
|               icon: ACTION_ICON.ADD, | ||||
|               auth: ['crm:product-category:create'], | ||||
|               onClick: handleAppend.bind(null, row), | ||||
|             }, | ||||
|             { | ||||
|               label: $t('common.edit'), | ||||
|               type: 'link', | ||||
|               icon: ACTION_ICON.EDIT, | ||||
|               auth: ['crm:product-category:update'], | ||||
|               onClick: handleEdit.bind(null, row), | ||||
|             }, | ||||
|             { | ||||
|               label: $t('common.delete'), | ||||
|               type: 'link', | ||||
|               danger: true, | ||||
|               icon: ACTION_ICON.DELETE, | ||||
|               auth: ['crm:product-category:delete'], | ||||
|               popConfirm: { | ||||
|                 title: $t('ui.actionMessage.deleteConfirm', [row.name]), | ||||
|                 confirm: handleDelete.bind(null, row), | ||||
|               }, | ||||
|             }, | ||||
|           ]" | ||||
|         /> | ||||
|       </template> | ||||
|     </Grid> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -0,0 +1,92 @@ | |||
| <script lang="ts" setup> | ||||
| import type { CrmProductCategoryApi } from '#/api/crm/product/category'; | ||||
| 
 | ||||
| import { computed, ref } from 'vue'; | ||||
| 
 | ||||
| import { useVbenModal } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { message } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenForm } from '#/adapter/form'; | ||||
| import { | ||||
|   createProductCategory, | ||||
|   getProductCategory, | ||||
|   updateProductCategory, | ||||
| } from '#/api/crm/product/category'; | ||||
| import { $t } from '#/locales'; | ||||
| 
 | ||||
| import { useFormSchema } from '../data'; | ||||
| 
 | ||||
| const emit = defineEmits(['success']); | ||||
| const formData = ref<CrmProductCategoryApi.ProductCategory>(); | ||||
| const getTitle = computed(() => { | ||||
|   return formData.value?.id | ||||
|     ? $t('ui.actionTitle.edit', ['产品分类']) | ||||
|     : $t('ui.actionTitle.create', ['产品分类']); | ||||
| }); | ||||
| 
 | ||||
| const [Form, formApi] = useVbenForm({ | ||||
|   commonConfig: { | ||||
|     componentProps: { | ||||
|       class: 'w-full', | ||||
|     }, | ||||
|     formItemClass: 'col-span-2', | ||||
|     labelWidth: 80, | ||||
|   }, | ||||
|   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 CrmProductCategoryApi.ProductCategory; | ||||
|     try { | ||||
|       await (formData.value?.id | ||||
|         ? updateProductCategory(data) | ||||
|         : createProductCategory(data)); | ||||
|       // 关闭并提示 | ||||
|       await modalApi.close(); | ||||
|       emit('success'); | ||||
|       message.success($t('ui.actionMessage.operationSuccess')); | ||||
|     } finally { | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|     if (!isOpen) { | ||||
|       formData.value = undefined; | ||||
|       return; | ||||
|     } | ||||
|     // 加载数据 | ||||
|     let data = modalApi.getData<CrmProductCategoryApi.ProductCategory>(); | ||||
|     if (!data) { | ||||
|       return; | ||||
|     } | ||||
|     modalApi.lock(); | ||||
|     try { | ||||
|       if (data.id) { | ||||
|         data = await getProductCategory(data.id as number); | ||||
|       } | ||||
|       // 设置到 values | ||||
|       formData.value = data; | ||||
|       await formApi.setValues(data); | ||||
|     } finally { | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Modal class="w-[600px]" :title="getTitle"> | ||||
|     <Form class="mx-4" /> | ||||
|   </Modal> | ||||
| </template> | ||||
		Loading…
	
		Reference in New Issue
	
	 jason
						jason