From 775fd1d7329a41135120744d0c1cc8a6be56c558 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 19 Nov 2025 08:23:18 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E3=80=90ele=E3=80=91=E3=80=90crm?= =?UTF-8?q?=E3=80=91contact=20=E7=9A=84=E8=BF=81=E7=A7=BB=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-ele/src/router/routes/modules/crm.ts | 18 +- .../src/views/crm/contact/components/data.ts | 62 +++ .../contact/components/detail-list-modal.vue | 148 +++++++ .../crm/contact/components/detail-list.vue | 210 ++++++++++ .../src/views/crm/contact/components/index.ts | 1 + apps/web-ele/src/views/crm/contact/data.ts | 365 ++++++++++++++++++ .../src/views/crm/contact/detail/data.ts | 106 +++++ .../src/views/crm/contact/detail/index.vue | 159 ++++++++ .../views/crm/contact/detail/modules/info.vue | 38 ++ apps/web-ele/src/views/crm/contact/index.vue | 203 ++++++++++ .../src/views/crm/contact/modules/form.vue | 80 ++++ 11 files changed, 1381 insertions(+), 9 deletions(-) create mode 100644 apps/web-ele/src/views/crm/contact/components/data.ts create mode 100644 apps/web-ele/src/views/crm/contact/components/detail-list-modal.vue create mode 100644 apps/web-ele/src/views/crm/contact/components/detail-list.vue create mode 100644 apps/web-ele/src/views/crm/contact/components/index.ts create mode 100644 apps/web-ele/src/views/crm/contact/data.ts create mode 100644 apps/web-ele/src/views/crm/contact/detail/data.ts create mode 100644 apps/web-ele/src/views/crm/contact/detail/index.vue create mode 100644 apps/web-ele/src/views/crm/contact/detail/modules/info.vue create mode 100644 apps/web-ele/src/views/crm/contact/index.vue create mode 100644 apps/web-ele/src/views/crm/contact/modules/form.vue diff --git a/apps/web-ele/src/router/routes/modules/crm.ts b/apps/web-ele/src/router/routes/modules/crm.ts index 13fc8772b..599a3b409 100644 --- a/apps/web-ele/src/router/routes/modules/crm.ts +++ b/apps/web-ele/src/router/routes/modules/crm.ts @@ -65,15 +65,15 @@ const routes: RouteRecordRaw[] = [ }, component: () => import('#/views/crm/receivable/detail/index.vue'), }, - // { - // path: 'contact/detail/:id', - // name: 'CrmContactDetail', - // meta: { - // title: '联系人详情', - // activePath: '/crm/contact', - // }, - // component: () => import('#/views/crm/contact/detail/index.vue'), - // }, + { + path: 'contact/detail/:id', + name: 'CrmContactDetail', + meta: { + title: '联系人详情', + activePath: '/crm/contact', + }, + component: () => import('#/views/crm/contact/detail/index.vue'), + }, { path: 'product/detail/:id', name: 'CrmProductDetail', diff --git a/apps/web-ele/src/views/crm/contact/components/data.ts b/apps/web-ele/src/views/crm/contact/components/data.ts new file mode 100644 index 000000000..b2e63f76e --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/components/data.ts @@ -0,0 +1,62 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 联系人明细列表列配置 */ +export function useDetailListColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'name', + title: '姓名', + fixed: 'left', + slots: { default: 'name' }, + }, + { + field: 'customerName', + title: '客户名称', + fixed: 'left', + slots: { default: 'customerName' }, + }, + { + field: 'sex', + title: '性别', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_USER_SEX }, + }, + }, + { + field: 'mobile', + title: '手机', + }, + { + field: 'telephone', + title: '电话', + }, + { + field: 'email', + title: '邮箱', + }, + { + field: 'post', + title: '职位', + }, + { + field: 'detailAddress', + title: '地址', + }, + { + field: 'master', + title: '关键决策人', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contact/components/detail-list-modal.vue b/apps/web-ele/src/views/crm/contact/components/detail-list-modal.vue new file mode 100644 index 000000000..9d665ce42 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/components/detail-list-modal.vue @@ -0,0 +1,148 @@ + + + + diff --git a/apps/web-ele/src/views/crm/contact/components/detail-list.vue b/apps/web-ele/src/views/crm/contact/components/detail-list.vue new file mode 100644 index 000000000..b49e768ac --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/components/detail-list.vue @@ -0,0 +1,210 @@ + + + + diff --git a/apps/web-ele/src/views/crm/contact/components/index.ts b/apps/web-ele/src/views/crm/contact/components/index.ts new file mode 100644 index 000000000..d16cd533d --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/components/index.ts @@ -0,0 +1 @@ +export { default as ContactDetailsList } from './detail-list.vue'; diff --git a/apps/web-ele/src/views/crm/contact/data.ts b/apps/web-ele/src/views/crm/contact/data.ts new file mode 100644 index 000000000..69f602b12 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/data.ts @@ -0,0 +1,365 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { useUserStore } from '@vben/stores'; + +import { getSimpleContactList } from '#/api/crm/contact'; +import { getCustomerSimpleList } from '#/api/crm/customer'; +import { getAreaTree } from '#/api/system/area'; +import { getSimpleUserList } from '#/api/system/user'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + const userStore = useUserStore(); + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '联系人姓名', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入联系人姓名', + }, + }, + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + rules: 'required', + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择负责人', + }, + defaultValue: userStore.userInfo?.id, + }, + { + fieldName: 'customerId', + label: '客户名称', + component: 'ApiSelect', + rules: 'required', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + }, + }, + { + fieldName: 'mobile', + label: '手机', + component: 'Input', + componentProps: { + placeholder: '请输入手机号', + }, + }, + { + fieldName: 'telephone', + label: '电话', + component: 'Input', + componentProps: { + placeholder: '请输入电话', + }, + }, + { + fieldName: 'email', + label: '邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入邮箱', + }, + }, + { + fieldName: 'wechat', + label: '微信', + component: 'Input', + componentProps: { + placeholder: '请输入微信', + }, + }, + { + fieldName: 'qq', + label: 'QQ', + component: 'Input', + componentProps: { + placeholder: '请输入QQ', + }, + }, + { + fieldName: 'post', + label: '职位', + component: 'Input', + componentProps: { + placeholder: '请输入职位', + }, + }, + { + fieldName: 'master', + label: '关键决策人', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + placeholder: '请选择是否关键决策人', + }, + defaultValue: false, + }, + { + fieldName: 'sex', + label: '性别', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + placeholder: '请选择性别', + }, + }, + { + fieldName: 'parentId', + label: '直属上级', + component: 'ApiSelect', + componentProps: { + api: getSimpleContactList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择直属上级', + }, + }, + { + fieldName: 'areaId', + label: '地址', + component: 'ApiTreeSelect', + componentProps: { + api: getAreaTree, + fieldNames: { label: 'name', value: 'id', children: 'children' }, + placeholder: '请选择地址', + }, + }, + { + fieldName: 'detailAddress', + label: '详细地址', + component: 'Input', + componentProps: { + placeholder: '请输入详细地址', + }, + }, + { + fieldName: 'contactNextTime', + label: '下次联系时间', + component: 'DatePicker', + componentProps: { + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + placeholder: '请选择下次联系时间', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + }, + }, + { + fieldName: 'name', + label: '姓名', + component: 'Input', + componentProps: { + placeholder: '请输入联系人姓名', + allowClear: true, + }, + }, + { + fieldName: 'mobile', + label: '手机号', + component: 'Input', + componentProps: { + placeholder: '请输入手机号', + allowClear: true, + }, + }, + { + fieldName: 'telephone', + label: '电话', + component: 'Input', + componentProps: { + placeholder: '请输入电话', + allowClear: true, + }, + }, + { + fieldName: 'wechat', + label: '微信', + component: 'Input', + componentProps: { + placeholder: '请输入微信', + allowClear: true, + }, + }, + { + fieldName: 'email', + label: '电子邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入电子邮箱', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '联系人姓名', + fixed: 'left', + minWidth: 240, + slots: { default: 'name' }, + }, + { + field: 'customerName', + title: '客户名称', + fixed: 'left', + minWidth: 240, + slots: { default: 'customerName' }, + }, + { + field: 'mobile', + title: '手机', + minWidth: 120, + }, + { + field: 'telephone', + title: '电话', + minWidth: 130, + }, + { + field: 'email', + title: '邮箱', + minWidth: 180, + }, + { + field: 'post', + title: '职位', + minWidth: 120, + }, + { + field: 'areaName', + title: '地址', + minWidth: 120, + }, + { + field: 'detailAddress', + title: '详细地址', + minWidth: 180, + }, + { + field: 'master', + title: '关键决策人', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'parentId', + title: '直属上级', + minWidth: 120, + slots: { default: 'parentId' }, + }, + { + field: 'contactNextTime', + title: '下次联系时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'sex', + title: '性别', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_USER_SEX }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'contactLastTime', + title: '最后跟进时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'ownerUserName', + title: '负责人', + minWidth: 120, + }, + { + field: 'ownerUserDeptName', + title: '所属部门', + minWidth: 120, + }, + { + field: 'updateTime', + title: '更新时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contact/detail/data.ts b/apps/web-ele/src/views/crm/contact/detail/data.ts new file mode 100644 index 000000000..3f1019325 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/detail/data.ts @@ -0,0 +1,106 @@ +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; + +/** 详情页的基础字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'post', + label: '职务', + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '姓名', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'telephone', + label: '电话', + }, + { + field: 'email', + label: '邮箱', + }, + { + field: 'qq', + label: 'QQ', + }, + { + field: 'wechat', + label: '微信', + }, + { + field: 'areaName', + label: '地址', + render: (val, data) => { + const areaName = val ?? ''; + const detailAddress = data?.detailAddress ?? ''; + return [areaName, detailAddress].filter((item) => !!item).join(' '); + }, + }, + { + field: 'post', + label: '职务', + }, + { + field: 'parentName', + label: '直属上级', + }, + { + field: 'master', + label: '关键决策人', + render: (val) => + h(DictTag, { + type: DICT_TYPE.INFRA_BOOLEAN_STRING, + value: val, + }), + }, + { + field: 'sex', + label: '性别', + render: (val) => + h(DictTag, { type: DICT_TYPE.SYSTEM_USER_SEX, value: val }), + }, + { + field: 'contactNextTime', + label: '下次联系时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'remark', + label: '备注', + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contact/detail/index.vue b/apps/web-ele/src/views/crm/contact/detail/index.vue new file mode 100644 index 000000000..d20b250d3 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/detail/index.vue @@ -0,0 +1,159 @@ + + + diff --git a/apps/web-ele/src/views/crm/contact/detail/modules/info.vue b/apps/web-ele/src/views/crm/contact/detail/modules/info.vue new file mode 100644 index 000000000..11d238617 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/detail/modules/info.vue @@ -0,0 +1,38 @@ + + + diff --git a/apps/web-ele/src/views/crm/contact/index.vue b/apps/web-ele/src/views/crm/contact/index.vue new file mode 100644 index 000000000..4b0f4166b --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/index.vue @@ -0,0 +1,203 @@ + + + diff --git a/apps/web-ele/src/views/crm/contact/modules/form.vue b/apps/web-ele/src/views/crm/contact/modules/form.vue new file mode 100644 index 000000000..5b0b3c7d9 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/modules/form.vue @@ -0,0 +1,80 @@ + + +