crm联系人init
							parent
							
								
									eb9f8c9f6f
								
							
						
					
					
						commit
						ed26142bb0
					
				|  | @ -0,0 +1,65 @@ | |||
| /* | ||||
|  * @Author: zyna | ||||
|  * @Date: 2023-11-05 13:34:41 | ||||
|  * @LastEditTime: 2023-11-11 16:20:19 | ||||
|  * @FilePath: \yudao-ui-admin-vue3\src\api\crm\contact\index.ts | ||||
|  * @Description: | ||||
|  */ | ||||
| import request from '@/config/axios' | ||||
| 
 | ||||
| export interface ContactVO { | ||||
|   name: string | ||||
|   nextTime: Date | ||||
|   mobile: string | ||||
|   telephone: string | ||||
|   email: string | ||||
|   post: string | ||||
|   customerId: number | ||||
|   address: string | ||||
|   remark: string | ||||
|   ownerUserId: string | ||||
|   lastTime: Date | ||||
|   id: number | ||||
|   parentId: number | ||||
|   qq: number | ||||
|   webchat: string | ||||
|   sex: number | ||||
|   policyMakers: boolean | ||||
|   creatorName: string | ||||
|   updateTime?: Date | ||||
|   createTime?: Date | ||||
|   customerName: string | ||||
| } | ||||
| 
 | ||||
| // 查询crm联系人列表
 | ||||
| export const getContactPage = async (params) => { | ||||
|   return await request.get({ url: `/crm/contact/page`, params }) | ||||
| } | ||||
| 
 | ||||
| // 查询crm联系人详情
 | ||||
| export const getContact = async (id: number) => { | ||||
|   return await request.get({ url: `/crm/contact/get?id=` + id }) | ||||
| } | ||||
| 
 | ||||
| // 新增crm联系人
 | ||||
| export const createContact = async (data: ContactVO) => { | ||||
|   return await request.post({ url: `/crm/contact/create`, data }) | ||||
| } | ||||
| 
 | ||||
| // 修改crm联系人
 | ||||
| export const updateContact = async (data: ContactVO) => { | ||||
|   return await request.put({ url: `/crm/contact/update`, data }) | ||||
| } | ||||
| 
 | ||||
| // 删除crm联系人
 | ||||
| export const deleteContact = async (id: number) => { | ||||
|   return await request.delete({ url: `/crm/contact/delete?id=` + id }) | ||||
| } | ||||
| 
 | ||||
| // 导出crm联系人 Excel
 | ||||
| export const exportContact = async (params) => { | ||||
|   return await request.download({ url: `/crm/contact/export-excel`, params }) | ||||
| } | ||||
| export const simpleAlllist = async () => { | ||||
|   return await request.get({ url: `/crm/contact/simpleAlllist` }) | ||||
| } | ||||
|  | @ -503,6 +503,16 @@ const remainingRouter: AppRouteRecordRaw[] = [ | |||
|           hidden: true | ||||
|         }, | ||||
|         component: () => import('@/views/crm/customer/detail/index.vue') | ||||
|       }, | ||||
|       { | ||||
|         path: 'contact/detail/:id', | ||||
|         name: 'CrmContactDetail', | ||||
|         meta: { | ||||
|           title: '联系人详情', | ||||
|           noCache: true, | ||||
|           hidden: true | ||||
|         }, | ||||
|         component: () => import('@/views/crm/contact/detail/index.vue') | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
|  |  | |||
|  | @ -0,0 +1,348 @@ | |||
| <template> | ||||
|   <Dialog :title="dialogTitle" v-model="dialogVisible" :width="800"> | ||||
|     <el-form | ||||
|       ref="formRef" | ||||
|       :model="formData" | ||||
|       :rules="formRules" | ||||
|       label-width="130px" | ||||
|       v-loading="formLoading" | ||||
|       :inline="true" | ||||
|     > | ||||
|       <el-form-item label="姓名" prop="name"> | ||||
|         <el-input v-model="formData.name" placeholder="请输入姓名" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="负责人" prop="ownerUserId"> | ||||
|         <el-select | ||||
|           v-model="ownerUserList" | ||||
|           placeholder="请选择负责人" | ||||
|           multiple | ||||
|           value-key="id" | ||||
|           lable-key="nickname" | ||||
|           @click="openOwerForm('open')" | ||||
|         > | ||||
|           <el-option | ||||
|             v-for="item in ownerUserList" | ||||
|             :key="item.id" | ||||
|             :label="item.nickname" | ||||
|             :value="item" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="客户名称" prop="customerName"> | ||||
|         <el-popover | ||||
|           placement="bottom" | ||||
|           :width="600" | ||||
|           trigger="click" | ||||
|           :teleported="false" | ||||
|           :visible="showCustomer" | ||||
|           :offset="10" | ||||
|         > | ||||
|           <template #reference> | ||||
|             <el-input | ||||
|               placeholder="请选择" | ||||
|               @click="openCustomerSelect" | ||||
|               v-model="formData.customerName" | ||||
|             /> | ||||
|           </template> | ||||
|           <el-table :data="list" ref="multipleTableRef" @select="handleSelectionChange"> | ||||
|             <el-table-column label="选择" type="selection" width="55" /> | ||||
|             <el-table-column width="100" property="id" label="编号" /> | ||||
|             <el-table-column width="150" property="name" label="客户名称" /> | ||||
|             <el-table-column label="客户来源" align="center" prop="source" width="100"> | ||||
|               <template #default="scope"> | ||||
|                 <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" /> | ||||
|               </template> | ||||
|             </el-table-column> | ||||
|             <el-table-column label="客户等级" align="center" prop="level" width="120"> | ||||
|               <template #default="scope"> | ||||
|                 <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" /> | ||||
|               </template> | ||||
|             </el-table-column> | ||||
|           </el-table> | ||||
|           <!-- 分页 --> | ||||
|           <el-row :gutter="20"> | ||||
|             <el-col> | ||||
|               <Pagination | ||||
|                 :total="total" | ||||
|                 v-model:page="queryParams.pageNo" | ||||
|                 v-model:limit="queryParams.pageSize" | ||||
|                 @pagination="getList" | ||||
|                 layout="sizes, prev, pager, next" | ||||
|               /> | ||||
|             </el-col> | ||||
|           </el-row> | ||||
|           <el-row :gutter="20"> | ||||
|             <el-col :span="10" :offset="13"> | ||||
|               <el-button @click="selectCustomer">确认</el-button> | ||||
|               <el-button @click="showCustomer = false">取消</el-button> | ||||
|             </el-col> | ||||
|           </el-row> | ||||
|         </el-popover> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="性别" prop="sex"> | ||||
|         <el-select v-model="formData.sex" placeholder="请选择"> | ||||
|           <el-option | ||||
|             v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)" | ||||
|             :key="dict.value" | ||||
|             :label="dict.label" | ||||
|             :value="dict.value" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
| 
 | ||||
