Procházet zdrojové kódy

✨ CRM:完善 CRM 跟进记录(商机的展示、添加)

YunaiV před 1 rokem
rodič
revize
28eb23738c

+ 0 - 5
src/api/crm/business/index.ts

@@ -92,11 +92,6 @@ export const getBusinessPageByContact = async (params) => {
   return await request.get({ url: `/crm/business/page-by-contact`, params })
 }
 
-// 获得 CRM 商机列表
-export const getBusinessListByIds = async (val: number[]) => {
-  return await request.get({ url: '/crm/business/list-by-ids', params: { ids: val.join(',') } })
-}
-
 // 商机转移
 export const transferBusiness = async (data: TransferReqVO) => {
   return await request.put({ url: '/crm/business/transfer', data })

+ 10 - 15
src/api/crm/followup/index.ts

@@ -11,7 +11,17 @@ export interface FollowUpRecordVO {
   fileUrls: string[] // 附件
   nextTime: Date // 下次联系时间
   businessIds: number[] // 关联的商机编号数组
+  businesses: {
+    id: number
+    name: string
+  }[] // 关联的商机数组
   contactIds: number[] // 关联的联系人编号数组
+  contacts: {
+    id: number
+    name: string
+  }[] // 关联的联系人数组
+  creator: string
+  creatorName?: string
 }
 
 // 跟进记录 API
@@ -21,28 +31,13 @@ export const FollowUpRecordApi = {
     return await request.get({ url: `/crm/follow-up-record/page`, params })
   },
 
-  // 查询跟进记录详情
-  getFollowUpRecord: async (id: number) => {
-    return await request.get({ url: `/crm/follow-up-record/get?id=` + id })
-  },
-
   // 新增跟进记录
   createFollowUpRecord: async (data: FollowUpRecordVO) => {
     return await request.post({ url: `/crm/follow-up-record/create`, data })
   },
 
-  // 修改跟进记录
-  updateFollowUpRecord: async (data: FollowUpRecordVO) => {
-    return await request.put({ url: `/crm/follow-up-record/update`, data })
-  },
-
   // 删除跟进记录
   deleteFollowUpRecord: async (id: number) => {
     return await request.delete({ url: `/crm/follow-up-record/delete?id=` + id })
-  },
-
-  // 导出跟进记录 Excel
-  exportFollowUpRecord: async (params) => {
-    return await request.download({ url: `/crm/follow-up-record/export-excel`, params })
   }
 }

+ 5 - 4
src/views/crm/business/components/BusinessListModal.vue

@@ -48,8 +48,8 @@
         <el-table-column
           label="商机金额"
           align="center"
-          prop="price"
-          :formatter="erpPriceInputFormatter"
+          prop="totalPrice"
+          :formatter="erpPriceTableColumnFormatter"
         />
         <el-table-column label="客户名称" align="center" prop="customerName" />
         <el-table-column label="商机组" align="center" prop="statusTypeName" />
@@ -75,7 +75,7 @@
 <script setup lang="ts">
 import * as BusinessApi from '@/api/crm/business'
 import BusinessForm from '../BusinessForm.vue'
