perf: crm
							parent
							
								
									b4a77266a1
								
							
						
					
					
						commit
						45bcc79241
					
				|  | @ -2,7 +2,6 @@ import type { Ref } from 'vue'; | ||||||
| 
 | 
 | ||||||
| import type { VbenFormSchema } from '#/adapter/form'; | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
| import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
| import type { CrmContractApi } from '#/api/crm/contract'; |  | ||||||
| import type { CrmReceivableApi } from '#/api/crm/receivable'; | import type { CrmReceivableApi } from '#/api/crm/receivable'; | ||||||
| 
 | 
 | ||||||
| import { useAccess } from '@vben/access'; | import { useAccess } from '@vben/access'; | ||||||
|  | @ -252,9 +251,7 @@ export function useContractRemindFormSchema(): VbenFormSchema[] { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 合同审核列表的字段 */ | /** 合同审核列表的字段 */ | ||||||
| export function useContractColumns<T = CrmContractApi.Contract>( | export function useContractColumns(): VxeTableGridOptions['columns'] { | ||||||
|   onActionClick: OnActionClickFn<T>, |  | ||||||
| ): VxeTableGridOptions['columns'] { |  | ||||||
|   return [ |   return [ | ||||||
|     { |     { | ||||||
|       field: 'no', |       field: 'no', | ||||||
|  | @ -355,25 +352,10 @@ export function useContractColumns<T = CrmContractApi.Contract>( | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       field: 'operation', |  | ||||||
|       title: '操作', |       title: '操作', | ||||||
|       width: 130, |       width: 80, | ||||||
|       align: 'center', |  | ||||||
|       fixed: 'right', |       fixed: 'right', | ||||||
|       cellRender: { |       slots: { default: 'actions' }, | ||||||
|         attrs: { |  | ||||||
|           nameField: 'no', |  | ||||||
|           nameTitle: '合同编号', |  | ||||||
|           onClick: onActionClick, |  | ||||||
|         }, |  | ||||||
|         name: 'CellOperation', |  | ||||||
|         options: [ |  | ||||||
|           { |  | ||||||
|             code: 'processDetail', |  | ||||||
|             show: hasAccessByCodes(['crm:contract:update']), |  | ||||||
|           }, |  | ||||||
|         ], |  | ||||||
|       }, |  | ||||||
|     }, |     }, | ||||||
|   ]; |   ]; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -13,8 +13,8 @@ import * as ReceivablePlanApi from '#/api/crm/receivable/plan'; | ||||||
| import { DocAlert } from '#/components/doc-alert'; | import { DocAlert } from '#/components/doc-alert'; | ||||||
| 
 | 
 | ||||||
| import { useLeftSides } from './data'; | import { useLeftSides } from './data'; | ||||||
| import ClueFollowList from './modules/ClueFollowList.vue'; | import ClueFollowList from './modules/clue-follow-list.vue'; | ||||||
| import ContractAuditList from './modules/ContractAuditList.vue'; | import ContractAuditList from './modules/contract-audit-list.vue'; | ||||||
| import ContractRemindList from './modules/ContractRemindList.vue'; | import ContractRemindList from './modules/ContractRemindList.vue'; | ||||||
| import CustomerFollowList from './modules/CustomerFollowList.vue'; | import CustomerFollowList from './modules/CustomerFollowList.vue'; | ||||||
| import CustomerPutPoolRemindList from './modules/CustomerPutPoolRemindList.vue'; | import CustomerPutPoolRemindList from './modules/CustomerPutPoolRemindList.vue'; | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ import { useClueFollowColumns, useClueFollowFormSchema } from '../data'; | ||||||
| const { push } = useRouter(); | const { push } = useRouter(); | ||||||
| 
 | 
 | ||||||
| /** 打开线索详情 */ | /** 打开线索详情 */ | ||||||
| function onDetail(row: CrmClueApi.Clue) { | function handleDetail(row: CrmClueApi.Clue) { | ||||||
|   push({ name: 'CrmClueDetail', params: { id: row.id } }); |   push({ name: 'CrmClueDetail', params: { id: row.id } }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -52,7 +52,7 @@ const [Grid] = useVbenVxeGrid({ | ||||||
| <template> | <template> | ||||||
|   <Grid table-title="分配给我的线索"> |   <Grid table-title="分配给我的线索"> | ||||||
|     <template #name="{ row }"> |     <template #name="{ row }"> | ||||||
|       <Button type="link" @click="onDetail(row)">{{ row.name }}</Button> |       <Button type="link" @click="handleDetail(row)">{{ row.name }}</Button> | ||||||
|     </template> |     </template> | ||||||
|   </Grid> |   </Grid> | ||||||
| </template> | </template> | ||||||
|  | @ -1,13 +1,12 @@ | ||||||
| <!-- 待审核合同 --> | <!-- 待审核合同 --> | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import type { OnActionClickParams } from '#/adapter/vxe-table'; |  | ||||||
| import type { CrmContractApi } from '#/api/crm/contract'; | import type { CrmContractApi } from '#/api/crm/contract'; | ||||||
| 
 | 
 | ||||||
| import { useRouter } from 'vue-router'; | import { useRouter } from 'vue-router'; | ||||||
| 
 | 
 | ||||||
| import { Button } from 'ant-design-vue'; | import { Button } from 'ant-design-vue'; | ||||||
| 
 | 
 | ||||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
| import { getContractPage } from '#/api/crm/contract'; | import { getContractPage } from '#/api/crm/contract'; | ||||||
| 
 | 
 | ||||||
| import { useContractAuditFormSchema, useContractColumns } from '../data'; | import { useContractAuditFormSchema, useContractColumns } from '../data'; | ||||||
|  | @ -15,7 +14,7 @@ import { useContractAuditFormSchema, useContractColumns } from '../data'; | ||||||
| const { push } = useRouter(); | const { push } = useRouter(); | ||||||
| 
 | 
 | ||||||
| /** 查看审批 */ | /** 查看审批 */ | ||||||
| function openProcessDetail(row: CrmContractApi.Contract) { | function handleProcessDetail(row: CrmContractApi.Contract) { | ||||||
|   push({ |   push({ | ||||||
|     name: 'BpmProcessInstanceDetail', |     name: 'BpmProcessInstanceDetail', | ||||||
|     query: { id: row.processInstanceId }, |     query: { id: row.processInstanceId }, | ||||||
|  | @ -23,43 +22,30 @@ function openProcessDetail(row: CrmContractApi.Contract) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 打开合同详情 */ | /** 打开合同详情 */ | ||||||
| function openContractDetail(row: CrmContractApi.Contract) { | function handleContractDetail(row: CrmContractApi.Contract) { | ||||||
|   push({ name: 'CrmContractDetail', params: { id: row.id } }); |   push({ name: 'CrmContractDetail', params: { id: row.id } }); | ||||||
| } | } | ||||||
| /** 打开客户详情 */ | /** 打开客户详情 */ | ||||||
| function openCustomerDetail(row: CrmContractApi.Contract) { | function handleCustomerDetail(row: CrmContractApi.Contract) { | ||||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.id } }); |   push({ name: 'CrmCustomerDetail', params: { id: row.id } }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 打开联系人详情 */ | /** 打开联系人详情 */ | ||||||
| function openContactDetail(row: CrmContractApi.Contract) { | function handleContactDetail(row: CrmContractApi.Contract) { | ||||||
|   push({ name: 'CrmContactDetail', params: { id: row.id } }); |   push({ name: 'CrmContactDetail', params: { id: row.id } }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 打开商机详情 */ | /** 打开商机详情 */ | ||||||
| function openBusinessDetail(row: CrmContractApi.Contract) { | function handleBusinessDetail(row: CrmContractApi.Contract) { | ||||||
|   push({ name: 'CrmBusinessDetail', params: { id: row.id } }); |   push({ name: 'CrmBusinessDetail', params: { id: row.id } }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 表格操作按钮的回调函数 */ |  | ||||||
| function onActionClick({ |  | ||||||
|   code, |  | ||||||
|   row, |  | ||||||
| }: OnActionClickParams<CrmContractApi.Contract>) { |  | ||||||
|   switch (code) { |  | ||||||
|     case 'processDetail': { |  | ||||||
|       openProcessDetail(row); |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const [Grid] = useVbenVxeGrid({ | const [Grid] = useVbenVxeGrid({ | ||||||
|   formOptions: { |   formOptions: { | ||||||
|     schema: useContractAuditFormSchema(), |     schema: useContractAuditFormSchema(), | ||||||
|   }, |   }, | ||||||
|   gridOptions: { |   gridOptions: { | ||||||
|     columns: useContractColumns(onActionClick), |     columns: useContractColumns(), | ||||||
|     height: 'auto', |     height: 'auto', | ||||||
|     keepSource: true, |     keepSource: true, | ||||||
|     proxyConfig: { |     proxyConfig: { | ||||||
|  | @ -88,24 +74,36 @@ const [Grid] = useVbenVxeGrid({ | ||||||
| <template> | <template> | ||||||
|   <Grid table-title="待审核合同"> |   <Grid table-title="待审核合同"> | ||||||
|     <template #name="{ row }"> |     <template #name="{ row }"> | ||||||
|       <Button type="link" @click="openContractDetail(row)"> |       <Button type="link" @click="handleContractDetail(row)"> | ||||||
|         {{ row.name }} |         {{ row.name }} | ||||||
|       </Button> |       </Button> | ||||||
|     </template> |     </template> | ||||||
|     <template #customerName="{ row }"> |     <template #customerName="{ row }"> | ||||||
|       <Button type="link" @click="openCustomerDetail(row)"> |       <Button type="link" @click="handleCustomerDetail(row)"> | ||||||
|         {{ row.customerName }} |         {{ row.customerName }} | ||||||
|       </Button> |       </Button> | ||||||
|     </template> |     </template> | ||||||
|     <template #businessName="{ row }"> |     <template #businessName="{ row }"> | ||||||
|       <Button type="link" @click="openBusinessDetail(row)"> |       <Button type="link" @click="handleBusinessDetail(row)"> | ||||||
|         {{ row.businessName }} |         {{ row.businessName }} | ||||||
|       </Button> |       </Button> | ||||||
|     </template> |     </template> | ||||||
|     <template #contactName="{ row }"> |     <template #contactName="{ row }"> | ||||||
|       <Button type="link" @click="openContactDetail(row)"> |       <Button type="link" @click="handleContactDetail(row)"> | ||||||
|         {{ row.contactName }} |         {{ row.contactName }} | ||||||
|       </Button> |       </Button> | ||||||
|     </template> |     </template> | ||||||
|  |     <template #actions="{ row }"> | ||||||
|  |       <TableAction | ||||||
|  |         :actions="[ | ||||||
|  |           { | ||||||
|  |             label: '查看审批', | ||||||
|  |             type: 'link', | ||||||
|  |             icon: ACTION_ICON.VIEW, | ||||||
|  |             onClick: handleProcessDetail.bind(null, row), | ||||||
|  |           }, | ||||||
|  |         ]" | ||||||
|  |       /> | ||||||
|  |     </template> | ||||||
|   </Grid> |   </Grid> | ||||||
| </template> | </template> | ||||||
|  | @ -1,12 +1,5 @@ | ||||||
| import type { VbenFormSchema } from '#/adapter/form'; | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
| import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
| import type { CrmBusinessApi } from '#/api/crm/business'; |  | ||||||
| 
 |  | ||||||
| import { useAccess } from '@vben/access'; |  | ||||||
| 
 |  | ||||||
| import { getRangePickerDefaultProps } from '#/utils'; |  | ||||||
| 
 |  | ||||||
| const { hasAccessByCodes } = useAccess(); |  | ||||||
| 
 | 
 | ||||||
| /** 新增/修改的表单 */ | /** 新增/修改的表单 */ | ||||||
| export function useFormSchema(): VbenFormSchema[] { | export function useFormSchema(): VbenFormSchema[] { | ||||||
|  | @ -37,7 +30,6 @@ export function useFormSchema(): VbenFormSchema[] { | ||||||
|       component: 'InputNumber', |       component: 'InputNumber', | ||||||
|       componentProps: { |       componentProps: { | ||||||
|         min: 0, |         min: 0, | ||||||
|         controlsPosition: 'right', |  | ||||||
|         placeholder: '请输入商机金额', |         placeholder: '请输入商机金额', | ||||||
|       }, |       }, | ||||||
|       rules: 'required', |       rules: 'required', | ||||||
|  | @ -79,22 +71,11 @@ export function useGridFormSchema(): VbenFormSchema[] { | ||||||
|       label: '商机名称', |       label: '商机名称', | ||||||
|       component: 'Input', |       component: 'Input', | ||||||
|     }, |     }, | ||||||
|     { |  | ||||||
|       fieldName: 'createTime', |  | ||||||
|       label: '创建时间', |  | ||||||
|       component: 'RangePicker', |  | ||||||
|       componentProps: { |  | ||||||
|         ...getRangePickerDefaultProps(), |  | ||||||
|         allowClear: true, |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|   ]; |   ]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 列表的字段 */ | /** 列表的字段 */ | ||||||
| export function useGridColumns<T = CrmBusinessApi.Business>( | export function useGridColumns(): VxeTableGridOptions['columns'] { | ||||||
|   onActionClick: OnActionClickFn<T>, |  | ||||||
| ): VxeTableGridOptions['columns'] { |  | ||||||
|   return [ |   return [ | ||||||
|     { |     { | ||||||
|       field: 'name', |       field: 'name', | ||||||
|  | @ -165,29 +146,10 @@ export function useGridColumns<T = CrmBusinessApi.Business>( | ||||||
|       fixed: 'right', |       fixed: 'right', | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       field: 'operation', |  | ||||||
|       title: '操作', |       title: '操作', | ||||||
|       width: 130, |       width: 130, | ||||||
|       fixed: 'right', |       fixed: 'right', | ||||||
|       align: 'center', |       slots: { default: 'actions' }, | ||||||
|       cellRender: { |  | ||||||
|         attrs: { |  | ||||||
|           nameField: 'name', |  | ||||||
|           nameTitle: '商机', |  | ||||||
|           onClick: onActionClick, |  | ||||||
|         }, |  | ||||||
|         name: 'CellOperation', |  | ||||||
|         options: [ |  | ||||||
|           { |  | ||||||
|             code: 'edit', |  | ||||||
|             show: hasAccessByCodes(['crm:business:update']), |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             code: 'delete', |  | ||||||
|             show: hasAccessByCodes(['crm:business:delete']), |  | ||||||
|           }, |  | ||||||
|         ], |  | ||||||
|       }, |  | ||||||
|     }, |     }, | ||||||
|   ]; |   ]; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,19 +1,15 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import type { | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|   OnActionClickParams, |  | ||||||
|   VxeTableGridOptions, |  | ||||||
| } from '#/adapter/vxe-table'; |  | ||||||
| import type { CrmBusinessApi } from '#/api/crm/business'; | import type { CrmBusinessApi } from '#/api/crm/business'; | ||||||
| 
 | 
 | ||||||
| import { useRouter } from 'vue-router'; | import { useRouter } from 'vue-router'; | ||||||
| 
 | 
 | ||||||
| import { Page, useVbenModal } from '@vben/common-ui'; | import { Page, useVbenModal } from '@vben/common-ui'; | ||||||
| import { Download, Plus } from '@vben/icons'; |  | ||||||
| import { downloadFileFromBlobPart } from '@vben/utils'; | import { downloadFileFromBlobPart } from '@vben/utils'; | ||||||
| 
 | 
 | ||||||
| import { Button, message } from 'ant-design-vue'; | import { Button, message } from 'ant-design-vue'; | ||||||
| 
 | 
 | ||||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
| import { | import { | ||||||
|   deleteBusiness, |   deleteBusiness, | ||||||
|   exportBusiness, |   exportBusiness, | ||||||
|  | @ -38,23 +34,23 @@ function onRefresh() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 导出表格 */ | /** 导出表格 */ | ||||||
| async function onExport() { | async function handleExport() { | ||||||
|   const data = await exportBusiness(await gridApi.formApi.getValues()); |   const data = await exportBusiness(await gridApi.formApi.getValues()); | ||||||
|   downloadFileFromBlobPart({ fileName: '商机.xls', source: data }); |   downloadFileFromBlobPart({ fileName: '商机.xls', source: data }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 创建商机 */ | /** 创建商机 */ | ||||||
| function onCreate() { | function handleCreate() { | ||||||
|   formModalApi.setData(null).open(); |   formModalApi.setData(null).open(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 编辑商机 */ | /** 编辑商机 */ | ||||||
| function onEdit(row: CrmBusinessApi.Business) { | function handleEdit(row: CrmBusinessApi.Business) { | ||||||
|   formModalApi.setData(row).open(); |   formModalApi.setData(row).open(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 删除商机 */ | /** 删除商机 */ | ||||||
| async function onDelete(row: CrmBusinessApi.Business) { | async function handleDelete(row: CrmBusinessApi.Business) { | ||||||
|   const hideLoading = message.loading({ |   const hideLoading = message.loading({ | ||||||
|     content: $t('ui.actionMessage.deleting', [row.name]), |     content: $t('ui.actionMessage.deleting', [row.name]), | ||||||
|     duration: 0, |     duration: 0, | ||||||
|  | @ -70,38 +66,21 @@ async function onDelete(row: CrmBusinessApi.Business) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 查看商机详情 */ | /** 查看商机详情 */ | ||||||
| function onDetail(row: CrmBusinessApi.Business) { | function handleDetail(row: CrmBusinessApi.Business) { | ||||||
|   push({ name: 'CrmBusinessDetail', params: { id: row.id } }); |   push({ name: 'CrmBusinessDetail', params: { id: row.id } }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 查看客户详情 */ | /** 查看客户详情 */ | ||||||
| function onCustomerDetail(row: CrmBusinessApi.Business) { | function handleCustomerDetail(row: CrmBusinessApi.Business) { | ||||||
|   push({ name: 'CrmCustomerDetail', params: { id: row.customerId } }); |   push({ name: 'CrmCustomerDetail', params: { id: row.customerId } }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 表格操作按钮的回调函数 */ |  | ||||||
| function onActionClick({ |  | ||||||
|   code, |  | ||||||
|   row, |  | ||||||
| }: OnActionClickParams<CrmBusinessApi.Business>) { |  | ||||||
|   switch (code) { |  | ||||||
|     case 'delete': { |  | ||||||
|       onDelete(row); |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|     case 'edit': { |  | ||||||
|       onEdit(row); |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const [Grid, gridApi] = useVbenVxeGrid({ | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|   formOptions: { |   formOptions: { | ||||||
|     schema: useGridFormSchema(), |     schema: useGridFormSchema(), | ||||||
|   }, |   }, | ||||||
|   gridOptions: { |   gridOptions: { | ||||||
|     columns: useGridColumns(onActionClick), |     columns: useGridColumns(), | ||||||
|     height: 'auto', |     height: 'auto', | ||||||
|     keepSource: true, |     keepSource: true, | ||||||
|     proxyConfig: { |     proxyConfig: { | ||||||
|  | @ -142,34 +121,59 @@ const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|     <FormModal @success="onRefresh" /> |     <FormModal @success="onRefresh" /> | ||||||
|     <Grid table-title="商机列表"> |     <Grid table-title="商机列表"> | ||||||
|       <template #toolbar-tools> |       <template #toolbar-tools> | ||||||
|         <Button |         <TableAction | ||||||
|           type="primary" |           :actions="[ | ||||||
|           @click="onCreate" |             { | ||||||
|           v-access:code="['crm:business:create']" |               label: $t('ui.actionTitle.create', ['商机']), | ||||||
|         > |               type: 'primary', | ||||||
|           <Plus class="size-5" /> |               icon: ACTION_ICON.ADD, | ||||||
|           {{ $t('ui.actionTitle.create', ['商机']) }} |               auth: ['crm:business:create'], | ||||||
|         </Button> |               onClick: handleCreate, | ||||||
|         <Button |             }, | ||||||
|           type="primary" |             { | ||||||
|           class="ml-2" |               label: $t('ui.actionTitle.export'), | ||||||
|           @click="onExport" |               type: 'primary', | ||||||
|           v-access:code="['crm:business:export']" |               icon: ACTION_ICON.DOWNLOAD, | ||||||
|         > |               auth: ['crm:business:export'], | ||||||
|           <Download class="size-5" /> |               onClick: handleExport, | ||||||
|           {{ $t('ui.actionTitle.export') }} |             }, | ||||||
|         </Button> |           ]" | ||||||
|  |         /> | ||||||
|       </template> |       </template> | ||||||
|       <template #name="{ row }"> |       <template #name="{ row }"> | ||||||
|         <Button type="link" @click="onDetail(row)"> |         <Button type="link" @click="handleDetail(row)"> | ||||||
|           {{ row.name }} |           {{ row.name }} | ||||||
|         </Button> |         </Button> | ||||||
|       </template> |       </template> | ||||||
|       <template #customerName="{ row }"> |       <template #customerName="{ row }"> | ||||||
|         <Button type="link" @click="onCustomerDetail(row)"> |         <Button type="link" @click="handleCustomerDetail(row)"> | ||||||
|           {{ row.customerName }} |           {{ row.customerName }} | ||||||
|         </Button> |         </Button> | ||||||
|       </template> |       </template> | ||||||
|  |       <template #actions="{ row }"> | ||||||
|  |         <TableAction | ||||||
|  |           :actions="[ | ||||||
|  |             { | ||||||
|  |               label: $t('common.edit'), | ||||||
|  |               type: 'link', | ||||||
|  |               icon: ACTION_ICON.EDIT, | ||||||
|  |               auth: ['crm:business:update'], | ||||||
|  |               onClick: handleEdit.bind(null, row), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: $t('common.delete'), | ||||||
|  |               type: 'link', | ||||||
|  |               danger: true, | ||||||
|  |               icon: ACTION_ICON.DELETE, | ||||||
|  |               auth: ['crm:business:delete'], | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: $t('ui.actionMessage.deleteConfirm', [row.name]), | ||||||
|  |                 confirm: handleDelete.bind(null, row), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|     </Grid> |     </Grid> | ||||||
|   </Page> |   </Page> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -1,18 +1,11 @@ | ||||||
| import type { VbenFormSchema } from '#/adapter/form'; | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
| import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
| import type { CrmBusinessStatusApi } from '#/api/crm/business/status'; |  | ||||||
| 
 | 
 | ||||||
| import { useAccess } from '@vben/access'; | import { handleTree } from '@vben/utils'; | ||||||
| 
 | 
 | ||||||
| import { z } from '#/adapter/form'; | import { z } from '#/adapter/form'; | ||||||
| import { | import { getDeptList } from '#/api/system/dept'; | ||||||
|   CommonStatusEnum, | import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; | ||||||
|   DICT_TYPE, |  | ||||||
|   getDictOptions, |  | ||||||
|   getRangePickerDefaultProps, |  | ||||||
| } from '#/utils'; |  | ||||||
| 
 |  | ||||||
| const { hasAccessByCodes } = useAccess(); |  | ||||||
| 
 | 
 | ||||||
| /** 新增/修改的表单 */ | /** 新增/修改的表单 */ | ||||||
| export function useFormSchema(): VbenFormSchema[] { | export function useFormSchema(): VbenFormSchema[] { | ||||||
|  | @ -34,12 +27,16 @@ export function useFormSchema(): VbenFormSchema[] { | ||||||
|     { |     { | ||||||
|       fieldName: 'deptIds', |       fieldName: 'deptIds', | ||||||
|       label: '应用部门', |       label: '应用部门', | ||||||
|       component: 'TreeSelect', |       component: 'ApiTreeSelect', | ||||||
|       componentProps: { |       componentProps: { | ||||||
|  |         api: async () => { | ||||||
|  |           const data = await getDeptList(); | ||||||
|  |           return handleTree(data); | ||||||
|  |         }, | ||||||
|         multiple: true, |         multiple: true, | ||||||
|         treeCheckable: true, |         fieldNames: { label: 'name', value: 'id', children: 'children' }, | ||||||
|         showCheckedStrategy: 'SHOW_PARENT', |  | ||||||
|         placeholder: '请选择应用部门', |         placeholder: '请选择应用部门', | ||||||
|  |         treeDefaultExpandAll: true, | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|  | @ -56,79 +53,37 @@ export function useFormSchema(): VbenFormSchema[] { | ||||||
|   ]; |   ]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 列表的搜索表单 */ |  | ||||||
| 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>( | export function useGridColumns(): VxeTableGridOptions['columns'] { | ||||||
|   onActionClick: OnActionClickFn<T>, |  | ||||||
| ): VxeTableGridOptions['columns'] { |  | ||||||
|   return [ |   return [ | ||||||
|     { |     { | ||||||
|       field: 'name', |       field: 'name', | ||||||
|       title: '状态组名', |       title: '状态组名', | ||||||
|       minWidth: 200, |       width: 200, | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       field: 'deptNames', |       field: 'deptNames', | ||||||
|       title: '应用部门', |       title: '应用部门', | ||||||
|       minWidth: 200, |       width: 200, | ||||||
|       formatter: ({ cellValue }) => { |       formatter: ({ cellValue }) => | ||||||
|         return cellValue?.length > 0 ? cellValue.join(' ') : '全公司'; |         cellValue?.length > 0 ? cellValue.join(' ') : '全公司', | ||||||
|       }, |  | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       field: 'creator', |       field: 'creator', | ||||||
|       title: '创建人', |       title: '创建人', | ||||||
|       minWidth: 100, |       width: 100, | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       field: 'createTime', |       field: 'createTime', | ||||||
|       title: '创建时间', |       title: '创建时间', | ||||||
|       minWidth: 180, |       width: 150, | ||||||
|       formatter: 'formatDateTime', |       formatter: 'formatDateTime', | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       field: 'operation', |  | ||||||
|       title: '操作', |       title: '操作', | ||||||
|       width: 160, |       width: 160, | ||||||
|       fixed: 'right', |       fixed: 'right', | ||||||
|       align: 'center', |       slots: { default: 'actions' }, | ||||||
|       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,16 +1,12 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import type { | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|   OnActionClickParams, |  | ||||||
|   VxeTableGridOptions, |  | ||||||
| } from '#/adapter/vxe-table'; |  | ||||||
| import type { CrmBusinessStatusApi } from '#/api/crm/business/status'; | import type { CrmBusinessStatusApi } from '#/api/crm/business/status'; | ||||||
| 
 | 
 | ||||||
| import { Page, useVbenModal } from '@vben/common-ui'; | import { Page, useVbenModal } from '@vben/common-ui'; | ||||||
| import { Plus } from '@vben/icons'; |  | ||||||
| 
 | 
 | ||||||
| import { Button, message } from 'ant-design-vue'; | import { message } from 'ant-design-vue'; | ||||||
| 
 | 
 | ||||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
| import { | import { | ||||||
|   deleteBusinessStatus, |   deleteBusinessStatus, | ||||||
|   getBusinessStatusPage, |   getBusinessStatusPage, | ||||||
|  | @ -18,7 +14,7 @@ import { | ||||||
| import { DocAlert } from '#/components/doc-alert'; | import { DocAlert } from '#/components/doc-alert'; | ||||||
| import { $t } from '#/locales'; | import { $t } from '#/locales'; | ||||||
| 
 | 
 | ||||||
| import { useGridColumns, useGridFormSchema } from './data'; | import { useGridColumns } from './data'; | ||||||
| import Form from './modules/form.vue'; | import Form from './modules/form.vue'; | ||||||
| 
 | 
 | ||||||
| const [FormModal, formModalApi] = useVbenModal({ | const [FormModal, formModalApi] = useVbenModal({ | ||||||
|  | @ -32,12 +28,12 @@ function onRefresh() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 创建商机状态 */ | /** 创建商机状态 */ | ||||||
| function onCreate() { | function handleCreate() { | ||||||
|   formModalApi.setData(null).open(); |   formModalApi.setData(null).open(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 删除商机状态 */ | /** 删除商机状态 */ | ||||||
| async function onDelete(row: CrmBusinessStatusApi.BusinessStatus) { | async function handleDelete(row: CrmBusinessStatusApi.BusinessStatus) { | ||||||
|   const hideLoading = message.loading({ |   const hideLoading = message.loading({ | ||||||
|     content: $t('ui.actionMessage.deleting', [row.name]), |     content: $t('ui.actionMessage.deleting', [row.name]), | ||||||
|     duration: 0, |     duration: 0, | ||||||
|  | @ -53,33 +49,13 @@ async function onDelete(row: CrmBusinessStatusApi.BusinessStatus) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 编辑商机状态 */ | /** 编辑商机状态 */ | ||||||
| function onEdit(row: CrmBusinessStatusApi.BusinessStatus) { | function handleEdit(row: CrmBusinessStatusApi.BusinessStatus) { | ||||||
|   formModalApi.setData(row).open(); |   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({ | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|   formOptions: { |  | ||||||
|     schema: useGridFormSchema(), |  | ||||||
|   }, |  | ||||||
|   gridOptions: { |   gridOptions: { | ||||||
|     columns: useGridColumns(onActionClick), |     columns: useGridColumns(), | ||||||
|     height: 'auto', |     height: 'auto', | ||||||
|     keepSource: true, |     keepSource: true, | ||||||
|     proxyConfig: { |     proxyConfig: { | ||||||
|  | @ -120,14 +96,41 @@ const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|     <FormModal @success="onRefresh" /> |     <FormModal @success="onRefresh" /> | ||||||
|     <Grid table-title="商机状态列表"> |     <Grid table-title="商机状态列表"> | ||||||
|       <template #toolbar-tools> |       <template #toolbar-tools> | ||||||
|         <Button |         <TableAction | ||||||
|           type="primary" |           :actions="[ | ||||||
|           @click="onCreate" |             { | ||||||
|           v-access:code="['crm:business-status:create']" |               label: $t('ui.actionTitle.create', ['商机状态']), | ||||||
|         > |               type: 'primary', | ||||||
|           <Plus class="size-5" /> |               icon: ACTION_ICON.ADD, | ||||||
|           {{ $t('ui.actionTitle.create', ['商机状态']) }} |               auth: ['system:post:create'], | ||||||
|         </Button> |               onClick: handleCreate, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |       <template #actions="{ row }"> | ||||||
|  |         <TableAction | ||||||
|  |           :actions="[ | ||||||
|  |             { | ||||||
|  |               label: $t('common.edit'), | ||||||
|  |               type: 'link', | ||||||
|  |               icon: ACTION_ICON.EDIT, | ||||||
|  |               auth: ['crm:business-status:update'], | ||||||
|  |               onClick: handleEdit.bind(null, row), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: $t('common.delete'), | ||||||
|  |               type: 'link', | ||||||
|  |               danger: true, | ||||||
|  |               icon: ACTION_ICON.DELETE, | ||||||
|  |               auth: ['crm:business-status:delete'], | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: $t('ui.actionMessage.deleteConfirm', [row.name]), | ||||||
|  |                 confirm: handleDelete.bind(null, row), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|       </template> |       </template> | ||||||
|     </Grid> |     </Grid> | ||||||
|   </Page> |   </Page> | ||||||
|  |  | ||||||
|  | @ -57,22 +57,22 @@ async function loadClueDetail() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 返回列表页 */ | /** 返回列表页 */ | ||||||
| function onBack() { | function handleBack() { | ||||||
|   router.push('/crm/clue'); |   router.push('/crm/clue'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 编辑线索 */ | /** 编辑线索 */ | ||||||
| function onEdit() { | function handleEdit() { | ||||||
|   formModalApi.setData({ id: clueId }).open(); |   formModalApi.setData({ id: clueId }).open(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 转移线索 */ | /** 转移线索 */ | ||||||
| function onTransfer() { | function handleTransfer() { | ||||||
|   transferModalApi.setData({ id: clueId }).open(); |   transferModalApi.setData({ id: clueId }).open(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 转化为客户 */ | /** 转化为客户 */ | ||||||
| async function onTransform() { | async function handleTransform() { | ||||||
|   try { |   try { | ||||||
|     await Modal.confirm({ |     await Modal.confirm({ | ||||||
|       title: '提示', |       title: '提示', | ||||||
|  | @ -99,14 +99,14 @@ onMounted(async () => { | ||||||
|   <Page auto-content-height :title="clue?.name" :loading="loading"> |   <Page auto-content-height :title="clue?.name" :loading="loading"> | ||||||
|     <template #extra> |     <template #extra> | ||||||
|       <div class="flex items-center gap-2"> |       <div class="flex items-center gap-2"> | ||||||
|         <Button @click="onBack"> |         <Button @click="handleBack"> | ||||||
|           <ArrowLeft class="size-5" /> |           <ArrowLeft class="size-5" /> | ||||||
|           返回 |           返回 | ||||||
|         </Button> |         </Button> | ||||||
|         <Button |         <Button | ||||||
|           v-if="permissionListRef?.validateWrite" |           v-if="permissionListRef?.validateWrite" | ||||||
|           type="primary" |           type="primary" | ||||||
|           @click="onEdit" |           @click="handleEdit" | ||||||
|           v-access:code="['crm:clue:update']" |           v-access:code="['crm:clue:update']" | ||||||
|         > |         > | ||||||
|           {{ $t('ui.actionTitle.edit') }} |           {{ $t('ui.actionTitle.edit') }} | ||||||
|  | @ -114,7 +114,7 @@ onMounted(async () => { | ||||||
|         <Button |         <Button | ||||||
|           v-if="permissionListRef?.validateOwnerUser" |           v-if="permissionListRef?.validateOwnerUser" | ||||||
|           type="primary" |           type="primary" | ||||||
|           @click="onTransfer" |           @click="handleTransfer" | ||||||
|           v-access:code="['crm:clue:update']" |           v-access:code="['crm:clue:update']" | ||||||
|         > |         > | ||||||
|           转移 |           转移 | ||||||
|  | @ -122,7 +122,7 @@ onMounted(async () => { | ||||||
|         <Button |         <Button | ||||||
|           v-if="permissionListRef?.validateOwnerUser && !clue?.transformStatus" |           v-if="permissionListRef?.validateOwnerUser && !clue?.transformStatus" | ||||||
|           type="primary" |           type="primary" | ||||||
|           @click="onTransform" |           @click="handleTransform" | ||||||
|           v-access:code="['crm:clue:update']" |           v-access:code="['crm:clue:update']" | ||||||
|         > |         > | ||||||
|           转化为客户 |           转化为客户 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,276 @@ | ||||||
|  | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
|  | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | 
 | ||||||
|  | import { getSimpleBusinessList } from '#/api/crm/business'; | ||||||
|  | import { getSimpleContactList } from '#/api/crm/contact'; | ||||||
|  | import { getCustomerSimpleList } from '#/api/crm/customer'; | ||||||
|  | import { floatToFixed2 } from '#/utils'; | ||||||
|  | import { DICT_TYPE } from '#/utils/dict'; | ||||||
|  | 
 | ||||||
|  | /** 新增/修改的表单 */ | ||||||
|  | export function useFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'no', | ||||||
|  |       label: '合同编号', | ||||||
|  |       component: 'Input', | ||||||
|  |       rules: 'required', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入合同编号', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'name', | ||||||
|  |       label: '合同名称', | ||||||
|  |       component: 'Input', | ||||||
|  |       rules: 'required', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入合同名称', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'customerId', | ||||||
|  |       label: '客户', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       rules: 'required', | ||||||
|  |       componentProps: { | ||||||
|  |         api: getCustomerSimpleList, | ||||||
|  |         labelField: 'name', | ||||||
|  |         valueField: 'id', | ||||||
|  |         placeholder: '请选择客户', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'businessId', | ||||||
|  |       label: '商机', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       rules: 'required', | ||||||
|  |       componentProps: { | ||||||
|  |         api: getSimpleBusinessList, | ||||||
|  |         labelField: 'name', | ||||||
|  |         valueField: 'id', | ||||||
|  |         placeholder: '请选择商机', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'totalPrice', | ||||||
|  |       label: '合同金额', | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       rules: 'required', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入合同金额', | ||||||
|  |         min: 0, | ||||||
|  |         precision: 2, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'orderDate', | ||||||
|  |       label: '下单时间', | ||||||
|  |       component: 'DatePicker', | ||||||
|  |       rules: 'required', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择下单时间', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'startTime', | ||||||
|  |       label: '合同开始时间', | ||||||
|  |       component: 'DatePicker', | ||||||
|  |       rules: 'required', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择合同开始时间', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'endTime', | ||||||
|  |       label: '合同结束时间', | ||||||
|  |       component: 'DatePicker', | ||||||
|  |       rules: 'required', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择合同结束时间', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'signContactId', | ||||||
|  |       label: '客户签约人', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       rules: 'required', | ||||||
|  |       componentProps: { | ||||||
|  |         api: getSimpleContactList, | ||||||
|  |         labelField: 'name', | ||||||
|  |         valueField: 'id', | ||||||
|  |         placeholder: '请选择客户签约人', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'remark', | ||||||
|  |       label: '备注', | ||||||
|  |       component: 'Textarea', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入备注', | ||||||
|  |         rows: 4, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的搜索表单 */ | ||||||
|  | export function useGridFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'no', | ||||||
|  |       label: '合同编号', | ||||||
|  |       component: 'Input', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'name', | ||||||
|  |       label: '合同名称', | ||||||
|  |       component: 'Input', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'customerId', | ||||||
|  |       label: '客户', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         api: getCustomerSimpleList, | ||||||
|  |         labelField: 'name', | ||||||
|  |         valueField: 'id', | ||||||
|  |         placeholder: '请选择客户', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useGridColumns(): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       title: '合同编号', | ||||||
|  |       field: 'no', | ||||||
|  |       width: 150, | ||||||
|  |       fixed: 'left', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '合同名称', | ||||||
|  |       field: 'name', | ||||||
|  |       width: 150, | ||||||
|  |       fixed: 'left', | ||||||
|  |       slots: { default: 'name' }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '客户名称', | ||||||
|  |       field: 'customerName', | ||||||
|  |       width: 150, | ||||||
|  |       slots: { default: 'customerName' }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '商机名称', | ||||||
|  |       field: 'businessName', | ||||||
|  |       width: 150, | ||||||
|  |       slots: { default: 'businessName' }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '合同金额(元)', | ||||||
|  |       field: 'totalPrice', | ||||||
|  |       width: 150, | ||||||
|  |       formatter: 'formatNumber', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '下单时间', | ||||||
|  |       field: 'orderDate', | ||||||
|  |       width: 150, | ||||||
|  |       formatter: 'formatDateTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '合同开始时间', | ||||||
|  |       field: 'startTime', | ||||||
|  |       width: 150, | ||||||
|  |       formatter: 'formatDateTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '合同结束时间', | ||||||
|  |       field: 'endTime', | ||||||
|  |       width: 150, | ||||||
|  |       formatter: 'formatDateTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '客户签约人', | ||||||
|  |       field: 'signContactName', | ||||||
|  |       width: 150, | ||||||
|  |       slots: { default: 'signContactName' }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '公司签约人', | ||||||
|  |       field: 'signUserName', | ||||||
|  |       width: 150, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '已回款金额(元)', | ||||||
|  |       field: 'totalReceivablePrice', | ||||||
|  |       width: 150, | ||||||
|  |       formatter: 'formatNumber', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '未回款金额(元)', | ||||||
|  |       field: 'unpaidPrice', | ||||||
|  |       width: 150, | ||||||
|  |       formatter: ({ row }) => { | ||||||
|  |         return floatToFixed2(row.totalPrice - row.totalReceivablePrice); | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '最后跟进时间', | ||||||
|  |       field: 'contactLastTime', | ||||||
|  |       width: 150, | ||||||
|  |       formatter: 'formatDateTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '负责人', | ||||||
|  |       field: 'ownerUserName', | ||||||
|  |       width: 150, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '所属部门', | ||||||
|  |       field: 'ownerUserDeptName', | ||||||
|  |       width: 150, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '更新时间', | ||||||
|  |       field: 'updateTime', | ||||||
|  |       width: 150, | ||||||
|  |       formatter: 'formatDateTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '创建时间', | ||||||
|  |       field: 'createTime', | ||||||
|  |       width: 150, | ||||||
|  |       formatter: 'formatDateTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '创建人', | ||||||
|  |       field: 'creatorName', | ||||||
|  |       width: 150, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '备注', | ||||||
|  |       field: 'remark', | ||||||
|  |       width: 150, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '合同状态', | ||||||
|  |       field: 'auditStatus', | ||||||
|  |       fixed: 'right', | ||||||
|  |       width: 100, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDict', | ||||||
|  |         props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '操作', | ||||||
|  |       field: 'actions', | ||||||
|  |       fixed: 'right', | ||||||
|  |       width: 130, | ||||||
|  |       slots: { default: 'actions' }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | @ -1,38 +1,265 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { Page } from '@vben/common-ui'; | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { CrmContractApi } from '#/api/crm/contract'; | ||||||
| 
 | 
 | ||||||
| import { Button } from 'ant-design-vue'; | import { ref } from 'vue'; | ||||||
|  | import { useRouter } from 'vue-router'; | ||||||
| 
 | 
 | ||||||
|  | import { Page, useVbenModal } from '@vben/common-ui'; | ||||||
|  | import { downloadFileFromBlobPart } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { Button, message, Tabs } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { | ||||||
|  |   deleteContract, | ||||||
|  |   exportContract, | ||||||
|  |   getContractPage, | ||||||
|  |   submitContract, | ||||||
|  | } from '#/api/crm/contract'; | ||||||
| import { DocAlert } from '#/components/doc-alert'; | import { DocAlert } from '#/components/doc-alert'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useGridColumns, useGridFormSchema } from './data'; | ||||||
|  | import Form from './modules/form.vue'; | ||||||
|  | 
 | ||||||
|  | const { push } = useRouter(); | ||||||
|  | const sceneType = ref('1'); | ||||||
|  | 
 | ||||||
|  | const [FormModal, formModalApi] = useVbenModal({ | ||||||
|  |   connectedComponent: Form, | ||||||
|  |   destroyOnClose: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 刷新表格 */ | ||||||
|  | function onRefresh() { | ||||||
|  |   gridApi.query(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出表格 */ | ||||||
|  | async function handleExport() { | ||||||
|  |   const data = await exportContract(await gridApi.formApi.getValues()); | ||||||
|  |   downloadFileFromBlobPart({ fileName: '合同.xls', source: data }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 创建合同 */ | ||||||
|  | function handleCreate() { | ||||||
|  |   formModalApi.setData(null).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 编辑合同 */ | ||||||
|  | function handleEdit(row: CrmContractApi.Contract) { | ||||||
|  |   formModalApi.setData(row).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除合同 */ | ||||||
|  | async function handleDelete(row: CrmContractApi.Contract) { | ||||||
|  |   const hideLoading = message.loading({ | ||||||
|  |     content: $t('ui.actionMessage.deleting', [row.name]), | ||||||
|  |     key: 'action_key_msg', | ||||||
|  |   }); | ||||||
|  |   try { | ||||||
|  |     await deleteContract(row.id as number); | ||||||
|  |     message.success({ | ||||||
|  |       content: $t('ui.actionMessage.deleteSuccess', [row.name]), | ||||||
|  |       key: 'action_key_msg', | ||||||
|  |     }); | ||||||
|  |     onRefresh(); | ||||||
|  |   } finally { | ||||||
|  |     hideLoading(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 提交审核 */ | ||||||
|  | async function handleSubmit(row: CrmContractApi.Contract) { | ||||||
|  |   const hideLoading = message.loading({ | ||||||
|  |     content: $t('ui.actionMessage.submitting', [row.name]), | ||||||
|  |     key: 'action_key_msg', | ||||||
|  |   }); | ||||||
|  |   try { | ||||||
|  |     await submitContract(row.id as number); | ||||||
|  |     message.success({ | ||||||
|  |       content: $t('ui.actionMessage.submitSuccess', [row.name]), | ||||||
|  |       key: 'action_key_msg', | ||||||
|  |     }); | ||||||
|  |     onRefresh(); | ||||||
|  |   } finally { | ||||||
|  |     hideLoading(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查看合同详情 */ | ||||||
|  | function handleDetail(row: CrmContractApi.Contract) { | ||||||
|  |   push({ name: 'CrmContractDetail', params: { id: row.id } }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查看客户详情 */ | ||||||
|  | function handleCustomerDetail(row: CrmContractApi.Contract) { | ||||||
|  |   push({ name: 'CrmCustomerDetail', params: { id: row.customerId } }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查看联系人详情 */ | ||||||
|  | function handleContactDetail(row: CrmContractApi.Contract) { | ||||||
|  |   push({ name: 'CrmContactDetail', params: { id: row.signContactId } }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查看商机详情 */ | ||||||
|  | function handleBusinessDetail(row: CrmContractApi.Contract) { | ||||||
|  |   push({ name: 'CrmBusinessDetail', params: { id: row.businessId } }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查看审批详情 */ | ||||||
|  | function handleProcessDetail(row: CrmContractApi.Contract) { | ||||||
|  |   push({ | ||||||
|  |     name: 'BpmProcessInstanceDetail', | ||||||
|  |     query: { id: row.processInstanceId }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   formOptions: { | ||||||
|  |     schema: useGridFormSchema(), | ||||||
|  |   }, | ||||||
|  |   gridOptions: { | ||||||
|  |     columns: useGridColumns(), | ||||||
|  |     height: 'auto', | ||||||
|  |     keepSource: true, | ||||||
|  |     proxyConfig: { | ||||||
|  |       ajax: { | ||||||
|  |         query: async ({ page }, formValues) => { | ||||||
|  |           return await getContractPage({ | ||||||
|  |             page: page.currentPage, | ||||||
|  |             pageSize: page.pageSize, | ||||||
|  |             sceneType: sceneType.value, | ||||||
|  |             ...formValues, | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       refresh: { code: 'query' }, | ||||||
|  |       search: true, | ||||||
|  |     }, | ||||||
|  |   } as VxeTableGridOptions<CrmContractApi.Contract>, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function onChangeSceneType(key: number | string) { | ||||||
|  |   sceneType.value = key.toString(); | ||||||
|  |   gridApi.query(); | ||||||
|  | } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <Page> |   <Page auto-content-height> | ||||||
|     <DocAlert |     <template #doc> | ||||||
|       title="【合同】合同管理、合同提醒" |       <DocAlert | ||||||
|       url="https://doc.iocoder.cn/crm/contract/" |         title="【合同】合同管理、合同提醒" | ||||||
|     /> |         url="https://doc.iocoder.cn/crm/contract/" | ||||||
|     <DocAlert |       /> | ||||||
|       title="【通用】数据权限" |       <DocAlert | ||||||
|       url="https://doc.iocoder.cn/crm/permission/" |         title="【通用】数据权限" | ||||||
|     /> |         url="https://doc.iocoder.cn/crm/permission/" | ||||||
|     <Button |       /> | ||||||
|       danger |     </template> | ||||||
|       type="link" | 
 | ||||||
|       target="_blank" |     <FormModal @success="onRefresh" /> | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" |     <Grid> | ||||||
|     > |       <template #top> | ||||||
|       该功能支持 Vue3 + element-plus 版本! |         <Tabs class="border-none" @change="onChangeSceneType"> | ||||||
|     </Button> |           <Tabs.TabPane tab="我负责的" key="1" /> | ||||||
|     <br /> |           <Tabs.TabPane tab="我参与的" key="2" /> | ||||||
|     <Button |           <Tabs.TabPane tab="下属负责的" key="3" /> | ||||||
|       type="link" |         </Tabs> | ||||||
|       target="_blank" |       </template> | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/crm/contract/index" |       <template #toolbar-tools> | ||||||
|     > |         <TableAction | ||||||
|       可参考 |           :actions="[ | ||||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/crm/contract/index |             { | ||||||
|       代码,pull request 贡献给我们! |               label: $t('ui.actionTitle.create', ['合同']), | ||||||
|     </Button> |               type: 'primary', | ||||||
|  |               icon: ACTION_ICON.ADD, | ||||||
|  |               auth: ['crm:contract:create'], | ||||||
|  |               onClick: handleCreate, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: $t('ui.actionTitle.export'), | ||||||
|  |               type: 'primary', | ||||||
|  |               icon: ACTION_ICON.DOWNLOAD, | ||||||
|  |               auth: ['crm:contract:export'], | ||||||
|  |               onClick: handleExport, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |       <template #name="{ row }"> | ||||||
|  |         <Button type="link" @click="handleDetail(row)"> | ||||||
|  |           {{ row.name }} | ||||||
|  |         </Button> | ||||||
|  |       </template> | ||||||
|  |       <template #customerName="{ row }"> | ||||||
|  |         <Button type="link" @click="handleCustomerDetail(row)"> | ||||||
|  |           {{ row.customerName }} | ||||||
|  |         </Button> | ||||||
|  |       </template> | ||||||
|  |       <template #businessName="{ row }"> | ||||||
|  |         <Button type="link" @click="handleBusinessDetail(row)"> | ||||||
|  |           {{ row.businessName }} | ||||||
|  |         </Button> | ||||||
|  |       </template> | ||||||
|  |       <template #signContactName="{ row }"> | ||||||
|  |         <Button type="link" @click="handleContactDetail(row)"> | ||||||
|  |           {{ row.signContactName }} | ||||||
|  |         </Button> | ||||||
|  |       </template> | ||||||
|  |       <template #actions="{ row }"> | ||||||
|  |         <TableAction | ||||||
|  |           :actions="[ | ||||||
|  |             { | ||||||
|  |               label: $t('common.edit'), | ||||||
|  |               type: 'link', | ||||||
|  |               icon: ACTION_ICON.EDIT, | ||||||
|  |               auth: ['crm:contract:update'], | ||||||
|  |               onClick: handleEdit.bind(null, row), | ||||||
|  |               ifShow: row.auditStatus === 0, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |           :drop-down-actions="[ | ||||||
|  |             { | ||||||
|  |               label: '提交审核', | ||||||
|  |               type: 'link', | ||||||
|  |               auth: ['crm:contract:update'], | ||||||
|  |               onClick: handleSubmit.bind(null, row), | ||||||
|  |               ifShow: row.auditStatus === 0, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: '查看审批', | ||||||
|  |               type: 'link', | ||||||
|  |               auth: ['crm:contract:update'], | ||||||
|  |               onClick: handleProcessDetail.bind(null, row), | ||||||
|  |               ifShow: row.auditStatus !== 0, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: $t('common.detail'), | ||||||
|  |               type: 'link', | ||||||
|  |               auth: ['crm:contract:query'], | ||||||
|  |               onClick: handleDetail.bind(null, row), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: $t('common.delete'), | ||||||
|  |               type: 'link', | ||||||
|  |               danger: true, | ||||||
|  |               auth: ['crm:contract:delete'], | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: $t('ui.actionMessage.deleteConfirm', [row.name]), | ||||||
|  |                 confirm: handleDelete.bind(null, row), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |     </Grid> | ||||||
|   </Page> |   </Page> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { CrmContractApi } from '#/api/crm/contract'; | ||||||
|  | 
 | ||||||
|  | import { computed, ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenForm, useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { | ||||||
|  |   createContract, | ||||||
|  |   getContract, | ||||||
|  |   updateContract, | ||||||
|  | } from '#/api/crm/contract'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useFormSchema } from '../data'; | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['success']); | ||||||
|  | const formData = ref<CrmContractApi.Contract>(); | ||||||
|  | const getTitle = computed(() => { | ||||||
|  |   return formData.value?.id | ||||||
|  |     ? $t('ui.actionTitle.edit', ['合同']) | ||||||
|  |     : $t('ui.actionTitle.create', ['合同']); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Form, formApi] = useVbenForm({ | ||||||
|  |   commonConfig: { | ||||||
|  |     componentProps: { | ||||||
|  |       class: 'w-full', | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   // 一共2列 | ||||||
|  |   wrapperClass: 'grid-cols-2', | ||||||
|  |   layout: 'horizontal', | ||||||
|  |   schema: useFormSchema(), | ||||||
|  |   showDefaultActions: false, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Modal, modalApi] = useVbenModal({ | ||||||
|  |   async onConfirm() { | ||||||
|  |     const { valid } = await formApi.validate(); | ||||||
|  |     if (!valid) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     modalApi.lock(); | ||||||
|  |     // 提交表单 | ||||||
|  |     const data = (await formApi.getValues()) as CrmContractApi.Contract; | ||||||
|  |     try { | ||||||
|  |       await (formData.value?.id ? updateContract(data) : createContract(data)); | ||||||
|  |       // 关闭并提示 | ||||||
|  |       await modalApi.close(); | ||||||
|  |       emit('success'); | ||||||
|  |       message.success($t('ui.actionMessage.operationSuccess')); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   async onOpenChange(isOpen: boolean) { | ||||||
|  |     if (!isOpen) { | ||||||
|  |       formData.value = undefined; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // 加载数据 | ||||||
|  |     const data = modalApi.getData<CrmContractApi.Contract>(); | ||||||
|  |     if (!data || !data.id) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     modalApi.lock(); | ||||||
|  |     try { | ||||||
|  |       formData.value = await getContract(data.id as number); | ||||||
|  |       // 设置到 values | ||||||
|  |       await formApi.setValues(formData.value); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal :title="getTitle" class="w-[40%]"> | ||||||
|  |     <Form class="mx-4" /> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
|  | @ -1,7 +1,10 @@ | ||||||
| import type { VbenFormSchema } from '#/adapter/form'; | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
| 
 | 
 | ||||||
|  | import { handleTree } from '@vben/utils'; | ||||||
|  | 
 | ||||||
| import { z } from '#/adapter/form'; | import { z } from '#/adapter/form'; | ||||||
|  | import { getProductCategoryList } from '#/api/crm/product/category'; | ||||||
| import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; | import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; | ||||||
| 
 | 
 | ||||||
| /** 新增/修改的表单 */ | /** 新增/修改的表单 */ | ||||||
|  | @ -28,17 +31,24 @@ export function useFormSchema(): VbenFormSchema[] { | ||||||
|       rules: 'required', |       rules: 'required', | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       component: 'Input', |       component: 'ApiTreeSelect', | ||||||
|       fieldName: 'categoryName', |       fieldName: 'categoryName', | ||||||
|       label: '产品类型', |       label: '产品类型', | ||||||
|       rules: 'required', |       rules: 'required', | ||||||
|  |       componentProps: { | ||||||
|  |         api: async () => { | ||||||
|  |           const data = await getProductCategoryList(); | ||||||
|  |           return handleTree(data); | ||||||
|  |         }, | ||||||
|  |         fieldNames: { label: 'name', value: 'id', children: 'children' }, | ||||||
|  |       }, | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       fieldName: 'unit', |       fieldName: 'unit', | ||||||
|       label: '产品单位', |       label: '产品单位', | ||||||
|       component: 'Select', |       component: 'Select', | ||||||
|       componentProps: { |       componentProps: { | ||||||
|         options: getDictOptions(DICT_TYPE.CRM_PRODUCT_UNIT), |         options: getDictOptions(DICT_TYPE.CRM_PRODUCT_UNIT, 'number'), | ||||||
|       }, |       }, | ||||||
|       rules: 'required', |       rules: 'required', | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 xingyu4j
						xingyu4j