diff --git a/apps/web-ele/src/router/routes/modules/crm.ts b/apps/web-ele/src/router/routes/modules/crm.ts index 0b36acbf7..a5d7f3212 100644 --- a/apps/web-ele/src/router/routes/modules/crm.ts +++ b/apps/web-ele/src/router/routes/modules/crm.ts @@ -38,15 +38,15 @@ const routes: RouteRecordRaw[] = [ // }, // component: () => import('#/views/crm/business/detail/index.vue'), // }, - // { - // path: 'contract/detail/:id', - // name: 'CrmContractDetail', - // meta: { - // title: '合同详情', - // activePath: '/crm/contract', - // }, - // component: () => import('#/views/crm/contract/detail/index.vue'), - // }, + { + path: 'contract/detail/:id', + name: 'CrmContractDetail', + meta: { + title: '合同详情', + activePath: '/crm/contract', + }, + component: () => import('#/views/crm/contract/detail/index.vue'), + }, { path: 'receivable-plan/detail/:id', name: 'CrmReceivablePlanDetail', diff --git a/apps/web-ele/src/views/crm/contract/components/data.ts b/apps/web-ele/src/views/crm/contract/components/data.ts new file mode 100644 index 000000000..c314faa99 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/components/data.ts @@ -0,0 +1,92 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { erpPriceInputFormatter } from '@vben/utils'; + +export function useDetailListColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '合同编号', + field: 'no', + minWidth: 150, + fixed: 'left', + }, + { + title: '合同名称', + field: 'name', + minWidth: 150, + fixed: 'left', + slots: { default: 'name' }, + }, + { + title: '合同金额(元)', + field: 'totalPrice', + minWidth: 150, + formatter: 'formatAmount2', + }, + { + title: '合同开始时间', + field: 'startTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '合同结束时间', + field: 'endTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '已回款金额(元)', + field: 'totalReceivablePrice', + minWidth: 150, + formatter: 'formatAmount2', + }, + { + title: '未回款金额(元)', + field: 'unpaidPrice', + minWidth: 150, + formatter: ({ row }) => { + return erpPriceInputFormatter( + row.totalPrice - row.totalReceivablePrice, + ); + }, + }, + { + title: '负责人', + field: 'ownerUserName', + minWidth: 150, + }, + { + title: '所属部门', + field: 'ownerUserDeptName', + minWidth: 150, + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '创建人', + field: 'creatorName', + minWidth: 150, + }, + { + title: '备注', + field: 'remark', + minWidth: 150, + }, + { + title: '合同状态', + field: 'auditStatus', + fixed: 'right', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contract/components/detail-list.vue b/apps/web-ele/src/views/crm/contract/components/detail-list.vue new file mode 100644 index 000000000..63dc35ca2 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/components/detail-list.vue @@ -0,0 +1,133 @@ + + + + diff --git a/apps/web-ele/src/views/crm/contract/components/index.ts b/apps/web-ele/src/views/crm/contract/components/index.ts new file mode 100644 index 000000000..d9f61793c --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/components/index.ts @@ -0,0 +1 @@ +export { default as ContractDetailsList } from './detail-list.vue'; diff --git a/apps/web-ele/src/views/crm/contract/config/data.ts b/apps/web-ele/src/views/crm/contract/config/data.ts new file mode 100644 index 000000000..8900429de --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/config/data.ts @@ -0,0 +1,39 @@ +import type { VbenFormSchema } from '#/adapter/form'; + +export const schema: VbenFormSchema[] = [ + { + component: 'RadioGroup', + fieldName: 'notifyEnabled', + label: '提前提醒设置', + componentProps: { + options: [ + { label: '提醒', value: true }, + { label: '不提醒', value: false }, + ], + }, + defaultValue: true, + }, + { + component: 'InputNumber', + fieldName: 'notifyDays', + componentProps: { + min: 0, + precision: 0, + controlsPosition: 'right', + class: '!w-full', + }, + renderComponentContent: () => ({ + addonBefore: () => '提前', + addonAfter: () => '天提醒', + }), + dependencies: { + triggerFields: ['notifyEnabled'], + show: (values) => values.notifyEnabled, + trigger(values) { + if (!values.notifyEnabled) { + values.notifyDays = undefined; + } + }, + }, + }, +]; diff --git a/apps/web-ele/src/views/crm/contract/config/index.vue b/apps/web-ele/src/views/crm/contract/config/index.vue new file mode 100644 index 000000000..c3dbb0845 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/config/index.vue @@ -0,0 +1,61 @@ + + + diff --git a/apps/web-ele/src/views/crm/contract/data.ts b/apps/web-ele/src/views/crm/contract/data.ts new file mode 100644 index 000000000..e271ee6b4 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/data.ts @@ -0,0 +1,415 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { useUserStore } from '@vben/stores'; +import { erpPriceInputFormatter, erpPriceMultiply } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getSimpleBusinessList } from '#/api/crm/business'; +import { getSimpleContactList } from '#/api/crm/contact'; +import { getCustomerSimpleList } from '#/api/crm/customer'; +import { getSimpleUserList } from '#/api/system/user'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + const userStore = useUserStore(); + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '合同编号', + component: 'Input', + componentProps: { + placeholder: '保存时自动生成', + disabled: true, + }, + }, + { + fieldName: 'name', + label: '合同名称', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入合同名称', + }, + }, + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + defaultValue: userStore.userInfo?.id, + rules: 'required', + }, + { + fieldName: 'customerId', + label: '客户名称', + component: 'ApiSelect', + rules: 'required', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + }, + }, + { + fieldName: 'businessId', + label: '商机名称', + component: 'Select', + componentProps: { + options: [], + placeholder: '请选择商机', + }, + dependencies: { + triggerFields: ['customerId'], + disabled: (values) => !values.customerId, + async componentProps(values) { + if (!values.customerId) { + return { + options: [], + placeholder: '请选择客户', + }; + } + const res = await getSimpleBusinessList(); + const list = res.filter( + (item) => item.customerId === values.customerId, + ); + return { + options: list.map((item) => ({ + label: item.name, + value: item.id, + })), + placeholder: '请选择商机', + }; + }, + }, + }, + { + fieldName: 'orderDate', + label: '下单日期', + component: 'DatePicker', + rules: 'required', + componentProps: { + format: 'YYYY-MM-DD', + valueFormat: 'x', + }, + }, + { + fieldName: 'startTime', + label: '合同开始时间', + component: 'DatePicker', + componentProps: { + format: 'YYYY-MM-DD', + valueFormat: 'x', + }, + }, + { + fieldName: 'endTime', + label: '合同结束时间', + component: 'DatePicker', + componentProps: { + format: 'YYYY-MM-DD', + valueFormat: 'x', + }, + }, + { + fieldName: 'signUserId', + label: '公司签约人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + defaultValue: userStore.userInfo?.id, + }, + { + fieldName: 'signContactId', + label: '客户签约人', + component: 'Select', + componentProps: { + options: [], + placeholder: '请选择客户签约人', + }, + dependencies: { + triggerFields: ['customerId'], + disabled: (values) => !values.customerId, + async componentProps(values) { + if (!values.customerId) { + return { + options: [], + placeholder: '请选择客户', + }; + } + const res = await getSimpleContactList(); + const list = res.filter( + (item) => item.customerId === values.customerId, + ); + return { + options: list.map((item) => ({ + label: item.name, + value: item.id, + })), + placeholder: '请选择客户签约人', + }; + }, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 4, + }, + }, + { + fieldName: 'product', + label: '产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'totalProductPrice', + label: '产品总金额', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + placeholder: '请输入产品总金额', + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional().default(0), + }, + { + fieldName: 'discountPercent', + label: '整单折扣(%)', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + placeholder: '请输入整单折扣', + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).max(100).optional().default(0), + }, + { + fieldName: 'totalPrice', + label: '折扣后金额', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['totalProductPrice', 'discountPercent'], + trigger(values, form) { + const discountPrice = + erpPriceMultiply( + values.totalProductPrice, + values.discountPercent / 100, + ) ?? 0; + form.setFieldValue( + 'totalPrice', + values.totalProductPrice - discountPrice, + ); + }, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '合同编号', + component: 'Input', + componentProps: { + placeholder: '请输入合同编号', + clearable: true, + }, + }, + { + fieldName: 'name', + label: '合同名称', + component: 'Input', + componentProps: { + placeholder: '请输入合同名称', + clearable: true, + }, + }, + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + clearable: true, + }, + }, + ]; +} + +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '合同编号', + field: 'no', + minWidth: 180, + fixed: 'left', + }, + { + title: '合同名称', + field: 'name', + minWidth: 160, + fixed: 'left', + slots: { default: 'name' }, + }, + { + title: '客户名称', + field: 'customerName', + minWidth: 120, + slots: { default: 'customerName' }, + }, + { + title: '商机名称', + field: 'businessName', + minWidth: 130, + slots: { default: 'businessName' }, + }, + { + title: '合同金额(元)', + field: 'totalPrice', + minWidth: 140, + formatter: 'formatAmount2', + }, + { + title: '下单时间', + field: 'orderDate', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '合同开始时间', + field: 'startTime', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '合同结束时间', + field: 'endTime', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '客户签约人', + field: 'signContactName', + minWidth: 130, + slots: { default: 'signContactName' }, + }, + { + title: '公司签约人', + field: 'signUserName', + minWidth: 130, + }, + { + title: '备注', + field: 'remark', + minWidth: 200, + }, + { + title: '已回款金额(元)', + field: 'totalReceivablePrice', + minWidth: 140, + formatter: 'formatAmount2', + }, + { + title: '未回款金额(元)', + field: 'unReceivablePrice', + minWidth: 140, + formatter: ({ row }) => { + return erpPriceInputFormatter( + row.totalPrice - row.totalReceivablePrice, + ); + }, + }, + { + title: '最后跟进时间', + field: 'contactLastTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '负责人', + field: 'ownerUserName', + minWidth: 120, + }, + { + title: '所属部门', + field: 'ownerUserDeptName', + minWidth: 100, + }, + { + title: '更新时间', + field: 'updateTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '创建人', + field: 'creatorName', + minWidth: 120, + }, + { + title: '合同状态', + field: 'auditStatus', + fixed: 'right', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, + }, + }, + { + title: '操作', + field: 'actions', + fixed: 'right', + minWidth: 130, + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contract/detail/data.ts b/apps/web-ele/src/views/crm/contract/detail/data.ts new file mode 100644 index 000000000..bfe7b5606 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/detail/data.ts @@ -0,0 +1,100 @@ +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; + +/** 详情头部的配置 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '合同金额(元)', + render: (val) => erpPriceInputFormatter(val) as string, + }, + { + field: 'orderDate', + label: '下单时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'totalReceivablePrice', + label: '回款金额(元)', + render: (val) => erpPriceInputFormatter(val) as string, + }, + { + field: 'ownerUserName', + label: '负责人', + }, + ]; +} + +/** 详情基本信息的配置 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '合同编号', + }, + { + field: 'name', + label: '合同名称', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'businessName', + label: '商机名称', + }, + { + field: 'totalPrice', + label: '合同金额(元)', + render: (val) => erpPriceInputFormatter(val) as string, + }, + { + field: 'orderDate', + label: '下单时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'startTime', + label: '合同开始时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'endTime', + label: '合同结束时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'signContactName', + label: '客户签约人', + }, + { + field: 'signUserName', + label: '公司签约人', + }, + { + field: 'remark', + label: '备注', + }, + { + field: 'auditStatus', + label: '合同状态', + render: (val) => + h(DictTag, { + type: DICT_TYPE.CRM_AUDIT_STATUS, + value: val, + }), + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contract/detail/index.vue b/apps/web-ele/src/views/crm/contract/detail/index.vue new file mode 100644 index 000000000..a172738db --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/detail/index.vue @@ -0,0 +1,175 @@ + + + diff --git a/apps/web-ele/src/views/crm/contract/detail/modules/info.vue b/apps/web-ele/src/views/crm/contract/detail/modules/info.vue new file mode 100644 index 000000000..a87db739f --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/detail/modules/info.vue @@ -0,0 +1,38 @@ + + + diff --git a/apps/web-ele/src/views/crm/contract/index.vue b/apps/web-ele/src/views/crm/contract/index.vue new file mode 100644 index 000000000..d583162ed --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/index.vue @@ -0,0 +1,257 @@ + + + diff --git a/apps/web-ele/src/views/crm/contract/modules/form.vue b/apps/web-ele/src/views/crm/contract/modules/form.vue new file mode 100644 index 000000000..b39065435 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/modules/form.vue @@ -0,0 +1,121 @@ + + +