-import { erpPriceInputFormatter } from '@/utils'
+import { erpPriceTableColumnFormatter } from '@/utils'
 
 const message = useMessage() // 消息弹窗
 const props = defineProps<{
@@ -99,6 +99,7 @@ const queryParams = reactive({
 /** 打开弹窗 */
 const open = async () => {
   dialogVisible.value = true
+  queryParams.customerId = props.customerId // 解决 props.customerId 没更新到 queryParams 上的问题
   await getList()
 }
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
@@ -144,7 +145,7 @@ const submitForm = async () => {
     return message.error('未选择商机')
   }
   dialogVisible.value = false
-  emit('success', businessIds)
+  emit('success', businessIds, businessRef.value.getSelectionRows())
 }
 
 /** 打开联系人详情 */

+ 45 - 35
src/views/crm/followup/FollowUpRecordForm.vue

@@ -1,6 +1,6 @@
 <!-- 跟进记录的添加表单弹窗 -->
 <template>
-  <Dialog v-model="dialogVisible" :title="dialogTitle" width="50%">
+  <Dialog v-model="dialogVisible" title="添加跟进记录" width="50%">
     <el-form
       ref="formRef"
       v-loading="formLoading"
@@ -36,17 +36,17 @@
             <el-input v-model="formData.content" :rows="3" type="textarea" />
           </el-form-item>
         </el-col>
-        <el-col :span="24">
-          <el-form-item label="图片" prop="content">
+        <el-col :span="12">
+          <el-form-item label="图片" prop="picUrls">
             <UploadImgs v-model="formData.picUrls" class="min-w-80px" />
           </el-form-item>
         </el-col>
-        <el-col :span="24">
-          <el-form-item label="附件" prop="content">
+        <el-col :span="12">
+          <el-form-item label="附件" prop="fileUrls">
             <UploadFile v-model="formData.fileUrls" class="min-w-80px" />
           </el-form-item>
         </el-col>
-        <el-col :span="24">
+        <el-col :span="24" v-if="formData.bizType == BizTypeEnum.CRM_CUSTOMER">
           <el-form-item label="关联联系人" prop="contactIds">
             <el-button @click="handleAddContact">
               <Icon class="mr-5px" icon="ep:plus" />
@@ -55,13 +55,13 @@
             <contact-list v-model:contactIds="formData.contactIds" />
           </el-form-item>
         </el-col>
-        <el-col :span="24">
+        <el-col :span="24" v-if="formData.bizType == BizTypeEnum.CRM_CUSTOMER">
           <el-form-item label="关联商机" prop="businessIds">
-            <el-button @click="handleAddBusiness">
+            <el-button @click="handleOpenBusiness">
               <Icon class="mr-5px" icon="ep:plus" />
               添加商机
             </el-button>
-            <business-list v-model:businessIds="formData.businessIds" />
+            <FollowUpRecordBusinessForm :businesses="formData.businesses" />
           </el-form-item>
         </el-col>
       </el-row>
@@ -71,13 +71,23 @@
       <el-button @click="dialogVisible = false">取 消</el-button>
     </template>
   </Dialog>
+
+  <!-- 弹窗 -->
   <ContactTableSelect ref="contactTableSelectRef" v-model="formData.contactIds" />
-  <BusinessTableSelect ref="businessTableSelectRef" v-model="formData.businessIds" />
+  <BusinessListModal
+    ref="businessTableSelectRef"
+    :customer-id="formData.bizId"
+    @success="handleAddBusiness"
+  />
 </template>
 <script lang="ts" setup>
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup'
-import { BusinessList, BusinessTableSelect, ContactList, ContactTableSelect } from './components'
+import { ContactList, ContactTableSelect } from './components'
+import { BizTypeEnum } from '@/api/crm/permission'
+import FollowUpRecordBusinessForm from './components/FollowUpRecordBusinessForm.vue'
+import BusinessListModal from '@/views/crm/business/components/BusinessListModal.vue'
+import * as BusinessApi from '@/api/crm/business'
 
 defineOptions({ name: 'FollowUpRecordForm' })
 
@@ -87,8 +97,12 @@ const message = useMessage() // 消息弹窗
 const dialogVisible = ref(false) // 弹窗的是否展示
 const dialogTitle = ref('') // 弹窗的标题
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
-const formType = ref('') // 表单的类型:create - 新增;update - 修改
-const formData = ref<FollowUpRecordVO>({} as FollowUpRecordVO)
+const formData = ref({
+  bizType: undefined,
+  bizId: undefined,
+  businesses: [],
+  contacts: []
+})
 const formRules = reactive({
   type: [{ required: true, message: '跟进类型不能为空', trigger: 'change' }],
   content: [{ required: true, message: '跟进内容不能为空', trigger: 'blur' }],
@@ -98,22 +112,11 @@ const formRules = reactive({
 const formRef = ref() // 表单 Ref
 
 /** 打开弹窗 */
-const open = async (bizType: number, bizId: number, type: string, id?: number) => {
+const open = async (bizType: number, bizId: number) => {
   dialogVisible.value = true
-  dialogTitle.value = t('action.' + type)
-  formType.value = type
   resetForm()
   formData.value.bizType = bizType
   formData.value.bizId = bizId
-  // 修改时,设置数据
-  if (id) {
-    formLoading.value = true
-    try {
-      formData.value = await FollowUpRecordApi.getFollowUpRecord(id)
-    } finally {
-      formLoading.value = false
-    }
-  }
 }
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
@@ -126,13 +129,8 @@ const submitForm = async () => {
   formLoading.value = true
   try {
     const data = formData.value as unknown as FollowUpRecordVO
-    if (formType.value === 'create') {
-      await FollowUpRecordApi.createFollowUpRecord(data)
-      message.success(t('common.createSuccess'))
-    } else {
-      await FollowUpRecordApi.updateFollowUpRecord(data)
-      message.success(t('common.updateSuccess'))
-    }
+    await FollowUpRecordApi.createFollowUpRecord(data)
+    message.success(t('common.createSuccess'))
     dialogVisible.value = false
     // 发送操作成功的事件
     emit('success')
@@ -148,14 +146,26 @@ const handleAddContact = () => {
 }
 
 /** 关联商机 */
-const businessTableSelectRef = ref<InstanceType<typeof BusinessTableSelect>>()
-const handleAddBusiness = () => {
+const businessTableSelectRef = ref<InstanceType<typeof BusinessListModal>>()
+const handleOpenBusiness = () => {
   businessTableSelectRef.value?.open()
 }
+const handleAddBusiness = (businessId: [], newBusinesses: BusinessApi.BusinessVO[]) => {
+  newBusinesses.forEach((business) => {
+    if (!formData.value.businesses.some((item) => item.id === business.id)) {
+      formData.value.businesses.push(business)
+    }
+  })
+}
 
 /** 重置表单 */
 const resetForm = () => {
   formRef.value?.resetFields()
-  formData.value = {} as FollowUpRecordVO
+  formData.value = {
+    bizId: undefined,
+    bizType: undefined,
+    businesses: [],
+    contacts: []
+  }
 }
 </script>

+ 0 - 71
src/views/crm/followup/components/BusinessList.vue

@@ -1,71 +0,0 @@
-<template>
-  <el-table :data="list" :show-overflow-tooltip="true" :stripe="true" height="200">
-    <el-table-column align="center" label="商机名称" prop="name" />
-    <el-table-column align="center" label="客户名称" prop="customerName" />
-    <el-table-column align="center" label="商机金额" prop="price" />
-    <el-table-column
-      :formatter="dateFormatter"
-      align="center"
-      label="预计成交日期"
-      prop="dealTime"
-      width="120px"
-    />
-    <el-table-column align="center" label="商机状态类型" prop="statusTypeName" width="120" />
-    <el-table-column align="center" label="商机状态" prop="statusName" />
-    <el-table-column
-      :formatter="dateFormatter"
-      align="center"
-      label="更新时间"
-      prop="updateTime"
-      width="180px"
-    />
-    <el-table-column
-      :formatter="dateFormatter"
-      align="center"
-      label="创建时间"
-      prop="createTime"
-      width="180px"
-    />
-    <el-table-column align="center" label="负责人" prop="ownerUserName" width="120" />
-    <el-table-column align="center" label="创建人" prop="creatorName" width="120" />
-    <el-table-column align="center" label="备注" prop="remark" />
-    <el-table-column align="center" fixed="right" label="操作" width="130">
-      <template #default="scope">
-        <el-button link type="danger" @click="handleDelete(scope.row.id)"> 移除</el-button>
-      </template>
-    </el-table-column>
-  </el-table>
-</template>
-
-<script lang="ts" setup>
-import { dateFormatter } from '@/utils/formatTime'
-import * as BusinessApi from '@/api/crm/business'
-
-defineOptions({ name: 'BusinessList' })
-const props = withDefaults(defineProps<{ businessIds: number[] }>(), {
-  businessIds: () => []
-})
-const list = ref<BusinessApi.BusinessVO[]>([] as BusinessApi.BusinessVO[])
-watch(
-  () => props.businessIds,
-  (val) => {
-    if (!val || val.length === 0) {
-      return
-    }
-    list.value = BusinessApi.getBusinessListByIds(unref(val)) as unknown as BusinessApi.BusinessVO[]
-  }
-)
-const emits = defineEmits<{
-  (e: 'update:businessIds', businessIds: number[]): void
-}>()
-const handleDelete = (id: number) => {
-  const index = list.value.findIndex((item) => item.id === id)
-  if (index !== -1) {
-    list.value.splice(index, 1)
-  }
-  emits(
-    'update:businessIds',
-    list.value.map((item) => item.id)
-  )
-}
-</script>

+ 0 - 88
src/views/crm/followup/components/BusinessTableSelect.vue

@@ -1,88 +0,0 @@
-<!-- 商机的选择列表 TODO 芋艿:后面看看要不要搞到统一封装里 -->
-<template>
-  <Dialog v-model="dialogVisible" :appendToBody="true" title="选择商机" width="700">
-    <el-table
-      ref="multipleTableRef"
-      v-loading="loading"
-      :data="list"
-      :show-overflow-tooltip="true"
-      :stripe="true"
-      @selection-change="handleSelectionChange"
-    >
-      <el-table-column type="selection" width="55" />
-      <el-table-column align="center" label="商机名称" prop="name" />
-      <el-table-column align="center" label="客户名称" prop="customerName" />
-      <el-table-column align="center" label="商机金额" prop="price" />
-      <el-table-column
-        :formatter="dateFormatter"
-        align="center"
-        label="预计成交日期"
-        prop="dealTime"
-        width="180px"
-      />
-      <el-table-column align="center" label="备注" prop="remark" />
-    </el-table>
-    <template #footer>
-      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
-      <el-button @click="dialogVisible = false">取 消</el-button>
-    </template>
-  </Dialog>
-</template>
-
-<script lang="ts" setup>
-import * as BusinessApi from '@/api/crm/business'
-import { dateFormatter } from '@/utils/formatTime'
-import { ElTable } from 'element-plus'
-
-defineOptions({ name: 'BusinessTableSelect' })
-withDefaults(defineProps<{ modelValue: number[] }>(), { modelValue: () => [] })
-
-const list = ref<BusinessApi.BusinessVO[]>([]) // 列表的数据
-const loading = ref(false) // 列表的加载中
-const dialogVisible = ref(false) // 弹窗的是否展示
-const formLoading = ref(false)
-
-// 确认选择时的触发事件
-const emits = defineEmits<{
-  (e: 'update:modelValue', v: number[]): void
-}>()
-const multipleTableRef = ref<InstanceType<typeof ElTable>>()
-const multipleSelection = ref<BusinessApi.BusinessVO[]>([])
-const handleSelectionChange = (val: BusinessApi.BusinessVO[]) => {
-  multipleSelection.value = val
-}
-/** 触发 */
-const submitForm = () => {
-  formLoading.value = true
-  try {
-    emits(
-      'update:modelValue',
-      multipleSelection.value.map((item) => item.id)
-    )
-  } finally {
-    formLoading.value = false
-    // 关闭弹窗
-    dialogVisible.value = false
-  }
-}
-
-const getList = async () => {
-  loading.value = true
-  try {
-    list.value = await BusinessApi.getSimpleBusinessList()
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 打开弹窗 */
-const open = async () => {
-  dialogVisible.value = true
-  await nextTick()
-  if (multipleSelection.value.length > 0) {
-    multipleTableRef.value!.clearSelection()
-  }
-  await getList()
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-</script>

+ 42 - 0
src/views/crm/followup/components/FollowUpRecordBusinessForm.vue

@@ -0,0 +1,42 @@
+<template>
+  <el-table :data="formData" :show-overflow-tooltip="true" :stripe="true" height="120">
+    <el-table-column label="商机名称" fixed="left" align="center" prop="name" />
+    <el-table-column
+      label="商机金额"
+      align="center"
+      prop="totalPrice"
+      :formatter="erpPriceTableColumnFormatter"
+    />
+    <el-table-column label="客户名称" align="center" prop="customerName" />
+    <el-table-column label="商机组" align="center" prop="statusTypeName" />
+    <el-table-column label="商机阶段" align="center" prop="statusName" />
+    <el-table-column align="center" fixed="right" label="操作" width="80">
+      <template #default="{ $index }">
+        <el-button link type="danger" @click="handleDelete($index)"> 移除</el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+</template>
+
+<script lang="ts" setup>
+import { erpPriceTableColumnFormatter } from '@/utils'
+
+const props = defineProps<{
+  businesses: undefined
+}>()
+const formData = ref([])
+
+/** 初始化商机列表 */
+watch(
+  () => props.businesses,
+  async (val) => {
+    formData.value = val
+  },
+  { immediate: true }
+)
+
+/** 删除按钮操作 */
+const handleDelete = (index: number) => {
+  formData.value.splice(index, 1)
+}
+</script>

+ 1 - 3
src/views/crm/followup/components/index.ts

@@ -1,6 +1,4 @@
-import BusinessList from './BusinessList.vue'
-import BusinessTableSelect from './BusinessTableSelect.vue'
 import ContactList from './ContactList.vue'
 import ContactTableSelect from './ContactTableSelect.vue'
 
-export { BusinessList, BusinessTableSelect, ContactList, ContactTableSelect }
+export { ContactList, ContactTableSelect }

+ 57 - 21
src/views/crm/followup/index.vue

@@ -2,7 +2,7 @@
 <template>
   <!-- 操作栏 -->
   <el-row class="mb-10px" justify="end">
-    <el-button @click="openForm('create')">
+    <el-button @click="openForm">
       <Icon class="mr-5px" icon="ep:edit" />
       写跟进
     </el-button>
@@ -10,8 +10,13 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
-      <el-table-column align="center" label="编号" prop="id" />
-      <!-- TODO @puhui999:展示不出来 -->
+      <el-table-column
+        :formatter="dateFormatter"
+        align="center"
+        label="创建时间"
+        prop="createTime"
+        width="180px"
+      />
       <el-table-column align="center" label="跟进人" prop="creatorName" />
       <el-table-column align="center" label="跟进类型" prop="type">
         <template #default="scope">
@@ -26,27 +31,46 @@
         prop="nextTime"
         width="180px"
       />
-      <!-- TODO @puhui999:点击后,查看关联联系人 -->
-      <el-table-column align="center" label="关联联系人" prop="contactIds" />
-      <!-- TODO @puhui999:点击后,查看关联商机 -->
-      <el-table-column align="center" label="关联商机" prop="businessIds" />
       <el-table-column
-        :formatter="dateFormatter"
         align="center"
-        label="创建时间"
-        prop="createTime"
-        width="180px"
-      />
-      <el-table-column align="center" label="操作">
+        label="关联联系人"
+        prop="contactIds"
+        v-if="bizType === BizTypeEnum.CRM_CUSTOMER"
+      >
         <template #default="scope">
-          <el-button
-            v-hasPermi="['crm:follow-up-record:update']"
-            link
+          <el-link
+            v-for="contact in scope.row.contacts"
+            :key="`key-${contact.id}`"
+            :underline="false"
             type="primary"
-            @click="openForm('update', scope.row.id)"
+            @click="openContactDetail(contact.id)"
+            class="ml-5px"
           >
-            编辑
-          </el-button>
+            {{ contact.name }}
+          </el-link>
+        </template>
+      </el-table-column>
+      <el-table-column
+        align="center"
+        label="关联商机"
+        prop="businessIds"
+        v-if="bizType === BizTypeEnum.CRM_CUSTOMER"
+      >
+        <template #default="scope">
+          <el-link
+            v-for="business in scope.row.businesses"
+            :key="`key-${business.id}`"
+            :underline="false"
+            type="primary"
+            @click="openBusinessDetail(business.id)"
+            class="ml-5px"
+          >
+            {{ business.name }}
+          </el-link>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="操作">
+        <template #default="scope">
           <el-button
             v-hasPermi="['crm:follow-up-record:delete']"
             link
@@ -76,6 +100,7 @@ import { dateFormatter } from '@/utils/formatTime'
 import { DICT_TYPE } from '@/utils/dict'
 import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup'
 import FollowUpRecordForm from './FollowUpRecordForm.vue'
+import { BizTypeEnum } from '@/api/crm/permission'
 
 /** 跟进记录列表 */
 defineOptions({ name: 'FollowUpRecord' })
@@ -110,8 +135,8 @@ const getList = async () => {
 
 /** 添加/修改操作 */
 const formRef = ref<InstanceType<typeof FollowUpRecordForm>>()
-const openForm = (type: string, id?: number) => {
-  formRef.value?.open(props.bizType, props.bizId, type, id)
+const openForm = () => {
+  formRef.value?.open(props.bizType, props.bizId)
 }
 
 /** 删除按钮操作 */
@@ -127,6 +152,17 @@ const handleDelete = async (id: number) => {
   } catch {}
 }
 
+/** 打开联系人详情 */
+const { push } = useRouter()
+const openContactDetail = (id: number) => {
+  push({ name: 'CrmContactDetail', params: { id } })
+}
+
+/** 打开商机详情 */
+const openBusinessDetail = (id: number) => {
+  push({ name: 'CrmBusinessDetail', params: { id } })
+}
+
 watch(
   () => props.bizId,
   () => {

+ 1 - 1
src/views/crm/permission/components/PermissionForm.vue

@@ -30,7 +30,7 @@
         </el-radio-group>
       </el-form-item>
       <!-- TODO @puhui999:同时添加至,还没想好下次搞 -->
-      <el-form-item v-if="formType === 'create'" label="同时添加至" prop="toBizType">
+      <el-form-item v-if="false && formType === 'create'" label="同时添加至" prop="toBizType">
         <el-select v-model="formData.userId">
           <el-option :value="1" label="联系人" />
           <el-option :value="1" label="商机" />