commit
						8ab311b46f
					
				|  | @ -15,6 +15,6 @@ export default { | |||
|   ], | ||||
|   'package.json': ['prettier --cache --write'], | ||||
|   '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [ | ||||
|     'prettier --cache --write--parser json', | ||||
|     'prettier --cache --write --parser json', | ||||
|   ], | ||||
| }; | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| 20.14.0 | ||||
| 22.1.0 | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ | |||
|   "editor.tabSize": 2, | ||||
|   "editor.detectIndentation": false, | ||||
|   "editor.cursorBlinking": "expand", | ||||
|   "editor.largeFileOptimizations": false, | ||||
|   "editor.largeFileOptimizations": true, | ||||
|   "editor.accessibilitySupport": "off", | ||||
|   "editor.cursorSmoothCaretAnimation": "on", | ||||
|   "editor.guides.bracketPairs": "active", | ||||
|  | @ -91,6 +91,7 @@ | |||
|     "**/bower_components": true, | ||||
|     "**/.turbo": true, | ||||
|     "**/.idea": true, | ||||
|     "**/.vitepress": true, | ||||
|     "**/tmp": true, | ||||
|     "**/.git": true, | ||||
|     "**/.svn": true, | ||||
|  | @ -112,6 +113,8 @@ | |||
|     "**/yarn.lock": true | ||||
|   }, | ||||
| 
 | ||||
|   "typescript.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"], | ||||
| 
 | ||||
|   // search | ||||
|   "search.searchEditor.singleClickBehaviour": "peekDefinition", | ||||
|   "search.followSymlinks": false, | ||||
|  |  | |||
|  | @ -21,3 +21,6 @@ VITE_APP_DOCALERT_ENABLE=true | |||
| 
 | ||||
| # 百度统计 | ||||
| VITE_APP_BAIDU_CODE = e98f2eab6ceb8688bc6d8fc5332ff093 | ||||
| 
 | ||||