|       <el-form-item label="手机号" prop="mobile"> | ||||
|         <el-input v-model="formData.mobile" placeholder="请输入手机号" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="座机" prop="telephone"> | ||||
|         <el-input v-model="formData.telephone" placeholder="请输入座机" style="width: 215px" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="邮箱" prop="email"> | ||||
|         <el-input v-model="formData.email" placeholder="请输入邮箱" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="QQ" prop="qq"> | ||||
|         <el-input v-model="formData.qq" placeholder="请输入QQ" style="width: 215px" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="微信" prop="webchat"> | ||||
|         <el-input v-model="formData.webchat" placeholder="请输入微信" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="下次联系时间" prop="nextTime"> | ||||
|         <el-date-picker | ||||
|           v-model="formData.nextTime" | ||||
|           type="date" | ||||
|           value-format="x" | ||||
|           placeholder="选择下次联系时间" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="地址" prop="address"> | ||||
|         <el-input v-model="formData.address" placeholder="请输入地址" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="直属上级" prop="parentId"> | ||||
|         <el-select v-model="formData.parentId" placeholder="请选择"> | ||||
|           <el-option | ||||
|             v-for="item in allContactList" | ||||
|             :key="item.id" | ||||
|             :label="item.name" | ||||
|             :value="item.id" | ||||
|             :disabled="item.id == formData.id" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
| 
 | ||||
|       <el-form-item label="职位" prop="post"> | ||||
|         <el-input v-model="formData.post" placeholder="请输入职位" /> | ||||
|       </el-form-item> | ||||
| 
 | ||||
|       <el-form-item label="是否关键决策人" prop="policyMakers" style="width: 400px"> | ||||
|         <el-radio-group v-model="formData.policyMakers"> | ||||
|           <el-radio | ||||
|             v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" | ||||
|             :key="dict.value" | ||||
|             :label="dict.value" | ||||
|           > | ||||
|             {{ dict.label }} | ||||
|           </el-radio> | ||||
|         </el-radio-group> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="备注" prop="remark"> | ||||
|         <el-input v-model="formData.remark" placeholder="请输入备注" /> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|     <template #footer> | ||||
|       <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> | ||||
|       <el-button @click="dialogVisible = false">取 消</el-button> | ||||
|     </template> | ||||
|   </Dialog> | ||||
|   <OwerSelect | ||||
|     ref="owerRef" | ||||
|     @confirmOwerSelect="owerSelectValue" | ||||
|     :initOwerUser="formData.ownerUserId" | ||||
|   /> | ||||
| </template> | ||||
| <script setup lang="ts"> | ||||
| import * as ContactApi from '@/api/crm/contact' | ||||
| import { DICT_TYPE, getIntDictOptions, getBoolDictOptions } from '@/utils/dict' | ||||
| import OwerSelect from './OwerSelect.vue' | ||||
| import * as UserApi from '@/api/system/user' | ||||
| import * as CustomerApi from '@/api/crm/customer' | ||||
| import { ElTable } from 'element-plus' | ||||
| 
 | ||||
| const { t } = useI18n() // 国际化 | ||||
| const message = useMessage() // 消息弹窗 | ||||
| const dialogVisible = ref(false) // 弹窗的是否展示 | ||||
| const dialogTitle = ref('') // 弹窗的标题 | ||||
| const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 | ||||
| const formType = ref('') // 表单的类型:create - 新增;update - 修改 | ||||
| const formData = ref({ | ||||
|   nextTime: undefined, | ||||
|   mobile: undefined, | ||||
|   telephone: undefined, | ||||
|   email: undefined, | ||||
|   customerId: undefined, | ||||
|   customerName: undefined, | ||||
|   address: undefined, | ||||
|   remark: undefined, | ||||
|   ownerUserId: undefined, | ||||
|   lastTime: undefined, | ||||
|   id: undefined, | ||||
|   parentId: undefined, | ||||
|   name: undefined, | ||||
|   post: undefined, | ||||
|   qq: undefined, | ||||
|   webchat: undefined, | ||||
|   sex: undefined, | ||||
|   policyMakers: undefined | ||||
| }) | ||||
| const loading = ref(true) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref([]) // 列表的数据 | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   name: null, | ||||
|   mobile: null, | ||||
|   industryId: null, | ||||
|   level: null, | ||||
|   source: null | ||||
| }) | ||||
| const formRules = reactive({ | ||||
|   name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }], | ||||
|   customerId: [{ required: true, message: '客户不能为空', trigger: 'blur' }], | ||||
|   ownerUserId: [{ required: true, message: '负责人不能为空', trigger: 'blur' }] | ||||
| }) | ||||
| const formRef = ref() // 表单 Ref | ||||
| const ownerUserList = ref<any[]>([]) | ||||
| const userList = ref<UserApi.UserVO[]>([]) // 用户列表 | ||||
| /** 打开弹窗 */ | ||||
| const open = async (type: string, id?: number) => { | ||||
|   dialogVisible.value = true | ||||
|   dialogTitle.value = t('action.' + type) | ||||
|   formType.value = type | ||||
|   allContactList.value = await ContactApi.simpleAlllist() | ||||
|   resetForm() | ||||
|   // 修改时,设置数据 | ||||
|   if (id) { | ||||
|     formLoading.value = true | ||||
|     try { | ||||
|       formData.value = await ContactApi.getContact(id) | ||||
|       userList.value = await UserApi.getSimpleUserList() | ||||
|       await gotOwnerUser(formData.value.ownerUserId) | ||||
|     } finally { | ||||
|       formLoading.value = false | ||||
|     } | ||||
|   } | ||||
| } | ||||
| /** 查询列表 */ | ||||
| const getList = async () => { | ||||
|   loading.value = true | ||||
|   try { | ||||
|     const data = await CustomerApi.getCustomerPage(queryParams) | ||||
|     list.value = data.list | ||||
|     total.value = data.total | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
| defineExpose({ open }) // 提供 open 方法,用于打开弹窗 | ||||
| const gotOwnerUser = (owerUserId: any) => { | ||||
|   if (owerUserId !== null) { | ||||
|     owerUserId.split(',').forEach((item: string) => { | ||||
|       userList.value.find((user: { id: any }) => { | ||||
|         if (user.id == item) { | ||||
|           ownerUserList.value.push(user) | ||||
|         } | ||||
|       }) | ||||
|     }) | ||||
|   } | ||||
| } | ||||
| /** 提交表单 */ | ||||
| const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 | ||||
| const submitForm = async () => { | ||||
|   owerSelectValue(ownerUserList) | ||||
|   // 校验表单 | ||||
|   if (!formRef) return | ||||
|   const valid = await formRef.value.validate() | ||||
|   if (!valid) return | ||||
|   // 提交请求 | ||||
|   formLoading.value = true | ||||
|   try { | ||||
|     const data = formData.value as unknown as ContactApi.ContactVO | ||||
|     if (formType.value === 'create') { | ||||
|       await ContactApi.createContact(data) | ||||
|       message.success(t('common.createSuccess')) | ||||
|     } else { | ||||
|       await ContactApi.updateContact(data) | ||||
|       message.success(t('common.updateSuccess')) | ||||
|     } | ||||
|     dialogVisible.value = false | ||||
|     // 发送操作成功的事件 | ||||
|     emit('success') | ||||
|   } finally { | ||||
|     formLoading.value = false | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 重置表单 */ | ||||
| const resetForm = () => { | ||||
|   formData.value = { | ||||
|     nextTime: undefined, | ||||
|     mobile: undefined, | ||||
|     telephone: undefined, | ||||
|     email: undefined, | ||||
|     customerId: undefined, | ||||
|     address: undefined, | ||||
|     remark: undefined, | ||||
|     ownerUserId: undefined, | ||||
|     lastTime: undefined, | ||||
|     id: undefined, | ||||
|     parentId: undefined, | ||||
|     name: undefined, | ||||
|     post: undefined, | ||||
|     qq: undefined, | ||||
|     webchat: undefined, | ||||
|     sex: undefined, | ||||
|     policyMakers: undefined | ||||
|   } | ||||
|   formRef.value?.resetFields() | ||||
|   ownerUserList.value = [] | ||||
| } | ||||
| /** 添加/修改操作 */ | ||||
| const owerRef = ref() | ||||
| const openOwerForm = (type: string) => { | ||||
|   owerRef.value.open(type, ownerUserList.value) | ||||
| } | ||||
| 
 | ||||
| const owerSelectValue = (value) => { | ||||
|   ownerUserList.value = value.value | ||||
|   formData.value.ownerUserId = undefined | ||||
|   value.value.forEach((item, index) => { | ||||
|     if (index != 0) { | ||||
|       formData.value.ownerUserId = formData.value.ownerUserId + ',' + item.id | ||||
|     } else { | ||||
|       formData.value.ownerUserId = item.id | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| //选择客户 | ||||
| const showCustomer = ref(false) | ||||
| const openCustomerSelect = () => { | ||||
|   showCustomer.value = !showCustomer.value | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
| const multipleTableRef = ref<InstanceType<typeof ElTable>>() | ||||
| const multipleSelection = ref() | ||||
| const handleSelectionChange = ({}, row) => { | ||||
|   multipleSelection.value = row | ||||
|   multipleTableRef.value!.clearSelection() | ||||
|   multipleTableRef.value!.toggleRowSelection(row, undefined) | ||||
| } | ||||
| const selectCustomer = () => { | ||||
|   formData.value.customerId = multipleSelection.value.id | ||||
|   formData.value.customerName = multipleSelection.value.name | ||||
|   showCustomer.value = !showCustomer.value | ||||
| } | ||||
| const allContactList = ref([]) //所有联系人列表 | ||||
| onMounted(async () => { | ||||
|   allContactList.value = await ContactApi.simpleAlllist() | ||||
| }) | ||||
| </script> | ||||
|  | @ -0,0 +1,71 @@ | |||
| <template> | ||||
|   <Dialog :title="dialogTitle" v-model="dialogVisible" width="600px"> | ||||
|     <el-transfer | ||||
|       v-model="value" | ||||
|       :data="data" | ||||
|       :titles="transferTitles" | ||||
|       :props="transferDataProp" | ||||
|       :right-default-checked="[1]" | ||||
|     /> | ||||
|     <el-row justify="end"> | ||||
|       <el-col :span="4"> | ||||
|         <el-button type="primary" @click="confirmOwerSelect">确认</el-button> | ||||
|       </el-col> | ||||
|       <el-col :span="4"> | ||||
|         <el-button type="primary" @click="confirmOwerSelect">取消</el-button> | ||||
|       </el-col> | ||||
|     </el-row> | ||||
|   </Dialog> | ||||
| </template> | ||||
| <script setup lang="ts"> | ||||
| import * as UserApi from '@/api/system/user' | ||||
| import { parseBigInt } from 'jsencrypt/lib/lib/jsbn/jsbn' | ||||
| const dialogVisible = ref(false) // 弹窗的是否展示 | ||||
| const dialogTitle = ref('选择') // 弹窗的标题 | ||||
| const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 | ||||
| const formType = ref('') | ||||
| const transferTitles = ref(['待选择', '已选择']) | ||||
| const transferDataProp = ref({ | ||||
|   key: 'id', | ||||
|   label: 'nickname' | ||||
| }) | ||||
| const emit = defineEmits(['confirmOwerSelect']) | ||||
| const data = ref<UserApi.UserVO[]>([]) | ||||
| const value = ref<any[]>([]) | ||||
| const rightDefaultChecked = ref<any[]>([]) | ||||
| /** 打开弹窗 */ | ||||
| const open = async (type: string, ownerUserList: any[]) => { | ||||
|   dialogVisible.value = true | ||||
|   formType.value = type | ||||
|   // 修改时,设置数据 | ||||
|   if (ownerUserList) { | ||||
|     formLoading.value = true | ||||
|     try { | ||||
|       ownerUserList.forEach((item) => { | ||||
|         value.value.push(item.id) | ||||
|       }) | ||||
|     } finally { | ||||
|       formLoading.value = false | ||||
|     } | ||||
|   } | ||||
|   rightDefaultChecked.value = [] | ||||
|   data.value = await UserApi.getSimpleUserList() | ||||
| } | ||||
| defineExpose({ open }) // 提供 open 方法,用于打开弹窗 | ||||
| const confirmOwerSelect = () => { | ||||
|   const returnData = ref<any[]>([]) | ||||
|   data.value.forEach((item) => { | ||||
|     if (value.value.indexOf(item.id) > -1) { | ||||
|       returnData.value.push(item) | ||||
|     } | ||||
|   }) | ||||
|   emit('confirmOwerSelect', returnData) | ||||
|   dialogVisible.value = false | ||||
|   value.value = [] | ||||
| } | ||||
| </script> | ||||
| <style> | ||||
| .el-row { | ||||
|   margin-top: 20px; | ||||
| } | ||||
| </style> | ||||
|  | @ -0,0 +1,23 @@ | |||
| <!-- | ||||
|  * @Author: zyna | ||||
|  * @Date: 2023-11-11 14:50:11 | ||||
|  * @LastEditTime: 2023-11-11 14:52:47 | ||||
|  * @FilePath: \yudao-ui-admin-vue3\src\views\crm\contact\detail\ContactBasicInfo.vue | ||||
|  * @Description:  | ||||
| --> | ||||
| <template> | ||||
|   <el-col> | ||||
|     <el-row> | ||||
|       <span class="text-xl font-bold">{{ contact.name }}</span> | ||||
|     </el-row> | ||||
|   </el-col> | ||||
|   <el-col class="mt-10px"> | ||||
|     <!-- TODO 标签 --> | ||||
|     <!--    <Icon icon="ant-design:tag-filled" />--> | ||||
|   </el-col> | ||||
| </template> | ||||
| <script setup lang="ts"> | ||||
| import * as ContactApi from '@/api/crm/contact' | ||||
| 
 | ||||
| const { contact } = defineProps<{ contact: ContactApi.ContactVO }>() | ||||
| </script> | ||||
|  | @ -0,0 +1,93 @@ | |||
| <template> | ||||
|   <el-collapse v-model="activeNames"> | ||||
|     <el-collapse-item name="basicInfo"> | ||||
|       <template #title> | ||||
|         <span class="text-base font-bold">基本信息</span> | ||||
|       </template> | ||||
|       <el-descriptions :column="4"> | ||||
|         <el-descriptions-item label="姓名"> | ||||
|           {{ contact.name }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="客户名称"> | ||||
|           {{ contact.customerName }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="手机"> | ||||
|           {{ contact.mobile }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="座机"> | ||||
|           {{ contact.telephone }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="邮箱"> | ||||
|           {{ contact.email }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="QQ"> | ||||
|           {{ contact.qq }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="微信"> | ||||
|           {{ contact.webchat }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="详细地址"> | ||||
|           {{ contact.address }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="下次联系时间"> | ||||
|           {{ contact.nextTime ? formatDate(contact.nextTime) : '空' }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="性别"> | ||||
|           <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="contact.sex" /> | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="备注"> | ||||
|           {{ contact.remark }} | ||||
|         </el-descriptions-item> | ||||
|       </el-descriptions> | ||||
|     </el-collapse-item> | ||||
|     <el-collapse-item name="systemInfo"> | ||||
|       <template #title> | ||||
|         <span class="text-base font-bold">系统信息</span> | ||||
|       </template> | ||||
|       <el-descriptions :column="2"> | ||||
|         <el-descriptions-item label="负责人"> | ||||
|           {{ gotOwnerUser(contact.ownerUserId) }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="创建人"> | ||||
|           {{ contact.creatorName }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="创建时间"> | ||||
|           {{ contact.createTime ? formatDate(contact.createTime) : '空' }} | ||||
|         </el-descriptions-item> | ||||
|         <el-descriptions-item label="更新时间"> | ||||
|           {{ contact.updateTime ? formatDate(contact.updateTime) : '空' }} | ||||
|         </el-descriptions-item> | ||||
|       </el-descriptions> | ||||
|     </el-collapse-item> | ||||
|   </el-collapse> | ||||
| </template> | ||||
| <script setup lang="ts"> | ||||
| import * as ContactApi from '@/api/crm/contact' | ||||
| import { DICT_TYPE } from '@/utils/dict' | ||||
| import { formatDate } from '@/utils/formatTime' | ||||
| import * as UserApi from '@/api/system/user' | ||||
| const { contact } = defineProps<{ contact: ContactApi.ContactVO }>() | ||||
| 
 | ||||
| // 展示的折叠面板 | ||||
| const activeNames = ref(['basicInfo', 'systemInfo']) | ||||
| const gotOwnerUser = (owerUserId: string) => { | ||||
|   let ownerName = '' | ||||
|   if (owerUserId !== null && owerUserId != undefined) { | ||||
|     owerUserId.split(',').forEach((item: string, index: number) => { | ||||
|       if (index != 0) { | ||||
|         ownerName = | ||||
|           ownerName + ',' + userList.value.find((user: { id: any }) => user.id == item)?.nickname | ||||
|       } else { | ||||
|         ownerName = userList.value.find((user: { id: any }) => user.id == item)?.nickname || '' | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
|   return ownerName | ||||
| } | ||||
| const userList = ref<UserApi.UserVO[]>([]) // 用户列表 | ||||
| /** 初始化 **/ | ||||
| onMounted(async () => { | ||||
|   userList.value = await UserApi.getSimpleUserList() | ||||
| }) | ||||
| </script> | ||||
| <style scoped lang="scss"></style> | ||||
|  | @ -0,0 +1,147 @@ | |||
| <template> | ||||
|   <div v-loading="loading"> | ||||
|     <div class="flex items-start justify-between"> | ||||
|       <div> | ||||
|         <!-- 左上:客户基本信息 --> | ||||
|         <ContactBasicInfo :contact="contact" /> | ||||
|       </div> | ||||
|       <div> | ||||
|         <!-- 右上:按钮 --> | ||||
|         <el-button @click="openForm('update', contact.id)" v-hasPermi="['crm:contact:update']"> | ||||
|           编辑 | ||||
|         </el-button> | ||||
|       </div> | ||||
|     </div> | ||||
|     <el-row class="mt-10px"> | ||||
|       <el-button> | ||||
|         <Icon icon="ph:calendar-fill" class="mr-5px" /> | ||||
|         创建任务 | ||||
|       </el-button> | ||||
|       <el-button> | ||||
|         <Icon icon="carbon:email" class="mr-5px" /> | ||||
|         发送邮件 | ||||
|       </el-button> | ||||
|       <el-button> | ||||
|         <Icon icon="system-uicons:contacts" class="mr-5px" /> | ||||
|         创建联系人 | ||||
|       </el-button> | ||||
|       <el-button> | ||||
|         <Icon icon="ep:opportunity" class="mr-5px" /> | ||||
|         创建商机 | ||||
|       </el-button> | ||||
|       <el-button> | ||||
|         <Icon icon="clarity:contract-line" class="mr-5px" /> | ||||
|         创建合同 | ||||
|       </el-button> | ||||
|       <el-button> | ||||
|         <Icon icon="icon-park:income-one" class="mr-5px" /> | ||||
|         创建回款 | ||||
|       </el-button> | ||||
|       <el-button> | ||||
|         <Icon icon="fluent:people-team-add-20-filled" class="mr-5px" /> | ||||
|         添加团队成员 | ||||
|       </el-button> | ||||
|     </el-row> | ||||
|   </div> | ||||
|   <ContentWrap class="mt-10px"> | ||||
|     <el-descriptions :column="5" direction="vertical"> | ||||
|       <el-descriptions-item label="客户名称"> | ||||
|         {{ contact.customerName }} | ||||
|       </el-descriptions-item> | ||||
|       <el-descriptions-item label="职务"> | ||||
|         {{ contact.post }} | ||||
|       </el-descriptions-item> | ||||
|       <el-descriptions-item label="手机"> | ||||
|         {{ contact.mobile }} | ||||
|       </el-descriptions-item> | ||||
|       <el-descriptions-item label="创建时间"> | ||||
|         {{ contact.createTime ? formatDate(contact.createTime) : '空' }} | ||||
|       </el-descriptions-item> | ||||
|     </el-descriptions> | ||||
|   </ContentWrap> | ||||
|   <!-- TODO wanwan:这个 tab 拉满哈,可以更好看; --> | ||||
|   <el-col :span="18"> | ||||
|     <el-tabs> | ||||
|       <el-tab-pane label="详细资料"> | ||||
|         <!-- TODO wanwan:这个 ml-2 是不是可以优化下,不要整个左移,而是里面的内容有个几 px 的偏移,不顶在框里 --> | ||||
|         <ContactDetails class="ml-2" :contact="contact" /> | ||||
|       </el-tab-pane> | ||||
|       <el-tab-pane label="活动" lazy> 活动</el-tab-pane> | ||||
|       <el-tab-pane label="邮件" lazy> 邮件</el-tab-pane> | ||||
|       <el-tab-pane label="工商信息" lazy> 工商信息</el-tab-pane> | ||||
|       <!-- TODO wanwan 以下标签上的数量需要接口统计返回 --> | ||||
|       <el-tab-pane label="客户" lazy> | ||||
|         <template #label> 客户<el-badge :value="12" class="item" type="primary" /> </template> | ||||
|         客户 | ||||
|       </el-tab-pane> | ||||
|       <el-tab-pane label="团队成员" lazy> | ||||
|         <template #label> 团队成员<el-badge :value="2" class="item" type="primary" /> </template> | ||||
|         团队成员 | ||||
|       </el-tab-pane> | ||||
|       <el-tab-pane label="商机" lazy> 商机</el-tab-pane> | ||||
|       <el-tab-pane label="合同" lazy> | ||||
|         <template #label> 合同<el-badge :value="3" class="item" type="primary" /> </template> | ||||
|         合同 | ||||
|       </el-tab-pane> | ||||
|       <el-tab-pane label="回款" lazy> | ||||
|         <template #label> 回款<el-badge :value="4" class="item" type="primary" /> </template> | ||||
|         回款 | ||||
|       </el-tab-pane> | ||||
|       <el-tab-pane label="回访" lazy> 回访</el-tab-pane> | ||||
|       <el-tab-pane label="发票" lazy> 发票</el-tab-pane> | ||||
|     </el-tabs> | ||||
|   </el-col> | ||||
| 
 | ||||
|   <!-- 表单弹窗:添加/修改 --> | ||||
|   <ContactForm ref="formRef" @success="getContactData(id)" /> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| import { ElMessage } from 'element-plus' | ||||
| import { useTagsViewStore } from '@/store/modules/tagsView' | ||||
| import * as ContactApi from '@/api/crm/contact' | ||||
| import ContactBasicInfo from '@/views/crm/contact/detail/ContactBasicInfo.vue' | ||||
| import ContactDetails from '@/views/crm/contact/detail/ContactDetails.vue' | ||||
| import ContactForm from '@/views/crm/contact/ContactForm.vue' | ||||
| import { formatDate } from '@/utils/formatTime' | ||||
| import * as CustomerApi from '@/api/crm/customer' | ||||
| 
 | ||||
| defineOptions({ name: 'ContactDetail' }) | ||||
| const { delView } = useTagsViewStore() // 视图操作 | ||||
| const route = useRoute() | ||||
| const { currentRoute } = useRouter() // 路由 | ||||
| const id = Number(route.params.id) | ||||
| const loading = ref(true) // 加载中 | ||||
| // 联系人详情 | ||||
| const contact = ref<ContactApi.ContactVO>({} as ContactApi.ContactVO) | ||||
| /** | ||||
|  * 获取详情 | ||||
|  * | ||||
|  * @param id | ||||
|  */ | ||||
| const getContactData = async (id: number) => { | ||||
|   loading.value = true | ||||
|   try { | ||||
|     contact.value = await ContactApi.getContact(id) | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const formRef = ref() | ||||
| const openForm = (type: string, id?: number) => { | ||||
|   formRef.value.open(type, id) | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 初始化 | ||||
|  */ | ||||
| onMounted(async () => { | ||||
|   if (!id) { | ||||
|     ElMessage.warning('参数错误,联系人不能为空!') | ||||
|     delView(unref(currentRoute)) | ||||
|     return | ||||
|   } | ||||
|   await getContactData(id) | ||||
| }) | ||||
| </script> | ||||
|  | @ -0,0 +1,333 @@ | |||
| <template> | ||||
|   <ContentWrap> | ||||
|     <!-- 搜索工作栏 --> | ||||
|     <el-form | ||||
|       class="-mb-15px" | ||||
|       :model="queryParams" | ||||
|       ref="queryFormRef" | ||||
|       :inline="true" | ||||
|       label-width="68px" | ||||
|     > | ||||
|       <el-form-item label="客户编号" prop="customerId"> | ||||
|         <el-input | ||||
|           v-model="queryParams.customerId" | ||||
|           placeholder="请输入客户编号" | ||||
|           clearable | ||||
|           @keyup.enter="handleQuery" | ||||
|           class="!w-240px" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="姓名" prop="name"> | ||||
|         <el-input | ||||
|           v-model="queryParams.name" | ||||
|           placeholder="请输入姓名" | ||||
|           clearable | ||||
|           @keyup.enter="handleQuery" | ||||
|           class="!w-240px" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="手机号" prop="mobile"> | ||||
|         <el-input | ||||
|           v-model="queryParams.mobile" | ||||
|           placeholder="请输入手机号" | ||||
|           clearable | ||||
|           @keyup.enter="handleQuery" | ||||
|           class="!w-240px" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="座机" prop="telephone"> | ||||
|         <el-input | ||||
|           v-model="queryParams.telephone" | ||||
|           placeholder="请输入电话" | ||||
|           clearable | ||||
|           @keyup.enter="handleQuery" | ||||
|           class="!w-240px" | ||||
|         /> | ||||
|       </el-form-item> | ||||
| 
 | ||||
|       <el-form-item label="QQ" prop="qq"> | ||||
|         <el-input | ||||
|           v-model="queryParams.qq" | ||||
|           placeholder="请输入QQ" | ||||
|           clearable | ||||
|           @keyup.enter="handleQuery" | ||||
|           class="!w-240px" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="微信" prop="webchat"> | ||||
|         <el-input | ||||
|           v-model="queryParams.webchat" | ||||
|           placeholder="请输入微信" | ||||
|           clearable | ||||
|           @keyup.enter="handleQuery" | ||||
|           class="!w-240px" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="电子邮箱" prop="email"> | ||||
|         <el-input | ||||
|           v-model="queryParams.email" | ||||
|           placeholder="请输入电子邮箱" | ||||
|           clearable | ||||
|           @keyup.enter="handleQuery" | ||||
|           class="!w-240px" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> | ||||
|         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> | ||||
|         <el-button type="primary" @click="openForm('create')" v-hasPermi="['crm:contact:create']"> | ||||
|           <Icon icon="ep:plus" class="mr-5px" /> 新增 | ||||
|         </el-button> | ||||
|         <el-button | ||||
|           type="success" | ||||
|           plain | ||||
|           @click="handleExport" | ||||
|           :loading="exportLoading" | ||||
|           v-hasPermi="['crm:contact:export']" | ||||
|         > | ||||
|           <Icon icon="ep:download" class="mr-5px" /> 导出 | ||||
|         </el-button> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|   </ContentWrap> | ||||
| 
 | ||||
|   <!-- 列表 --> | ||||
|   <ContentWrap> | ||||
|     <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> | ||||
|       <el-table-column label="姓名" fixed="left" align="center" prop="name"> | ||||
|         <template #default="scope"> | ||||
|           <el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">{{ | ||||
|             scope.row.name | ||||
|           }}</el-link> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="客户名称" fixed="left" align="center" prop="customerName" /> | ||||
|       <el-table-column label="性别" align="center" prop="sex"> | ||||
|         <template #default="scope"> | ||||
|           <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" /> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="职位" align="center" prop="post" /> | ||||
|       <el-table-column label="是否关键决策人" align="center" prop="policyMakers"> | ||||
|         <template #default="scope"> | ||||
|           <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.policyMakers" /> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="直属上级" align="center" prop="parentId"> | ||||
|         <template #default="scope"> | ||||
|           {{ allContactList.find((contact) => contact.id === scope.row.parentId)?.name }} | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="手机号" align="center" prop="mobile" /> | ||||
|       <el-table-column label="座机" align="center" prop="telephone" /> | ||||
|       <el-table-column label="QQ" align="center" prop="qq" /> | ||||
|       <el-table-column label="微信" align="center" prop="webchat" /> | ||||
|       <el-table-column label="邮箱" align="center" prop="email" /> | ||||
|       <el-table-column label="地址" align="center" prop="address" /> | ||||
|       <el-table-column | ||||
|         label="下次联系时间" | ||||
|         align="center" | ||||
|         prop="nextTime" | ||||
|         width="180px" | ||||
|         :formatter="dateFormatter" | ||||
|       /> | ||||
|       <el-table-column label="备注" align="center" prop="remark" /> | ||||
|       <el-table-column | ||||
|         label="最后跟进时间" | ||||
|         align="center" | ||||
|         prop="lastTime" | ||||
|         :formatter="dateFormatter" | ||||
|         width="180px" | ||||
|       /> | ||||
|       <el-table-column label="负责人" align="center" prop="ownerUserId"> | ||||
|         <template #default="scope"> | ||||
|           {{ gotOwnerUser(scope.row.ownerUserId) }} | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <!-- <el-table-column label="所属部门" align="center" prop="ownerUserId" /> --> | ||||
|       <el-table-column | ||||
|         label="更新时间" | ||||
|         align="center" | ||||
|         prop="updateTime" | ||||
|         :formatter="dateFormatter" | ||||
|         width="180px" | ||||
|       /> | ||||
|       <el-table-column | ||||
|         label="创建时间" | ||||
|         align="center" | ||||
|         prop="createTime" | ||||
|         :formatter="dateFormatter" | ||||
|         width="180px" | ||||
|       /> | ||||
|       <!-- <el-table-column | ||||
|         label="创建人" | ||||
|         align="center" | ||||
|         prop="creator" | ||||
|         :formatter="dateFormatter" | ||||
|         width="180px" | ||||
|       > | ||||
|         <template #default="scope"> | ||||
|           {{ userList.find((user) => user.id === scope.row.creator)?.nickname }} | ||||
|         </template> | ||||
|       </el-table-column> --> | ||||
|       <el-table-column label="操作" align="center" fixed="right" width="200"> | ||||
|         <template #default="scope"> | ||||
|           <el-button | ||||
|             plain | ||||
|             type="primary" | ||||
|             @click="openForm('update', scope.row.id)" | ||||
|             v-hasPermi="['crm:contact:update']" | ||||
|           > | ||||
|             编辑 | ||||
|           </el-button> | ||||
|           <el-button | ||||
|             plain | ||||
|             type="danger" | ||||
|             @click="handleDelete(scope.row.id)" | ||||
|             v-hasPermi="['crm:contact:delete']" | ||||
|           > | ||||
|             删除 | ||||
|           </el-button> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </el-table> | ||||
|     <!-- 分页 --> | ||||
|     <Pagination | ||||
|       :total="total" | ||||
|       v-model:page="queryParams.pageNo" | ||||
|       v-model:limit="queryParams.pageSize" | ||||
|       @pagination="getList" | ||||
|     /> | ||||
|   </ContentWrap> | ||||
| 
 | ||||
|   <!-- 表单弹窗:添加/修改 --> | ||||
|   <ContactForm ref="formRef" @success="getList" /> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| import { dateFormatter } from '@/utils/formatTime' | ||||
| import download from '@/utils/download' | ||||
| import * as ContactApi from '@/api/crm/contact' | ||||
| import ContactForm from './ContactForm.vue' | ||||
| import { DICT_TYPE } from '@/utils/dict' | ||||
| import * as UserApi from '@/api/system/user' | ||||
| import * as CustomerApi from '@/api/crm/customer' | ||||
| 
 | ||||
| defineOptions({ name: 'CrmContact' }) | ||||
| const message = useMessage() // 消息弹窗 | ||||
| const { t } = useI18n() // 国际化 | ||||
| 
 | ||||
| const loading = ref(true) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref([]) // 列表的数据 | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   nextTime: [], | ||||
|   mobile: null, | ||||
|   telephone: null, | ||||
|   email: null, | ||||
|   customerId: null, | ||||
|   address: null, | ||||
|   remark: null, | ||||
|   ownerUserId: null, | ||||
|   createTime: [], | ||||
|   lastTime: [], | ||||
|   parentId: null, | ||||
|   name: null, | ||||
|   post: null, | ||||
|   qq: null, | ||||
|   webchat: null, | ||||
|   sex: null, | ||||
|   policyMakers: null | ||||
| }) | ||||
| const queryFormRef = ref() // 搜索的表单 | ||||
| const exportLoading = ref(false) // 导出的加载中 | ||||
| const userList = ref<UserApi.UserVO[]>([]) // 用户列表 | ||||
| /** 查询列表 */ | ||||
| const getList = async () => { | ||||
|   loading.value = true | ||||
|   try { | ||||
|     const data = await ContactApi.getContactPage(queryParams) | ||||
|     list.value = data.list | ||||
|     total.value = data.total | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = () => { | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
| 
 | ||||
| /** 重置按钮操作 */ | ||||
| const resetQuery = () => { | ||||
|   queryFormRef.value.resetFields() | ||||
|   handleQuery() | ||||
| } | ||||
| 
 | ||||
| /** 添加/修改操作 */ | ||||
| const formRef = ref() | ||||
| const openForm = (type: string, id?: number) => { | ||||
|   formRef.value.open(type, id) | ||||
| } | ||||
| 
 | ||||
| /** 删除按钮操作 */ | ||||
| const handleDelete = async (id: number) => { | ||||
|   try { | ||||
|     // 删除的二次确认 | ||||
|     await message.delConfirm() | ||||
|     // 发起删除 | ||||
|     await ContactApi.deleteContact(id) | ||||
|     message.success(t('common.delSuccess')) | ||||
|     // 刷新列表 | ||||
|     await getList() | ||||
|   } catch {} | ||||
| } | ||||
| 
 | ||||
| /** 导出按钮操作 */ | ||||
| const handleExport = async () => { | ||||
|   try { | ||||
|     // 导出的二次确认 | ||||
|     await message.exportConfirm() | ||||
|     // 发起导出 | ||||
|     exportLoading.value = true | ||||
|     const data = await ContactApi.exportContact(queryParams) | ||||
|     download.excel(data, '联系人.xls') | ||||
|   } catch { | ||||
|   } finally { | ||||
|     exportLoading.value = false | ||||
|   } | ||||
| } | ||||
| const gotOwnerUser = (owerUserId: string) => { | ||||
|   let ownerName = '' | ||||
|   if (owerUserId !== null) { | ||||
|     owerUserId.split(',').forEach((item: string, index: number) => { | ||||
|       if (index != 0) { | ||||
|         ownerName = | ||||
|           ownerName + ',' + userList.value.find((user: { id: any }) => user.id == item)?.nickname | ||||
|       } else { | ||||
|         ownerName = userList.value.find((user: { id: any }) => user.id == item)?.nickname || '' | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
|   return ownerName | ||||
| } | ||||
| /** 详情页面 */ | ||||
| /** 打开客户详情 */ | ||||
| const { push } = useRouter() | ||||
| const openDetail = (id: number) => { | ||||
|   push({ name: 'CrmContactDetail', params: { id } }) | ||||
| } | ||||
| 
 | ||||
| const allContactList = ref([]) //所有联系人列表 | ||||
| const allCustomerList = ref([]) //客户列表 | ||||
| /** 初始化 **/ | ||||
| onMounted(async () => { | ||||
|   await getList() | ||||
|   userList.value = await UserApi.getSimpleUserList() | ||||
|   allContactList.value = await ContactApi.simpleAlllist() | ||||
| }) | ||||
| </script> | ||||
		Loading…
	
		Reference in New Issue
	
	 zyna
						zyna