| # GoView域名 | ||||
| VITE_GOVIEW_URL='http://127.0.0.1:3000' | ||||
|  |  | |||
|  | @ -44,6 +44,7 @@ | |||
|     "@vben/types": "workspace:*", | ||||
|     "@vben/utils": "workspace:*", | ||||
|     "@vueuse/core": "catalog:", | ||||
|     "@vueuse/integrations": "catalog:", | ||||
|     "ant-design-vue": "catalog:", | ||||
|     "cropperjs": "catalog:", | ||||
|     "crypto-js": "catalog:", | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ setupVbenVxeTable({ | |||
|         }, | ||||
|         toolbarConfig: { | ||||
|           import: false, // 是否导入
 | ||||
|           export: false, // 四否导出
 | ||||
|           export: false, // 是否导出
 | ||||
|           refresh: true, // 是否刷新
 | ||||
|           print: false, // 是否打印
 | ||||
|           zoom: true, // 是否缩放
 | ||||
|  | @ -259,6 +259,21 @@ setupVbenVxeTable({ | |||
| 
 | ||||
|     // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
 | ||||
|     // vxeUI.formats.add
 | ||||
|     vxeUI.formats.add('formatAmount', { | ||||
|       cellFormatMethod({ cellValue }, digits = 2) { | ||||
|         if (cellValue === null || cellValue === undefined) { | ||||
|           return ''; | ||||
|         } | ||||
|         if (isString(cellValue)) { | ||||
|           cellValue = Number.parseFloat(cellValue); | ||||
|         } | ||||
|         // 如果非 number,则直接返回空串
 | ||||
|         if (Number.isNaN(cellValue)) { | ||||
|           return ''; | ||||
|         } | ||||
|         return cellValue.toFixed(digits); | ||||
|       }, | ||||
|     }); | ||||
|   }, | ||||
|   useVbenForm, | ||||
| }); | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import type { BpmModelApi } from '#/api/bpm/model'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace BpmCategoryApi { | ||||
|  | @ -11,6 +13,13 @@ export namespace BpmCategoryApi { | |||
|     status: number; | ||||
|     sort: number; // 分类排序
 | ||||
|   } | ||||
| 
 | ||||
|   /** 模型分类信息 */ | ||||
|   export interface ModelCategoryInfo { | ||||
|     id: number; | ||||
|     name: string; | ||||
|     modelList: BpmModelApi.ModelVO[]; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询流程分类分页 */ | ||||
|  | @ -30,15 +39,30 @@ export async function getCategory(id: number) { | |||
| 
 | ||||
| /** 新增流程分类 */ | ||||
| export async function createCategory(data: BpmCategoryApi.CategoryVO) { | ||||
|   return requestClient.post('/bpm/category/create', data); | ||||
|   return requestClient.post<number>('/bpm/category/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改流程分类 */ | ||||
| export async function updateCategory(data: BpmCategoryApi.CategoryVO) { | ||||
|   return requestClient.put('/bpm/category/update', data); | ||||
|   return requestClient.put<boolean>('/bpm/category/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除流程分类 */ | ||||
| export async function deleteCategory(id: number) { | ||||
|   return requestClient.delete(`/bpm/category/delete?id=${id}`); | ||||
|   return requestClient.delete<boolean>(`/bpm/category/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 查询流程分类列表 */ | ||||
| export async function getCategorySimpleList() { | ||||
|   return requestClient.get<BpmCategoryApi.CategoryVO[]>( | ||||
|     `/bpm/category/simple-list`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 批量修改流程分类的排序 */ | ||||
| export async function updateCategorySortBatch(ids: number[]) { | ||||
|   const params = ids.join(','); | ||||
|   return requestClient.put<boolean>( | ||||
|     `/bpm/category/update-sort-batch?ids=${params}`, | ||||
|   ); | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,107 @@ | |||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace BpmModelApi { | ||||
|   /** 用户信息 TODO 这个是不是可以抽取出来定义在公共模块 */ | ||||
|   export interface UserInfo { | ||||
|     id: number; | ||||
|     nickname: string; | ||||
|     avatar?: string; | ||||
|     deptId?: number; | ||||
|     deptName?: string; | ||||
|   } | ||||
|   /** 流程定义 VO */ | ||||
|   export interface ProcessDefinitionVO { | ||||
|     id: string; | ||||
|     version: number; | ||||
|     deploymentTime: number; | ||||
|     suspensionState: number; | ||||
|     formType?: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 流程模型 VO */ | ||||
|   export interface ModelVO { | ||||
|     id: number; | ||||
|     key: string; | ||||
|     name: string; | ||||
|     icon?: string; | ||||
|     description: string; | ||||
|     category: string; | ||||
|     formName: string; | ||||
|     formType: number; | ||||
|     formId: number; | ||||
|     formCustomCreatePath: string; | ||||
|     formCustomViewPath: string; | ||||
|     processDefinition: ProcessDefinitionVO; | ||||
|     status: number; | ||||
|     remark: string; | ||||
|     createTime: string; | ||||
|     bpmnXml: string; | ||||
|     startUsers?: UserInfo[]; | ||||
|   } | ||||
| 
 | ||||
|   /** 模型分类信息 */ | ||||
|   export interface ModelCategoryInfo { | ||||
|     id: number; | ||||
|     name: string; | ||||
|     modelList: ModelVO[]; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 获取流程模型列表 */ | ||||
| export async function getModelList(name: string | undefined) { | ||||
|   return requestClient.get<BpmModelApi.ModelVO[]>('/bpm/model/list', { | ||||
|     params: { name }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 获取流程模型详情 */ | ||||
| export async function getModel(id: string) { | ||||
|   return requestClient.get<BpmModelApi.ModelVO>(`/bpm/model/get?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 更新流程模型 */ | ||||
| export async function updateModel(data: BpmModelApi.ModelVO) { | ||||
|   return requestClient.put('/bpm/model/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 批量修改流程模型排序 */ | ||||
| export async function updateModelSortBatch(ids: number[]) { | ||||
|   const params = ids.join(','); | ||||
|   return requestClient.put<boolean>( | ||||
|     `/bpm/model/update-sort-batch?ids=${params}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 更新流程模型的 BPMN XML */ | ||||
| export async function updateModelBpmn(data: BpmModelApi.ModelVO) { | ||||
|   return requestClient.put('/bpm/model/update-bpmn', data); | ||||
| } | ||||
| 
 | ||||
| /** 更新流程模型状态 */ | ||||
| export async function updateModelState(id: number, state: number) { | ||||
|   const data = { | ||||
|     id, | ||||
|     state, | ||||
|   }; | ||||
|   return requestClient.put('/bpm/model/update-state', data); | ||||
| } | ||||
| 
 | ||||
| /** 创建流程模型 */ | ||||
| export async function createModel(data: BpmModelApi.ModelVO) { | ||||
|   return requestClient.post('/bpm/model/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除流程模型 */ | ||||
| export async function deleteModel(id: number) { | ||||
|   return requestClient.delete(`/bpm/model/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 部署流程模型 */ | ||||
| export async function deployModel(id: number) { | ||||
|   return requestClient.post(`/bpm/model/deploy?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 清理流程模型 */ | ||||
| export async function cleanModel(id: number) { | ||||
|   return requestClient.delete(`/bpm/model/clean?id=${id}`); | ||||
| } | ||||
|  | @ -0,0 +1,118 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import type { CrmPermissionApi } from '#/api/crm/permission'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmBusinessApi { | ||||
|   /** 商机产品信息 */ | ||||
|   export interface BusinessProduct { | ||||
|     id: number; | ||||
|     productId: number; | ||||
|     productName: string; | ||||
|     productNo: string; | ||||
|     productUnit: number; | ||||
|     productPrice: number; | ||||
|     businessPrice: number; | ||||
|     count: number; | ||||
|     totalPrice: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 商机信息 */ | ||||
|   export interface Business { | ||||
|     id: number; | ||||
|     name: string; | ||||
|     customerId: number; | ||||
|     customerName?: string; | ||||
|     followUpStatus: boolean; | ||||
|     contactLastTime: Date; | ||||
|     contactNextTime: Date; | ||||
|     ownerUserId: number; | ||||
|     ownerUserName?: string; // 负责人的用户名称
 | ||||
|     ownerUserDept?: string; // 负责人的部门名称
 | ||||
|     statusTypeId: number; | ||||
|     statusTypeName?: string; | ||||
|     statusId: number; | ||||
|     statusName?: string; | ||||
|     endStatus: number; | ||||
|     endRemark: string; | ||||
|     dealTime: Date; | ||||
|     totalProductPrice: number; | ||||
|     totalPrice: number; | ||||
|     discountPercent: number; | ||||
|     remark: string; | ||||
|     creator: string; // 创建人
 | ||||
|     creatorName?: string; // 创建人名称
 | ||||
|     createTime: Date; // 创建时间
 | ||||
|     updateTime: Date; // 更新时间
 | ||||
|     products?: BusinessProduct[]; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询商机列表 */ | ||||
| export function getBusinessPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmBusinessApi.Business>>( | ||||
|     '/crm/business/page', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询商机列表,基于指定客户 */ | ||||
| export function getBusinessPageByCustomer(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmBusinessApi.Business>>( | ||||
|     '/crm/business/page-by-customer', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询商机详情 */ | ||||
| export function getBusiness(id: number) { | ||||
|   return requestClient.get<CrmBusinessApi.Business>( | ||||
|     `/crm/business/get?id=${id}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获得商机列表(精简) */ | ||||
| export function getSimpleBusinessList() { | ||||
|   return requestClient.get<CrmBusinessApi.Business[]>( | ||||
|     '/crm/business/simple-all-list', | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增商机 */ | ||||
| export function createBusiness(data: CrmBusinessApi.Business) { | ||||
|   return requestClient.post('/crm/business/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改商机 */ | ||||
| export function updateBusiness(data: CrmBusinessApi.Business) { | ||||
|   return requestClient.put('/crm/business/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改商机状态 */ | ||||
| export function updateBusinessStatus(data: CrmBusinessApi.Business) { | ||||
|   return requestClient.put('/crm/business/update-status', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除商机 */ | ||||
| export function deleteBusiness(id: number) { | ||||
|   return requestClient.delete(`/crm/business/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 导出商机 */ | ||||
| export function exportBusiness(params: any) { | ||||
|   return requestClient.download('/crm/business/export-excel', params); | ||||
| } | ||||
| 
 | ||||
| /** 联系人关联商机列表 */ | ||||
| export function getBusinessPageByContact(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmBusinessApi.Business>>( | ||||
|     '/crm/business/page-by-contact', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 商机转移 */ | ||||
| export function transferBusiness(data: CrmPermissionApi.TransferReq) { | ||||
|   return requestClient.put('/crm/business/transfer', data); | ||||
| } | ||||
|  | @ -0,0 +1,91 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmBusinessStatusApi { | ||||
|   /** 商机状态信息 */ | ||||
|   export interface BusinessStatus { | ||||
|     id: number; | ||||
|     name: string; | ||||
|     percent: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 商机状态组信息 */ | ||||
|   export interface BusinessStatusType { | ||||
|     id: number; | ||||
|     name: string; | ||||
|     deptIds: number[]; | ||||
|     statuses?: BusinessStatus[]; | ||||
|   } | ||||
| 
 | ||||
|   /** 默认商机状态 */ | ||||
|   export const DEFAULT_STATUSES = [ | ||||
|     { | ||||
|       endStatus: 1, | ||||
|       key: '结束', | ||||
|       name: '赢单', | ||||
|       percent: 100, | ||||
|     }, | ||||
|     { | ||||
|       endStatus: 2, | ||||
|       key: '结束', | ||||
|       name: '输单', | ||||
|       percent: 0, | ||||
|     }, | ||||
|     { | ||||
|       endStatus: 3, | ||||
|       key: '结束', | ||||
|       name: '无效', | ||||
|       percent: 0, | ||||
|     }, | ||||
|   ] as const; | ||||
| } | ||||
| 
 | ||||
| /** 查询商机状态组列表 */ | ||||
| export function getBusinessStatusPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmBusinessStatusApi.BusinessStatusType>>( | ||||
|     '/crm/business-status/page', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增商机状态组 */ | ||||
| export function createBusinessStatus( | ||||
|   data: CrmBusinessStatusApi.BusinessStatusType, | ||||
| ) { | ||||
|   return requestClient.post('/crm/business-status/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改商机状态组 */ | ||||
| export function updateBusinessStatus( | ||||
|   data: CrmBusinessStatusApi.BusinessStatusType, | ||||
| ) { | ||||
|   return requestClient.put('/crm/business-status/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 查询商机状态类型详情 */ | ||||
| export function getBusinessStatus(id: number) { | ||||
|   return requestClient.get<CrmBusinessStatusApi.BusinessStatusType>( | ||||
|     `/crm/business-status/get?id=${id}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 删除商机状态 */ | ||||
| export function deleteBusinessStatus(id: number) { | ||||
|   return requestClient.delete(`/crm/business-status/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 获得商机状态组列表 */ | ||||
| export function getBusinessStatusTypeSimpleList() { | ||||
|   return requestClient.get<CrmBusinessStatusApi.BusinessStatusType[]>( | ||||
|     '/crm/business-status/type-simple-list', | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获得商机阶段列表 */ | ||||
| export function getBusinessStatusSimpleList(typeId: number) { | ||||
|   return requestClient.get<CrmBusinessStatusApi.BusinessStatus[]>( | ||||
|     '/crm/business-status/status-simple-list', | ||||
|     { params: { typeId } }, | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,86 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import type { CrmPermissionApi } from '#/api/crm/permission'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmClueApi { | ||||
|   /** 线索信息 */ | ||||
|   export interface Clue { | ||||
|     id: number; // 编号
 | ||||
|     name: string; // 线索名称
 | ||||
|     followUpStatus: boolean; // 跟进状态
 | ||||
|     contactLastTime: Date; // 最后跟进时间
 | ||||
|     contactLastContent: string; // 最后跟进内容
 | ||||
|     contactNextTime: Date; // 下次联系时间
 | ||||
|     ownerUserId: number; // 负责人的用户编号
 | ||||
|     ownerUserName?: string; // 负责人的用户名称
 | ||||
|     ownerUserDept?: string; // 负责人的部门名称
 | ||||
|     transformStatus: boolean; // 转化状态
 | ||||
|     customerId: number; // 客户编号
 | ||||
|     customerName?: string; // 客户名称
 | ||||
|     mobile: string; // 手机号
 | ||||
|     telephone: string; // 电话
 | ||||
|     qq: string; // QQ
 | ||||
|     wechat: string; // wechat
 | ||||
|     email: string; // email
 | ||||
|     areaId: number; // 所在地
 | ||||
|     areaName?: string; // 所在地名称
 | ||||
|     detailAddress: string; // 详细地址
 | ||||
|     industryId: number; // 所属行业
 | ||||
|     level: number; // 客户等级
 | ||||
|     source: number; // 客户来源
 | ||||
|     remark: string; // 备注
 | ||||
|     creator: string; // 创建人
 | ||||
|     creatorName?: string; // 创建人名称
 | ||||
|     createTime: Date; // 创建时间
 | ||||
|     updateTime: Date; // 更新时间
 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询线索列表 */ | ||||
| export function getCluePage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmClueApi.Clue>>('/crm/clue/page', { | ||||
|     params, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 查询线索详情 */ | ||||
| export function getClue(id: number) { | ||||
|   return requestClient.get<CrmClueApi.Clue>(`/crm/clue/get?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 新增线索 */ | ||||
| export function createClue(data: CrmClueApi.Clue) { | ||||
|   return requestClient.post('/crm/clue/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改线索 */ | ||||
| export function updateClue(data: CrmClueApi.Clue) { | ||||
|   return requestClient.put('/crm/clue/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除线索 */ | ||||
| export function deleteClue(id: number) { | ||||
|   return requestClient.delete(`/crm/clue/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 导出线索 */ | ||||
| export function exportClue(params: any) { | ||||
|   return requestClient.download('/crm/clue/export-excel', params); | ||||
| } | ||||
| 
 | ||||
| /** 线索转移 */ | ||||
| export function transferClue(data: CrmPermissionApi.TransferReq) { | ||||
|   return requestClient.put('/crm/clue/transfer', data); | ||||
| } | ||||
| 
 | ||||
| /** 线索转化为客户 */ | ||||
| export function transformClue(id: number) { | ||||
|   return requestClient.put('/crm/clue/transform', { id }); | ||||
| } | ||||
| 
 | ||||
| /** 获得分配给我的、待跟进的线索数量 */ | ||||
| export function getFollowClueCount() { | ||||
|   return requestClient.get<number>('/crm/clue/follow-count'); | ||||
| } | ||||
|  | @ -0,0 +1,140 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import type { CrmPermissionApi } from '#/api/crm/permission'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmContactApi { | ||||
|   /** 联系人信息 */ | ||||
|   export interface Contact { | ||||
|     id: number; // 编号
 | ||||
|     name: string; // 联系人名称
 | ||||
|     customerId: number; // 客户编号
 | ||||
|     customerName?: string; // 客户名称
 | ||||
|     contactLastTime: Date; // 最后跟进时间
 | ||||
|     contactLastContent: string; // 最后跟进内容
 | ||||
|     contactNextTime: Date; // 下次联系时间
 | ||||
|     ownerUserId: number; // 负责人的用户编号
 | ||||
|     ownerUserName?: string; // 负责人的用户名称
 | ||||
|     ownerUserDept?: string; // 负责人的部门名称
 | ||||
|     mobile: string; // 手机号
 | ||||
|     telephone: string; // 电话
 | ||||
|     qq: string; // QQ
 | ||||
|     wechat: string; // wechat
 | ||||
|     email: string; // email
 | ||||
|     areaId: number; // 所在地
 | ||||
|     areaName?: string; // 所在地名称
 | ||||
|     detailAddress: string; // 详细地址
 | ||||
|     sex: number; // 性别
 | ||||
|     master: boolean; // 是否主联系人
 | ||||
|     post: string; // 职务
 | ||||
|     parentId: number; // 上级联系人编号
 | ||||
|     parentName?: string; // 上级联系人名称
 | ||||
|     remark: string; // 备注
 | ||||
|     creator: string; // 创建人
 | ||||
|     creatorName?: string; // 创建人名称
 | ||||
|     createTime: Date; // 创建时间
 | ||||
|     updateTime: Date; // 更新时间
 | ||||
|   } | ||||
| 
 | ||||
|   /** 联系人商机关联请求 */ | ||||
|   export interface ContactBusinessReq { | ||||
|     contactId: number; | ||||
|     businessIds: number[]; | ||||
|   } | ||||
| 
 | ||||
|   /** 商机联系人关联请求 */ | ||||
|   export interface BusinessContactReq { | ||||
|     businessId: number; | ||||
|     contactIds: number[]; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询联系人列表 */ | ||||
| export function getContactPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmContactApi.Contact>>( | ||||
|     '/crm/contact/page', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询联系人列表,基于指定客户 */ | ||||
| export function getContactPageByCustomer(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmContactApi.Contact>>( | ||||
|     '/crm/contact/page-by-customer', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询联系人列表,基于指定商机 */ | ||||
| export function getContactPageByBusiness(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmContactApi.Contact>>( | ||||
|     '/crm/contact/page-by-business', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询联系人详情 */ | ||||
| export function getContact(id: number) { | ||||
|   return requestClient.get<CrmContactApi.Contact>(`/crm/contact/get?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 新增联系人 */ | ||||
| export function createContact(data: CrmContactApi.Contact) { | ||||
|   return requestClient.post('/crm/contact/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改联系人 */ | ||||
| export function updateContact(data: CrmContactApi.Contact) { | ||||
|   return requestClient.put('/crm/contact/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除联系人 */ | ||||
| export function deleteContact(id: number) { | ||||
|   return requestClient.delete(`/crm/contact/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 导出联系人 */ | ||||
| export function exportContact(params: any) { | ||||
|   return requestClient.download('/crm/contact/export-excel', params); | ||||
| } | ||||
| 
 | ||||
| /** 获得联系人列表(精简) */ | ||||
| export function getSimpleContactList() { | ||||
|   return requestClient.get<CrmContactApi.Contact[]>( | ||||
|     '/crm/contact/simple-all-list', | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 批量新增联系人商机关联 */ | ||||
| export function createContactBusinessList( | ||||
|   data: CrmContactApi.ContactBusinessReq, | ||||
| ) { | ||||
|   return requestClient.post('/crm/contact/create-business-list', data); | ||||
| } | ||||
| 
 | ||||
| /** 批量新增商机联系人关联 */ | ||||
| export function createBusinessContactList( | ||||
|   data: CrmContactApi.BusinessContactReq, | ||||
| ) { | ||||
|   return requestClient.post('/crm/contact/create-business-list2', data); | ||||
| } | ||||
| 
 | ||||
| /** 解除联系人商机关联 */ | ||||
| export function deleteContactBusinessList( | ||||
|   data: CrmContactApi.ContactBusinessReq, | ||||
| ) { | ||||
|   return requestClient.delete('/crm/contact/delete-business-list', { data }); | ||||
| } | ||||
| 
 | ||||
| /** 解除商机联系人关联 */ | ||||
| export function deleteBusinessContactList( | ||||
|   data: CrmContactApi.BusinessContactReq, | ||||
| ) { | ||||
|   return requestClient.delete('/crm/contact/delete-business-list2', { data }); | ||||
| } | ||||
| 
 | ||||
| /** 联系人转移 */ | ||||
| export function transferContact(data: CrmPermissionApi.TransferReq) { | ||||
|   return requestClient.put('/crm/contact/transfer', data); | ||||
| } | ||||
|  | @ -0,0 +1,21 @@ | |||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmContractConfigApi { | ||||
|   /** 合同配置信息 */ | ||||
|   export interface Config { | ||||
|     notifyEnabled?: boolean; | ||||
|     notifyDays?: number; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 获取合同配置 */ | ||||
| export function getContractConfig() { | ||||
|   return requestClient.get<CrmContractConfigApi.Config>( | ||||
|     '/crm/contract-config/get', | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 更新合同配置 */ | ||||
| export function saveContractConfig(data: CrmContractConfigApi.Config) { | ||||
|   return requestClient.put('/crm/contract-config/save', data); | ||||
| } | ||||
|  | @ -0,0 +1,132 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import type { CrmPermissionApi } from '#/api/crm/permission'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmContractApi { | ||||
|   /** 合同产品信息 */ | ||||
|   export interface ContractProduct { | ||||
|     id: number; | ||||
|     productId: number; | ||||
|     productName: string; | ||||
|     productNo: string; | ||||
|     productUnit: number; | ||||
|     productPrice: number; | ||||
|     contractPrice: number; | ||||
|     count: number; | ||||
|     totalPrice: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 合同信息 */ | ||||
|   export interface Contract { | ||||
|     id: number; | ||||
|     name: string; | ||||
|     no: string; | ||||
|     customerId: number; | ||||
|     customerName?: string; | ||||
|     businessId: number; | ||||
|     businessName: string; | ||||
|     contactLastTime: Date; | ||||
|     ownerUserId: number; | ||||
|     ownerUserName?: string; | ||||
|     ownerUserDeptName?: string; | ||||
|     processInstanceId: number; | ||||
|     auditStatus: number; | ||||
|     orderDate: Date; | ||||
|     startTime: Date; | ||||
|     endTime: Date; | ||||
|     totalProductPrice: number; | ||||
|     discountPercent: number; | ||||
|     totalPrice: number; | ||||
|     totalReceivablePrice: number; | ||||
|     signContactId: number; | ||||
|     signContactName?: string; | ||||
|     signUserId: number; | ||||
|     signUserName: string; | ||||
|     remark: string; | ||||
|     createTime?: Date; | ||||
|     creator: string; | ||||
|     creatorName: string; | ||||
|     updateTime?: Date; | ||||
|     products?: ContractProduct[]; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询合同列表 */ | ||||
| export function getContractPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmContractApi.Contract>>( | ||||
|     '/crm/contract/page', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询合同列表,基于指定客户 */ | ||||
| export function getContractPageByCustomer(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmContractApi.Contract>>( | ||||
|     '/crm/contract/page-by-customer', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询合同列表,基于指定商机 */ | ||||
| export function getContractPageByBusiness(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmContractApi.Contract>>( | ||||
|     '/crm/contract/page-by-business', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询合同详情 */ | ||||
| export function getContract(id: number) { | ||||
|   return requestClient.get<CrmContractApi.Contract>( | ||||
|     `/crm/contract/get?id=${id}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询合同下拉列表 */ | ||||
| export function getContractSimpleList(customerId: number) { | ||||
|   return requestClient.get<CrmContractApi.Contract[]>( | ||||
|     `/crm/contract/simple-list?customerId=${customerId}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增合同 */ | ||||
| export function createContract(data: CrmContractApi.Contract) { | ||||
|   return requestClient.post('/crm/contract/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改合同 */ | ||||
| export function updateContract(data: CrmContractApi.Contract) { | ||||
|   return requestClient.put('/crm/contract/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除合同 */ | ||||
| export function deleteContract(id: number) { | ||||
|   return requestClient.delete(`/crm/contract/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 导出合同 */ | ||||
| export function exportContract(params: any) { | ||||
|   return requestClient.download('/crm/contract/export-excel', params); | ||||
| } | ||||
| 
 | ||||
| /** 提交审核 */ | ||||
| export function submitContract(id: number) { | ||||
|   return requestClient.put(`/crm/contract/submit?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 合同转移 */ | ||||
| export function transferContract(data: CrmPermissionApi.TransferReq) { | ||||
|   return requestClient.put('/crm/contract/transfer', data); | ||||
| } | ||||
| 
 | ||||
| /** 获得待审核合同数量 */ | ||||
| export function getAuditContractCount() { | ||||
|   return requestClient.get<number>('/crm/contract/audit-count'); | ||||
| } | ||||
| 
 | ||||
| /** 获得即将到期(提醒)的合同数量 */ | ||||
| export function getRemindContractCount() { | ||||
|   return requestClient.get<number>('/crm/contract/remind-count'); | ||||
| } | ||||
|  | @ -0,0 +1,146 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import type { CrmPermissionApi } from '#/api/crm/permission'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmCustomerApi { | ||||
|   /** 客户信息 */ | ||||
|   export interface Customer { | ||||
|     id: number; // 编号
 | ||||
|     name: string; // 客户名称
 | ||||
|     followUpStatus: boolean; // 跟进状态
 | ||||
|     contactLastTime: Date; // 最后跟进时间
 | ||||
|     contactLastContent: string; // 最后跟进内容
 | ||||
|     contactNextTime: Date; // 下次联系时间
 | ||||
|     ownerUserId: number; // 负责人的用户编号
 | ||||
|     ownerUserName?: string; // 负责人的用户名称
 | ||||
|     ownerUserDept?: string; // 负责人的部门名称
 | ||||
|     lockStatus?: boolean; | ||||
|     dealStatus?: boolean; | ||||
|     mobile: string; // 手机号
 | ||||
|     telephone: string; // 电话
 | ||||
|     qq: string; // QQ
 | ||||
|     wechat: string; // wechat
 | ||||
|     email: string; // email
 | ||||
|     areaId: number; // 所在地
 | ||||
|     areaName?: string; // 所在地名称
 | ||||
|     detailAddress: string; // 详细地址
 | ||||
|     industryId: number; // 所属行业
 | ||||
|     level: number; // 客户等级
 | ||||
|     source: number; // 客户来源
 | ||||
|     remark: string; // 备注
 | ||||
|     creator: string; // 创建人
 | ||||
|     creatorName?: string; // 创建人名称
 | ||||
|     createTime: Date; // 创建时间
 | ||||
|     updateTime: Date; // 更新时间
 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询客户列表 */ | ||||
| export function getCustomerPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmCustomerApi.Customer>>( | ||||
|     '/crm/customer/page', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询客户详情 */ | ||||
| export function getCustomer(id: number) { | ||||
|   return requestClient.get<CrmCustomerApi.Customer>( | ||||
|     `/crm/customer/get?id=${id}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增客户 */ | ||||
| export function createCustomer(data: CrmCustomerApi.Customer) { | ||||
|   return requestClient.post('/crm/customer/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改客户 */ | ||||
| export function updateCustomer(data: CrmCustomerApi.Customer) { | ||||
|   return requestClient.put('/crm/customer/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除客户 */ | ||||
| export function deleteCustomer(id: number) { | ||||
|   return requestClient.delete(`/crm/customer/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 导出客户 */ | ||||
| export function exportCustomer(params: any) { | ||||
|   return requestClient.download('/crm/customer/export-excel', params); | ||||
| } | ||||
| 
 | ||||
| /** 下载客户导入模板 */ | ||||
| export function importCustomerTemplate() { | ||||
|   return requestClient.download('/crm/customer/get-import-template'); | ||||
| } | ||||
| 
 | ||||
| /** 导入客户 */ | ||||
| export function importCustomer(file: File) { | ||||
|   return requestClient.upload('/crm/customer/import', { file }); | ||||
| } | ||||
| 
 | ||||
| /** 获取客户精简信息列表 */ | ||||
| export function getCustomerSimpleList() { | ||||
|   return requestClient.get<CrmCustomerApi.Customer[]>( | ||||
|     '/crm/customer/simple-list', | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 客户转移 */ | ||||
| export function transferCustomer(data: CrmPermissionApi.TransferReq) { | ||||
|   return requestClient.put('/crm/customer/transfer', data); | ||||
| } | ||||
| 
 | ||||
| /** 锁定/解锁客户 */ | ||||
| export function lockCustomer(id: number, lockStatus: boolean) { | ||||
|   return requestClient.put('/crm/customer/lock', { id, lockStatus }); | ||||
| } | ||||
| 
 | ||||
| /** 领取公海客户 */ | ||||
| export function receiveCustomer(ids: number[]) { | ||||
|   return requestClient.put('/crm/customer/receive', { ids: ids.join(',') }); | ||||
| } | ||||
| 
 | ||||
| /** 分配公海给对应负责人 */ | ||||
| export function distributeCustomer(ids: number[], ownerUserId: number) { | ||||
|   return requestClient.put('/crm/customer/distribute', { ids, ownerUserId }); | ||||
| } | ||||
| 
 | ||||
| /** 客户放入公海 */ | ||||
| export function putCustomerPool(id: number) { | ||||
|   return requestClient.put(`/crm/customer/put-pool?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 更新客户的成交状态 */ | ||||
| export function updateCustomerDealStatus(id: number, dealStatus: boolean) { | ||||
|   return requestClient.put('/crm/customer/update-deal-status', { | ||||
|     id, | ||||
|     dealStatus, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 进入公海客户提醒的客户列表 */ | ||||
| export function getPutPoolRemindCustomerPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmCustomerApi.Customer>>( | ||||
|     '/crm/customer/put-pool-remind-page', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获得待进入公海客户数量 */ | ||||
| export function getPutPoolRemindCustomerCount() { | ||||
|   return requestClient.get<number>('/crm/customer/put-pool-remind-count'); | ||||
| } | ||||
| 
 | ||||
| /** 获得今日需联系客户数量 */ | ||||
| export function getTodayContactCustomerCount() { | ||||
|   return requestClient.get<number>('/crm/customer/today-contact-count'); | ||||
| } | ||||
| 
 | ||||
| /** 获得分配给我、待跟进的线索数量的客户数量 */ | ||||
| export function getFollowCustomerCount() { | ||||
|   return requestClient.get<number>('/crm/customer/follow-count'); | ||||
| } | ||||
|  | @ -0,0 +1,58 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmCustomerLimitConfigApi { | ||||
|   /** 客户限制配置 */ | ||||
|   export interface CustomerLimitConfig { | ||||
|     id?: number; | ||||
|     type?: number; | ||||
|     userIds?: string; | ||||
|     deptIds?: string; | ||||
|     maxCount?: number; | ||||
|     dealCountEnabled?: boolean; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * 客户限制配置类型 | ||||
|    */ | ||||
|   export enum LimitConfType { | ||||
|     /** 锁定客户数限制 */ | ||||
|     CUSTOMER_LOCK_LIMIT = 2, | ||||
|     /** 拥有客户数限制 */ | ||||
|     CUSTOMER_QUANTITY_LIMIT = 1, | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询客户限制配置列表 */ | ||||
| export function getCustomerLimitConfigPage(params: PageParam) { | ||||
|   return requestClient.get< | ||||
|     PageResult<CrmCustomerLimitConfigApi.CustomerLimitConfig> | ||||
|   >('/crm/customer-limit-config/page', { params }); | ||||
| } | ||||
| 
 | ||||
| /** 查询客户限制配置详情 */ | ||||
| export function getCustomerLimitConfig(id: number) { | ||||
|   return requestClient.get<CrmCustomerLimitConfigApi.CustomerLimitConfig>( | ||||
|     `/crm/customer-limit-config/get?id=${id}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增客户限制配置 */ | ||||
| export function createCustomerLimitConfig( | ||||
|   data: CrmCustomerLimitConfigApi.CustomerLimitConfig, | ||||
| ) { | ||||
|   return requestClient.post('/crm/customer-limit-config/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改客户限制配置 */ | ||||
| export function updateCustomerLimitConfig( | ||||
|   data: CrmCustomerLimitConfigApi.CustomerLimitConfig, | ||||
| ) { | ||||
|   return requestClient.put('/crm/customer-limit-config/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除客户限制配置 */ | ||||
| export function deleteCustomerLimitConfig(id: number) { | ||||
|   return requestClient.delete(`/crm/customer-limit-config/delete?id=${id}`); | ||||
| } | ||||
|  | @ -0,0 +1,26 @@ | |||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmCustomerPoolConfigApi { | ||||
|   /** 客户公海规则设置 */ | ||||
|   export interface CustomerPoolConfig { | ||||
|     enabled?: boolean; | ||||
|     contactExpireDays?: number; | ||||
|     dealExpireDays?: number; | ||||
|     notifyEnabled?: boolean; | ||||
|     notifyDays?: number; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 获取客户公海规则设置 */ | ||||
| export function getCustomerPoolConfig() { | ||||
|   return requestClient.get<CrmCustomerPoolConfigApi.CustomerPoolConfig>( | ||||
|     '/crm/customer-pool-config/get', | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 更新客户公海规则设置 */ | ||||
| export function saveCustomerPoolConfig( | ||||
|   data: CrmCustomerPoolConfigApi.CustomerPoolConfig, | ||||
| ) { | ||||
|   return requestClient.put('/crm/customer-pool-config/save', data); | ||||
| } | ||||
|  | @ -0,0 +1,53 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmFollowUpApi { | ||||
|   /** 关联商机信息 */ | ||||
|   export interface Business { | ||||
|     id: number; | ||||
|     name: string; | ||||
|   } | ||||
| 
 | ||||
|   /** 关联联系人信息 */ | ||||
|   export interface Contact { | ||||
|     id: number; | ||||
|     name: string; | ||||
|   } | ||||
| 
 | ||||
|   /** 跟进记录信息 */ | ||||
|   export interface FollowUpRecord { | ||||
|     id: number; // 编号
 | ||||
|     bizType: number; // 数据类型
 | ||||
|     bizId: number; // 数据编号
 | ||||
|     type: number; // 跟进类型
 | ||||
|     content: string; // 跟进内容
 | ||||
|     picUrls: string[]; // 图片
 | ||||
|     fileUrls: string[]; // 附件
 | ||||
|     nextTime: Date; // 下次联系时间
 | ||||
|     businessIds: number[]; // 关联的商机编号数组
 | ||||
|     businesses: Business[]; // 关联的商机数组
 | ||||
|     contactIds: number[]; // 关联的联系人编号数组
 | ||||
|     contacts: Contact[]; // 关联的联系人数组
 | ||||
|     creator: string; | ||||
|     creatorName?: string; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询跟进记录分页 */ | ||||
| export function getFollowUpRecordPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmFollowUpApi.FollowUpRecord>>( | ||||
|     '/crm/follow-up-record/page', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增跟进记录 */ | ||||
| export function createFollowUpRecord(data: CrmFollowUpApi.FollowUpRecord) { | ||||
|   return requestClient.post('/crm/follow-up-record/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除跟进记录 */ | ||||
| export function deleteFollowUpRecord(id: number) { | ||||
|   return requestClient.delete(`/crm/follow-up-record/delete?id=${id}`); | ||||
| } | ||||
|  | @ -0,0 +1,31 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmOperateLogApi { | ||||
|   /** 操作日志查询参数 */ | ||||
|   export interface OperateLogQuery extends PageParam { | ||||
|     bizType: number; | ||||
|     bizId: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 操作日志信息 */ | ||||
|   export interface OperateLog { | ||||
|     id: number; | ||||
|     bizType: number; | ||||
|     bizId: number; | ||||
|     type: number; | ||||
|     content: string; | ||||
|     creator: string; | ||||
|     creatorName?: string; | ||||
|     createTime: Date; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 获得操作日志 */ | ||||
| export function getOperateLogPage(params: CrmOperateLogApi.OperateLogQuery) { | ||||
|   return requestClient.get<PageResult<CrmOperateLogApi.OperateLog>>( | ||||
|     '/crm/operate-log/page', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,79 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmPermissionApi { | ||||
|   /** 数据权限信息 */ | ||||
|   export interface Permission { | ||||
|     id?: number; // 数据权限编号
 | ||||
|     userId: number; // 用户编号
 | ||||
|     bizType: number; // Crm 类型
 | ||||
|     bizId: number; // Crm 类型数据编号
 | ||||
|     level: number; // 权限级别
 | ||||
|     toBizTypes?: number[]; // 同时添加至
 | ||||
|     deptName?: string; // 部门名称
 | ||||
|     nickname?: string; // 用户昵称
 | ||||
|     postNames?: string[]; // 岗位名称数组
 | ||||
|     createTime?: Date; | ||||
|     ids?: number[]; | ||||
|   } | ||||
| 
 | ||||
|   /** 数据权限转移请求 */ | ||||
|   export interface TransferReq { | ||||
|     id: number; // 模块编号
 | ||||
|     newOwnerUserId: number; // 新负责人的用户编号
 | ||||
|     oldOwnerPermissionLevel?: number; // 老负责人加入团队后的权限级别
 | ||||
|     toBizTypes?: number[]; // 转移客户时,需要额外有【联系人】【商机】【合同】的 checkbox 选择
 | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * CRM 业务类型枚举 | ||||
|    */ | ||||
|   export enum BizType { | ||||
|     CRM_BUSINESS = 4, // 商机
 | ||||
|     CRM_CLUE = 1, // 线索
 | ||||
|     CRM_CONTACT = 3, // 联系人
 | ||||
|     CRM_CONTRACT = 5, // 合同
 | ||||
|     CRM_CUSTOMER = 2, // 客户
 | ||||
|     CRM_PRODUCT = 6, // 产品
 | ||||
|     CRM_RECEIVABLE = 7, // 回款
 | ||||
|     CRM_RECEIVABLE_PLAN = 8, // 回款计划
 | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * CRM 数据权限级别枚举 | ||||
|    */ | ||||
|   export enum PermissionLevel { | ||||
|     OWNER = 1, // 负责人
 | ||||
|     READ = 2, // 只读
 | ||||
|     WRITE = 3, // 读写
 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 获得数据权限列表(查询团队成员列表) */ | ||||
| export function getPermissionList(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmPermissionApi.Permission>>( | ||||
|     '/crm/permission/list', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 创建数据权限(新增团队成员) */ | ||||
| export function createPermission(data: CrmPermissionApi.Permission) { | ||||
|   return requestClient.post('/crm/permission/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 编辑数据权限(修改团队成员权限级别) */ | ||||
| export function updatePermission(data: CrmPermissionApi.Permission) { | ||||
|   return requestClient.put('/crm/permission/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除数据权限(删除团队成员) */ | ||||
| export function deletePermissionBatch(ids: number[]) { | ||||
|   return requestClient.delete(`/crm/permission/delete?ids=${ids.join(',')}`); | ||||
| } | ||||
| 
 | ||||
| /** 删除自己的数据权限(退出团队) */ | ||||
| export function deleteSelfPermission(id: number) { | ||||
|   return requestClient.delete(`/crm/permission/delete-self?id=${id}`); | ||||
| } | ||||
|  | @ -0,0 +1,46 @@ | |||
| import type { PageParam } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmProductCategoryApi { | ||||
|   /** 产品分类信息 */ | ||||
|   export interface ProductCategory { | ||||
|     id: number; | ||||
|     name: string; | ||||
|     parentId: number; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询产品分类详情 */ | ||||
| export function getProductCategory(id: number) { | ||||
|   return requestClient.get<CrmProductCategoryApi.ProductCategory>( | ||||
|     `/crm/product-category/get?id=${id}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增产品分类 */ | ||||
| export function createProductCategory( | ||||
|   data: CrmProductCategoryApi.ProductCategory, | ||||
| ) { | ||||
|   return requestClient.post('/crm/product-category/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改产品分类 */ | ||||
| export function updateProductCategory( | ||||
|   data: CrmProductCategoryApi.ProductCategory, | ||||
| ) { | ||||
|   return requestClient.put('/crm/product-category/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除产品分类 */ | ||||
| export function deleteProductCategory(id: number) { | ||||
|   return requestClient.delete(`/crm/product-category/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 产品分类列表 */ | ||||
| export function getProductCategoryList(params?: PageParam) { | ||||
|   return requestClient.get<CrmProductCategoryApi.ProductCategory[]>( | ||||
|     '/crm/product-category/list', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,57 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmProductApi { | ||||
|   /** 产品信息 */ | ||||
|   export interface Product { | ||||
|     id: number; | ||||
|     name: string; | ||||
|     no: string; | ||||
|     unit: number; | ||||
|     price: number; | ||||
|     status: number; | ||||
|     categoryId: number; | ||||
|     categoryName?: string; | ||||
|     description: string; | ||||
|     ownerUserId: number; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询产品列表 */ | ||||
| export function getProductPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmProductApi.Product>>( | ||||
|     '/crm/product/page', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获得产品精简列表 */ | ||||
| export function getProductSimpleList() { | ||||
|   return requestClient.get<CrmProductApi.Product[]>('/crm/product/simple-list'); | ||||
| } | ||||
| 
 | ||||
| /** 查询产品详情 */ | ||||
| export function getProduct(id: number) { | ||||
|   return requestClient.get<CrmProductApi.Product>(`/crm/product/get?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 新增产品 */ | ||||
| export function createProduct(data: CrmProductApi.Product) { | ||||
|   return requestClient.post('/crm/product/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改产品 */ | ||||
| export function updateProduct(data: CrmProductApi.Product) { | ||||
|   return requestClient.put('/crm/product/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除产品 */ | ||||
| export function deleteProduct(id: number) { | ||||
|   return requestClient.delete(`/crm/product/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 导出产品 */ | ||||
| export function exportProduct(params: any) { | ||||
|   return requestClient.download('/crm/product/export-excel', params); | ||||
| } | ||||
|  | @ -0,0 +1,90 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmReceivableApi { | ||||
|   /** 合同信息 */ | ||||
|   export interface Contract { | ||||
|     id?: number; | ||||
|     name?: string; | ||||
|     no: string; | ||||
|     totalPrice: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 回款信息 */ | ||||
|   export interface Receivable { | ||||
|     id: number; | ||||
|     no: string; | ||||
|     planId?: number; | ||||
|     period?: number; | ||||
|     customerId?: number; | ||||
|     customerName?: string; | ||||
|     contractId?: number; | ||||
|     contract?: Contract; | ||||
|     auditStatus: number; | ||||
|     processInstanceId: number; | ||||
|     returnTime: Date; | ||||
|     returnType: number; | ||||
|     price: number; | ||||
|     ownerUserId: number; | ||||
|     ownerUserName?: string; | ||||
|     remark: string; | ||||
|     creator: string; // 创建人
 | ||||
|     creatorName?: string; // 创建人名称
 | ||||
|     createTime: Date; // 创建时间
 | ||||
|     updateTime: Date; // 更新时间
 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询回款列表 */ | ||||
| export function getReceivablePage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmReceivableApi.Receivable>>( | ||||
|     '/crm/receivable/page', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询回款列表,基于指定客户 */ | ||||
| export function getReceivablePageByCustomer(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmReceivableApi.Receivable>>( | ||||
|     '/crm/receivable/page-by-customer', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询回款详情 */ | ||||
| export function getReceivable(id: number) { | ||||
|   return requestClient.get<CrmReceivableApi.Receivable>( | ||||
|     `/crm/receivable/get?id=${id}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增回款 */ | ||||
| export function createReceivable(data: CrmReceivableApi.Receivable) { | ||||
|   return requestClient.post('/crm/receivable/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改回款 */ | ||||
| export function updateReceivable(data: CrmReceivableApi.Receivable) { | ||||
|   return requestClient.put('/crm/receivable/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除回款 */ | ||||
| export function deleteReceivable(id: number) { | ||||
|   return requestClient.delete(`/crm/receivable/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 导出回款 */ | ||||
| export function exportReceivable(params: any) { | ||||
|   return requestClient.download('/crm/receivable/export-excel', params); | ||||
| } | ||||
| 
 | ||||
| /** 提交审核 */ | ||||
| export function submitReceivable(id: number) { | ||||
|   return requestClient.put(`/crm/receivable/submit?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 获得待审核回款数量 */ | ||||
| export function getAuditReceivableCount() { | ||||
|   return requestClient.get<number>('/crm/receivable/audit-count'); | ||||
| } | ||||
|  | @ -0,0 +1,98 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmReceivablePlanApi { | ||||
|   /** 回款计划信息 */ | ||||
|   export interface Plan { | ||||
|     id: number; | ||||
|     period: number; | ||||
|     receivableId: number; | ||||
|     price: number; | ||||
|     returnTime: Date; | ||||
|     remindDays: number; | ||||
|     returnType: number; | ||||
|     remindTime: Date; | ||||
|     customerId: number; | ||||
|     customerName?: string; | ||||
|     contractId?: number; | ||||
|     contractNo?: string; | ||||
|     ownerUserId: number; | ||||
|     ownerUserName?: string; | ||||
|     remark: string; | ||||
|     creator: string; | ||||
|     creatorName?: string; | ||||
|     createTime: Date; | ||||
|     updateTime: Date; | ||||
|     receivable?: { | ||||
|       price: number; | ||||
|       returnTime: Date; | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询回款计划列表 */ | ||||
| export function getReceivablePlanPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmReceivablePlanApi.Plan>>( | ||||
|     '/crm/receivable-plan/page', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询回款计划列表(按客户) */ | ||||
| export function getReceivablePlanPageByCustomer(params: PageParam) { | ||||
|   return requestClient.get<PageResult<CrmReceivablePlanApi.Plan>>( | ||||
|     '/crm/receivable-plan/page-by-customer', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询回款计划详情 */ | ||||
| export function getReceivablePlan(id: number) { | ||||
|   return requestClient.get<CrmReceivablePlanApi.Plan>( | ||||
|     '/crm/receivable-plan/get', | ||||
|     { params: { id } }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询回款计划下拉数据 */ | ||||
| export function getReceivablePlanSimpleList( | ||||
|   customerId: number, | ||||
|   contractId: number, | ||||
| ) { | ||||
|   return requestClient.get<CrmReceivablePlanApi.Plan[]>( | ||||
|     '/crm/receivable-plan/simple-list', | ||||
|     { | ||||
|       params: { customerId, contractId }, | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增回款计划 */ | ||||
| export function createReceivablePlan(data: CrmReceivablePlanApi.Plan) { | ||||
|   return requestClient.post('/crm/receivable-plan/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改回款计划 */ | ||||
| export function updateReceivablePlan(data: CrmReceivablePlanApi.Plan) { | ||||
|   return requestClient.put('/crm/receivable-plan/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除回款计划 */ | ||||
| export function deleteReceivablePlan(id: number) { | ||||
|   return requestClient.delete('/crm/receivable-plan/delete', { | ||||
|     params: { id }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 导出回款计划 Excel */ | ||||
| export function exportReceivablePlan(params: PageParam) { | ||||
|   return requestClient.download('/crm/receivable-plan/export-excel', { | ||||
|     params, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 获得待回款提醒数量 */ | ||||
| export function getReceivablePlanRemindCount() { | ||||
|   return requestClient.get<number>('/crm/receivable-plan/remind-count'); | ||||
| } | ||||
|  | @ -0,0 +1,191 @@ | |||
| import type { PageParam } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmStatisticsCustomerApi { | ||||
|   /** 客户总量分析(按日期) */ | ||||
|   export interface CustomerSummaryByDate { | ||||
|     time: string; | ||||
|     customerCreateCount: number; | ||||
|     customerDealCount: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户总量分析(按用户) */ | ||||
|   export interface CustomerSummaryByUser { | ||||
|     ownerUserName: string; | ||||
|     customerCreateCount: number; | ||||
|     customerDealCount: number; | ||||
|     contractPrice: number; | ||||
|     receivablePrice: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户跟进次数分析(按日期) */ | ||||
|   export interface FollowUpSummaryByDate { | ||||
|     time: string; | ||||
|     followUpRecordCount: number; | ||||
|     followUpCustomerCount: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户跟进次数分析(按用户) */ | ||||
|   export interface FollowUpSummaryByUser { | ||||
|     ownerUserName: string; | ||||
|     followupRecordCount: number; | ||||
|     followupCustomerCount: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户跟进方式统计 */ | ||||
|   export interface FollowUpSummaryByType { | ||||
|     followUpType: string; | ||||
|     followUpRecordCount: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 合同摘要信息 */ | ||||
|   export interface CustomerContractSummary { | ||||
|     customerName: string; | ||||
|     contractName: string; | ||||
|     totalPrice: number; | ||||
|     receivablePrice: number; | ||||
|     customerType: string; | ||||
|     customerSource: string; | ||||
|     ownerUserName: string; | ||||
|     creatorUserName: string; | ||||
|     createTime: Date; | ||||
|     orderDate: Date; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户公海分析(按日期) */ | ||||
|   export interface PoolSummaryByDate { | ||||
|     time: string; | ||||
|     customerPutCount: number; | ||||
|     customerTakeCount: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户公海分析(按用户) */ | ||||
|   export interface PoolSummaryByUser { | ||||
|     ownerUserName: string; | ||||
|     customerPutCount: number; | ||||
|     customerTakeCount: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户成交周期(按日期) */ | ||||
|   export interface CustomerDealCycleByDate { | ||||
|     time: string; | ||||
|     customerDealCycle: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户成交周期(按用户) */ | ||||
|   export interface CustomerDealCycleByUser { | ||||
|     ownerUserName: string; | ||||
|     customerDealCycle: number; | ||||
|     customerDealCount: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户成交周期(按地区) */ | ||||
|   export interface CustomerDealCycleByArea { | ||||
|     areaName: string; | ||||
|     customerDealCycle: number; | ||||
|     customerDealCount: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户成交周期(按产品) */ | ||||
|   export interface CustomerDealCycleByProduct { | ||||
|     productName: string; | ||||
|     customerDealCycle: number; | ||||
|     customerDealCount: number; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 客户总量分析(按日期) */ | ||||
| export function getCustomerSummaryByDate(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsCustomerApi.CustomerSummaryByDate[]>( | ||||
|     '/crm/statistics-customer/get-customer-summary-by-date', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 客户总量分析(按用户) */ | ||||
| export function getCustomerSummaryByUser(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsCustomerApi.CustomerSummaryByUser[]>( | ||||
|     '/crm/statistics-customer/get-customer-summary-by-user', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 客户跟进次数分析(按日期) */ | ||||
| export function getFollowUpSummaryByDate(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsCustomerApi.FollowUpSummaryByDate[]>( | ||||
|     '/crm/statistics-customer/get-follow-up-summary-by-date', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 客户跟进次数分析(按用户) */ | ||||
| export function getFollowUpSummaryByUser(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsCustomerApi.FollowUpSummaryByUser[]>( | ||||
|     '/crm/statistics-customer/get-follow-up-summary-by-user', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取客户跟进方式统计数 */ | ||||
| export function getFollowUpSummaryByType(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsCustomerApi.FollowUpSummaryByType[]>( | ||||
|     '/crm/statistics-customer/get-follow-up-summary-by-type', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 合同摘要信息(客户转化率页面) */ | ||||
| export function getContractSummary(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsCustomerApi.CustomerContractSummary[]>( | ||||
|     '/crm/statistics-customer/get-contract-summary', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取客户公海分析(按日期) */ | ||||
| export function getPoolSummaryByDate(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsCustomerApi.PoolSummaryByDate[]>( | ||||
|     '/crm/statistics-customer/get-pool-summary-by-date', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取客户公海分析(按用户) */ | ||||
| export function getPoolSummaryByUser(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsCustomerApi.PoolSummaryByUser[]>( | ||||
|     '/crm/statistics-customer/get-pool-summary-by-user', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取客户成交周期(按日期) */ | ||||
| export function getCustomerDealCycleByDate(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsCustomerApi.CustomerDealCycleByDate[]>( | ||||
|     '/crm/statistics-customer/get-customer-deal-cycle-by-date', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取客户成交周期(按用户) */ | ||||
| export function getCustomerDealCycleByUser(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsCustomerApi.CustomerDealCycleByUser[]>( | ||||
|     '/crm/statistics-customer/get-customer-deal-cycle-by-user', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取客户成交周期(按地区) */ | ||||
| export function getCustomerDealCycleByArea(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsCustomerApi.CustomerDealCycleByArea[]>( | ||||
|     '/crm/statistics-customer/get-customer-deal-cycle-by-area', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取客户成交周期(按产品) */ | ||||
| export function getCustomerDealCycleByProduct(params: PageParam) { | ||||
|   return requestClient.get< | ||||
|     CrmStatisticsCustomerApi.CustomerDealCycleByProduct[] | ||||
|   >('/crm/statistics-customer/get-customer-deal-cycle-by-product', { params }); | ||||
| } | ||||
|  | @ -0,0 +1,67 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmStatisticsFunnelApi { | ||||
|   /** 销售漏斗统计数据 */ | ||||
|   export interface FunnelSummary { | ||||
|     customerCount: number; // 客户数
 | ||||
|     businessCount: number; // 商机数
 | ||||
|     businessWinCount: number; // 赢单数
 | ||||
|   } | ||||
| 
 | ||||
|   /** 商机分析(按日期) */ | ||||
|   export interface BusinessSummaryByDate { | ||||
|     time: string; // 时间
 | ||||
|     businessCreateCount: number; // 商机数
 | ||||
|     totalPrice: number | string; // 商机金额
 | ||||
|   } | ||||
| 
 | ||||
|   /** 商机转化率分析(按日期) */ | ||||
|   export interface BusinessInversionRateSummaryByDate { | ||||
|     time: string; // 时间
 | ||||
|     businessCount: number; // 商机数量
 | ||||
|     businessWinCount: number; // 赢单商机数
 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 获取销售漏斗统计数据 */ | ||||
| export function getFunnelSummary(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsFunnelApi.FunnelSummary>( | ||||
|     '/crm/statistics-funnel/get-funnel-summary', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取商机结束状态统计 */ | ||||
| export function getBusinessSummaryByEndStatus(params: PageParam) { | ||||
|   return requestClient.get<Record<string, number>>( | ||||
|     '/crm/statistics-funnel/get-business-summary-by-end-status', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取新增商机分析(按日期) */ | ||||
| export function getBusinessSummaryByDate(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsFunnelApi.BusinessSummaryByDate[]>( | ||||
|     '/crm/statistics-funnel/get-business-summary-by-date', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取商机转化率分析(按日期) */ | ||||
| export function getBusinessInversionRateSummaryByDate(params: PageParam) { | ||||
|   return requestClient.get< | ||||
|     CrmStatisticsFunnelApi.BusinessInversionRateSummaryByDate[] | ||||
|   >('/crm/statistics-funnel/get-business-inversion-rate-summary-by-date', { | ||||
|     params, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 获取商机列表(按日期) */ | ||||
| export function getBusinessPageByDate(params: PageParam) { | ||||
|   return requestClient.get<PageResult<any>>( | ||||
|     '/crm/statistics-funnel/get-business-page-by-date', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,37 @@ | |||
| import type { PageParam } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmStatisticsPerformanceApi { | ||||
|   /** 员工业绩统计 */ | ||||
|   export interface Performance { | ||||
|     time: string; | ||||
|     currentMonthCount: number; | ||||
|     lastMonthCount: number; | ||||
|     lastYearCount: number; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 员工获得合同金额统计 */ | ||||
| export function getContractPricePerformance(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsPerformanceApi.Performance[]>( | ||||
|     '/crm/statistics-performance/get-contract-price-performance', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 员工获得回款统计 */ | ||||
| export function getReceivablePricePerformance(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsPerformanceApi.Performance[]>( | ||||
|     '/crm/statistics-performance/get-receivable-price-performance', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 员工获得签约合同数量统计 */ | ||||
| export function getContractCountPerformance(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsPerformanceApi.Performance[]>( | ||||
|     '/crm/statistics-performance/get-contract-count-performance', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,69 @@ | |||
| import type { PageParam } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmStatisticsPortraitApi { | ||||
|   /** 客户基础统计信息 */ | ||||
|   export interface CustomerBase { | ||||
|     customerCount: number; | ||||
|     dealCount: number; | ||||
|     dealPortion: number | string; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户行业统计信息 */ | ||||
|   export interface CustomerIndustry extends CustomerBase { | ||||
|     industryId: number; | ||||
|     industryPortion: number | string; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户来源统计信息 */ | ||||
|   export interface CustomerSource extends CustomerBase { | ||||
|     source: number; | ||||
|     sourcePortion: number | string; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户级别统计信息 */ | ||||
|   export interface CustomerLevel extends CustomerBase { | ||||
|     level: number; | ||||
|     levelPortion: number | string; | ||||
|   } | ||||
| 
 | ||||
|   /** 客户地区统计信息 */ | ||||
|   export interface CustomerArea extends CustomerBase { | ||||
|     areaId: number; | ||||
|     areaName: string; | ||||
|     areaPortion: number | string; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 获取客户行业统计数据 */ | ||||
| export function getCustomerIndustry(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsPortraitApi.CustomerIndustry[]>( | ||||
|     '/crm/statistics-portrait/get-customer-industry-summary', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取客户来源统计数据 */ | ||||
| export function getCustomerSource(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsPortraitApi.CustomerSource[]>( | ||||
|     '/crm/statistics-portrait/get-customer-source-summary', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取客户级别统计数据 */ | ||||
| export function getCustomerLevel(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsPortraitApi.CustomerLevel[]>( | ||||
|     '/crm/statistics-portrait/get-customer-level-summary', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获取客户地区统计数据 */ | ||||
| export function getCustomerArea(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsPortraitApi.CustomerArea[]>( | ||||
|     '/crm/statistics-portrait/get-customer-area-summary', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,76 @@ | |||
| import type { PageParam } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace CrmStatisticsRankApi { | ||||
|   /** 排行统计数据 */ | ||||
|   export interface Rank { | ||||
|     count: number; | ||||
|     nickname: string; | ||||
|     deptName: string; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 获得合同排行榜 */ | ||||
| export function getContractPriceRank(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsRankApi.Rank[]>( | ||||
|     '/crm/statistics-rank/get-contract-price-rank', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获得回款排行榜 */ | ||||
| export function getReceivablePriceRank(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsRankApi.Rank[]>( | ||||
|     '/crm/statistics-rank/get-receivable-price-rank', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 签约合同排行 */ | ||||
| export function getContractCountRank(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsRankApi.Rank[]>( | ||||
|     '/crm/statistics-rank/get-contract-count-rank', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 产品销量排行 */ | ||||
| export function getProductSalesRank(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsRankApi.Rank[]>( | ||||
|     '/crm/statistics-rank/get-product-sales-rank', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增客户数排行 */ | ||||
| export function getCustomerCountRank(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsRankApi.Rank[]>( | ||||
|     '/crm/statistics-rank/get-customer-count-rank', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增联系人数排行 */ | ||||
| export function getContactsCountRank(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsRankApi.Rank[]>( | ||||
|     '/crm/statistics-rank/get-contacts-count-rank', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 跟进次数排行 */ | ||||
| export function getFollowCountRank(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsRankApi.Rank[]>( | ||||
|     '/crm/statistics-rank/get-follow-count-rank', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 跟进客户数排行 */ | ||||
| export function getFollowCustomerCountRank(params: PageParam) { | ||||
|   return requestClient.get<CrmStatisticsRankApi.Rank[]>( | ||||
|     '/crm/statistics-rank/get-follow-customer-count-rank', | ||||
|     { params }, | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,63 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace PayAppApi { | ||||
|   /** 支付应用信息 */ | ||||
|   export interface App { | ||||
|     id?: number; | ||||
|     appKey: string; | ||||
|     name: string; | ||||
|     status: number; | ||||
|     remark: string; | ||||
|     payNotifyUrl: string; | ||||
|     refundNotifyUrl: string; | ||||
|     transferNotifyUrl: string; | ||||
|     merchantId: number; | ||||
|     merchantName: string; | ||||
|     createTime?: Date; | ||||
|   } | ||||
| 
 | ||||
|   /** 更新状态请求 */ | ||||
|   export interface UpdateStatusReq { | ||||
|     id: number; | ||||
|     status: number; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询支付应用列表 */ | ||||
| export function getAppPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<PayAppApi.App>>('/pay/app/page', { | ||||
|     params, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 查询支付应用详情 */ | ||||
| export function getApp(id: number) { | ||||
|   return requestClient.get<PayAppApi.App>(`/pay/app/get?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 新增支付应用 */ | ||||
| export function createApp(data: PayAppApi.App) { | ||||
|   return requestClient.post('/pay/app/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改支付应用 */ | ||||
| export function updateApp(data: PayAppApi.App) { | ||||
|   return requestClient.put('/pay/app/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改支付应用状态 */ | ||||
| export function changeAppStatus(data: PayAppApi.UpdateStatusReq) { | ||||
|   return requestClient.put('/pay/app/update-status', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除支付应用 */ | ||||
| export function deleteApp(id: number) { | ||||
|   return requestClient.delete(`/pay/app/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 获取支付应用列表 */ | ||||
| export function getAppList() { | ||||
|   return requestClient.get<PayAppApi.App[]>('/pay/app/list'); | ||||
| } | ||||
|  | @ -0,0 +1,54 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace PayChannelApi { | ||||
|   /** 支付渠道信息 */ | ||||
|   export interface Channel { | ||||
|     id: number; | ||||
|     code: string; | ||||
|     config: string; | ||||
|     status: number; | ||||
|     remark: string; | ||||
|     feeRate: number; | ||||
|     appId: number; | ||||
|     createTime: Date; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询支付渠道列表 */ | ||||
| export function getChannelPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<PayChannelApi.Channel>>( | ||||
|     '/pay/channel/page', | ||||
|     { | ||||
|       params, | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询支付渠道详情 */ | ||||
| export function getChannel(appId: string, code: string) { | ||||
|   return requestClient.get<PayChannelApi.Channel>('/pay/channel/get', { | ||||
|     params: { appId, code }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 新增支付渠道 */ | ||||
| export function createChannel(data: PayChannelApi.Channel) { | ||||
|   return requestClient.post('/pay/channel/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改支付渠道 */ | ||||
| export function updateChannel(data: PayChannelApi.Channel) { | ||||
|   return requestClient.put('/pay/channel/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除支付渠道 */ | ||||
| export function deleteChannel(id: number) { | ||||
|   return requestClient.delete(`/pay/channel/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 导出支付渠道 */ | ||||
| export function exportChannel(params: PageParam) { | ||||
|   return requestClient.download('/pay/channel/export-excel', { params }); | ||||
| } | ||||
|  | @ -0,0 +1,38 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace PayDemoApi { | ||||
|   /** 示例订单信息 */ | ||||
|   export interface DemoOrder { | ||||
|     spuId: number; | ||||
|     createTime: Date; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 创建示例订单 */ | ||||
| export function createDemoOrder(data: PayDemoApi.DemoOrder) { | ||||
|   return requestClient.post('/pay/demo-order/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 获得示例订单 */ | ||||
| export function getDemoOrder(id: number) { | ||||
|   return requestClient.get<PayDemoApi.DemoOrder>( | ||||
|     `/pay/demo-order/get?id=${id}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 获得示例订单分页 */ | ||||
| export function getDemoOrderPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<PayDemoApi.DemoOrder>>( | ||||
|     '/pay/demo-order/page', | ||||
|     { | ||||
|       params, | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 退款示例订单 */ | ||||
| export function refundDemoOrder(id: number) { | ||||
|   return requestClient.put(`/pay/demo-order/refund?id=${id}`); | ||||
| } | ||||
|  | @ -0,0 +1,29 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace PayDemoTransferApi { | ||||
|   /** 示例转账单信息 */ | ||||
|   export interface DemoTransfer { | ||||
|     price: number; | ||||
|     type: number; | ||||
|     userName: string; | ||||
|     alipayLogonId: string; | ||||
|     openid: string; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 创建示例转账单 */ | ||||
| export function createDemoTransfer(data: PayDemoTransferApi.DemoTransfer) { | ||||
|   return requestClient.post('/pay/demo-transfer/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 获得示例转账单分页 */ | ||||
| export function getDemoTransferPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<PayDemoTransferApi.DemoTransfer>>( | ||||
|     '/pay/demo-transfer/page', | ||||
|     { | ||||
|       params, | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,15 @@ | |||
| import type { PageParam } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| /** 获得支付通知明细 */ | ||||
| export function getNotifyTaskDetail(id: number) { | ||||
|   return requestClient.get(`/pay/notify/get-detail?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 获得支付通知分页 */ | ||||
| export function getNotifyTaskPage(params: PageParam) { | ||||
|   return requestClient.get('/pay/notify/page', { | ||||
|     params, | ||||
|   }); | ||||
| } | ||||
|  | @ -0,0 +1,118 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace PayOrderApi { | ||||
|   /** 支付订单信息 */ | ||||
|   export interface Order { | ||||
|     id: number; | ||||
|     merchantId: number; | ||||
|     appId: number; | ||||
|     channelId: number; | ||||
|     channelCode: string; | ||||
|     merchantOrderId: string; | ||||
|     subject: string; | ||||
|     body: string; | ||||
|     notifyUrl: string; | ||||
|     notifyStatus: number; | ||||
|     amount: number; | ||||
|     channelFeeRate: number; | ||||
|     channelFeeAmount: number; | ||||
|     status: number; | ||||
|     userIp: string; | ||||
|     expireTime: Date; | ||||
|     successTime: Date; | ||||
|     notifyTime: Date; | ||||
|     successExtensionId: number; | ||||
|     refundStatus: number; | ||||
|     refundTimes: number; | ||||
|     refundAmount: number; | ||||
|     channelUserId: string; | ||||
|     channelOrderNo: string; | ||||
|     createTime: Date; | ||||
|   } | ||||
| 
 | ||||
|   /** 支付订单分页请求 */ | ||||
|   export interface OrderPageReqVO extends PageParam { | ||||
|     merchantId?: number; | ||||
|     appId?: number; | ||||
|     channelId?: number; | ||||
|     channelCode?: string; | ||||
|     merchantOrderId?: string; | ||||
|     subject?: string; | ||||
|     body?: string; | ||||
|     notifyUrl?: string; | ||||
|     notifyStatus?: number; | ||||
|     amount?: number; | ||||
|     channelFeeRate?: number; | ||||
|     channelFeeAmount?: number; | ||||
|     status?: number; | ||||
|     expireTime?: Date[]; | ||||
|     successTime?: Date[]; | ||||
|     notifyTime?: Date[]; | ||||
|     successExtensionId?: number; | ||||
|     refundStatus?: number; | ||||
|     refundTimes?: number; | ||||
|     channelUserId?: string; | ||||
|     channelOrderNo?: string; | ||||
|     createTime?: Date[]; | ||||
|   } | ||||
| 
 | ||||
|   /** 支付订单导出请求 */ | ||||
|   export interface OrderExportReqVO { | ||||
|     merchantId?: number; | ||||
|     appId?: number; | ||||
|     channelId?: number; | ||||
|     channelCode?: string; | ||||
|     merchantOrderId?: string; | ||||
|     subject?: string; | ||||
|     body?: string; | ||||
|     notifyUrl?: string; | ||||
|     notifyStatus?: number; | ||||
|     amount?: number; | ||||
|     channelFeeRate?: number; | ||||
|     channelFeeAmount?: number; | ||||
|     status?: number; | ||||
|     expireTime?: Date[]; | ||||
|     successTime?: Date[]; | ||||
|     notifyTime?: Date[]; | ||||
|     successExtensionId?: number; | ||||
|     refundStatus?: number; | ||||
|     refundTimes?: number; | ||||
|     channelUserId?: string; | ||||
|     channelOrderNo?: string; | ||||
|     createTime?: Date[]; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询支付订单列表 */ | ||||
| export function getOrderPage(params: PayOrderApi.OrderPageReqVO) { | ||||
|   return requestClient.get<PageResult<PayOrderApi.Order>>('/pay/order/page', { | ||||
|     params, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 查询支付订单详情 */ | ||||
| export function getOrder(id: number, sync?: boolean) { | ||||
|   return requestClient.get<PayOrderApi.Order>('/pay/order/get', { | ||||
|     params: { | ||||
|       id, | ||||
|       sync, | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 获得支付订单的明细 */ | ||||
| export function getOrderDetail(id: number) { | ||||
|   return requestClient.get<PayOrderApi.Order>(`/pay/order/get-detail?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 提交支付订单 */ | ||||
| export function submitOrder(data: any) { | ||||
|   return requestClient.post('/pay/order/submit', data); | ||||
| } | ||||
| 
 | ||||
| /** 导出支付订单 */ | ||||
| export function exportOrder(params: PayOrderApi.OrderExportReqVO) { | ||||
|   return requestClient.download('/pay/order/export-excel', { params }); | ||||
| } | ||||
|  | @ -0,0 +1,129 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace PayRefundApi { | ||||
|   /** 退款订单信息 */ | ||||
|   export interface Refund { | ||||
|     id: number; | ||||
|     merchantId: number; | ||||
|     appId: number; | ||||
|     channelId: number; | ||||
|     channelCode: string; | ||||
|     orderId: string; | ||||
|     tradeNo: string; | ||||
|     merchantOrderId: string; | ||||
|     merchantRefundNo: string; | ||||
|     notifyUrl: string; | ||||
|     notifyStatus: number; | ||||
|     status: number; | ||||
|     type: number; | ||||
|     payAmount: number; | ||||
|     refundAmount: number; | ||||
|     reason: string; | ||||
|     userIp: string; | ||||
|     channelOrderNo: string; | ||||
|     channelRefundNo: string; | ||||
|     channelErrorCode: string; | ||||
|     channelErrorMsg: string; | ||||
|     channelExtras: string; | ||||
|     expireTime: Date; | ||||
|     successTime: Date; | ||||
|     notifyTime: Date; | ||||
|     createTime: Date; | ||||
|     updateTime: Date; | ||||
|   } | ||||
| 
 | ||||
|   /** 退款订单分页请求 */ | ||||
|   export interface RefundPageReqVO extends PageParam { | ||||
|     merchantId?: number; | ||||
|     appId?: number; | ||||
|     channelId?: number; | ||||
|     channelCode?: string; | ||||
|     orderId?: string; | ||||
|     tradeNo?: string; | ||||
|     merchantOrderId?: string; | ||||
|     merchantRefundNo?: string; | ||||
|     notifyUrl?: string; | ||||
|     notifyStatus?: number; | ||||
|     status?: number; | ||||
|     type?: number; | ||||
|     payAmount?: number; | ||||
|     refundAmount?: number; | ||||
|     reason?: string; | ||||
|     userIp?: string; | ||||
|     channelOrderNo?: string; | ||||
|     channelRefundNo?: string; | ||||
|     channelErrorCode?: string; | ||||
|     channelErrorMsg?: string; | ||||
|     channelExtras?: string; | ||||
|     expireTime?: Date[]; | ||||
|     successTime?: Date[]; | ||||
|     notifyTime?: Date[]; | ||||
|     createTime?: Date[]; | ||||
|   } | ||||
| 
 | ||||
|   /** 退款订单导出请求 */ | ||||
|   export interface RefundExportReqVO { | ||||
|     merchantId?: number; | ||||
|     appId?: number; | ||||
|     channelId?: number; | ||||
|     channelCode?: string; | ||||
|     orderId?: string; | ||||
|     tradeNo?: string; | ||||
|     merchantOrderId?: string; | ||||
|     merchantRefundNo?: string; | ||||
|     notifyUrl?: string; | ||||
|     notifyStatus?: number; | ||||
|     status?: number; | ||||
|     type?: number; | ||||
|     payAmount?: number; | ||||
|     refundAmount?: number; | ||||
|     reason?: string; | ||||
|     userIp?: string; | ||||
|     channelOrderNo?: string; | ||||
|     channelRefundNo?: string; | ||||
|     channelErrorCode?: string; | ||||
|     channelErrorMsg?: string; | ||||
|     channelExtras?: string; | ||||
|     expireTime?: Date[]; | ||||
|     successTime?: Date[]; | ||||
|     notifyTime?: Date[]; | ||||
|     createTime?: Date[]; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询退款订单列表 */ | ||||
| export function getRefundPage(params: PayRefundApi.RefundPageReqVO) { | ||||
|   return requestClient.get<PageResult<PayRefundApi.Refund>>( | ||||
|     '/pay/refund/page', | ||||
|     { | ||||
|       params, | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询退款订单详情 */ | ||||
| export function getRefund(id: number) { | ||||
|   return requestClient.get<PayRefundApi.Refund>(`/pay/refund/get?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 创建退款订单 */ | ||||
| export function createRefund(data: PayRefundApi.Refund) { | ||||
|   return requestClient.post('/pay/refund/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 更新退款订单 */ | ||||
| export function updateRefund(data: PayRefundApi.Refund) { | ||||
|   return requestClient.put('/pay/refund/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除退款订单 */ | ||||
| export function deleteRefund(id: number) { | ||||
|   return requestClient.delete(`/pay/refund/delete?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| /** 导出退款订单 */ | ||||
| export function exportRefund(params: PayRefundApi.RefundExportReqVO) { | ||||
|   return requestClient.download('/pay/refund/export-excel', { params }); | ||||
| } | ||||
|  | @ -0,0 +1,58 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace PayTransferApi { | ||||
|   /** 转账单信息 */ | ||||
|   export interface Transfer { | ||||
|     id: number; | ||||
|     appId: number; | ||||
|     channelId: number; | ||||
|     channelCode: string; | ||||
|     merchantTransferId: string; | ||||
|     type: number; | ||||
|     price: number; | ||||
|     subject: string; | ||||
|     userName: string; | ||||
|     alipayLogonId: string; | ||||
|     openid: string; | ||||
|     status: number; | ||||
|     createTime: Date; | ||||
|   } | ||||
| 
 | ||||
|   /** 转账单分页请求 */ | ||||
|   export interface TransferPageReqVO extends PageParam { | ||||
|     appId?: number; | ||||
|     channelId?: number; | ||||
|     channelCode?: string; | ||||
|     merchantTransferId?: string; | ||||
|     type?: number; | ||||
|     price?: number; | ||||
|     subject?: string; | ||||
|     userName?: string; | ||||
|     status?: number; | ||||
|     createTime?: Date[]; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询转账单列表 */ | ||||
| export function getTransferPage(params: PayTransferApi.TransferPageReqVO) { | ||||
|   return requestClient.get<PageResult<PayTransferApi.Transfer>>( | ||||
|     '/pay/transfer/page', | ||||
|     { | ||||
|       params, | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询转账单详情 */ | ||||
| export function getTransfer(id: number) { | ||||
|   return requestClient.get<PayTransferApi.Transfer>( | ||||
|     `/pay/transfer/get?id=${id}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 创建转账单 */ | ||||
| export function createTransfer(data: PayTransferApi.Transfer) { | ||||
|   return requestClient.post('/pay/transfer/create', data); | ||||
| } | ||||
|  | @ -0,0 +1,53 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace PayWalletApi { | ||||
|   /** 用户钱包查询参数 */ | ||||
|   export interface PayWalletUserReqVO { | ||||
|     userId: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 钱包信息 */ | ||||
|   export interface WalletVO { | ||||
|     id: number; | ||||
|     userId: number; | ||||
|     userType: number; | ||||
|     balance: number; | ||||
|     totalExpense: number; | ||||
|     totalRecharge: number; | ||||
|     freezePrice: number; | ||||
|   } | ||||
| 
 | ||||
|   /** 钱包分页请求 */ | ||||
|   export interface WalletPageReqVO extends PageParam { | ||||
|     userId?: number; | ||||
|     userType?: number; | ||||
|     balance?: number; | ||||
|     totalExpense?: number; | ||||
|     totalRecharge?: number; | ||||
|     freezePrice?: number; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询用户钱包详情 */ | ||||
| export function getWallet(params: PayWalletApi.PayWalletUserReqVO) { | ||||
|   return requestClient.get<PayWalletApi.WalletVO>('/pay/wallet/get', { | ||||
|     params, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 查询会员钱包列表 */ | ||||
| export function getWalletPage(params: PayWalletApi.WalletPageReqVO) { | ||||
|   return requestClient.get<PageResult<PayWalletApi.WalletVO>>( | ||||
|     '/pay/wallet/page', | ||||
|     { | ||||
|       params, | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 修改会员钱包余额 */ | ||||
| export function updateWalletBalance(data: PayWalletApi.WalletVO) { | ||||
|   return requestClient.put('/pay/wallet/update-balance', data); | ||||
| } | ||||
|  | @ -0,0 +1,46 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace WalletRechargePackageApi { | ||||
|   /** 充值套餐信息 */ | ||||
|   export interface Package { | ||||
|     id?: number; | ||||
|     name: string; | ||||
|     payPrice: number; | ||||
|     bonusPrice: number; | ||||
|     status: number; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询充值套餐列表 */ | ||||
| export function getPackagePage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<WalletRechargePackageApi.Package>>( | ||||
|     '/pay/wallet-recharge-package/page', | ||||
|     { | ||||
|       params, | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 查询充值套餐详情 */ | ||||
| export function getPackage(id: number) { | ||||
|   return requestClient.get<WalletRechargePackageApi.Package>( | ||||
|     `/pay/wallet-recharge-package/get?id=${id}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** 新增充值套餐 */ | ||||
| export function createPackage(data: WalletRechargePackageApi.Package) { | ||||
|   return requestClient.post('/pay/wallet-recharge-package/create', data); | ||||
| } | ||||
| 
 | ||||
| /** 修改充值套餐 */ | ||||
| export function updatePackage(data: WalletRechargePackageApi.Package) { | ||||
|   return requestClient.put('/pay/wallet-recharge-package/update', data); | ||||
| } | ||||
| 
 | ||||
| /** 删除充值套餐 */ | ||||
| export function deletePackage(id: number) { | ||||
|   return requestClient.delete(`/pay/wallet-recharge-package/delete?id=${id}`); | ||||
| } | ||||
|  | @ -0,0 +1,24 @@ | |||
| import type { PageParam, PageResult } from '@vben/request'; | ||||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| export namespace WalletTransactionApi { | ||||
|   /** 钱包交易流水信息 */ | ||||
|   export interface Transaction { | ||||
|     id: number; | ||||
|     walletId: number; | ||||
|     title: string; | ||||
|     price: number; | ||||
|     balance: number; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 查询钱包交易流水列表 */ | ||||
| export function getTransactionPage(params: PageParam) { | ||||
|   return requestClient.get<PageResult<WalletTransactionApi.Transaction>>( | ||||
|     '/pay/wallet-transaction/page', | ||||
|     { | ||||
|       params, | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
|  | @ -4,6 +4,7 @@ import ImageUpload from '#/components/upload/image-upload.vue'; | |||
| 
 | ||||
| export const useImagesUpload = () => { | ||||
|   return defineComponent({ | ||||
|     name: 'ImagesUpload', | ||||
|     props: { | ||||
|       multiple: { | ||||
|         type: Boolean, | ||||
|  | @ -20,6 +21,5 @@ export const useImagesUpload = () => { | |||
|         <ImageUpload maxNumber={props.maxNumber} multiple={props.multiple} /> | ||||
|       ); | ||||
|     }, | ||||
|     name: 'ImagesUpload', | ||||
|   }); | ||||
| }; | ||||
|  |  | |||
|  | @ -81,7 +81,7 @@ function openWindow(url: string) { | |||
|       </div> | ||||
|       <p class="mt-2 flex justify-center"> | ||||
|         <span> | ||||
|           <Image src="../../../public/wx-xingyu.png" alt="数舵科技" /> | ||||
|           <Image src="/wx-xingyu.png" alt="数舵科技" /> | ||||
|         </span> | ||||
|       </p> | ||||
|       <p class="mt-2 flex justify-center pt-4 text-sm italic"> | ||||
|  |  | |||
|  | @ -1,28 +0,0 @@ | |||
| import type { RouteRecordRaw } from 'vue-router'; | ||||
| 
 | ||||
| // import { $t } from '#/locales';
 | ||||
| 
 | ||||
| const routes: RouteRecordRaw[] = [ | ||||
|   // {
 | ||||
|   //   meta: {
 | ||||
|   //     icon: 'ic:baseline-view-in-ar',
 | ||||
|   //     keepAlive: true,
 | ||||
|   //     order: 1000,
 | ||||
|   //     title: $t('demos.title'),
 | ||||
|   //   },
 | ||||
|   //   name: 'Demos',
 | ||||
|   //   path: '/demos',
 | ||||
|   //   children: [
 | ||||
|   //     {
 | ||||
|   //       meta: {
 | ||||
|   //         title: $t('demos.antd'),
 | ||||
|   //       },
 | ||||
|   //       name: 'AntDesignDemos',
 | ||||
|   //       path: '/demos/ant-design',
 | ||||
|   //       component: () => import('#/views/demos/antd/index.vue'),
 | ||||
|   //     },
 | ||||
|   //   ],
 | ||||
|   // },
 | ||||
| ]; | ||||
| 
 | ||||
| export default routes; // update by 芋艿:不展示
 | ||||
|  | @ -15,14 +15,22 @@ export function getRangePickerDefaultProps() { | |||
|     valueFormat: 'YYYY-MM-DD HH:mm:ss', | ||||
|     format: 'YYYY-MM-DD HH:mm:ss', | ||||
|     placeholder: ['开始时间', '结束时间'], | ||||
|     // prettier-ignore
 | ||||
|     ranges: { | ||||
|       '今天': [dayjs().startOf('day'), dayjs().endOf('day')], | ||||
|       '昨天': [dayjs().subtract(1, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day')], | ||||
|       '本周': [dayjs().startOf('week'), dayjs().endOf('day')], | ||||
|       '本月': [dayjs().startOf('month'), dayjs().endOf('day')], | ||||
|       '最近 7 天': [dayjs().subtract(7, 'day').startOf('day'), dayjs().endOf('day')], | ||||
|       '最近 30 天': [dayjs().subtract(30, 'day').startOf('day'), dayjs().endOf('day')], | ||||
|       今天: [dayjs().startOf('day'), dayjs().endOf('day')], | ||||
|       昨天: [ | ||||
|         dayjs().subtract(1, 'day').startOf('day'), | ||||
|         dayjs().subtract(1, 'day').endOf('day'), | ||||
|       ], | ||||
|       本周: [dayjs().startOf('week'), dayjs().endOf('day')], | ||||
|       本月: [dayjs().startOf('month'), dayjs().endOf('day')], | ||||
|       '最近 7 天': [ | ||||
|         dayjs().subtract(7, 'day').startOf('day'), | ||||
|         dayjs().endOf('day'), | ||||
|       ], | ||||
|       '最近 30 天': [ | ||||
|         dayjs().subtract(30, 'day').startOf('day'), | ||||
|         dayjs().endOf('day'), | ||||
|       ], | ||||
|     }, | ||||
|     transformDateFunc: (dates: any) => { | ||||
|       if (dates && dates.length === 2) { | ||||
|  |  | |||
|  | @ -1,11 +1,12 @@ | |||
| <script setup lang="ts"> | ||||
| import type { Recordable } from '@vben/types'; | ||||
| 
 | ||||
| import { $t } from '@vben/locales'; | ||||
| 
 | ||||
| import { message } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenForm, z } from '#/adapter/form'; | ||||
| import { updateUserPassword } from '#/api/system/user/profile'; | ||||
| import { $t } from '@vben/locales'; | ||||
| 
 | ||||
| const [Form, formApi] = useVbenForm({ | ||||
|   commonConfig: { | ||||
|  |  | |||
|  | @ -2,26 +2,27 @@ | |||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { SystemSocialUserApi } from '#/api/system/social/user'; | ||||
| 
 | ||||
| import { computed, onMounted, ref } from 'vue'; | ||||
| import { useRoute } from 'vue-router'; | ||||
| 
 | ||||
| import { Button, Card, Image, message, Modal } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { computed, ref, onMounted } from 'vue'; | ||||
| import { useRoute } from 'vue-router'; | ||||
| import { $t } from '#/locales'; | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { socialAuthRedirect } from '#/api/core/auth'; | ||||
| import { | ||||
|   getBindSocialUserList, | ||||
|   socialUnbind, | ||||
|   socialBind, | ||||
|   socialUnbind, | ||||
| } from '#/api/system/social/user'; | ||||
| import { socialAuthRedirect } from '#/api/core/auth'; | ||||
| import { DICT_TYPE, getDictLabel } from '#/utils/dict'; | ||||
| import { $t } from '#/locales'; | ||||
| import { SystemUserSocialTypeEnum } from '#/utils/constants'; | ||||
| import { DICT_TYPE, getDictLabel } from '#/utils/dict'; | ||||
| 
 | ||||
| const route = useRoute(); | ||||
| const emit = defineEmits<{ | ||||
|   (e: 'update:activeName', v: string): void; | ||||
| }>(); | ||||
| 
 | ||||
| const route = useRoute(); | ||||
| /** 已经绑定的平台 */ | ||||
| const bindList = ref<SystemSocialUserApi.SocialUser[]>([]); | ||||
| const allBindList = computed<any[]>(() => { | ||||
|  | @ -126,8 +127,7 @@ async function onBind(bind: any) { | |||
|   try { | ||||
|     // 计算 redirectUri | ||||
|     // tricky: type 需要先 encode 一次,否则钉钉回调会丢失。配合 getUrlValue() 使用 | ||||
|     const redirectUri = | ||||
|       location.origin + '/profile?' + encodeURIComponent(`type=${type}`); | ||||
|     const redirectUri = `${location.origin}/profile?${encodeURIComponent(`type=${type}`)}`; | ||||
| 
 | ||||
|     // 进行跳转 | ||||
|     window.location.href = await socialAuthRedirect(type, redirectUri); | ||||
|  |  | |||
|  | @ -50,7 +50,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -69,7 +69,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       // 设置到 values | ||||
|       await formApi.setValues(formData.value); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -1,28 +1,242 @@ | |||
| <script lang="ts" setup> | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import type { BpmModelApi } from '#/api/bpm/model'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| import { onActivated, reactive, ref, useTemplateRef, watch } from 'vue'; | ||||
| 
 | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import { Plus, Search, Settings } from '@vben/icons'; | ||||
| import { cloneDeep } from '@vben/utils'; | ||||
| 
 | ||||
| import { refAutoReset } from '@vueuse/core'; | ||||
| import { useSortable } from '@vueuse/integrations/useSortable'; | ||||
| import { | ||||
|   Button, | ||||
|   Card, | ||||
|   Divider, | ||||
|   Dropdown, | ||||
|   Form, | ||||
|   Input, | ||||
|   Menu, | ||||
|   message, | ||||
| } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { | ||||
|   getCategorySimpleList, | ||||
|   updateCategorySortBatch, | ||||
| } from '#/api/bpm/category'; | ||||
| import { getModelList } from '#/api/bpm/model'; | ||||
| 
 | ||||
| import CategoryDraggableModel from './modules/category-draggable-model.vue'; | ||||
| // 模型列表加载状态 | ||||
| const modelListSpinning = refAutoReset(false, 3000); | ||||
| // 保存排序状态 | ||||
| const saveSortLoading = ref(false); | ||||
| // 按照 category 分组的数据 | ||||
| const categoryGroup = ref<BpmModelApi.ModelCategoryInfo[]>([]); | ||||
| // 未排序前的原始数据 | ||||
| const originalData = ref<BpmModelApi.ModelCategoryInfo[]>([]); | ||||
| // 可以排序元素的容器 | ||||
| const sortable = useTemplateRef<HTMLElement>('categoryGroupRef'); | ||||
| // 排序引用,以便后续启用或禁用排序 | ||||
| const sortableInstance = ref<any>(null); | ||||
| // 分类排序状态 | ||||
| const isCategorySorting = ref(false); | ||||
| // 查询参数 | ||||
| const queryParams = reactive({ | ||||
|   name: '', | ||||
| }); | ||||
| 
 | ||||
| // 监听分类排序模式切换 | ||||
| watch( | ||||
|   () => isCategorySorting.value, | ||||
|   (newValue) => { | ||||
|     if (sortableInstance.value) { | ||||
|       if (newValue) { | ||||
|         // 启用排序功能 | ||||
|         sortableInstance.value.option('disabled', false); | ||||
|       } else { | ||||
|         // 禁用排序功能 | ||||
|         sortableInstance.value.option('disabled', true); | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| /** 加载数据 */ | ||||
| const getList = async () => { | ||||
|   modelListSpinning.value = true; | ||||
|   try { | ||||
|     const modelList = await getModelList(queryParams.name); | ||||
|     const categoryList = await getCategorySimpleList(); | ||||
|     // 按照 category 聚合 | ||||
|     categoryGroup.value = categoryList.map((category: any) => ({ | ||||
|       ...category, | ||||
|       modelList: modelList.filter( | ||||
|         (model: any) => model.categoryName === category.name, | ||||
|       ), | ||||
|     })); | ||||
|     // 重置排序实例 | ||||
|     sortableInstance.value = null; | ||||
|   } finally { | ||||
|     modelListSpinning.value = false; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 初始化 */ | ||||
| onActivated(() => { | ||||
|   getList(); | ||||
| }); | ||||
| 
 | ||||
| /** 查询方法 */ | ||||
| const handleQuery = () => { | ||||
|   getList(); | ||||
| }; | ||||
| 
 | ||||
| /** 新增模型 */ | ||||
| const createModel = () => { | ||||
|   // TODO 新增模型 | ||||
| }; | ||||
| 
 | ||||
| /** 处理下拉菜单命令 */ | ||||
| const handleCommand = (command: string) => { | ||||
|   if (command === 'handleCategoryAdd') { | ||||
|     //  TODO 新建分类逻辑 | ||||
|   } else if (command === 'handleCategorySort') { | ||||
|     originalData.value = cloneDeep(categoryGroup.value); | ||||
|     isCategorySorting.value = true; | ||||
|     // 如果排序实例不存在,则初始化 | ||||
|     if (sortableInstance.value) { | ||||
|       // 已存在实例,则启用排序功能 | ||||
|       sortableInstance.value.option('disabled', false); | ||||
|     } else { | ||||
|       sortableInstance.value = useSortable(sortable, categoryGroup, { | ||||
|         disabled: false, // 启用排序 | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 取消分类排序 */ | ||||
| const handleCategorySortCancel = () => { | ||||
|   // 恢复初始数据 | ||||
|   categoryGroup.value = cloneDeep(originalData.value); | ||||
|   isCategorySorting.value = false; | ||||
|   // 直接禁用排序功能 | ||||
|   if (sortableInstance.value) { | ||||
|     sortableInstance.value.option('disabled', true); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 提交分类排序 */ | ||||
| const handleCategorySortSubmit = async () => { | ||||
|   saveSortLoading.value = true; | ||||
|   try { | ||||
|     // 保存排序逻辑 | ||||
|     const ids = categoryGroup.value.map((item: any) => item.id); | ||||
|     await updateCategorySortBatch(ids); | ||||
|   } finally { | ||||
|     saveSortLoading.value = false; | ||||
|   } | ||||
|   message.success('分类排序成功'); | ||||
|   isCategorySorting.value = false; | ||||
|   // 刷新列表 | ||||
|   await getList(); | ||||
|   // 禁用排序功能 | ||||
|   if (sortableInstance.value) { | ||||
|     sortableInstance.value.option('disabled', true); | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|     <Button | ||||
|       danger | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" | ||||
|   <Page auto-content-height> | ||||
|     <Card | ||||
|       :body-style="{ padding: '10px' }" | ||||
|       class="mb-4" | ||||
|       v-spinning="modelListSpinning" | ||||
|     > | ||||
|       该功能支持 Vue3 + element-plus 版本! | ||||
|     </Button> | ||||
|     <br /> | ||||
|     <Button | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/model/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/model/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|       <div class="flex items-center justify-between pl-5"> | ||||
|         <span class="-mb-4 text-lg font-extrabold">流程模型</span> | ||||
|         <!-- 搜索工作栏 --> | ||||
|         <Form | ||||
|           v-if="!isCategorySorting" | ||||
|           class="-mb-4 mr-2.5 flex" | ||||
|           :model="queryParams" | ||||
|           layout="inline" | ||||
|         > | ||||
|           <Form.Item name="name" class="ml-auto"> | ||||
|             <Input | ||||
|               v-model:value="queryParams.name" | ||||
|               placeholder="搜索流程" | ||||
|               allow-clear | ||||
|               @press-enter="handleQuery" | ||||
|               class="!w-60" | ||||
|             > | ||||
|               <template #prefix> | ||||
|                 <Search class="mx-2.5" /> | ||||
|               </template> | ||||
|             </Input> | ||||
|           </Form.Item> | ||||
|           <!-- 右上角:新建模型、更多操作 --> | ||||
|           <Form.Item> | ||||
|             <Button type="primary" @click="createModel"> | ||||
|               <Plus class="size-5" /> 新建模型 | ||||
|             </Button> | ||||
|           </Form.Item> | ||||
|           <Form.Item> | ||||
|             <Dropdown placement="bottomRight"> | ||||
|               <Button> | ||||
|                 <template #icon> | ||||
|                   <Settings class="size-4" /> | ||||
|                 </template> | ||||
|               </Button> | ||||
|               <template #overlay> | ||||
|                 <Menu @click="(e) => handleCommand(e.key as string)"> | ||||
|                   <Menu.Item key="handleCategoryAdd"> | ||||
|                     <div class="flex items-center"> | ||||
|                       <span | ||||
|                         class="icon-[ant-design--plus-outlined] mr-1.5 text-[18px]" | ||||
|                       ></span> | ||||
|                       新建分类 | ||||
|                     </div> | ||||
|                   </Menu.Item> | ||||
|                   <Menu.Item key="handleCategorySort"> | ||||
|                     <div class="flex items-center"> | ||||
|                       <span class="icon-[fa--sort-amount-desc] mr-1.5"></span> | ||||
|                       分类排序 | ||||
|                     </div> | ||||
|                   </Menu.Item> | ||||
|                 </Menu> | ||||
|               </template> | ||||
|             </Dropdown> | ||||
|           </Form.Item> | ||||
|         </Form> | ||||
|         <div class="-mb-4 mr-6" v-else> | ||||
|           <Button @click="handleCategorySortCancel" class="mr-3"> | ||||
|             取 消 | ||||
|           </Button> | ||||
|           <Button | ||||
|             type="primary" | ||||
|             :loading="saveSortLoading" | ||||
|             @click="handleCategorySortSubmit" | ||||
|           > | ||||
|             保存排序 | ||||
|           </Button> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <Divider /> | ||||
|       <!-- 按照分类,展示其所属的模型列表 --> | ||||
|       <div class="px-5" ref="categoryGroupRef"> | ||||
|         <CategoryDraggableModel | ||||
|           v-for="element in categoryGroup" | ||||
|           :class="isCategorySorting ? 'cursor-move' : ''" | ||||
|           :key="element.id" | ||||
|           :category-info="element" | ||||
|           :is-category-sorting="isCategorySorting" | ||||
|           @success="getList" | ||||
|         /> | ||||
|       </div> | ||||
|     </Card> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -0,0 +1,398 @@ | |||
| <script lang="ts" setup> | ||||
| import type { BpmCategoryApi } from '#/api/bpm/category'; | ||||
| import type { BpmModelApi } from '#/api/bpm/model'; | ||||
| 
 | ||||
| import { computed, ref, watchEffect } from 'vue'; | ||||
| 
 | ||||
| import { cloneDeep, formatDateTime, isEqual } from '@vben/utils'; | ||||
| 
 | ||||
| import { useDebounceFn } from '@vueuse/core'; | ||||
| import { useSortable } from '@vueuse/integrations/useSortable'; | ||||
| import { | ||||
|   Button, | ||||
|   Card, | ||||
|   Collapse, | ||||
|   message, | ||||
|   Table, | ||||
|   Tag, | ||||
|   Tooltip, | ||||
| } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { updateModelSortBatch } from '#/api/bpm/model'; | ||||
| import { DictTag } from '#/components/dict-tag'; | ||||
| import { DICT_TYPE } from '#/utils/dict'; | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
|   categoryInfo: BpmCategoryApi.ModelCategoryInfo; | ||||
|   isCategorySorting: boolean; | ||||
| }>(); | ||||
| 
 | ||||
| const emit = defineEmits(['success']); | ||||
| 
 | ||||
| const isModelSorting = ref(false); | ||||
| const originalData = ref<BpmModelApi.ModelVO[]>([]); | ||||
| const modelList = ref<BpmModelApi.ModelVO[]>([]); | ||||
| const isExpand = ref(false); | ||||
| const tableRef = ref(); | ||||
| 
 | ||||
| // 排序引用,以便后续启用或禁用排序 | ||||
| const sortableInstance = ref<any>(null); | ||||
| /** 解决 v-model 问题,使用计算属性 */ | ||||
| const expandKeys = computed(() => (isExpand.value ? ['1'] : [])); | ||||
| 
 | ||||
| // 表格列配置 | ||||
| const columns = [ | ||||
|   { | ||||
|     title: '流程名', | ||||
|     dataIndex: 'name', | ||||
|     key: 'name', | ||||
|     align: 'left' as const, | ||||
|     minWidth: 250, | ||||
|   }, | ||||
|   { | ||||
|     title: '可见范围', | ||||
|     dataIndex: 'startUserIds', | ||||
|     key: 'startUserIds', | ||||
|     align: 'center' as const, | ||||
|     minWidth: 150, | ||||
|   }, | ||||
|   { | ||||
|     title: '流程类型', | ||||
|     dataIndex: 'type', | ||||
|     key: 'type', | ||||
|     align: 'center' as const, | ||||
|     minWidth: 120, | ||||
|   }, | ||||
|   { | ||||
|     title: '表单信息', | ||||
|     dataIndex: 'formType', | ||||
|     key: 'formType', | ||||
|     align: 'center' as const, | ||||
|     minWidth: 150, | ||||
|   }, | ||||
|   { | ||||
|     title: '最后发布', | ||||
|     dataIndex: 'deploymentTime', | ||||
|     key: 'deploymentTime', | ||||
|     align: 'center' as const, | ||||
|     minWidth: 250, | ||||
|   }, | ||||
|   { | ||||
|     title: '操作', | ||||
|     key: 'operation', | ||||
|     align: 'center' as const, | ||||
|     fixed: 'right' as const, | ||||
|     width: 150, | ||||
|   }, | ||||
| ]; | ||||
| 
 | ||||
| /** 处理模型的排序 */ | ||||
| const handleModelSort = () => { | ||||
|   // 保存初始数据 | ||||
|   originalData.value = cloneDeep(props.categoryInfo.modelList); | ||||
|   // 展开数据 | ||||
|   isExpand.value = true; | ||||
|   isModelSorting.value = true; | ||||
|   // 如果排序实例不存在,则初始化 | ||||
|   if (sortableInstance.value) { | ||||
|     // 已存在实例,则启用排序功能 | ||||
|     sortableInstance.value.option('disabled', false); | ||||
|   } else { | ||||
|     const sortableClass = `.category-${props.categoryInfo.id} .ant-table .ant-table-tbody`; | ||||
|     sortableInstance.value = useSortable(sortableClass, modelList, { | ||||
|       disabled: false, // 启用排序 | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 处理模型的排序提交 */ | ||||
| const handleModelSortSubmit = async () => { | ||||
|   try { | ||||
|     // 保存排序 | ||||
|     const ids = modelList.value.map((item) => item.id); | ||||
|     await updateModelSortBatch(ids); | ||||
|     // 刷新列表 | ||||
|     isModelSorting.value = false; | ||||
|     message.success('排序模型成功'); | ||||
|     emit('success'); | ||||
|   } catch (error) { | ||||
|     console.error('排序保存失败', error); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 处理模型的排序取消 */ | ||||
| const handleModelSortCancel = () => { | ||||
|   // 恢复初始数据 | ||||
|   modelList.value = cloneDeep(originalData.value); | ||||
|   isModelSorting.value = false; | ||||
|   // 禁用排序功能 | ||||
|   if (sortableInstance.value) { | ||||
|     sortableInstance.value.option('disabled', true); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 处理表单详情点击 */ | ||||
| const handleFormDetail = (row: any) => { | ||||
|   // TODO 待实现 | ||||
|   console.warn('待实现', row); | ||||
| }; | ||||
| 
 | ||||
| /** 更新 modelList 模型列表 */ | ||||
| const updateModelList = useDebounceFn(() => { | ||||
|   const newModelList = props.categoryInfo.modelList; | ||||
|   if (!isEqual(modelList.value, newModelList)) { | ||||
|     modelList.value = cloneDeep(newModelList); | ||||
|     if (newModelList?.length > 0) { | ||||
|       isExpand.value = true; | ||||
|     } | ||||
|     // 关闭排序 | ||||
|     isModelSorting.value = false; | ||||
|     // 重置排序实例 | ||||
|     sortableInstance.value = null; | ||||
|   } | ||||
| }, 100); | ||||
| 
 | ||||
| /** 监听分类信息和排序状态变化 */ | ||||
| watchEffect(() => { | ||||
|   if (props.categoryInfo?.modelList) { | ||||
|     updateModelList(); | ||||
|   } | ||||
| 
 | ||||
|   if (props.isCategorySorting) { | ||||
|     isExpand.value = false; | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| /** 自定义表格行渲染 */ | ||||
| const customRow = (_record: any) => { | ||||
|   return { | ||||
|     class: isModelSorting.value ? 'cursor-move' : '', | ||||
|   }; | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Card | ||||
|     :body-style="{ padding: 0 }" | ||||
|     class="category-draggable-model mb-5 rounded-lg transition-all duration-300 ease-in-out hover:shadow-xl" | ||||
|   > | ||||
|     <div class="flex h-12 items-center"> | ||||
|       <!-- 头部:分类名 --> | ||||
|       <div class="flex items-center"> | ||||
|         <Tooltip v-if="isCategorySorting" title="拖动排序"> | ||||
|           <span | ||||
|             class="icon-[ic--round-drag-indicator] ml-2.5 cursor-move text-2xl text-gray-500" | ||||
|           ></span> | ||||
|         </Tooltip> | ||||
|         <div class="ml-4 mr-2 text-lg font-medium">{{ categoryInfo.name }}</div> | ||||
|         <div class="text-gray-500"> | ||||
|           ({{ categoryInfo.modelList?.length || 0 }}) | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <!-- 头部:操作 --> | ||||
|       <div class="flex flex-1 items-center" v-show="!isCategorySorting"> | ||||
|         <div | ||||
|           v-if="categoryInfo.modelList.length > 0" | ||||
|           class="ml-3 flex cursor-pointer items-center transition-transform duration-300" | ||||
|           :class="isExpand ? 'rotate-180' : 'rotate-0'" | ||||
|           @click="isExpand = !isExpand" | ||||
|         > | ||||
|           <span | ||||
|             class="icon-[ic--round-expand-more] text-3xl text-gray-400" | ||||
|           ></span> | ||||
|         </div> | ||||
| 
 | ||||
|         <div | ||||
|           class="ml-auto flex items-center" | ||||
|           :class="isModelSorting ? 'mr-4' : 'mr-12'" | ||||
|         > | ||||
|           <template v-if="!isModelSorting"> | ||||
|             <Button | ||||
|               v-if="categoryInfo.modelList.length > 0" | ||||
|               type="link" | ||||
|               class="mr-5 flex items-center text-[14px]" | ||||
|               @click.stop="handleModelSort" | ||||
|             > | ||||
|               <template #icon> | ||||
|                 <span class="icon-[fa--sort-amount-desc] mr-1"></span> | ||||
|               </template> | ||||
|               排序 | ||||
|             </Button> | ||||
|           </template> | ||||
| 
 | ||||
|           <template v-else> | ||||
|             <Button @click.stop="handleModelSortCancel" class="mr-2"> | ||||
|               取 消 | ||||
|             </Button> | ||||
|             <Button type="primary" @click.stop="handleModelSortSubmit"> | ||||
|               保存排序 | ||||
|             </Button> | ||||
|           </template> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <!-- 模型列表 --> | ||||
|     <Collapse :active-key="expandKeys" :bordered="false" class="bg-transparent"> | ||||
|       <Collapse.Panel | ||||
|         key="1" | ||||
|         :show-arrow="false" | ||||
|         class="border-0 bg-transparent p-0" | ||||
|         v-show="isExpand" | ||||
|       > | ||||
|         <Table | ||||
|           v-if="modelList && modelList.length > 0" | ||||
|           :class="`category-${categoryInfo.id}`" | ||||
|           ref="tableRef" | ||||
|           :data-source="modelList" | ||||
|           :columns="columns" | ||||
|           :pagination="false" | ||||
|           :custom-row="customRow" | ||||
|           row-key="id" | ||||
|         > | ||||
|           <template #bodyCell="{ column, record }"> | ||||
|             <!-- 流程名 --> | ||||
|             <template v-if="column.key === 'name'"> | ||||
|               <div class="flex items-center"> | ||||
|                 <Tooltip v-if="isModelSorting" title="拖动排序"> | ||||
|                   <span | ||||
|                     class="icon-[ic--round-drag-indicator] mr-2.5 cursor-move text-2xl text-gray-500" | ||||
|                   ></span> | ||||
|                 </Tooltip> | ||||
|                 <div | ||||
|                   v-if="!record.icon" | ||||
|                   class="mr-2.5 flex h-9 w-9 items-center justify-center rounded bg-blue-500 text-white" | ||||
|                 > | ||||
|                   <span style="font-size: 12px">{{ | ||||
|                     record.name.substring(0, 2) | ||||
|                   }}</span> | ||||
|                 </div> | ||||
|                 <img | ||||
|                   v-else | ||||
|                   :src="record.icon" | ||||
|                   class="mr-2.5 h-9 w-9 rounded" | ||||
|                   alt="图标" | ||||
|                 /> | ||||
|                 {{ record.name }} | ||||
|               </div> | ||||
|             </template> | ||||
| 
 | ||||
|             <!-- 可见范围列--> | ||||
|             <template v-else-if="column.key === 'startUserIds'"> | ||||
|               <span | ||||
|                 v-if="!record.startUsers?.length && !record.startDepts?.length" | ||||
|               > | ||||
|                 全部可见 | ||||
|               </span> | ||||
|               <span v-else-if="record.startUsers?.length === 1"> | ||||
|                 {{ record.startUsers[0].nickname }} | ||||
|               </span> | ||||
|               <span v-else-if="record.startDepts?.length === 1"> | ||||
|                 {{ record.startDepts[0].name }} | ||||
|               </span> | ||||
|               <span v-else-if="record.startDepts?.length > 1"> | ||||
|                 <Tooltip | ||||
|                   placement="top" | ||||
|                   :title=" | ||||
|                     record.startDepts.map((dept: any) => dept.name).join('、') | ||||
|                   " | ||||
|                 > | ||||
|                   {{ record.startDepts[0].name }}等 | ||||
|                   {{ record.startDepts.length }} 个部门可见 | ||||
|                 </Tooltip> | ||||
|               </span> | ||||
|               <span v-else-if="record.startUsers?.length > 1"> | ||||
|                 <Tooltip | ||||
|                   placement="top" | ||||
|                   :title=" | ||||
|                     record.startUsers | ||||
|                       .map((user: any) => user.nickname) | ||||
|                       .join('、') | ||||
|                   " | ||||
|                 > | ||||
|                   {{ record.startUsers[0].nickname }}等 | ||||
|                   {{ record.startUsers.length }} 人可见 | ||||
|                 </Tooltip> | ||||
|               </span> | ||||
|             </template> | ||||
|             <!-- 流程类型列 --> | ||||
|             <template v-else-if="column.key === 'type'"> | ||||
|               <!-- <DictTag :value="record.type" :type="DICT_TYPE.BPM_MODEL_TYPE" /> --> | ||||
|               <!-- <Tag>{{ record.type }}</Tag> --> | ||||
|               <DictTag :type="DICT_TYPE.BPM_MODEL_TYPE" :value="record.type" /> | ||||
|             </template> | ||||
|             <!-- 表单信息列 --> | ||||
|             <template v-else-if="column.key === 'formType'"> | ||||
|               <!-- TODO BpmModelFormType.NORMAL --> | ||||
|               <Button | ||||
|                 v-if="record.formType === 10" | ||||
|                 type="link" | ||||
|                 @click="handleFormDetail(record)" | ||||
|               > | ||||
|                 {{ record.formName }} | ||||
|               </Button> | ||||
|               <!-- TODO BpmModelFormType.CUSTOM --> | ||||
|               <Button | ||||
|                 v-else-if="record.formType === 20" | ||||
|                 type="link" | ||||
|                 @click="handleFormDetail(record)" | ||||
|               > | ||||
|                 {{ record.formCustomCreatePath }} | ||||
|               </Button> | ||||
|               <span v-else>暂无表单</span> | ||||
|             </template> | ||||
|             <!-- 最后发布列 --> | ||||
|             <template v-else-if="column.key === 'deploymentTime'"> | ||||
|               <div class="flex items-center justify-center"> | ||||
|                 <span v-if="record.processDefinition" class="w-[150px]"> | ||||
|                   {{ formatDateTime(record.processDefinition.deploymentTime) }} | ||||
|                 </span> | ||||
|                 <Tag v-if="record.processDefinition"> | ||||
|                   v{{ record.processDefinition.version }} | ||||
|                 </Tag> | ||||
|                 <Tag v-else color="warning">未部署</Tag> | ||||
|                 <Tag | ||||
|                   v-if="record.processDefinition?.suspensionState === 2" | ||||
|                   color="warning" | ||||
|                   class="ml-[10px]" | ||||
|                 > | ||||
|                   已停用 | ||||
|                 </Tag> | ||||
|               </div> | ||||
|             </template> | ||||
|             <!-- 操作列 --> | ||||
|             <template v-else-if="column.key === 'operation'"> | ||||
|               <div class="flex items-center justify-center">待实现</div> | ||||
|             </template> | ||||
|           </template> | ||||
|         </Table> | ||||
|       </Collapse.Panel> | ||||
|     </Collapse> | ||||
|   </Card> | ||||
| </template> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .category-draggable-model { | ||||
|   // ant-table-tbody 自定义样式 | ||||
|   :deep(.ant-table-tbody > tr > td) { | ||||
|     overflow: hidden; | ||||
|     border-bottom: none; | ||||
|   } | ||||
|   // ant-collapse-header 自定义样式 | ||||
|   :deep(.ant-collapse-header) { | ||||
|     padding: 0; | ||||
|   } | ||||
| 
 | ||||
|   // 优化表格渲染性能 | ||||
|   :deep(.ant-table-tbody) { | ||||
|     transform: translateZ(0); | ||||
|     will-change: transform; | ||||
|   } | ||||
| 
 | ||||
|   // 折叠面板样式 | ||||
|   :deep(.ant-collapse-content-box) { | ||||
|     padding: 0; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  | @ -0,0 +1,895 @@ | |||
| import type { Ref } from 'vue'; | ||||
| 
 | ||||
| import type { VbenFormSchema } from '#/adapter/form'; | ||||
| import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmContractApi } from '#/api/crm/contract'; | ||||
| import type { CrmReceivableApi } from '#/api/crm/receivable'; | ||||
| 
 | ||||
| import { useAccess } from '@vben/access'; | ||||
| 
 | ||||
| import { DICT_TYPE } from '#/utils/dict'; | ||||
| 
 | ||||
| const { hasAccessByCodes } = useAccess(); | ||||
| 
 | ||||
| export interface LeftSideItem { | ||||
|   name: string; | ||||
|   menu: string; | ||||
|   count: Ref<number>; | ||||
| } | ||||
| 
 | ||||
| /** 跟进状态 */ | ||||
| export const FOLLOWUP_STATUS = [ | ||||
|   { label: '待跟进', value: false }, | ||||
|   { label: '已跟进', value: true }, | ||||
| ]; | ||||
| 
 | ||||
| /** 归属范围 */ | ||||
| export const SCENE_TYPES = [ | ||||
|   { label: '我负责的', value: 1 }, | ||||
|   { label: '我参与的', value: 2 }, | ||||
|   { label: '下属负责的', value: 3 }, | ||||
| ]; | ||||
| 
 | ||||
| /** 联系状态 */ | ||||
| export const CONTACT_STATUS = [ | ||||
|   { label: '今日需联系', value: 1 }, | ||||
|   { label: '已逾期', value: 2 }, | ||||
|   { label: '已联系', value: 3 }, | ||||
| ]; | ||||
| 
 | ||||
| /** 审批状态 */ | ||||
| export const AUDIT_STATUS = [ | ||||
|   { label: '待审批', value: 10 }, | ||||
|   { label: '审核通过', value: 20 }, | ||||
|   { label: '审核不通过', value: 30 }, | ||||
| ]; | ||||
| 
 | ||||
| /** 回款提醒类型 */ | ||||
| export const RECEIVABLE_REMIND_TYPE = [ | ||||
|   { label: '待回款', value: 1 }, | ||||
|   { label: '已逾期', value: 2 }, | ||||
|   { label: '已回款', value: 3 }, | ||||
| ]; | ||||
| 
 | ||||
| /** 合同过期状态 */ | ||||
| export const CONTRACT_EXPIRY_TYPE = [ | ||||
|   { label: '即将过期', value: 1 }, | ||||
|   { label: '已过期', value: 2 }, | ||||
| ]; | ||||
| 
 | ||||
| export const useLeftSides = ( | ||||
|   customerTodayContactCount: Ref<number>, | ||||
|   clueFollowCount: Ref<number>, | ||||
|   customerFollowCount: Ref<number>, | ||||
|   customerPutPoolRemindCount: Ref<number>, | ||||
|   contractAuditCount: Ref<number>, | ||||
|   contractRemindCount: Ref<number>, | ||||
|   receivableAuditCount: Ref<number>, | ||||
|   receivablePlanRemindCount: Ref<number>, | ||||
| ): LeftSideItem[] => { | ||||
|   return [ | ||||
|     { | ||||
|       name: '今日需联系客户', | ||||
|       menu: 'customerTodayContact', | ||||
|       count: customerTodayContactCount, | ||||
|     }, | ||||
|     { | ||||
|       name: '分配给我的线索', | ||||
|       menu: 'clueFollow', | ||||
|       count: clueFollowCount, | ||||
|     }, | ||||
|     { | ||||
|       name: '分配给我的客户', | ||||
|       menu: 'customerFollow', | ||||
|       count: customerFollowCount, | ||||
|     }, | ||||
|     { | ||||
|       name: '待进入公海的客户', | ||||
|       menu: 'customerPutPoolRemind', | ||||
|       count: customerPutPoolRemindCount, | ||||
|     }, | ||||
|     { | ||||
|       name: '待审核合同', | ||||
|       menu: 'contractAudit', | ||||
|       count: contractAuditCount, | ||||
|     }, | ||||
|     { | ||||
|       name: '待审核回款', | ||||
|       menu: 'receivableAudit', | ||||
|       count: receivableAuditCount, | ||||
|     }, | ||||
|     { | ||||
|       name: '待回款提醒', | ||||
|       menu: 'receivablePlanRemind', | ||||
|       count: receivablePlanRemindCount, | ||||
|     }, | ||||
|     { | ||||
|       name: '即将到期的合同', | ||||
|       menu: 'contractRemind', | ||||
|       count: contractRemindCount, | ||||
|     }, | ||||
|   ]; | ||||
| }; | ||||
| 
 | ||||
| /** 分配给我的线索 列表的搜索表单 */ | ||||
| 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: '线索名称', | ||||
|       minWidth: 160, | ||||
|       fixed: 'left', | ||||
|       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: 'detailAddress', | ||||
|       title: '地址', | ||||
|       minWidth: 180, | ||||
|     }, | ||||
|     { | ||||
|       field: 'industryId', | ||||
|       title: '客户行业', | ||||
|       minWidth: 100, | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'level', | ||||
|       title: '客户级别', | ||||
|       minWidth: 100, | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_CUSTOMER_LEVEL }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactNextTime', | ||||
|       title: '下次联系时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'remark', | ||||
|       title: '备注', | ||||
|       minWidth: 200, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactLastTime', | ||||
|       title: '最后跟进时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactLastContent', | ||||
|       title: '最后跟进记录', | ||||
|       minWidth: 200, | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserName', | ||||
|       title: '负责人', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserDeptName', | ||||
|       title: '所属部门', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'updateTime', | ||||
|       title: '更新时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'creatorName', | ||||
|       title: '创建人', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 合同审核列表的搜索表单 */ | ||||
| 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<T = CrmContractApi.Contract>( | ||||
|   onActionClick: OnActionClickFn<T>, | ||||
| ): VxeTableGridOptions['columns'] { | ||||
|   return [ | ||||
|     { | ||||
|       field: 'no', | ||||
|       title: '合同编号', | ||||
|       minWidth: 160, | ||||
|       fixed: 'left', | ||||
|     }, | ||||
|     { | ||||
|       field: 'name', | ||||
|       title: '合同名称', | ||||
|       minWidth: 160, | ||||
|       slots: { | ||||
|         default: 'name', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'customerName', | ||||
|       title: '客户名称', | ||||
|       minWidth: 160, | ||||
|       slots: { | ||||
|         default: 'customerName', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'businessName', | ||||
|       title: '商机名称', | ||||
|       minWidth: 160, | ||||
|       slots: { | ||||
|         default: 'businessName', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'price', | ||||
|       title: '合同金额(元)', | ||||
|       minWidth: 120, | ||||
|       formatter: 'formatAmount', | ||||
|     }, | ||||
|     { | ||||
|       field: 'orderDate', | ||||
|       title: '下单时间', | ||||
|       minWidth: 120, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'startTime', | ||||
|       title: '合同开始时间', | ||||
|       minWidth: 120, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'endTime', | ||||
|       title: '合同结束时间', | ||||
|       minWidth: 120, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactName', | ||||
|       title: '客户签约人', | ||||
|       minWidth: 130, | ||||
|       slots: { | ||||
|         default: 'contactName', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'signUserName', | ||||
|       title: '公司签约人', | ||||
|       minWidth: 130, | ||||
|     }, | ||||
|     { | ||||
|       field: 'remark', | ||||
|       title: '备注', | ||||
|       minWidth: 200, | ||||
|     }, | ||||
|     { | ||||
|       field: 'totalReceivablePrice', | ||||
|       title: '已回款金额(元)', | ||||
|       minWidth: 140, | ||||
|       formatter: 'formatAmount', | ||||
|     }, | ||||
|     { | ||||
|       field: 'noReceivablePrice', | ||||
|       title: '未回款金额(元)', | ||||
|       minWidth: 120, | ||||
|       formatter: 'formatAmount', | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactLastTime', | ||||
|       title: '最后跟进时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserName', | ||||
|       title: '负责人', | ||||
|       minWidth: 120, | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserDeptName', | ||||
|       title: '所属部门', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'updateTime', | ||||
|       title: '更新时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'creatorName', | ||||
|       title: '创建人', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'auditStatus', | ||||
|       title: '合同状态', | ||||
|       minWidth: 120, | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'operation', | ||||
|       title: '操作', | ||||
|       minWidth: 130, | ||||
|       align: 'center', | ||||
|       fixed: 'right', | ||||
|       cellRender: { | ||||
|         attrs: { | ||||
|           nameField: 'no', | ||||
|           nameTitle: '合同编号', | ||||
|           onClick: onActionClick, | ||||
|         }, | ||||
|         name: 'CellOperation', | ||||
|         options: [ | ||||
|           { | ||||
|             code: 'processDetail', | ||||
|             show: hasAccessByCodes(['crm:contract:update']), | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 客户跟进列表的搜索表单 */ | ||||
| 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: '客户名称', | ||||
|       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: 100, | ||||
|       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: '下次联系时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'remark', | ||||
|       title: '备注', | ||||
|       minWidth: 200, | ||||
|     }, | ||||
|     { | ||||
|       field: 'lockStatus', | ||||
|       title: '锁定状态', | ||||
|       minWidth: 100, | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'dealStatus', | ||||
|       title: '成交状态', | ||||
|       minWidth: 100, | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactLastTime', | ||||
|       title: '最后跟进时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'contactLastContent', | ||||
|       title: '最后跟进记录', | ||||
|       minWidth: 200, | ||||
|     }, | ||||
|     { | ||||
|       field: 'detailAddress', | ||||
|       title: '地址', | ||||
|       minWidth: 200, | ||||
|     }, | ||||
|     { | ||||
|       field: 'poolDay', | ||||
|       title: '距离进入公海天数', | ||||
|       minWidth: 180, | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserName', | ||||
|       title: '负责人', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserDeptName', | ||||
|       title: '所属部门', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'updateTime', | ||||
|       title: '更新时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'creatorName', | ||||
|       title: '创建人', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 回款审核列表的搜索表单 */ | ||||
| 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: '回款编号', | ||||
|       minWidth: 180, | ||||
|       fixed: 'left', | ||||
|       slots: { | ||||
|         default: 'no', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'customerName', | ||||
|       title: '客户名称', | ||||
|       minWidth: 120, | ||||
|       slots: { | ||||
|         default: 'customerName', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contractNo', | ||||
|       title: '合同编号', | ||||
|       minWidth: 180, | ||||
|       slots: { | ||||
|         default: 'contractNo', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'returnTime', | ||||
|       title: '回款日期', | ||||
|       minWidth: 150, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'price', | ||||
|       title: '回款金额(元)', | ||||
|       minWidth: 140, | ||||
|       formatter: 'formatAmount', | ||||
|     }, | ||||
|     { | ||||
|       field: 'returnType', | ||||
|       title: '回款方式', | ||||
|       minWidth: 130, | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'remark', | ||||
|       title: '备注', | ||||
|       minWidth: 200, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contract.totalPrice', | ||||
|       title: '合同金额(元)', | ||||
|       minWidth: 140, | ||||
|       formatter: 'formatAmount', | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserName', | ||||
|       title: '负责人', | ||||
|       minWidth: 120, | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserDeptName', | ||||
|       title: '所属部门', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'updateTime', | ||||
|       title: '更新时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'creatorName', | ||||
|       title: '创建人', | ||||
|       minWidth: 120, | ||||
|     }, | ||||
|     { | ||||
|       field: 'auditStatus', | ||||
|       title: '回款状态', | ||||
|       minWidth: 120, | ||||
|       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: '客户名称', | ||||
|       minWidth: 160, | ||||
|       fixed: 'left', | ||||
|       slots: { | ||||
|         default: 'customerName', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contractNo', | ||||
|       title: '合同编号', | ||||
|       minWidth: 200, | ||||
|     }, | ||||
|     { | ||||
|       field: 'period', | ||||
|       title: '期数', | ||||
|       minWidth: 160, | ||||
|       slots: { | ||||
|         default: 'period', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'price', | ||||
|       title: '计划回款金额(元)', | ||||
|       minWidth: 120, | ||||
|       formatter: 'formatAmount', | ||||
|     }, | ||||
|     { | ||||
|       field: 'returnTime', | ||||
|       title: '计划回款日期', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'remindDays', | ||||
|       title: '提前几天提醒', | ||||
|       minWidth: 150, | ||||
|     }, | ||||
|     { | ||||
|       field: 'remindTime', | ||||
|       title: '提醒日期', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'returnType', | ||||
|       title: '回款方式', | ||||
|       minWidth: 120, | ||||
|       fixed: 'right', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'remark', | ||||
|       title: '备注', | ||||
|       minWidth: 200, | ||||
|     }, | ||||
|     { | ||||
|       field: 'ownerUserName', | ||||
|       title: '负责人', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'receivable.price', | ||||
|       title: '实际回款金额(元)', | ||||
|       minWidth: 160, | ||||
|       formatter: 'formatAmount', | ||||
|     }, | ||||
|     { | ||||
|       field: 'receivable.returnTime', | ||||
|       title: '实际回款日期', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'updateTime', | ||||
|       title: '更新时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'creatorName', | ||||
|       title: '创建人', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       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']), | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
|  | @ -1,34 +1,121 @@ | |||
| <script lang="ts" setup> | ||||
| import { computed, onActivated, onMounted, ref } from 'vue'; | ||||
| 
 | ||||
| import { Page } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| import { Badge, Card, List } from 'ant-design-vue'; | ||||
| 
 | ||||
| import * as ClueApi from '#/api/crm/clue'; | ||||
| 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'; | ||||
| </script> | ||||
| 
 | ||||
| import { useLeftSides } from './data'; | ||||
| import ClueFollowList from './modules/ClueFollowList.vue'; | ||||
| import ContractAuditList from './modules/ContractAuditList.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'; | ||||
| 
 | ||||
| defineOptions({ name: 'CrmBacklog' }); | ||||
| 
 | ||||
| const leftMenu = ref('customerTodayContact'); | ||||
| 
 | ||||
| const clueFollowCount = ref(0); | ||||
| const customerFollowCount = ref(0); | ||||
| const customerPutPoolRemindCount = ref(0); | ||||
| const customerTodayContactCount = ref(0); | ||||
| const contractAuditCount = ref(0); | ||||
| const contractRemindCount = ref(0); | ||||
| const receivableAuditCount = ref(0); | ||||
| const receivablePlanRemindCount = ref(0); | ||||
| 
 | ||||
| const leftSides = useLeftSides( | ||||
|   customerTodayContactCount, | ||||
|   clueFollowCount, | ||||
|   customerFollowCount, | ||||
|   customerPutPoolRemindCount, | ||||
|   contractAuditCount, | ||||
|   contractRemindCount, | ||||
|   receivableAuditCount, | ||||
|   receivablePlanRemindCount, | ||||
| ); | ||||
| 
 | ||||
| const currentComponent = computed(() => { | ||||
|   const components = { | ||||
|     customerTodayContact: CustomerTodayContactList, | ||||
|     clueFollow: ClueFollowList, | ||||
|     contractAudit: ContractAuditList, | ||||
|     receivableAudit: ReceivableAuditList, | ||||
|     contractRemind: ContractRemindList, | ||||
|     customerFollow: CustomerFollowList, | ||||
|     customerPutPoolRemind: CustomerPutPoolRemindList, | ||||
|     receivablePlanRemind: ReceivablePlanRemindList, | ||||
|   } as const; | ||||
|   return components[leftMenu.value as keyof typeof components]; | ||||
| }); | ||||
| 
 | ||||
| /** 侧边点击 */ | ||||
| function sideClick(item: { menu: string }) { | ||||
|   leftMenu.value = item.menu; | ||||
| } | ||||
| 
 | ||||
| /** 获取数量 */ | ||||
| async function getCount() { | ||||
|   customerTodayContactCount.value = | ||||
|     await CustomerApi.getTodayContactCustomerCount(); | ||||
|   customerPutPoolRemindCount.value = | ||||
|     await CustomerApi.getPutPoolRemindCustomerCount(); | ||||
|   customerFollowCount.value = await CustomerApi.getFollowCustomerCount(); | ||||
|   clueFollowCount.value = await ClueApi.getFollowClueCount(); | ||||
|   contractAuditCount.value = await ContractApi.getAuditContractCount(); | ||||
|   contractRemindCount.value = await ContractApi.getRemindContractCount(); | ||||
|   receivableAuditCount.value = await ReceivableApi.getAuditReceivableCount(); | ||||
|   receivablePlanRemindCount.value = | ||||
|     await ReceivablePlanApi.getReceivablePlanRemindCount(); | ||||
| } | ||||
| 
 | ||||
| /** 激活时 */ | ||||
| onActivated(async () => { | ||||
|   getCount(); | ||||
| }); | ||||
| 
 | ||||
| /** 初始化 */ | ||||
| onMounted(async () => { | ||||
|   getCount(); | ||||
| }); | ||||
| </script> | ||||
| <template> | ||||
|   <Page> | ||||
|     <DocAlert | ||||
|       title="【通用】跟进记录、待办事项" | ||||
|       url="https://doc.iocoder.cn/crm/follow-up/" | ||||
|     /> | ||||
|     <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/backlog/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/crm/backlog/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|   <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"> | ||||
|           <template #renderItem="{ item }"> | ||||
|             <List.Item> | ||||
|               <List.Item.Meta> | ||||
|                 <template #title> | ||||
|                   <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> | ||||
|             </List.Item> | ||||
|           </template> | ||||
|         </List> | ||||
|       </Card> | ||||
|       <component class="ml-4 w-4/5" :is="currentComponent" /> | ||||
|     </div> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -0,0 +1,58 @@ | |||
| <!-- 分配给我的线索 --> | ||||
| <script lang="ts" setup> | ||||
| import type { CrmClueApi } from '#/api/crm/clue'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getCluePage } from '#/api/crm/clue'; | ||||
| 
 | ||||
| import { useClueFollowColumns, useClueFollowFormSchema } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 打开线索详情 */ | ||||
| function onDetail(row: CrmClueApi.Clue) { | ||||
|   push({ name: 'CrmClueDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useClueFollowFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useClueFollowColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getCluePage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             transformStatus: false, | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="分配给我的线索"> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="onDetail(row)">{{ row.name }}</Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -0,0 +1,111 @@ | |||
| <!-- 待审核合同 --> | ||||
| <script lang="ts" setup> | ||||
| import type { OnActionClickParams } 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 { getContractPage } from '#/api/crm/contract'; | ||||
| 
 | ||||
| import { useContractAuditFormSchema, useContractColumns } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 查看审批 */ | ||||
| function openProcessDetail(row: CrmContractApi.Contract) { | ||||
|   push({ | ||||
|     name: 'BpmProcessInstanceDetail', | ||||
|     query: { id: row.processInstanceId }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 打开合同详情 */ | ||||
| function openContractDetail(row: CrmContractApi.Contract) { | ||||
|   push({ name: 'CrmContractDetail', params: { id: row.id } }); | ||||
| } | ||||
| /** 打开客户详情 */ | ||||
| function openCustomerDetail(row: CrmContractApi.Contract) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| /** 打开联系人详情 */ | ||||
| function openContactDetail(row: CrmContractApi.Contract) { | ||||
|   push({ name: 'CrmContactDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| /** 打开商机详情 */ | ||||
| function openBusinessDetail(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: useContractAuditFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useContractColumns(onActionClick), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getContractPage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             sceneType: 1, // 我负责的 | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="待审核合同"> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="openContractDetail(row)"> | ||||
|         {{ row.name }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #customerName="{ row }"> | ||||
|       <Button type="link" @click="openCustomerDetail(row)"> | ||||
|         {{ row.customerName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #businessName="{ row }"> | ||||
|       <Button type="link" @click="openBusinessDetail(row)"> | ||||
|         {{ row.businessName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #contactName="{ row }"> | ||||
|       <Button type="link" @click="openContactDetail(row)"> | ||||
|         {{ row.contactName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -0,0 +1,111 @@ | |||
| <!-- 即将到期的合同 --> | ||||
| <script lang="ts" setup> | ||||
| import type { OnActionClickParams } 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 { getContractPage } from '#/api/crm/contract'; | ||||
| 
 | ||||
| import { useContractColumns, useContractRemindFormSchema } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 查看审批 */ | ||||
| function openProcessDetail(row: CrmContractApi.Contract) { | ||||
|   push({ | ||||
|     name: 'BpmProcessInstanceDetail', | ||||
|     query: { id: row.processInstanceId }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 打开合同详情 */ | ||||
| function openContractDetail(row: CrmContractApi.Contract) { | ||||
|   push({ name: 'CrmContractDetail', params: { id: row.id } }); | ||||
| } | ||||
| /** 打开客户详情 */ | ||||
| function openCustomerDetail(row: CrmContractApi.Contract) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| /** 打开联系人详情 */ | ||||
| function openContactDetail(row: CrmContractApi.Contract) { | ||||
|   push({ name: 'CrmContactDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| /** 打开商机详情 */ | ||||
| function openBusinessDetail(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(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useContractColumns(onActionClick), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getContractPage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             sceneType: 1, // 自己负责的 | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="即将到期的合同"> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="openContractDetail(row)"> | ||||
|         {{ row.name }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #customerName="{ row }"> | ||||
|       <Button type="link" @click="openCustomerDetail(row)"> | ||||
|         {{ row.customerName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #businessName="{ row }"> | ||||
|       <Button type="link" @click="openBusinessDetail(row)"> | ||||
|         {{ row.businessName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #contactName="{ row }"> | ||||
|       <Button type="link" @click="openContactDetail(row)"> | ||||
|         {{ row.contactName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -0,0 +1,58 @@ | |||
| <!-- 分配给我的客户 --> | ||||
| <script lang="ts" setup> | ||||
| import type { CrmCustomerApi } from '#/api/crm/customer'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getCustomerPage } from '#/api/crm/customer'; | ||||
| 
 | ||||
| import { useCustomerColumns, useCustomerFollowFormSchema } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 打开客户详情 */ | ||||
| function onDetail(row: CrmCustomerApi.Customer) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useCustomerFollowFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useCustomerColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getCustomerPage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             sceneType: 1, | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="分配给我的客户"> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="onDetail(row)">{{ row.name }}</Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -0,0 +1,58 @@ | |||
| <!-- 待进入公海的客户 --> | ||||
| <script lang="ts" setup> | ||||
| import type { CrmCustomerApi } from '#/api/crm/customer'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getCustomerPage } from '#/api/crm/customer'; | ||||
| 
 | ||||
| import { useCustomerColumns, useCustomerPutPoolFormSchema } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 打开客户详情 */ | ||||
| function onDetail(row: CrmCustomerApi.Customer) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useCustomerPutPoolFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useCustomerColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getCustomerPage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             pool: true, // 固定 公海参数为 true | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="待进入公海的客户"> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="onDetail(row)">{{ row.name }}</Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -0,0 +1,58 @@ | |||
| <!-- 今日需联系客户 --> | ||||
| <script lang="ts" setup> | ||||
| import type { CrmCustomerApi } from '#/api/crm/customer'; | ||||
| 
 | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getCustomerPage } from '#/api/crm/customer'; | ||||
| 
 | ||||
| import { useCustomerColumns, useCustomerTodayContactFormSchema } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 打开客户详情 */ | ||||
| function onDetail(row: CrmCustomerApi.Customer) { | ||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| const [Grid] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useCustomerTodayContactFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useCustomerColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getCustomerPage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             pool: null, // 是否公海数据 | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Grid table-title="今日需联系客户"> | ||||
|     <template #name="{ row }"> | ||||
|       <Button type="link" @click="onDetail(row)">{{ row.name }}</Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -0,0 +1,104 @@ | |||
| <!-- 待审核回款 --> | ||||
| <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 { | ||||
|   useReceivableAuditColumns, | ||||
|   useReceivableAuditFormSchema, | ||||
| } from '../data'; | ||||
| 
 | ||||
| const { push } = useRouter(); | ||||
| 
 | ||||
| /** 查看审批 */ | ||||
| function openProcessDetail(row: CrmReceivableApi.Receivable) { | ||||
|   push({ | ||||
|     name: 'BpmProcessInstanceDetail', | ||||
|     query: { id: row.processInstanceId }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 打开回款详情 */ | ||||
| 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 openContractDetail(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(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useReceivableAuditColumns(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 #no="{ row }"> | ||||
|       <Button type="link" @click="openDetail(row)"> | ||||
|         {{ row.no }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #customerName="{ row }"> | ||||
|       <Button type="link" @click="openCustomerDetail(row)"> | ||||
|         {{ row.customerName }} | ||||
|       </Button> | ||||
|     </template> | ||||
|     <template #contractNo="{ row }"> | ||||
|       <Button type="link" @click="openContractDetail(row)"> | ||||
|         {{ row.contractNo }} | ||||
|       </Button> | ||||
|     </template> | ||||
|   </Grid> | ||||
| </template> | ||||
|  | @ -0,0 +1,90 @@ | |||
| <!-- 待回款提醒 --> | ||||
| <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> | ||||
|  | @ -0,0 +1,131 @@ | |||
| import type { VbenFormSchema } from '#/adapter/form'; | ||||
| import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { CrmBusinessStatusApi } from '#/api/crm/business/status'; | ||||
| 
 | ||||
| import { useAccess } from '@vben/access'; | ||||
| import { getRangePickerDefaultProps } from '@vben/utils'; | ||||
| 
 | ||||
| import { z } from '#/adapter/form'; | ||||
| import { CommonStatusEnum } from '#/utils/constants'; | ||||
| import { DICT_TYPE, getDictOptions } from '#/utils/dict'; | ||||
| 
 | ||||
| const { hasAccessByCodes } = useAccess(); | ||||
| 
 | ||||
| /** 新增/修改的表单 */ | ||||
| export function useFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'id', | ||||
|       component: 'Input', | ||||
|       dependencies: { | ||||
|         triggerFields: [''], | ||||
|         show: () => false, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'name', | ||||
|       label: '状态组名', | ||||
|       component: 'Input', | ||||
|       rules: 'required', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'deptIds', | ||||
|       label: '应用部门', | ||||
|       component: 'TreeSelect', | ||||
|       componentProps: { | ||||
|         multiple: true, | ||||
|         treeCheckable: true, | ||||
|         showCheckedStrategy: 'SHOW_PARENT', | ||||
|         placeholder: '请选择应用部门', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'status', | ||||
|       label: '状态', | ||||
|       component: 'RadioGroup', | ||||
|       componentProps: { | ||||
|         options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), | ||||
|         buttonStyle: 'solid', | ||||
|         optionType: 'button', | ||||
|       }, | ||||
|       rules: z.number().default(CommonStatusEnum.ENABLE), | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 列表的搜索表单 */ | ||||
| export function useGridFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'name', | ||||
|       label: '状态组名', | ||||
|       component: 'Input', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'createTime', | ||||
|       label: '创建时间', | ||||
|       component: 'RangePicker', | ||||
|       componentProps: { | ||||
|         ...getRangePickerDefaultProps(), | ||||
|         allowClear: true, | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 列表的字段 */ | ||||
| export function useGridColumns<T = CrmBusinessStatusApi.BusinessStatus>( | ||||
|   onActionClick: OnActionClickFn<T>, | ||||
| ): VxeTableGridOptions['columns'] { | ||||
|   return [ | ||||
|     { | ||||
|       field: 'name', | ||||
|       title: '状态组名', | ||||
|       minWidth: 200, | ||||
|     }, | ||||
|     { | ||||
|       field: 'deptNames', | ||||
|       title: '应用部门', | ||||
|       minWidth: 200, | ||||
|       formatter: ({ cellValue }) => { | ||||
|         return cellValue?.length > 0 ? cellValue.join(' ') : '全公司'; | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'creator', | ||||
|       title: '创建人', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'operation', | ||||
|       title: '操作', | ||||
|       width: 160, | ||||
|       fixed: 'right', | ||||
|       align: 'center', | ||||
|       cellRender: { | ||||
|         name: 'TableAction', | ||||
|         props: { | ||||
|           actions: [ | ||||
|             { | ||||
|               label: '编辑', | ||||
|               code: 'edit', | ||||
|               show: hasAccessByCodes(['crm:business-status:update']), | ||||
|             }, | ||||
|             { | ||||
|               label: '删除', | ||||
|               code: 'delete', | ||||
|               show: hasAccessByCodes(['crm:business-status:delete']), | ||||
|             }, | ||||
|           ], | ||||
|           onActionClick, | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
|  | @ -1,38 +1,137 @@ | |||
| <script lang="ts" setup> | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import type { | ||||
|   OnActionClickParams, | ||||
|   VxeTableGridOptions, | ||||
| } from '#/adapter/vxe-table'; | ||||
| import type { CrmBusinessStatusApi } from '#/api/crm/business/status'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| import { Page, useVbenModal } from '@vben/common-ui'; | ||||
| import { Plus } from '@vben/icons'; | ||||
| 
 | ||||
| import { Button, message } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { | ||||
|   deleteBusinessStatus, | ||||
|   getBusinessStatusPage, | ||||
| } from '#/api/crm/business/status'; | ||||
| 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 onCreate() { | ||||
|   formModalApi.setData(null).open(); | ||||
| } | ||||
| 
 | ||||
| /** 删除商机状态 */ | ||||
| async function onDelete(row: CrmBusinessStatusApi.BusinessStatus) { | ||||
|   const hideLoading = message.loading({ | ||||
|     content: $t('ui.actionMessage.deleting', [row.name]), | ||||
|     duration: 0, | ||||
|     key: 'action_process_msg', | ||||
|   }); | ||||
|   try { | ||||
|     await deleteBusinessStatus(row.id as number); | ||||
|     message.success({ | ||||
|       content: $t('ui.actionMessage.deleteSuccess', [row.name]), | ||||
|       key: 'action_process_msg', | ||||
|     }); | ||||
|     onRefresh(); | ||||
|   } catch { | ||||
|     hideLoading(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 编辑商机状态 */ | ||||
| function onEdit(row: CrmBusinessStatusApi.BusinessStatus) { | ||||
|   formModalApi.setData(row).open(); | ||||
| } | ||||
| 
 | ||||
| /** 表格操作按钮的回调函数 */ | ||||
| function onActionClick({ | ||||
|   code, | ||||
|   row, | ||||
| }: OnActionClickParams<CrmBusinessStatusApi.BusinessStatus>) { | ||||
|   switch (code) { | ||||
|     case 'delete': { | ||||
|       onDelete(row); | ||||
|       break; | ||||
|     } | ||||
|     case 'edit': { | ||||
|       onEdit(row); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const [Grid, gridApi] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useGridFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useGridColumns(onActionClick), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getBusinessStatusPage({ | ||||
|             page: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   } as VxeTableGridOptions<CrmBusinessStatusApi.BusinessStatus>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|     <DocAlert | ||||
|       title="【商机】商机管理、商机状态" | ||||
|       url="https://doc.iocoder.cn/crm/business/" | ||||
|     /> | ||||
|     <DocAlert | ||||
|       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/business/status/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/crm/business/status/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="【商机】商机管理、商机状态" | ||||
|         url="https://doc.iocoder.cn/crm/business/" | ||||
|       /> | ||||
|       <DocAlert | ||||
|         title="【通用】数据权限" | ||||
|         url="https://doc.iocoder.cn/crm/permission/" | ||||
|       /> | ||||
|     </template> | ||||
| 
 | ||||
|     <FormModal @success="onRefresh" /> | ||||
|     <Grid table-title="商机状态列表"> | ||||
|       <template #toolbar-tools> | ||||
|         <Button | ||||
|           type="primary" | ||||
|           @click="onCreate" | ||||
|           v-access:code="['crm:business-status:create']" | ||||
|         > | ||||
|           <Plus class="size-5" /> | ||||
|           {{ $t('ui.actionTitle.create', ['商机状态']) }} | ||||
|         </Button> | ||||
|       </template> | ||||
|     </Grid> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -0,0 +1,94 @@ | |||
| <script lang="ts" setup> | ||||
| import type { CrmBusinessStatusApi } from '#/api/crm/business/status'; | ||||
| 
 | ||||
| import { computed, ref } from 'vue'; | ||||
| 
 | ||||
| import { useVbenModal } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { message } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenForm } from '#/adapter/form'; | ||||
| import { | ||||
|   createBusinessStatus, | ||||
|   getBusinessStatus, | ||||
|   updateBusinessStatus, | ||||
| } from '#/api/crm/business/status'; | ||||
| import { $t } from '#/locales'; | ||||
| 
 | ||||
| import { useFormSchema } from '../data'; | ||||
| 
 | ||||
| const emit = defineEmits(['success']); | ||||
| const formData = ref<CrmBusinessStatusApi.BusinessStatusType>(); | ||||
| 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 CrmBusinessStatusApi.BusinessStatusType; | ||||
|     try { | ||||
|       await (formData.value?.id | ||||
|         ? updateBusinessStatus(data) | ||||
|         : createBusinessStatus(data)); | ||||
|       // 关闭并提示 | ||||
|       await modalApi.close(); | ||||
|       emit('success'); | ||||
|       message.success({ | ||||
|         content: $t('ui.actionMessage.operationSuccess'), | ||||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|     if (!isOpen) { | ||||
|       formData.value = undefined; | ||||
|       return; | ||||
|     } | ||||
|     // 加载数据 | ||||
|     const data = modalApi.getData<CrmBusinessStatusApi.BusinessStatusType>(); | ||||
|     if (!data || !data.id) { | ||||
|       return; | ||||
|     } | ||||
|     modalApi.lock(); | ||||
|     try { | ||||
|       formData.value = await getBusinessStatus(data.id as number); | ||||
|       // 设置到 values | ||||
|       if (formData.value) { | ||||
|         await formApi.setValues(formData.value); | ||||
|       } | ||||
|     } finally { | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Modal :title="getTitle" class="w-1/2"> | ||||
|     <Form class="mx-4" /> | ||||
|   </Modal> | ||||
| </template> | ||||
|  | @ -1,66 +0,0 @@ | |||
| <script lang="ts" setup> | ||||
| import { Page } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { Button, Card, message, notification, Space } from 'ant-design-vue'; | ||||
| 
 | ||||
| type NotificationType = 'error' | 'info' | 'success' | 'warning'; | ||||
| 
 | ||||
| function info() { | ||||
|   message.info('How many roads must a man walk down'); | ||||
| } | ||||
| 
 | ||||
| function error() { | ||||
|   message.error({ | ||||
|     content: 'Once upon a time you dressed so fine', | ||||
|     duration: 2500, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function warning() { | ||||
|   message.warning('How many roads must a man walk down'); | ||||
| } | ||||
| function success() { | ||||
|   message.success('Cause you walked hand in hand With another man in my place'); | ||||
| } | ||||
| 
 | ||||
| function notify(type: NotificationType) { | ||||
|   notification[type]({ | ||||
|     duration: 2500, | ||||
|     message: '说点啥呢', | ||||
|     type, | ||||
|   }); | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Page | ||||
|     description="支持多语言,主题功能集成切换等" | ||||
|     title="Ant Design Vue组件使用演示" | ||||
|   > | ||||
|     <Card class="mb-5" title="按钮"> | ||||
|       <Space> | ||||
|         <Button>Default</Button> | ||||
|         <Button type="primary"> Primary </Button> | ||||
|         <Button> Info </Button> | ||||
|         <Button danger> Error </Button> | ||||
|       </Space> | ||||
|     </Card> | ||||
|     <Card class="mb-5" title="Message"> | ||||
|       <Space> | ||||
|         <Button @click="info"> 信息 </Button> | ||||
|         <Button danger @click="error"> 错误 </Button> | ||||
|         <Button @click="warning"> 警告 </Button> | ||||
|         <Button @click="success"> 成功 </Button> | ||||
|       </Space> | ||||
|     </Card> | ||||
| 
 | ||||
|     <Card class="mb-5" title="Notification"> | ||||
|       <Space> | ||||
|         <Button @click="notify('info')"> 信息 </Button> | ||||
|         <Button danger @click="notify('error')"> 错误 </Button> | ||||
|         <Button @click="notify('warning')"> 警告 </Button> | ||||
|         <Button @click="notify('success')"> 成功 </Button> | ||||
|       </Space> | ||||
|     </Card> | ||||
|   </Page> | ||||
| </template> | ||||
|  | @ -28,7 +28,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|     try { | ||||
|       formData.value = data; | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|     try { | ||||
|       formData.value = data; | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -110,7 +110,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       }); | ||||
|     } finally { | ||||
|       hideLoading(); | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -72,7 +72,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       // 设置到 values | ||||
|       await formApi.setValues(formData.value); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -60,7 +60,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -79,7 +79,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       // 设置到 values | ||||
|       await formApi.setValues(formData.value); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -78,7 +78,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       try { | ||||
|         data = await getDemo01Contact(data.id); | ||||
|       } finally { | ||||
|         modalApi.lock(false); | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } | ||||
|     // 设置到 values | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -86,7 +86,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       try { | ||||
|         data = await getDemo02Category(data.id); | ||||
|       } finally { | ||||
|         modalApi.lock(false); | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } | ||||
|     // 设置到 values | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -80,7 +80,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       try { | ||||
|         data = await getDemo03Course(data.id); | ||||
|       } finally { | ||||
|         modalApi.lock(false); | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } | ||||
|     // 设置到 values | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -80,7 +80,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       try { | ||||
|         data = await getDemo03Grade(data.id); | ||||
|       } finally { | ||||
|         modalApi.lock(false); | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } | ||||
|     // 设置到 values | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -78,7 +78,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       try { | ||||
|         data = await getDemo03Student(data.id); | ||||
|       } finally { | ||||
|         modalApi.lock(false); | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } | ||||
|     // 设置到 values | ||||
|  |  | |||
|  | @ -61,9 +61,8 @@ const [Modal, modalApi] = useVbenModal({ | |||
|     // 提交表单 | ||||
|     const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Student; | ||||
|     // 拼接子表的数据 | ||||
|     // TODO @puhui999:字段对不上 | ||||
|     data.demo03Courses = demo03CourseFormRef.value?.getData(); | ||||
|     data.demo03Grade = await demo03GradeFormRef.value?.getValues(); | ||||
|     data.demo03courses = demo03CourseFormRef.value?.getData(); | ||||
|     data.demo03grade = await demo03GradeFormRef.value?.getValues(); | ||||
|     try { | ||||
|       await (formData.value?.id | ||||
|         ? updateDemo03Student(data) | ||||
|  | @ -76,7 +75,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -95,7 +94,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       try { | ||||
|         data = await getDemo03Student(data.id); | ||||
|       } finally { | ||||
|         modalApi.lock(false); | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } | ||||
|     // 设置到 values | ||||
|  |  | |||
|  | @ -61,8 +61,8 @@ const [Modal, modalApi] = useVbenModal({ | |||
|     // 提交表单 | ||||
|     const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Student; | ||||
|     // 拼接子表的数据 | ||||
|     data.demo03Courses = demo03CourseFormRef.value?.getData(); | ||||
|     data.demo03Grade = await demo03GradeFormRef.value?.getValues(); | ||||
|     data.demo03courses = demo03CourseFormRef.value?.getData(); | ||||
|     data.demo03grade = await demo03GradeFormRef.value?.getValues(); | ||||
|     try { | ||||
|       await (formData.value?.id | ||||
|         ? updateDemo03Student(data) | ||||
|  | @ -75,7 +75,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -94,7 +94,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       try { | ||||
|         data = await getDemo03Student(data.id); | ||||
|       } finally { | ||||
|         modalApi.lock(false); | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } | ||||
|     // 设置到 values | ||||
|  |  | |||
|  | @ -82,7 +82,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -101,7 +101,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       try { | ||||
|         data = await getDemo01Contact(data.id); | ||||
|       } finally { | ||||
|         modalApi.lock(false); | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } | ||||
|     formData.value = data; | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -78,7 +78,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       // 设置到 values | ||||
|       await formApi.setValues(formData.value); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|     try { | ||||
|       formData.value = await getJobLog(data.id); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       // 获取下一次执行时间 | ||||
|       nextTimes.value = await getJobNextTimes(data.id); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -72,7 +72,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       // 设置到 values | ||||
|       await formApi.setValues(formData.value); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -0,0 +1,187 @@ | |||
| import type { VbenFormSchema } from '#/adapter/form'; | ||||
| import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| 
 | ||||
| import { useAccess } from '@vben/access'; | ||||
| 
 | ||||
| import { getAppList } from '#/api/pay/app'; | ||||
| import { DICT_TYPE, getDictOptions } from '#/utils/dict'; | ||||
| 
 | ||||
| const { hasAccessByCodes } = useAccess(); | ||||
| 
 | ||||
| /** 列表的搜索表单 */ | ||||
| export function useGridFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'appId', | ||||
|       label: '应用编号', | ||||
|       component: 'ApiSelect', | ||||
|       componentProps: { | ||||
|         api: async () => { | ||||
|           const data = await getAppList(); | ||||
|           return data.map((item) => ({ | ||||
|             label: item.name, | ||||
|             value: item.id, | ||||
|           })); | ||||
|         }, | ||||
|         autoSelect: 'first', | ||||
|         placeholder: '请选择数据源', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'type', | ||||
|       label: '通知类型', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: getDictOptions(DICT_TYPE.PAY_NOTIFY_TYPE, 'number'), | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'dataId', | ||||
|       label: '关联编号', | ||||
|       component: 'Input', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'status', | ||||
|       label: '通知状态', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: getDictOptions(DICT_TYPE.PAY_NOTIFY_STATUS, 'number'), | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'merchantOrderId', | ||||
|       label: '商户订单编号', | ||||
|       component: 'Input', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'createTime', | ||||
|       label: '创建时间', | ||||
|       component: 'DatePicker', | ||||
|       componentProps: { | ||||
|         type: 'daterange', | ||||
|         valueFormat: 'YYYY-MM-DD HH:mm:ss', | ||||
|         defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')], | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 列表的字段 */ | ||||
| export function useGridColumns<T = any>( | ||||
|   onActionClick: OnActionClickFn<T>, | ||||
| ): VxeTableGridOptions['columns'] { | ||||
|   return [ | ||||
|     { | ||||
|       field: 'id', | ||||
|       title: '任务编号', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'appName', | ||||
|       title: '应用编号', | ||||
|       minWidth: 120, | ||||
|     }, | ||||
|     { | ||||
|       field: 'merchantOrderId', | ||||
|       title: '商户订单编号', | ||||
|       minWidth: 180, | ||||
|     }, | ||||
|     { | ||||
|       field: 'type', | ||||
|       title: '通知类型', | ||||
|       minWidth: 120, | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.PAY_NOTIFY_TYPE }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'dataId', | ||||
|       title: '关联编号', | ||||
|       minWidth: 120, | ||||
|     }, | ||||
|     { | ||||
|       field: 'status', | ||||
|       title: '通知状态', | ||||
|       minWidth: 120, | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.PAY_NOTIFY_STATUS }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'lastExecuteTime', | ||||
|       title: '最后通知时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'nextNotifyTime', | ||||
|       title: '下次通知时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'notifyTimes', | ||||
|       title: '通知次数', | ||||
|       minWidth: 120, | ||||
|       cellRender: { | ||||
|         name: 'CellTag', | ||||
|         props: { | ||||
|           type: 'success', | ||||
|           content: '{notifyTimes} / {maxNotifyTimes}', | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'operation', | ||||
|       title: '操作', | ||||
|       minWidth: 100, | ||||
|       align: 'center', | ||||
|       fixed: 'right', | ||||
|       cellRender: { | ||||
|         attrs: { | ||||
|           onClick: onActionClick, | ||||
|         }, | ||||
|         name: 'CellOperation', | ||||
|         options: [ | ||||
|           { | ||||
|             code: 'detail', | ||||
|             show: hasAccessByCodes(['pay:notify:query']), | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 详情列表的字段 */ | ||||
| export const detailColumns = [ | ||||
|   { | ||||
|     label: '日志编号', | ||||
|     prop: 'id', | ||||
|     key: 'id', | ||||
|   }, | ||||
|   { | ||||
|     label: '通知状态', | ||||
|     prop: 'status', | ||||
|     key: 'status', | ||||
|   }, | ||||
|   { | ||||
|     label: '通知次数', | ||||
|     prop: 'notifyTimes', | ||||
|     key: 'notifyTimes', | ||||
|   }, | ||||
|   { | ||||
|     label: '通知时间', | ||||
|     prop: 'lastExecuteTime', | ||||
|     key: 'lastExecuteTime', | ||||
|   }, | ||||
|   { | ||||
|     label: '响应结果', | ||||
|     prop: 'response', | ||||
|     key: 'response', | ||||
|   }, | ||||
| ]; | ||||
|  | @ -1,31 +1,78 @@ | |||
| <script lang="ts" setup> | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import type { | ||||
|   OnActionClickParams, | ||||
|   VxeTableGridOptions, | ||||
| } from '#/adapter/vxe-table'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| import { Page, useVbenModal } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import * as PayNotifyApi from '#/api/pay/notify'; | ||||
| import { DocAlert } from '#/components/doc-alert'; | ||||
| </script> | ||||
| 
 | ||||
| import { useGridColumns, useGridFormSchema } from './data'; | ||||
| import Detail from './modules/detail.vue'; | ||||
| 
 | ||||
| const [NotifyDetailModal, notifyDetailModalApi] = useVbenModal({ | ||||
|   connectedComponent: Detail, | ||||
|   destroyOnClose: true, | ||||
| }); | ||||
| 
 | ||||
| /** 刷新表格 */ | ||||
| function onRefresh() { | ||||
|   gridApi.query(); | ||||
| } | ||||
| 
 | ||||
| /** 查看详情 */ | ||||
| function onDetail(row: any) { | ||||
|   notifyDetailModalApi.setData(row).open(); | ||||
| } | ||||
| 
 | ||||
| /** 表格操作按钮的回调函数 */ | ||||
| function onActionClick({ code, row }: OnActionClickParams<any>) { | ||||
|   switch (code) { | ||||
|     case 'detail': { | ||||
|       onDetail(row); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const [Grid, gridApi] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useGridFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useGridColumns(onActionClick), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await PayNotifyApi.getNotifyTaskPage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   } as VxeTableGridOptions<any>, | ||||
| }); | ||||
| </script> | ||||
| <template> | ||||
|   <Page> | ||||
|     <DocAlert title="支付功能开启" url="https://doc.iocoder.cn/pay/build/" /> | ||||
|     <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/pay/notify/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/pay/notify/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert title="支付功能开启" url="https://doc.iocoder.cn/pay/build/" /> | ||||
|     </template> | ||||
|     <NotifyDetailModal @success="onRefresh" /> | ||||
|     <Grid table-title="支付通知列表" /> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -0,0 +1,107 @@ | |||
| <script lang="ts" setup> | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| import { useVbenModal } from '@vben/common-ui'; | ||||
| import { formatDateTime } from '@vben/utils'; | ||||
| 
 | ||||
| import { Descriptions, Divider, Table, Tag } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { getNotifyTaskDetail } from '#/api/pay/notify'; | ||||
| import { DictTag } from '#/components/dict-tag'; | ||||
| import { DICT_TYPE } from '#/utils/dict'; | ||||
| 
 | ||||
| import { detailColumns } from '../data'; | ||||
| 
 | ||||
| const formData = ref(); | ||||
| 
 | ||||
| const [Modal, modalApi] = useVbenModal({ | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|     if (!isOpen) { | ||||
|       formData.value = undefined; | ||||
|       return; | ||||
|     } | ||||
|     // 加载数据 | ||||
|     const data = modalApi.getData(); | ||||
|     if (!data || !data.id) { | ||||
|       return; | ||||
|     } | ||||
|     modalApi.lock(); | ||||
|     try { | ||||
|       formData.value = await getNotifyTaskDetail(data.id); | ||||
|     } finally { | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| /** 打开弹窗 */ | ||||
| const open = (id: number) => { | ||||
|   modalApi.setData({ id }).open(); | ||||
| }; | ||||
| 
 | ||||
| defineExpose({ open }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Modal | ||||
|     title="通知详情" | ||||
|     class="w-1/2" | ||||
|     :show-cancel-button="false" | ||||
|     :show-confirm-button="false" | ||||
|   > | ||||
|     <Descriptions bordered :column="2" size="middle" class="mx-4"> | ||||
|       <Descriptions.Item label="商户订单编号"> | ||||
|         <Tag>{{ formData?.merchantOrderId }}</Tag> | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="通知状态"> | ||||
|         <DictTag | ||||
|           :type="DICT_TYPE.PAY_NOTIFY_STATUS" | ||||
|           :value="formData?.status" | ||||
|         /> | ||||
|       </Descriptions.Item> | ||||
| 
 | ||||
|       <Descriptions.Item label="应用编号"> | ||||
|         {{ formData?.appId }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="应用名称"> | ||||
|         {{ formData?.appName }} | ||||
|       </Descriptions.Item> | ||||
| 
 | ||||
|       <Descriptions.Item label="关联编号"> | ||||
|         {{ formData?.dataId }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="通知类型"> | ||||
|         <DictTag :type="DICT_TYPE.PAY_NOTIFY_TYPE" :value="formData?.type" /> | ||||
|       </Descriptions.Item> | ||||
| 
 | ||||
|       <Descriptions.Item label="通知次数"> | ||||
|         {{ formData?.notifyTimes }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="最大通知次数"> | ||||
|         {{ formData?.maxNotifyTimes }} | ||||
|       </Descriptions.Item> | ||||
| 
 | ||||
|       <Descriptions.Item label="最后通知时间"> | ||||
|         {{ formatDateTime(formData?.lastExecuteTime || '') }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="下次通知时间"> | ||||
|         {{ formatDateTime(formData?.nextNotifyTime || '') }} | ||||
|       </Descriptions.Item> | ||||
| 
 | ||||
|       <Descriptions.Item label="创建时间"> | ||||
|         {{ formatDateTime(formData?.createTime || '') }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="更新时间"> | ||||
|         {{ formatDateTime(formData?.updateTime || '') }} | ||||
|       </Descriptions.Item> | ||||
|     </Descriptions> | ||||
| 
 | ||||
|     <Divider /> | ||||
| 
 | ||||
|     <Descriptions bordered :column="1" size="middle" class="mx-4"> | ||||
|       <Descriptions.Item label="回调日志"> | ||||
|         <Table :data="formData.logs" :columns="detailColumns" /> | ||||
|       </Descriptions.Item> | ||||
|     </Descriptions> | ||||
|   </Modal> | ||||
| </template> | ||||
|  | @ -0,0 +1,177 @@ | |||
| import type { VbenFormSchema } from '#/adapter/form'; | ||||
| import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { PayRefundApi } from '#/api/pay/refund'; | ||||
| 
 | ||||
| import { useAccess } from '@vben/access'; | ||||
| 
 | ||||
| import { getAppList } from '#/api/pay/app'; | ||||
| import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '#/utils/dict'; | ||||
| 
 | ||||
| const { hasAccessByCodes } = useAccess(); | ||||
| 
 | ||||
| /** 列表的搜索表单 */ | ||||
| export function useGridFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'appId', | ||||
|       label: '应用编号', | ||||
|       component: 'ApiSelect', | ||||
|       componentProps: { | ||||
|         api: async () => { | ||||
|           const data = await getAppList(); | ||||
|           return data.map((item) => ({ | ||||
|             label: item.name, | ||||
|             value: item.id, | ||||
|           })); | ||||
|         }, | ||||
|         autoSelect: 'first', | ||||
|         placeholder: '请选择数据源', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'channelCode', | ||||
|       label: '退款渠道', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: getStrDictOptions(DICT_TYPE.PAY_CHANNEL_CODE), | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'merchantOrderId', | ||||
|       label: '商户支付单号', | ||||
|       component: 'Input', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'merchantRefundId', | ||||
|       label: '商户退款单号', | ||||
|       component: 'Input', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'channelOrderNo', | ||||
|       label: '渠道支付单号', | ||||
|       component: 'Input', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'channelRefundNo', | ||||
|       label: '渠道退款单号', | ||||
|       component: 'Input', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'status', | ||||
|       label: '退款状态', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         options: getIntDictOptions(DICT_TYPE.PAY_REFUND_STATUS), | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'createTime', | ||||
|       label: '创建时间', | ||||
|       component: 'DatePicker', | ||||
|       componentProps: { | ||||
|         type: 'daterange', | ||||
|         valueFormat: 'YYYY-MM-DD HH:mm:ss', | ||||
|         defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')], | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 列表的字段 */ | ||||
| export function useGridColumns<T = PayRefundApi.Refund>( | ||||
|   onActionClick: OnActionClickFn<T>, | ||||
| ): VxeTableGridOptions['columns'] { | ||||
|   return [ | ||||
|     { | ||||
|       field: 'id', | ||||
|       title: '编号', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '创建时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'payPrice', | ||||
|       title: '支付金额', | ||||
|       minWidth: 120, | ||||
|       cellRender: { | ||||
|         name: 'CellTag', | ||||
|         props: { | ||||
|           type: 'success', | ||||
|           content: '¥{payPrice}', | ||||
|           formatter: (value: number) => (value / 100).toFixed(2), | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'refundPrice', | ||||
|       title: '退款金额', | ||||
|       minWidth: 120, | ||||
|       cellRender: { | ||||
|         name: 'CellTag', | ||||
|         props: { | ||||
|           type: 'danger', | ||||
|           content: '¥{refundPrice}', | ||||
|           formatter: (value: number) => (value / 100).toFixed(2), | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'merchantRefundId', | ||||
|       title: '退款订单号', | ||||
|       minWidth: 300, | ||||
|       cellRender: { | ||||
|         name: 'CellTag', | ||||
|         props: { | ||||
|           type: 'info', | ||||
|           content: '商户 {merchantRefundId}', | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'channelRefundNo', | ||||
|       title: '渠道退款单号', | ||||
|       minWidth: 200, | ||||
|       cellRender: { | ||||
|         name: 'CellTag', | ||||
|         props: { | ||||
|           type: 'success', | ||||
|           content: '{channelRefundNo}', | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'status', | ||||
|       title: '退款状态', | ||||
|       minWidth: 120, | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.PAY_REFUND_STATUS }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'operation', | ||||
|       title: '操作', | ||||
|       minWidth: 100, | ||||
|       align: 'center', | ||||
|       fixed: 'right', | ||||
|       cellRender: { | ||||
|         attrs: { | ||||
|           onClick: onActionClick, | ||||
|         }, | ||||
|         name: 'CellOperation', | ||||
|         options: [ | ||||
|           { | ||||
|             code: 'detail', | ||||
|             show: hasAccessByCodes(['pay:refund:query']), | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
|  | @ -1,34 +1,104 @@ | |||
| <script lang="ts" setup> | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import type { | ||||
|   OnActionClickParams, | ||||
|   VxeTableGridOptions, | ||||
| } from '#/adapter/vxe-table'; | ||||
| 
 | ||||
| import { Page, useVbenModal } from '@vben/common-ui'; | ||||
| import { Download } from '@vben/icons'; | ||||
| import { downloadFileFromBlobPart } from '@vben/utils'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import * as RefundApi from '#/api/pay/refund'; | ||||
| import { DocAlert } from '#/components/doc-alert'; | ||||
| </script> | ||||
| import { $t } from '#/locales'; | ||||
| 
 | ||||
| import { useGridColumns, useGridFormSchema } from './data'; | ||||
| import Detail from './modules/detail.vue'; | ||||
| 
 | ||||
| const [RefundDetailModal, refundDetailModalApi] = useVbenModal({ | ||||
|   connectedComponent: Detail, | ||||
|   destroyOnClose: true, | ||||
| }); | ||||
| 
 | ||||
| /** 刷新表格 */ | ||||
| function onRefresh() { | ||||
|   gridApi.query(); | ||||
| } | ||||
| 
 | ||||
| /** 导出表格 */ | ||||
| async function onExport() { | ||||
|   const data = await RefundApi.exportRefund(await gridApi.formApi.getValues()); | ||||
|   downloadFileFromBlobPart({ fileName: '支付退款.xls', source: data }); | ||||
| } | ||||
| 
 | ||||
| /** 查看详情 */ | ||||
| function onDetail(row: any) { | ||||
|   refundDetailModalApi.setData(row).open(); | ||||
| } | ||||
| 
 | ||||
| /** 表格操作按钮的回调函数 */ | ||||
| function onActionClick({ code, row }: OnActionClickParams<any>) { | ||||
|   switch (code) { | ||||
|     case 'detail': { | ||||
|       onDetail(row); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const [Grid, gridApi] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useGridFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useGridColumns(onActionClick), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await RefundApi.getRefundPage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   } as VxeTableGridOptions<any>, | ||||
| }); | ||||
| </script> | ||||
| <template> | ||||
|   <Page> | ||||
|     <DocAlert | ||||
|       title="支付宝、微信退款接入" | ||||
|       url="https://doc.iocoder.cn/pay/refund-demo/" | ||||
|     /> | ||||
|     <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/pay/refund/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/pay/refund/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="支付宝、微信退款接入" | ||||
|         url="https://doc.iocoder.cn/pay/refund-demo/" | ||||
|       /> | ||||
|     </template> | ||||
|     <RefundDetailModal @success="onRefresh" /> | ||||
|     <Grid table-title="支付退款列表"> | ||||
|       <template #toolbar-tools> | ||||
|         <Button | ||||
|           type="primary" | ||||
|           class="ml-2" | ||||
|           @click="onExport" | ||||
|           v-access:code="['pay:refund:export']" | ||||
|         > | ||||
|           <Download class="size-5" /> | ||||
|           {{ $t('ui.actionTitle.export') }} | ||||
|         </Button> | ||||
|       </template> | ||||
|     </Grid> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -0,0 +1,137 @@ | |||
| <script lang="ts" setup> | ||||
| import type { PayRefundApi } from '#/api/pay/refund'; | ||||
| 
 | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| import { useVbenModal } from '@vben/common-ui'; | ||||
| import { formatDateTime } from '@vben/utils'; | ||||
| 
 | ||||
| import { Descriptions, Divider, Tag } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { getRefund } from '#/api/pay/refund'; | ||||
| import { DictTag } from '#/components/dict-tag'; | ||||
| import { DICT_TYPE } from '#/utils/dict'; | ||||
| 
 | ||||
| const formData = ref<PayRefundApi.Refund>(); | ||||
| 
 | ||||
| const [Modal, modalApi] = useVbenModal({ | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|     if (!isOpen) { | ||||
|       formData.value = undefined; | ||||
|       return; | ||||
|     } | ||||
|     // 加载数据 | ||||
|     const data = modalApi.getData<PayRefundApi.Refund>(); | ||||
|     if (!data || !data.id) { | ||||
|       return; | ||||
|     } | ||||
|     modalApi.lock(); | ||||
|     try { | ||||
|       formData.value = await getRefund(data.id); | ||||
|     } finally { | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| /** 打开弹窗 */ | ||||
| const open = (id: number) => { | ||||
|   modalApi.setData({ id }).open(); | ||||
| }; | ||||
| 
 | ||||
| defineExpose({ open }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Modal | ||||
|     title="退款详情" | ||||
|     class="w-1/2" | ||||
|     :show-cancel-button="false" | ||||
|     :show-confirm-button="false" | ||||
|   > | ||||
|     <Descriptions bordered :column="2" size="middle" class="mx-4"> | ||||
|       <Descriptions.Item label="商户退款单号"> | ||||
|         <Tag size="small">{{ formData?.merchantRefundId }}</Tag> | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="渠道退款单号"> | ||||
|         <Tag type="success" size="small" v-if="formData?.channelRefundNo"> | ||||
|           {{ formData?.channelRefundNo }} | ||||
|         </Tag> | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="商户支付单号"> | ||||
|         <Tag size="small">{{ formData?.merchantOrderId }}</Tag> | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="渠道支付单号"> | ||||
|         <Tag type="success" size="small"> | ||||
|           {{ formData?.channelOrderNo }} | ||||
|         </Tag> | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="应用编号"> | ||||
|         {{ formData?.appId }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="应用名称"> | ||||
|         {{ formData?.appName }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="支付金额"> | ||||
|         <Tag type="success" size="small"> | ||||
|           ¥{{ (formData?.payPrice || 0) / 100.0 }} | ||||
|         </Tag> | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="退款金额"> | ||||
|         <Tag size="mini" type="danger"> | ||||
|           ¥{{ (formData?.refundPrice || 0) / 100.0 }} | ||||
|         </Tag> | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="退款状态"> | ||||
|         <DictTag | ||||
|           :type="DICT_TYPE.PAY_REFUND_STATUS" | ||||
|           :value="formData?.status" | ||||
|         /> | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="退款时间"> | ||||
|         {{ formatDateTime(formData?.successTime || '') }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="创建时间"> | ||||
|         {{ formatDateTime(formData?.createTime || '') }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="更新时间"> | ||||
|         {{ formatDateTime(formData?.updateTime || '') }} | ||||
|       </Descriptions.Item> | ||||
|     </Descriptions> | ||||
|     <Divider /> | ||||
|     <Descriptions bordered :column="2" size="middle" class="mx-4"> | ||||
|       <Descriptions.Item label="退款渠道"> | ||||
|         <DictTag | ||||
|           :type="DICT_TYPE.PAY_CHANNEL_CODE" | ||||
|           :value="formData?.channelCode" | ||||
|         /> | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="退款原因"> | ||||
|         {{ formData?.reason }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="退款 IP"> | ||||
|         {{ formData?.userIp }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="通知 URL"> | ||||
|         {{ formData?.notifyUrl }} | ||||
|       </Descriptions.Item> | ||||
|     </Descriptions> | ||||
|     <Divider /> | ||||
|     <Descriptions bordered :column="2" size="middle" class="mx-4"> | ||||
|       <Descriptions.Item label="渠道错误码"> | ||||
|         {{ formData?.channelErrorCode }} | ||||
|       </Descriptions.Item> | ||||
|       <Descriptions.Item label="渠道错误码描述"> | ||||
|         {{ formData?.channelErrorMsg }} | ||||
|       </Descriptions.Item> | ||||
|     </Descriptions> | ||||
| 
 | ||||
|     <Descriptions bordered :column="1" size="middle" class="mx-4"> | ||||
|       <Descriptions.Item label="支付通道异步回调内容"> | ||||
|         <p class="whitespace-pre-wrap break-words"> | ||||
|           {{ formData?.channelNotifyData }} | ||||
|         </p> | ||||
|       </Descriptions.Item> | ||||
|     </Descriptions> | ||||
|   </Modal> | ||||
| </template> | ||||
|  | @ -1,31 +1,25 @@ | |||
| <script lang="ts" setup> | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| import { Page } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { DocAlert } from '#/components/doc-alert'; | ||||
| import { IFrame } from '#/components/iframe'; | ||||
| 
 | ||||
| defineOptions({ name: 'GoView' }); | ||||
| 
 | ||||
| const src = ref(import.meta.env.VITE_GOVIEW_URL); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|     <DocAlert title="大屏设计器" url="https://doc.iocoder.cn/report/screen/" /> | ||||
|     <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/report/goview/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/report/goview/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="大屏设计器" | ||||
|         url="https://doc.iocoder.cn/report/screen/" | ||||
|       /> | ||||
|     </template> | ||||
| 
 | ||||
|     <IFrame :src="src" /> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -1,31 +1,28 @@ | |||
| <script lang="ts" setup> | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import { useAccessStore } from '@vben/stores'; | ||||
| 
 | ||||
| import { DocAlert } from '#/components/doc-alert'; | ||||
| import { IFrame } from '#/components/iframe'; | ||||
| 
 | ||||
| defineOptions({ name: 'JimuReport' }); | ||||
| const accessStore = useAccessStore(); | ||||
| 
 | ||||
| const src = ref( | ||||
|   `${import.meta.env.VITE_BASE_URL}/jmreport/list?token=${ | ||||
|     accessStore.refreshToken | ||||
|   }`, | ||||
| ); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|     <DocAlert title="报表设计器" url="https://doc.iocoder.cn/report/" /> | ||||
|     <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/report/jmreport/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/report/jmreport/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert title="报表设计器" url="https://doc.iocoder.cn/report/" /> | ||||
|     </template> | ||||
| 
 | ||||
|     <IFrame :src="src" /> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -40,7 +40,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -71,7 +71,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       try { | ||||
|         data = await getDept(data.id); | ||||
|       } finally { | ||||
|         modalApi.lock(false); | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } | ||||
|     // 设置到 values | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -82,7 +82,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|           await formApi.setValues(formData.value); | ||||
|         } | ||||
|       } finally { | ||||
|         modalApi.lock(false); | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } else if (data && 'dictType' in data && data.dictType) { | ||||
|       // 新增时,如果传入了dictType,则需要设置 | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -78,7 +78,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         await formApi.setValues(formData.value); | ||||
|       } | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|     try { | ||||
|       formData.value = data; | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -60,7 +60,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -79,7 +79,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       // 设置到 values | ||||
|       await formApi.setValues(formData.value); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|     try { | ||||
|       formData.value = data; | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -60,7 +60,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -79,7 +79,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       // 设置到 values | ||||
|       await formApi.setValues(formData.value); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|     } catch (error) { | ||||
|       console.error('发送邮件失败', error); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -71,7 +71,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|       try { | ||||
|         data = await getMenu(data.id as number); | ||||
|       } finally { | ||||
|         modalApi.lock(false); | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } | ||||
|     // 设置到 values | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         key: 'action_process_msg', | ||||
|       }); | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|  | @ -74,7 +74,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|         await formApi.setValues(formData.value); | ||||
|       } | ||||
|     } finally { | ||||
|       modalApi.lock(false); | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	 xingyu
						xingyu